commit14
This commit is contained in:
@@ -1,24 +1,48 @@
|
||||
<?php
|
||||
|
||||
use App\Models\CriticalityLevel;
|
||||
use App\Models\Equipment;
|
||||
use App\Models\EquipmentSparePart;
|
||||
use App\Models\Requisition;
|
||||
use App\Models\SparePart;
|
||||
use App\Models\SupplyRecord;
|
||||
use App\Models\User;
|
||||
use App\Services\AutomaticPurchases;
|
||||
use App\Services\InventoryLots;
|
||||
use App\Services\ReplenishmentQueue;
|
||||
use App\Services\ReplenishmentRun;
|
||||
use App\Services\RequisitionCatalogue;
|
||||
use App\Services\RequisitionWorkflow;
|
||||
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 Illuminate\Validation\ValidationException;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
require_once __DIR__.'/../Support/SuggestionFixture.php';
|
||||
|
||||
function pilotWork($test): int
|
||||
{
|
||||
activateAutomaticRule($test);
|
||||
$queue = Mockery::mock(ReplenishmentQueue::class)->makePartial();
|
||||
$queue->shouldReceive('interactive')->andReturn(false);
|
||||
app()->instance(ReplenishmentQueue::class, $queue);
|
||||
automaticIssue($test, '120.000');
|
||||
|
||||
return DB::table('replenishment_work')->where('tenant_id', $test->f['tenant']->id)->where('stock_item_id', $test->f['stock']->id)->value('id');
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
$this->f = suggestionFixture();
|
||||
app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'suspend', $this->f['policy']));
|
||||
$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]));
|
||||
$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']);
|
||||
$this->f['policy']=app(SupplyPlanning::class)->save($this->f['receiver'],'policy',supplyInput([],'approve',$policy));
|
||||
$this->f['policy'] = app(SupplyPlanning::class)->save($this->f['receiver'], 'policy', supplyInput([], 'approve', $policy));
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$this->input = ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'save', 'reason' => 'Parametrização autorizada para o ensaio isolado.',
|
||||
'data' => ['policy_key' => $this->f['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()]];
|
||||
@@ -33,11 +57,11 @@ function activateAutomaticRule($test)
|
||||
return app(AutomaticPurchases::class)->save($test->f['receiver'], ['operation_key' => (string) Str::uuid(), 'record_key' => $rule->record_key, 'version' => $rule->version, 'action' => 'approve', 'reason' => 'Activação independente da parametrização verificada.', 'data' => []]);
|
||||
}
|
||||
|
||||
function automaticIssue($test, string $quantity)
|
||||
function automaticIssue($test, string $quantity, array $extra = [])
|
||||
{
|
||||
$test->actingAs($test->f['receiver']);
|
||||
|
||||
return app(StockWorkflow::class)->record($test->f['receiver'], ['kind' => 'issue', 'reference' => 'TEST-'.Str::uuid(), 'idempotency_key' => (string) Str::uuid(), 'responsible_id' => $test->f['issuer']->id, 'warehouse_id' => $test->f['warehouse']->id, 'stock_item_id' => $test->f['stock']->id, 'quantity' => $quantity, 'destination' => 'Manutenção interna', 'reason' => 'Consumo físico confirmado no ensaio isolado.']);
|
||||
return app(StockWorkflow::class)->record($test->f['receiver'], ['kind' => 'issue', 'reference' => 'TEST-'.Str::uuid(), 'idempotency_key' => (string) Str::uuid(), 'responsible_id' => $test->f['issuer']->id, 'warehouse_id' => $test->f['warehouse']->id, 'stock_item_id' => $test->f['stock']->id, 'quantity' => $quantity, 'destination' => 'Manutenção interna', 'reason' => 'Consumo físico confirmado no ensaio isolado.', ...$extra]);
|
||||
}
|
||||
|
||||
test('automatic purchase reuses approved policy catalogue agreement and tax while preserving human submission and approvals', function () {
|
||||
@@ -66,6 +90,145 @@ test('missing configuration records a pending need and never invents a requester
|
||||
expect($need->state)->toBe('pending')->and($need->data['purchases'])->toBe([])->and($need->data['message'])->toContain('Parametrize')->and(Requisition::query()->count())->toBe($before);
|
||||
});
|
||||
|
||||
test('durable signals survive the missing immediate callback and scheduled recovery never impersonates or duplicates drafts', function () {
|
||||
$id = pilotWork($this);
|
||||
$before = Requisition::query()->count();
|
||||
$row = DB::table('replenishment_work')->find($id);
|
||||
expect($row->generation)->toBeGreaterThan($row->processed_generation);
|
||||
auth()->guard()->logout();
|
||||
expect(app(ReplenishmentQueue::class)->scheduled($id))->toBeTrue()->and(auth()->check())->toBeFalse();
|
||||
$r = Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->latest('id')->firstOrFail();
|
||||
expect(Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->count())->toBe($before + 1)->and($r->status)->toBe('draft');
|
||||
$audit = DB::table('audit_logs')->where('requisition_id', $r->id)->where('action', 'requisition.automatically_created')->sole();
|
||||
expect(json_decode($audit->after, true)['execution']['mode'])->toBe('scheduled_replenishment')->and($audit->ip_address)->toBeNull();
|
||||
DB::table('replenishment_work')->where('id', $id)->update(['due_at' => now()->subMinute()]);
|
||||
expect(app(ReplenishmentQueue::class)->scheduled($id))->toBeTrue();
|
||||
expect(Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->count())->toBe($before + 1)->and(app(ReplenishmentRun::class)->tenantId())->toBeNull();
|
||||
});
|
||||
|
||||
test('failure after draft creation rolls back the draft and checkpoint before a successful retry', function () {
|
||||
$id = pilotWork($this);
|
||||
$before = Requisition::query()->count();
|
||||
$checkpoint = DB::table('replenishment_work')->where('id', $id)->value('processed_generation');
|
||||
auth()->guard()->logout();
|
||||
$injected = false;
|
||||
DB::listen(function ($query) use (&$injected) {
|
||||
if (! $injected && str_contains($query->sql, 'replenishment_work') && str_contains($query->sql, 'processed_generation') && str_starts_with(strtolower($query->sql), 'update')) {
|
||||
$injected = true;
|
||||
throw new RuntimeException('Injected checkpoint interruption');
|
||||
}
|
||||
});
|
||||
expect(app(ReplenishmentQueue::class)->scheduled($id))->toBeFalse();
|
||||
expect(Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->count())->toBe($before);
|
||||
$row = DB::table('replenishment_work')->find($id);
|
||||
expect($row->processed_generation)->toBe($checkpoint)->and($row->failures)->toBe(1);
|
||||
DB::table('replenishment_work')->where('id', $id)->update(['due_at' => now()->subMinute()]);
|
||||
expect(app(ReplenishmentQueue::class)->scheduled($id))->toBeTrue();
|
||||
expect(Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->count())->toBe($before + 1);
|
||||
});
|
||||
|
||||
test('periodic discovery rechecks configured minimum and expired rules without stock movements', function () {
|
||||
activateAutomaticRule($this);
|
||||
automaticIssue($this, '100.000');
|
||||
auth()->guard()->logout();
|
||||
$queue = app(ReplenishmentQueue::class);
|
||||
$queue->discover();
|
||||
expect($queue->drain(100)['failed'])->toBe(0);
|
||||
$need = SupplyRecord::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->where('kind', 'auto_need')->latest('id')->firstOrFail();
|
||||
expect($need->data['purchases'])->toHaveCount(1);
|
||||
$count = DB::table('stock_movements')->where('tenant_id', $this->f['tenant']->id)->count();
|
||||
$this->travel(32)->days();
|
||||
expect($queue->drain(100)['failed'])->toBe(0);
|
||||
$latest = SupplyRecord::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->where('kind', 'auto_need')->latest('id')->firstOrFail();
|
||||
expect($latest->state)->toBe('pending')->and($latest->data['message'])->toContain('expirou')->and(DB::table('stock_movements')->where('tenant_id', $this->f['tenant']->id)->count())->toBe($count);
|
||||
});
|
||||
|
||||
test('pending replenishment pagination retains old needs and never exposes another tenant', function () {
|
||||
$tenant = $this->f['tenant']->id;
|
||||
for ($i = 0; $i < 105; $i++) {
|
||||
SupplyRecord::query()->create(['tenant_id' => $tenant, 'kind' => 'auto_need', 'record_key' => (string) Str::uuid(), 'version' => 1, 'state' => 'pending', 'stock_item_id' => $this->f['stock']->id, 'warehouse_id' => $this->f['warehouse']->id, 'created_by' => $this->f['issuer']->id, 'data' => ['message' => 'Necessidade pendente de teste.'], 'snapshot' => ['item' => ['id' => $this->f['stock']->id], 'warehouse' => ['id' => $this->f['warehouse']->id], 'origins' => []], 'reason' => 'Registo isolado para conferir paginação.', 'operation_key' => (string) Str::uuid(), 'input_hash' => hash('sha256', (string) $i), 'created_at' => now()->subDays(106 - $i)]);
|
||||
}
|
||||
$first = SupplyRecord::query()->where('kind', 'auto_need')->oldest('id')->value('id');
|
||||
$foreign = receiptFixture();
|
||||
SupplyRecord::query()->create(['tenant_id' => $foreign['tenant']->id, 'kind' => 'auto_need', 'record_key' => (string) Str::uuid(), 'version' => 1, 'state' => 'pending', 'stock_item_id' => $foreign['stock']->id, 'warehouse_id' => $foreign['warehouse']->id, 'created_by' => $foreign['issuer']->id, 'data' => ['message' => 'Outra empresa'], 'snapshot' => [], 'reason' => 'Origem de outra empresa no ensaio isolado.', 'operation_key' => (string) Str::uuid(), 'input_hash' => str_repeat('a', 64), 'created_at' => now()->subYear()]);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$this->get('/stock/reposicao?need_state=pending')->assertOk()->assertInertia(fn (Assert $p) => $p->where('needs.total', 105)->has('needs.data', 20)->where('needs.data.0.id', $first)->where('needs.current_page', 1));
|
||||
$this->get('/stock/reposicao?need_state=pending&needs_page=6')->assertOk()->assertInertia(fn (Assert $p) => $p->where('needs.total', 105)->has('needs.data', 5)->where('needs.current_page', 6));
|
||||
});
|
||||
|
||||
test('a rolled back physical command also rolls back its durable replenishment signal', function () {
|
||||
$this->actingAs($this->f['receiver']);
|
||||
$before = DB::table('replenishment_work')->where('tenant_id', $this->f['tenant']->id)->value('generation');
|
||||
expect(fn () => DB::transaction(function () {
|
||||
automaticIssue($this, '1.000');
|
||||
throw new RuntimeException('Interrupted physical transaction');
|
||||
}))->toThrow(RuntimeException::class);
|
||||
expect(DB::table('replenishment_work')->where('tenant_id', $this->f['tenant']->id)->value('generation'))->toBe($before)
|
||||
->and(app(StockWorkflow::class)->balance($this->f['stock']->id, $this->f['warehouse']->id)['quantity'])->toBe(120000);
|
||||
});
|
||||
|
||||
test('background execution cannot be borrowed by an authenticated session', function () {
|
||||
$id = pilotWork($this);
|
||||
expect(fn () => app(ReplenishmentQueue::class)->scheduled($id))->toThrow(HttpException::class);
|
||||
expect(app(ReplenishmentRun::class)->tenantId())->toBeNull();
|
||||
});
|
||||
|
||||
test('the pending work list exposes interrupted articles but recovery still requires an explicit operational task', function () {
|
||||
$id = pilotWork($this);
|
||||
DB::table('replenishment_work')->where('id', $id)->update(['failures' => 1, 'last_error' => 'Recuperação automática pendente.']);
|
||||
$admin = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['issuer']->department_id]);
|
||||
supplyGrant($admin, ['admin']);
|
||||
$this->actingAs($admin)->get('/stock/reposicao')->assertOk()->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->where('canReview', false)->where('workItems.total', 1)->where('workItems.data.0.id', $id));
|
||||
$this->post('/stock/reposicao/trabalhos/'.$id.'/avaliar')->assertForbidden();
|
||||
$this->actingAs($this->f['issuer'])->post('/stock/reposicao/trabalhos/'.$id.'/avaliar')->assertRedirect();
|
||||
});
|
||||
|
||||
test('replenishment workers do not process the SGF composition', function () {
|
||||
$id = pilotWork($this);
|
||||
auth()->guard()->logout();
|
||||
config(['work_context.composition' => 'sgf']);
|
||||
expect(app(ReplenishmentQueue::class)->ready())->toBeFalse();
|
||||
expect(fn () => app(ReplenishmentQueue::class)->scheduled($id))->toThrow(HttpException::class);
|
||||
expect(app(ReplenishmentRun::class)->tenantId())->toBeNull();
|
||||
});
|
||||
|
||||
test('periodic evaluation detects lot expiry without physical movements and uses the existing draft workflow', function () {
|
||||
supplyGrant($this->f['issuer'], ['inventory_lot_prepare']);
|
||||
supplyGrant($this->f['receiver'], ['inventory_lot_review', 'inventory_lot_release']);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$setup = app(InventoryLots::class)->save($this->f['issuer'], supplyInput(['stock_item_id' => $this->f['stock']->id, 'warehouse_id' => $this->f['warehouse']->id, 'requires_expiry' => true, 'minimum_remaining_days' => 0, 'lots' => [['code' => 'EXP-PILOT', 'expires_on' => today()->addDays(2)->toDateString()]]], 'prepare'));
|
||||
$this->actingAs($this->f['receiver']);
|
||||
app(InventoryLots::class)->save($this->f['receiver'], supplyInput([], 'activate', $setup));
|
||||
$lot = app(InventoryLots::class)->context($this->f['stock']->id, $this->f['warehouse']->id)['lots'][0];
|
||||
app(InventoryLots::class)->save($this->f['receiver'], supplyInput(['lot_id' => $lot['id'], 'warehouse_id' => $this->f['warehouse']->id, 'state_version' => $lot['state_version']], 'release', SupplyRecord::query()->findOrFail($lot['metadata_id'])));
|
||||
activateAutomaticRule($this);
|
||||
$before = Requisition::query()->count();
|
||||
$moves = DB::table('stock_movements')->count();
|
||||
auth()->guard()->logout();
|
||||
$this->travel(3)->days();
|
||||
$queue = app(ReplenishmentQueue::class);
|
||||
$queue->discover();
|
||||
expect($queue->drain(100)['failed'])->toBe(0)->and(DB::table('stock_movements')->count())->toBe($moves)
|
||||
->and(Requisition::withoutGlobalScopes()->where('tenant_id', $this->f['tenant']->id)->count())->toBe($before + 1);
|
||||
});
|
||||
|
||||
test('a rejected automatic purchase does not reopen itself or repeat the rejected purchase', function () {
|
||||
activateAutomaticRule($this);
|
||||
automaticIssue($this, '120.000');
|
||||
app(AutomaticPurchases::class)->evaluate($this->f['receiver'], $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
$need = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
$r = Requisition::query()->findOrFail($need->data['purchases'][0]['id']);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
app(RequisitionWorkflow::class)->submit($this->f['issuer'], $r);
|
||||
$reviewer = User::factory()->create(['tenant_id' => $this->f['tenant']->id, 'department_id' => $this->f['issuer']->department_id]);
|
||||
supplyGrant($reviewer, ['department_head']);
|
||||
$this->actingAs($reviewer);
|
||||
app(RequisitionWorkflow::class)->decide($reviewer, $r->fresh(), 'rejected', 'Compra não autorizada para esta necessidade.');
|
||||
app(AutomaticPurchases::class)->evaluate($reviewer, $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
$current = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
expect($r->fresh()->status)->toBe('rejected')->and($current->state)->toBe('pending')->and($current->data['message'])->toContain('rejeitada')->and($current->data['purchases'])->toHaveCount(1);
|
||||
});
|
||||
|
||||
test('a preparer cannot activate their own rule and stale or changed commands do not create duplicate rules', function () {
|
||||
supplyGrant($this->f['issuer'], ['supply_review']);
|
||||
$rule = app(AutomaticPurchases::class)->save($this->f['issuer'], $this->input);
|
||||
@@ -92,6 +255,55 @@ test('expired or changed sources keep the need pending without preventing the ph
|
||||
test('configuration UI separates preparation review and read and does not generate purchases on GET', function () {
|
||||
$before = Requisition::query()->count();
|
||||
$this->get('/stock/reposicao')->assertOk()->assertInertia(fn (Assert $p) => $p->component('stock/replenishment')->where('canPrepare', true)->where('canReview', false)->has('requesters', 1));
|
||||
$this->actingAs($this->f['receiver'])->get('/stock/reposicao')->assertOk()->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->where('canReview', true)->has('requesters',0));
|
||||
$this->actingAs($this->f['receiver'])->get('/stock/reposicao')->assertOk()->assertInertia(fn (Assert $p) => $p->where('canPrepare', false)->where('canReview', true)->has('requesters', 0));
|
||||
expect(Requisition::query()->count())->toBe($before);
|
||||
});
|
||||
|
||||
test('warehouse shortage carries the original destination and equipment and is not counted twice after reservation', function () {
|
||||
activateAutomaticRule($this);
|
||||
supplyGrant($this->f['issuer'], ['warehouse_request_prepare']);
|
||||
supplyGrant($this->f['receiver'], ['warehouse_request_review']);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$level = CriticalityLevel::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'code' => 'NORMAL', 'name' => 'Normal', 'criteria' => 'Critério do ensaio.', 'is_critical' => false, 'created_at' => now()]);
|
||||
$equipment = Equipment::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'department_id' => $this->f['issuer']->department_id, 'criticality_level_id' => $level->id, 'code' => 'EQ-OF', 'description' => 'Equipamento da oficina central', 'area' => 'Oficina', 'status' => 'active']);
|
||||
$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, 'destination' => 'Oficina central, bancada de montagem', 'reason' => 'Componentes para reposição e manutenção interna.', 'priority' => 'normal', 'lines' => [['stock_item_id' => $this->f['stock']->id, 'quantity' => '125.000', 'use' => 'equipment', 'equipment_id' => $equipment->id]]]);
|
||||
app(WarehouseFulfilment::class)->command($this->f['issuer'], $request->id, ['operation_key' => (string) Str::uuid(), 'version' => 0, 'action' => 'submit', 'reason' => 'Submissão do material para utilização interna.', 'partial_allowed' => true]);
|
||||
$this->actingAs($this->f['receiver']);
|
||||
app(WarehouseFulfilment::class)->command($this->f['receiver'], $request->id, ['operation_key' => (string) Str::uuid(), 'version' => 1, 'action' => 'authorize', 'reason' => 'Autorização parcial com saldo em falta.', 'partial_allowed' => true]);
|
||||
app(AutomaticPurchases::class)->evaluate($this->f['receiver'], $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
$need = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
$r = Requisition::query()->findOrFail($need->data['purchases'][0]['id']);
|
||||
expect($r->items()->sole()->quantity)->toBe('108.000')->and($need->snapshot['origins'][0]['destination'])->toBe('Oficina central, bancada de montagem')
|
||||
->and($need->snapshot['origins'][0]['reserved_milli'])->toBe(120000)->and($need->snapshot['unreserved_milli'])->toBe(5000);
|
||||
$origin = app(AutomaticPurchases::class)->origin($r);
|
||||
expect($origin['origins'][0]['warehouse_request_id'])->toBe($request->id);
|
||||
expect($origin['origins'][0]['equipment']['code'])->toBe('EQ-OF');
|
||||
$this->actingAs($this->f['issuer'])->get('/requisicoes/'.$r->id)->assertOk()->assertInertia(fn (Assert $p) => $p->where('automaticSource.origins.0.destination', 'Oficina central, bancada de montagem'));
|
||||
});
|
||||
|
||||
test('revoked requester authority or expired work context cannot be borrowed for automatic purchases', function () {
|
||||
activateAutomaticRule($this);
|
||||
DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['expires_at' => now()->subMinute()]);
|
||||
$before = Requisition::query()->count();
|
||||
automaticIssue($this, '120.000');
|
||||
app(AutomaticPurchases::class)->evaluate($this->f['receiver'], $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
expect(app(SupplyPlanning::class)->latest('auto_need')->sole()->data['message'])->toContain('competência')->and(Requisition::query()->count())->toBe($before);
|
||||
});
|
||||
|
||||
test('automatic spare part purchases reuse the registered compatible equipment and reject a missing technical destination', function () {
|
||||
$level = CriticalityLevel::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'code' => 'PART', 'name' => 'Normal', 'criteria' => 'Critério do ensaio.', 'is_critical' => false, 'created_at' => now()]);
|
||||
$equipment = Equipment::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'department_id' => $this->f['issuer']->department_id, 'criticality_level_id' => $level->id, 'code' => 'EQ-P', 'description' => 'Máquina de teste', 'area' => 'Oficina', 'status' => 'active']);
|
||||
$part = SparePart::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'stock_item_id' => $this->f['stock']->id, 'criticality_level_id' => $level->id, 'minimum_milli' => 20000]);
|
||||
EquipmentSparePart::query()->create(['tenant_id' => $this->f['tenant']->id, 'created_by' => $this->f['issuer']->id, 'equipment_id' => $equipment->id, 'spare_part_id' => $part->id, 'created_at' => now()]);
|
||||
$this->actingAs($this->f['receiver']);
|
||||
app(RequisitionCatalogue::class)->save($this->f['receiver'], $this->f['stock']->id, ['version' => 1, 'stock_basis' => app(RequisitionCatalogue::class)->basis($this->f['stock']), 'category' => 'Peças', 'purchase_type' => 'spare_part', 'published' => true, 'reason' => 'Peça publicada para aquisição com destino técnico.']);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
expect(fn () => app(AutomaticPurchases::class)->save($this->f['issuer'], $this->input))->toThrow(ValidationException::class);
|
||||
$this->input['data']['equipment_id'] = $equipment->id;
|
||||
activateAutomaticRule($this);
|
||||
automaticIssue($this, '120.000', ['equipment_id' => $equipment->id]);
|
||||
app(AutomaticPurchases::class)->evaluate($this->f['receiver'], $this->f['stock']->id, $this->f['warehouse']->id);
|
||||
$need = app(SupplyPlanning::class)->latest('auto_need')->sole();
|
||||
$r = Requisition::query()->findOrFail($need->data['purchases'][0]['id']);
|
||||
expect($r->items()->sole()->equipment_id)->toBe($equipment->id)->and($r->items()->sole()->spare_part_id)->toBe($part->id)->and($r->status)->toBe('draft');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user