@@ -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]);
|
||||
|
||||
Reference in New Issue
Block a user