[FIX] 同步 hotfix:銷售紀錄機台權限隔離

訂單/發票/出貨清單依使用者可存取機台過濾。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-25 20:06:03 +08:00
commit 1722a192a8
2 changed files with 110 additions and 0 deletions

View File

@ -85,6 +85,16 @@ class SalesController extends Controller
$invoicesQuery->whereBetween('created_at', [$start, $end]);
$dispenseQuery->whereBetween('machine_time', [$start, $end]);
// 機台權限隔離:非系統管理員只能看到「自己可存取機台」的交易。沿用 Machine 的 machine_access 全域 scope
// 取得可存取機台集合(與上方機台下拉清單一致),修正訂單/發票/出貨清單先前未依機台授權過濾、
// 導致無該機台權限的帳號仍可見其交易紀錄的資料隔離漏洞。
if (!auth()->user()->isSystemAdmin()) {
$accessibleMachineIds = Machine::pluck('id');
$ordersQuery->whereIn('machine_id', $accessibleMachineIds);
$invoicesQuery->whereIn('machine_id', $accessibleMachineIds);
$dispenseQuery->whereIn('machine_id', $accessibleMachineIds);
}
// 共用過濾器:機台
if ($machineId) {
$ordersQuery->where('machine_id', $machineId);

View File

@ -0,0 +1,100 @@
<?php
namespace Tests\Feature\Admin;
use App\Models\Machine\Machine;
use App\Models\System\Company;
use App\Models\System\Role;
use App\Models\System\User;
use Spatie\Permission\Models\Permission;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Tests\TestCase;
/**
* 銷售紀錄機台權限隔離:使用者只能看到「自己可存取機台」的交易訂單,
* 無該機台權限者不得看到其交易(修正先前訂單清單未依機台授權過濾的漏洞)。
*/
class SalesMachineAccessTest extends TestCase
{
use RefreshDatabase;
private Company $company;
private User $main;
private Machine $mA;
private Machine $mB;
private int $orderA;
private int $orderB;
protected function setUp(): void
{
parent::setUp();
Permission::create(['name' => 'menu.sales', 'guard_name' => 'web']);
$this->company = Company::create(['name' => 'Co', 'code' => 'CO']);
$role = Role::create(['name' => 'r', 'company_id' => $this->company->id, 'is_system' => false, 'guard_name' => 'web']);
$role->syncPermissions(['menu.sales']);
$this->main = User::factory()->create(['company_id' => $this->company->id, 'is_admin' => true, 'level' => 0]);
$this->main->assignRole($role);
$this->mA = Machine::factory()->create(['company_id' => $this->company->id]);
$this->mB = Machine::factory()->create(['company_id' => $this->company->id]);
$this->orderA = $this->makeOrder($this->mA->id);
$this->orderB = $this->makeOrder($this->mB->id);
}
private function makeOrder(int $machineId): int
{
return DB::table('orders')->insertGetId([
'company_id' => $this->company->id,
'machine_id' => $machineId,
'payment_type' => 1,
'total_amount' => 100,
'pay_amount' => 100,
'payment_status' => 1,
'status' => 'completed',
'created_at' => now(),
'updated_at' => now(),
]);
}
private function visibleOrderIds(User $user): array
{
$response = $this->actingAs($user)->get(route('admin.sales.index'));
$response->assertOk();
return collect($response->viewData('orders')->items())->pluck('id')->all();
}
public function test_sub_account_with_machine_access_sees_only_that_machine_orders(): void
{
$sub = User::factory()->create(['company_id' => $this->company->id, 'is_admin' => false, 'level' => 1, 'parent_id' => $this->main->id]);
$sub->assignRole(Role::where('company_id', $this->company->id)->first());
$sub->machines()->attach($this->mA->id); // 只被授權 mA
$ids = $this->visibleOrderIds($sub);
$this->assertContains($this->orderA, $ids);
$this->assertNotContains($this->orderB, $ids); // 未授權 mB → 看不到其訂單
}
public function test_sub_account_without_any_machine_access_sees_no_orders(): void
{
$sub = User::factory()->create(['company_id' => $this->company->id, 'is_admin' => false, 'level' => 1, 'parent_id' => $this->main->id]);
$sub->assignRole(Role::where('company_id', $this->company->id)->first());
// 不授權任何機台
$ids = $this->visibleOrderIds($sub);
$this->assertNotContains($this->orderA, $ids);
$this->assertNotContains($this->orderB, $ids);
}
public function test_system_admin_sees_all_orders(): void
{
$sysAdmin = User::factory()->create(['company_id' => null]);
$ids = $this->visibleOrderIds($sysAdmin);
$this->assertContains($this->orderA, $ids);
$this->assertContains($this->orderB, $ids);
}
}