Files
compras/tests/Feature/WarehousePilotJourneyTest.php
T
2026-11-02 20:27:11 +02:00

69 lines
6.1 KiB
PHP

<?php
use App\Models\Requisition;
use App\Models\WarehouseRequestEvent;
use App\Services\AutomaticPurchases;
use App\Services\ReceiptWorkflow;
use App\Services\RequisitionCatalogue;
use App\Services\StockWorkflow;
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/ReceiptFixture.php';
require_once __DIR__.'/../Support/SupplyFixture.php';
test('synthetic pilot connects partial delivery shortage purchase draft replenishment reservation refill and receipt without an invoice or automatic approval', function () {
$f = receiptFixture('300.000');
supplyGrant($f['issuer'], ['requester', 'warehouse_request_prepare', 'supply_prepare']);
supplyGrant($f['receiver'], ['warehouse', 'warehouse_request_review', 'supply_review']);
supplyGrant($f['otherReceiver'], ['warehouse', 'warehouse_request_deliver']);
$this->actingAs($f['receiver']);
app(ReceiptWorkflow::class)->confirm($f['receiver'], $f['order'], receiptPayload($f, '120.000'));
app(RequisitionCatalogue::class)->save($f['receiver'], $f['stock']->id, ['version' => 0, 'stock_basis' => app(RequisitionCatalogue::class)->basis($f['stock']), 'category' => 'Material', 'purchase_type' => 'consumable', 'published' => true, 'reason' => 'Publicação do artigo para o ensaio sintético integrado.']);
$this->actingAs($f['issuer']);
$policy = app(SupplyPlanning::class)->save($f['issuer'], 'policy', supplyInput([...supplyPolicy($f), 'purpose' => 'internal', 'origin' => 'local']));
$this->actingAs($f['receiver']);
$policy = app(SupplyPlanning::class)->save($f['receiver'], 'policy', supplyInput([], 'approve', $policy));
$this->actingAs($f['issuer']);
$rule = app(AutomaticPurchases::class)->save($f['issuer'], ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'save', 'reason' => 'Estimativa de dez MZN por unidade apenas para o ensaio descartável.', 'data' => ['policy_key' => $policy->record_key, 'requester_id' => $f['issuer']->id, 'cost_center_id' => $f['r']->cost_center_id, 'unit_price' => '10.00', 'tax_rate' => '0', 'needed_days' => 5, 'valid_until' => today()->addMonth()->toDateString()]]);
$this->actingAs($f['receiver']);
app(AutomaticPurchases::class)->save($f['receiver'], ['operation_key' => (string) Str::uuid(), 'record_key' => $rule->record_key, 'version' => 1, 'action' => 'approve', 'reason' => 'Conferência independente da parametrização sintética.', 'data' => []]);
$this->actingAs($f['issuer']);
$request = app(WarehouseRequests::class)->draft($f['issuer'], ['operation_key' => (string) Str::uuid(), 'warehouse_id' => $f['warehouse']->id, 'cost_center_id' => $f['r']->cost_center_id, 'destination' => 'Oficina sintética do piloto', 'reason' => 'Necessidade interna de material para manutenção no ensaio.', 'priority' => 'normal', 'lines' => [['stock_item_id' => $f['stock']->id, 'quantity' => '125.000', 'use' => 'internal']]]);
$command = function ($actor, string $action, array $extra = []) use ($request) {
$this->actingAs($actor);
$version = app(WarehouseFulfilment::class)->progress($request)['version'];
$this->postJson('/stock/requisicoes/'.$request->id.'/eventos', ['operation_key' => (string) Str::uuid(), 'version' => $version, 'action' => $action, 'reason' => 'Intervenção efectiva no percurso sintético do piloto.', ...$extra])->assertRedirect();
};
$command($f['issuer'], 'submit', ['partial_allowed' => true]);
$command($f['receiver'], 'authorize', ['partial_allowed' => true]);
app(AutomaticPurchases::class)->evaluate($f['receiver'], $f['stock']->id, $f['warehouse']->id);
$need = app(SupplyPlanning::class)->latest('auto_need')->sole();
expect($need->data['purchases'])->toHaveCount(1);
$purchase = Requisition::query()->findOrFail($need->data['purchases'][0]['id']);
expect($purchase->status)->toBe('draft')->and($purchase->items()->sole()->quantity)->toBe('108.000')->and($purchase->steps()->count())->toBe(0);
$this->actingAs($f['otherReceiver'])->get('/stock/requisicoes?view=delivery')->assertOk()->assertInertia(fn (Assert $p) => $p->where('records.total', 1)->where('records.data.0.health.needs_reservation', true)->where('records.data.0.health.delivery_ready', true));
$command($f['otherReceiver'], 'deliver', ['lines' => [['index' => 0, 'quantity' => '100.000']]]);
$delivery = WarehouseRequestEvent::query()->where('warehouse_request_id', $request->id)->where('action', 'deliver')->sole();
$command($f['issuer'], 'receive', ['delivery_event_id' => $delivery->id]);
expect(app(WarehouseFulfilment::class)->progress($request)['reserved'])->toBe([20000]);
$this->actingAs($f['otherReceiver']);
// Independent stock can arrive from an already issued order. It does not approve the automatic draft.
app(ReceiptWorkflow::class)->confirm($f['otherReceiver'], $f['order'], receiptPayload($f, '108.000'));
$command($f['receiver'], 'reserve');
expect(app(WarehouseFulfilment::class)->progress($request)['reserved'])->toBe([25000]);
$command($f['otherReceiver'], 'deliver', ['lines' => [['index' => 0, 'quantity' => '25.000']]]);
$last = WarehouseRequestEvent::query()->where('warehouse_request_id', $request->id)->where('action', 'deliver')->latest('id')->firstOrFail();
$command($f['issuer'], 'receive', ['delivery_event_id' => $last->id]);
$p = app(WarehouseFulfilment::class)->progress($request);
expect($p['state'])->toBe('closed')->and($p['reserved'])->toBe([0])->and($p['delivered'])->toBe([125000])
->and(app(StockWorkflow::class)->balance($f['stock']->id, $f['warehouse']->id)['quantity'])->toBe(103000)
->and($purchase->fresh()->status)->toBe('draft')->and(DB::table('invoices')->count())->toBe(0)->and(DB::table('budget_entries')->count())->toBe(0)
->and(Requisition::query()->where('status', 'draft')->count())->toBe(1);
$this->get('/stock/requisicoes?view=receipt')->assertOk()->assertInertia(fn (Assert $p) => $p->where('records.total',0));
});