commit9
tests / ci (push) Canceled after 0s

This commit is contained in:
2026-10-30 20:02:30 +02:00
parent 6d14b4e6f0
commit c6373365bf
225 changed files with 15365 additions and 285 deletions
+56
View File
@@ -28,6 +28,7 @@ use App\Models\Tenant;
use App\Models\User;
use App\Models\Warehouse;
use App\Services\BudgetWorkflow;
use App\Services\ComparisonPolicy;
use App\Services\ConflictGuard;
use App\Services\DocumentaryQualificationWorkflow;
use App\Services\EquipmentWorkflow;
@@ -37,6 +38,7 @@ use App\Services\ProposalComparison;
use App\Services\ProposalOpeningWorkflow;
use App\Services\PurchaseControlPolicyWorkflow;
use App\Services\PurchaseRevisionWorkflow;
use App\Services\RequisitionCatalogue;
use App\Services\RequisitionWorkflow;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
@@ -102,6 +104,32 @@ function issuePurchaseOrder(object $test, PurchaseAward $award): PurchaseOrder
return PurchaseOrder::query()->firstOrFail();
}
test('catalogue origin survives order issue and automatically identifies received stock without remapping', function () {
$this->actingAs($this->admin);
$stock = StockItem::query()->create(['tenant_id' => $this->tenant->id, 'created_by' => $this->admin->id, 'code' => 'CAT-ORDER', 'name' => $this->item->description, 'unit' => $this->item->unit]);
app(RequisitionCatalogue::class)->save($this->admin, $stock->id, ['version' => 0, 'stock_basis' => app(RequisitionCatalogue::class)->basis($stock), 'category' => $this->item->category,
'purchase_type' => 'consumable', 'published' => true, 'reason' => 'Fonte fictícia da linha antes de emitir.']);
$entry = DB::table('requisition_catalogue')->where('stock_item_id', $stock->id)->sole();
// Bind the synthetic fixture's source before exercising the real issue workflow.
$resolved = app(RequisitionCatalogue::class)->line($this->owner, ['catalogue_entry_id' => $entry->id, 'catalogue_version' => 1], []);
$this->item->update(['catalogue_entry_id' => $entry->id, 'catalogue_snapshot' => $resolved['catalogue_snapshot']]);
$order = issuePurchaseOrder($this, selectPurchaseAward($this));
expect($order->snapshot['catalogue_sources'][$this->item->id]['stock_item_id'])->toBe($stock->id);
$receiver = User::factory()->create(['tenant_id' => $this->tenant->id, 'department_id' => $this->department->id, 'purchasing_role' => PurchasingRole::Warehouse]);
$warehouse = Warehouse::query()->create(['tenant_id' => $this->tenant->id, 'created_by' => $receiver->id, 'code' => 'REC-CAT', 'name' => 'Armazém fictício']);
$wrong = StockItem::query()->create(['tenant_id' => $this->tenant->id, 'created_by' => $receiver->id, 'code' => 'WRONG-CAT', 'name' => 'Artigo distinto', 'unit' => $stock->unit]);
$line = $order->items()->sole();
$body = ['idempotency_key' => (string) Str::uuid(), 'delivery_note' => 'GUIA-CAT-1', 'received_on' => today()->toDateString(), 'warehouse_id' => $warehouse->id,
'items' => [['purchase_order_item_id' => $line->id, 'received_quantity' => '1.000', 'accepted_quantity' => '1.000', 'quality_status' => 'compliant']]];
$bad = $body;
$bad['items'][0]['stock_item_id'] = $wrong->id;
$this->actingAs($receiver)->postJson('/ordens/'.$order->id.'/recepcoes', $bad)->assertUnprocessable();
$this->get('/ordens/'.$order->id.'/recepcoes')->assertInertia(fn (Assert $p) => $p->where('progress.lines.0.stock_item_id', $stock->id));
$this->postJson('/ordens/'.$order->id.'/recepcoes', $body)->assertCreated();
$this->postJson('/ordens/'.$order->id.'/recepcoes', $body)->assertOk()->assertJsonPath('created', false);
expect(StockMovement::query()->where('stock_item_id', $stock->id)->sum('quantity_milli'))->toBe(1000);
});
function prepareEquipmentInvestment(object $test, string $ceiling = '100.00', bool $includesTax = true): InvestmentPolicy
{
require_once __DIR__.'/../Support/InvestmentFixture.php';
@@ -173,6 +201,34 @@ test('award starts a new approval cycle and order freezes all agreed data', func
$this->get('/ordens')->assertInertia(fn (Assert $page) => $page->has('orders.data', 1)->has('awards.data', 1));
});
test('discount evidence and net prices survive award order and printable report', function () {
$quote = $this->quotes[0];
$terms = ['version' => 1, 'discount_basis' => 'unit_before_vat', 'rounding' => 'net_unit_half_up_cent',
'lines' => [['requisition_item_id' => $this->item->id, 'gross_unit_minor' => 12500, 'discount_type' => 'percent', 'discount_value' => '20.00', 'net_unit_minor' => 10000]]];
$quote->update(['commercial_terms' => $terms]);
$award = selectPurchaseAward($this);
$order = issuePurchaseOrder($this, $award);
expect($order->items()->sole()->unit_price_minor)->toBe(10000)
->and($order->snapshot['decision_matrix'][0]['commercial_terms'])->toBe($terms)
->and($order->total_minor)->toBe(10000);
$this->actingAs($this->buyer)->get('/ordens/'.$order->id.'/imprimir')->assertOk()->assertSee('20.00%');
});
test('comparison rule is printed with the order and policy tampering invalidates an approved award', function () {
$policy = [...ComparisonPolicy::DEFAULTS, 'revision' => 1, 'configured' => true];
$this->r->update(['comparison_policy' => $policy]);
$award = selectPurchaseAward($this);
approvePurchaseAward($this, $award);
$this->r->update(['comparison_policy' => [...$policy, 'minimum_proposals' => 1]]);
$this->actingAs($this->buyer)->post('/adjudicacoes/'.$award->id.'/emitir')->assertSessionHasErrors('comparison');
expect(PurchaseOrder::query()->count())->toBe(0);
$this->r->update(['comparison_policy' => $policy]);
$this->post('/adjudicacoes/'.$award->id.'/emitir')->assertSessionHasNoErrors();
$order = PurchaseOrder::query()->sole();
expect($order->snapshot['comparison_policy'])->toBe($policy);
$this->get('/ordens/'.$order->id.'/imprimir')->assertOk()->assertSee('Regra de comparação preservada: revisão 1');
});
test('final approval tier uses inclusive boundaries of the stored process rules', function (int $priceMinor, int $count) {
awardPrices($this, $priceMinor);
$this->tenant->update(['first_limit_minor' => 1, 'second_limit_minor' => 2]);