refresh()->only(['id', 'version', 'code', 'description', 'area', 'serial_number', 'status', 'department_id', 'stock_item_id', 'criticality_level_id']); } beforeEach(function () { $this->withoutVite(); config(['geography.enabled' => true, 'geography.operational_enabled' => true]); $this->f = receiptFixture(); $this->actingAs($this->f['receiver']); $level = CriticalityLevel::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['receiver']->id, 'code' => 'CRIT', 'name' => 'Categoria sintética', 'criteria' => 'Critério sintético de teste', 'is_critical' => false, 'created_at' => now()]); $this->asset = Equipment::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['receiver']->id, 'department_id' => $this->f['department']->id, 'criticality_level_id' => $level->id, 'code' => 'EQ-GEO', 'description' => 'Equipamento sintético', 'area' => 'Área textual original', 'status' => 'active']); $this->reference = ['country' => 'MZ', 'catalogue' => config('geography.source_sha256'), 'record_id' => '2']; $this->warehouseUrl = '/stock/armazens/'.$this->f['warehouse']->id.'/localizacao'; }); test('operational geography creates warehouse and equipment without movements or implicit locations', function () { $this->post('/stock/armazens', ['code' => 'GEO', 'name' => 'Armazém geográfico', 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); $warehouse = Warehouse::query()->where('code', 'GEO')->sole(); expect($warehouse->geography_reference)->toBe($this->reference); $data = operationalAssetData($this->asset); unset($data['id'], $data['version']); $this->post('/equipamentos/activos', [...$data, 'code' => 'EQ-NOVO', 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); expect(Equipment::query()->where('code', 'EQ-NOVO')->sole()->geography_reference)->toBe($this->reference); $this->post('/stock/armazens', ['code' => 'SEM-GEO', 'name' => 'Sem classificação'])->assertSessionHasNoErrors(); expect(Warehouse::query()->where('code', 'SEM-GEO')->sole()->geography_reference)->toBeNull(); expect(DB::table('stock_movements')->count())->toBe(0)->and(DB::table('purchase_orders')->count())->toBe(1); }); test('operational geography warehouse update is isolated audited and rejects stale overwrite', function () { $before = $this->f['warehouse']->refresh()->only(['code', 'name', 'created_by']); $payload = ['geography_reference' => $this->reference, 'expected_reference' => null, 'name' => 'Não aplicar', 'tenant_id' => 999]; $this->post($this->warehouseUrl, $payload)->assertSessionHasNoErrors(); expect($this->f['warehouse']->refresh()->only(array_keys($before)))->toBe($before)->and($this->f['warehouse']->geography_reference)->toBe($this->reference); $this->post($this->warehouseUrl, ['geography_reference' => null, 'expected_reference' => null])->assertSessionHasErrors('geography_reference'); $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => $this->reference])->assertSessionHasNoErrors(); expect(AuditLog::query()->where('action', 'stock.warehouse_geography_updated')->count())->toBe(1); $this->post($this->warehouseUrl, ['geography_reference' => null, 'expected_reference' => $this->reference])->assertSessionHasNoErrors(); expect($this->f['warehouse']->refresh()->geography_reference)->toBeNull(); }); test('operational geography warehouse changes require native role and company ownership', function () { $this->actingAs($this->f['issuer'])->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null])->assertForbidden(); $this->actingAs($this->f['receiver']); $foreign = Tenant::query()->create(['name' => 'Outra empresa']); $warehouse = Warehouse::query()->create(['tenant_id' => $foreign->id, 'created_by' => $this->f['receiver']->id, 'code' => 'OUTRO', 'name' => 'Não visível']); $this->post('/stock/armazens/'.$warehouse->id.'/localizacao', ['geography_reference' => $this->reference, 'expected_reference' => null])->assertNotFound(); }); test('operational geography revalidates current warehouse authority after company locking', function () { $actor = $this->f['receiver']; $this->partialMock(ConflictGuard::class)->shouldReceive('lockCompany')->once()->andReturnUsing(function () use ($actor) { DB::table('users')->where('id', $actor->id)->update(['purchasing_role' => PurchasingRole::Finance->value]); }); $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null])->assertForbidden(); expect($this->f['warehouse']->refresh()->geography_reference)->toBeNull(); }); test('operational geography warehouse audit failure rolls back the reference', function () { $this->partialMock(RequisitionWorkflow::class)->shouldReceive('audit')->andThrow(new RuntimeException('Synthetic audit failure')); $this->withoutExceptionHandling(); expect(fn () => $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null]))->toThrow(RuntimeException::class, 'Synthetic audit failure'); expect($this->f['warehouse']->refresh()->geography_reference)->toBeNull(); }); test('operational geography invalid ambiguous or stale references never change the operational registries', function () { $service = app(AdministrativeGeography::class); $bad = collect($service->catalogue()['records'])->first(fn ($record) => in_array('duplicate_code_path', $record['issues'], true)); foreach ([['record_id' => '999999'], ['record_id' => $bad['id']], ['catalogue' => str_repeat('0', 64)]] as $change) { $reference = [...$this->reference, ...$change]; $this->post($this->warehouseUrl, ['geography_reference' => $reference, 'expected_reference' => null])->assertSessionHasErrors('geography_reference'); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => $reference])->assertSessionHasErrors('geography_reference'); } expect($this->asset->refresh()->geography_reference)->toBeNull()->and($this->f['warehouse']->refresh()->geography_reference)->toBeNull(); }); test('operational geography equipment updates retain native version checks area and approved documents', function () { $before = []; foreach (['requisitions', 'requisition_items', 'approval_steps', 'purchase_orders', 'purchase_order_items'] as $table) { $before[$table] = DB::table($table)->orderBy('id')->get()->toArray(); } $payload = operationalAssetData($this->asset); $this->post('/equipamentos/activos', [...$payload, 'geography_reference' => $this->reference])->assertSessionHasNoErrors(); expect($this->asset->refresh()->geography_reference)->toBe($this->reference)->and($this->asset->area)->toBe('Área textual original')->and($this->asset->version)->toBe(2); $this->post('/equipamentos/activos', [...$payload, 'geography_reference' => null])->assertSessionHasErrors('version'); $this->post('/equipamentos/activos', operationalAssetData($this->asset))->assertSessionHasNoErrors(); expect($this->asset->refresh()->geography_reference)->toBe($this->reference); foreach ($before as $table => $rows) { expect(DB::table($table)->orderBy('id')->get()->toArray())->toEqual($rows); } $this->get('/equipamentos/'.$this->asset->id)->assertInertia(fn (Assert $page) => $page->where('geography.path.0.name', 'Niassa')); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => null])->assertSessionHasNoErrors(); expect($this->asset->refresh()->geography_reference)->toBeNull(); }); test('operational geography warehouse filter includes descendants and does not rewrite receipt snapshots', function () { app(ReceiptWorkflow::class)->confirm($this->f['receiver'], $this->f['order'], receiptPayload($this->f)); $before = DB::table('stock_movements')->orderBy('id')->get()->toArray(); $child = collect(app(AdministrativeGeography::class)->catalogue()['records'])->first(fn ($record) => $record['selectable'] && $record['level'] > 1 && $record['codes'][0] === '01'); $this->post($this->warehouseUrl, ['geography_reference' => [...$this->reference, 'record_id' => $child['id']], 'expected_reference' => null])->assertSessionHasNoErrors(); $this->get('/stock?location=2')->assertInertia(fn (Assert $page) => $page->has('warehouses', 1)->has('movements.data', 1)->has('balances', 1)); $this->get('/stock?location=999999')->assertSessionHasErrors('geography_reference'); expect(DB::table('stock_movements')->orderBy('id')->get()->toArray())->toEqual($before); }); test('operational geography equipment filter retains tenant and department scope', function () { $this->asset->update(['geography_reference' => $this->reference]); $otherDepartment = Department::query()->create(['tenant_id' => $this->f['tenant']->id, 'name' => 'Outra área']); $data = $this->asset->only(['tenant_id', 'created_by', 'description', 'area', 'status', 'criticality_level_id']); Equipment::query()->create([...$data, 'department_id' => $otherDepartment->id, 'code' => 'FORA-AREA', 'geography_reference' => $this->reference]); $otherTenant = Tenant::query()->create(['name' => 'Outra empresa']); Equipment::query()->create([...$data, 'tenant_id' => $otherTenant->id, 'department_id' => $otherDepartment->id, 'code' => 'FORA-EMPRESA', 'geography_reference' => $this->reference]); $head = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['department']->id, 'purchasing_role' => PurchasingRole::DepartmentHead]); $this->actingAs($head)->get('/equipamentos?location=2')->assertInertia(fn (Assert $page) => $page->has('equipment', 1)->where('equipment.0.id', $this->asset->id)); }); test('operational geography missing columns hide only the new controls and preserve legacy creation', function () { foreach (['warehouses', 'equipment'] as $table) { Schema::table($table, fn ($schema) => $schema->dropColumn('geography_reference')); } $this->get('/stock')->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', false)); $this->get('/equipamentos')->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', false)); $this->actingAs($this->f['issuer'])->get('/fornecedores')->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', true)); $this->actingAs($this->f['receiver']); $this->post('/stock/armazens', ['code' => 'LEGACY', 'name' => 'Cadastro sem nova coluna'])->assertSessionHasNoErrors(); $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null])->assertServiceUnavailable(); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => $this->reference])->assertSessionHasErrors('geography_reference'); }); test('operational geography maintenance access cannot mutate either registry even without route middleware', function () { $this->withoutMiddleware(EnsureOrganizationAccess::class); $this->partialMock(OrganizationAccess::class)->shouldReceive('maintenanceOnly')->andReturn(true); $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null])->assertForbidden(); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => $this->reference])->assertForbidden(); expect($this->f['warehouse']->refresh()->geography_reference)->toBeNull()->and($this->asset->refresh()->geography_reference)->toBeNull(); }); test('operational geography equipment audit failure rolls back version and location together', function () { $before = operationalAssetData($this->asset); $this->partialMock(RequisitionWorkflow::class)->shouldReceive('audit')->andThrow(new RuntimeException('Synthetic equipment audit failure')); $this->withoutExceptionHandling(); expect(fn () => $this->post('/equipamentos/activos', [...$before, 'geography_reference' => $this->reference]))->toThrow(RuntimeException::class, 'Synthetic equipment audit failure'); expect($this->asset->refresh()->geography_reference)->toBeNull()->and($this->asset->version)->toBe($before['version']); }); test('operational geography revalidates current equipment authority before saving', function () { $actor = $this->f['receiver']; $this->partialMock(ConflictGuard::class)->shouldReceive('lockCompany')->once()->andReturnUsing(function () use ($actor) { DB::table('users')->where('id', $actor->id)->update(['purchasing_role' => PurchasingRole::Finance->value]); }); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => $this->reference])->assertForbidden(); expect($this->asset->refresh()->geography_reference)->toBeNull(); }); test('operational geography can be suspended without disabling company supplier or catalogue selection', function () { config(['geography.operational_enabled' => false]); expect(app(AdministrativeGeography::class)->enabled())->toBeTrue(); $this->getJson('/api/divisao-administrativa')->assertOk()->assertJsonCount(11, 'data'); $this->get('/stock')->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', false)); $this->get('/equipamentos')->assertInertia(fn (Assert $page) => $page->where('geographyEnabled', false)); $this->post($this->warehouseUrl, ['geography_reference' => $this->reference, 'expected_reference' => null])->assertServiceUnavailable(); $this->post('/equipamentos/activos', [...operationalAssetData($this->asset), 'geography_reference' => $this->reference])->assertSessionHasErrors('geography_reference'); });