commit14
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Requisition;
|
||||
use App\Models\SupplyRecord;
|
||||
use App\Models\User;
|
||||
use App\Services\AutomaticPurchases;
|
||||
use App\Services\InternalSupplyTrace;
|
||||
use App\Services\InventoryReversals;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
use App\Services\SupplyPlanning;
|
||||
use App\Services\WarehouseFulfilment;
|
||||
use App\Services\WarehouseRequests;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
require_once __DIR__.'/../Support/SuggestionFixture.php';
|
||||
|
||||
beforeEach(function () {
|
||||
$this->f = suggestionFixture();
|
||||
app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'suspend', $this->f['policy']));
|
||||
supplyGrant($this->f['issuer'], ['warehouse_request_prepare']);
|
||||
supplyGrant($this->f['receiver'], ['warehouse_request_review']);
|
||||
supplyGrant($this->f['otherReceiver'], ['warehouse', 'warehouse_request_deliver']);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$policy = app(SupplyPlanning::class)->save($this->f['issuer'], 'policy', supplyInput([...supplyPolicy($this->f), 'purpose' => 'internal', 'agreement_key' => $this->f['agreement']->record_key]));
|
||||
$this->actingAs($this->f['receiver']);
|
||||
$policy = app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'approve', $policy));
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$rule = app(AutomaticPurchases::class)->save($this->f['issuer'], ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'save', 'reason' => 'Parametrização sintética para integrar os módulos internos.',
|
||||
'data' => ['policy_key' => $policy->record_key, 'requester_id' => $this->f['issuer']->id, 'cost_center_id' => $this->f['r']->cost_center_id, 'unit_price' => null, 'tax_rate' => null, 'needed_days' => 5, 'valid_until' => today()->addMonth()->toDateString()]]);
|
||||
$this->actingAs($this->f['receiver']);
|
||||
app(AutomaticPurchases::class)->save($this->f['receiver'], ['operation_key' => (string) Str::uuid(), 'record_key' => $rule->record_key, 'version' => $rule->version, 'action' => 'approve', 'reason' => 'Conferência independente da regra no ensaio.', 'data' => []]);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$this->request = app(WarehouseRequests::class)->draft($this->f['issuer'], ['operation_key' => (string) Str::uuid(), 'warehouse_id' => $this->f['warehouse']->id,
|
||||
'cost_center_id' => $this->f['r']->cost_center_id, 'priority' => 'normal', 'destination' => 'Oficina do ensaio', 'reason' => 'Necessidade interna de manutenção para o ensaio.',
|
||||
'lines' => [['stock_item_id' => $this->f['stock']->id, 'quantity' => '125.000', 'use' => 'internal']]]);
|
||||
app(WarehouseFulfilment::class)->command($this->f['issuer'], $this->request->id, ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'submit', 'partial_allowed' => true, 'reason' => 'Submissão efectiva do pedido para o ensaio.']);
|
||||
app(AutomaticPurchases::class)->evaluate($this->f['issuer'], $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
$this->need = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
$this->purchase = Requisition::query()->findOrFail($this->need->data['requisition_ids'][0]);
|
||||
});
|
||||
|
||||
function internalTrace($test, ?User $actor = null): array
|
||||
{
|
||||
$actor ??= $test->f['issuer'];
|
||||
$test->actingAs($actor);
|
||||
|
||||
return app(InternalSupplyTrace::class)->forRequest($actor, $test->request, app(WarehouseFulfilment::class)->progress($test->request));
|
||||
}
|
||||
|
||||
/** A documentary fixture version; not a new business approval or manual-link UI. */
|
||||
function internalTraceVersion($test, array $data, ?array $snapshot = null): SupplyRecord
|
||||
{
|
||||
$old = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
$copy = $old->replicate();
|
||||
$copy->fill(['version' => $old->version + 1, 'data' => $data, 'snapshot' => $snapshot ?? $old->snapshot,
|
||||
'operation_key' => (string) Str::uuid(), 'input_hash' => hash('sha256', (string) Str::uuid()), 'created_at' => now()])->save();
|
||||
|
||||
return $copy;
|
||||
}
|
||||
|
||||
test('native modules link warehouse shortage to the canonical purchase and its pending human approval', function () {
|
||||
$trace = internalTrace($this);
|
||||
expect($trace['groups'])->toHaveCount(1)->and($trace['groups'][0]['purchases'][0]['id'])->toBe($this->purchase->id)
|
||||
->and($trace['groups'][0]['purchases'][0]['status'])->toBe('draft')->and($trace['groups'][0]['unreserved_milli'])->toBe(125000);
|
||||
app(RequisitionWorkflow::class)->submit($this->f['issuer'], $this->purchase);
|
||||
$trace = internalTrace($this);
|
||||
expect($trace['groups'][0]['purchases'][0]['status'])->toBe('pending')->and($trace['groups'][0]['purchases'][0]['next_task'])->not->toBeNull()
|
||||
->and($this->purchase->fresh()->steps->where('decision', 'approved'))->toHaveCount(0);
|
||||
$this->get('/stock/requisicoes/'.$this->request->id)->assertOk()->assertInertia(fn (Assert $page) => $page->where('supplyTrace.groups.0.purchases.0.id', $this->purchase->id));
|
||||
$this->get('/requisicoes/'.$this->purchase->id)->assertOk()->assertInertia(fn (Assert $page) => $page->where('automaticSource.origins.0.href', '/stock/requisicoes/'.$this->request->id));
|
||||
});
|
||||
|
||||
test('trace queries reuse documents and do not create purchases decisions movements or reservations', function () {
|
||||
$tables = ['requisitions', 'approval_steps', 'warehouse_request_events', 'warehouse_reservations', 'stock_movements', 'supply_records', 'audit_logs', 'invoices', 'budget_entries'];
|
||||
$before = [];
|
||||
foreach ($tables as $table) {
|
||||
$before[$table] = DB::table($table)->get()->toArray();
|
||||
}
|
||||
internalTrace($this);
|
||||
internalTrace($this);
|
||||
foreach ($tables as $table) {
|
||||
expect(DB::table($table)->get()->toArray())->toEqual($before[$table]);
|
||||
}
|
||||
});
|
||||
|
||||
test('warehouse responsibilities do not acquire access to other peoples purchase processes or financial values', function () {
|
||||
$trace = internalTrace($this, $this->f['otherReceiver']);
|
||||
expect($trace['groups'][0]['purchases'])->toBe([])->and($trace['groups'][0]['restricted'])->toBeTrue()
|
||||
->and(json_encode($trace))->not->toContain($this->purchase->number)->not->toContain('unit_price')->not->toContain('total_minor');
|
||||
$this->actingAs($this->f['otherReceiver'])->get('/stock/requisicoes/'.$this->request->id)->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page->where('supplyTrace.groups.0.restricted', true)->has('supplyTrace.groups.0.purchases', 0));
|
||||
});
|
||||
|
||||
test('later shared needs do not retrospectively assign a future purchase to the old warehouse request', function () {
|
||||
$new = internalTraceVersion($this, [...$this->need->data, 'requisition_ids' => [$this->purchase->id, $this->f['r']->id]], [...$this->need->snapshot, 'origins' => []]);
|
||||
$trace = internalTrace($this);
|
||||
expect($trace['groups'][0]['current_need_id'])->toBe($new->id)->and($trace['groups'][0]['purchases'])->toHaveCount(1)
|
||||
->and($trace['groups'][0]['purchases'][0]['id'])->toBe($this->purchase->id);
|
||||
});
|
||||
|
||||
test('documentary receipt projection follows the linked issued order and respects the reversal engine', function () {
|
||||
// Already issued order/receipt from the fixture, not the new automatic draft.
|
||||
internalTraceVersion($this, [...$this->need->data, 'requisition_ids' => [$this->f['r']->id]]);
|
||||
$trace = internalTrace($this);
|
||||
$row = $trace['groups'][0]['purchases'][0]['orders'][0];
|
||||
expect($row['id'])->toBe($this->f['order']->id)->and($row['effective_received_milli'])->toBe(120000)
|
||||
->and($row['receipts'][0]['reversed'])->toBeFalse();
|
||||
$corrections = Mockery::mock(InventoryReversals::class)->makePartial();
|
||||
$corrections->shouldReceive('corrections')->with('receipt')->andReturn([$row['receipts'][0]['item_id'] => $this->need]);
|
||||
app()->instance(InventoryReversals::class, $corrections);
|
||||
$trace = internalTrace($this);
|
||||
expect($trace['groups'][0]['purchases'][0]['orders'][0]['effective_received_milli'])->toBe(0)
|
||||
->and($trace['groups'][0]['purchases'][0]['orders'][0]['receipts'][0]['reversed'])->toBeTrue()
|
||||
->and((int) DB::table('goods_receipt_items')->sum('accepted_milli'))->toBe(120000);
|
||||
});
|
||||
|
||||
test('unrelated people and companies cannot read or link warehouse requests through a purchase', function () {
|
||||
$outsider = User::factory()->create(['tenant_id' => $this->f['issuer']->tenant_id, 'department_id' => $this->f['issuer']->department_id]);
|
||||
supplyGrant($outsider, ['procurement']);
|
||||
$this->actingAs($outsider);
|
||||
$origin = app(InternalSupplyTrace::class)->purchaseOrigin($outsider, $this->purchase);
|
||||
expect($origin['origins'][0]['href'])->toBeNull();
|
||||
$this->get('/stock/requisicoes/'.$this->request->id)->assertForbidden();
|
||||
$foreign = receiptFixture();
|
||||
supplyGrant($foreign['issuer'], ['admin']);
|
||||
$this->actingAs($foreign['issuer'])->get('/stock/requisicoes/'.$this->request->id)->assertNotFound();
|
||||
});
|
||||
Reference in New Issue
Block a user