commit21
This commit is contained in:
@@ -16,8 +16,11 @@ use App\Services\StockWorkflow;
|
||||
use App\Services\SupplyCoordination;
|
||||
use App\Services\SupplyHistory;
|
||||
use App\Services\SupplyPlanning;
|
||||
use App\Services\WorkflowTaskInbox;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Inertia\Testing\AssertableInertia as Assert;
|
||||
|
||||
require_once __DIR__.'/../Support/ReceiptFixture.php';
|
||||
require_once __DIR__.'/../Support/SupplyFixture.php';
|
||||
@@ -91,12 +94,17 @@ test('full issue reversal preserves the original and is exactly once even on an
|
||||
expect(fn () => approvedReversal($this, ['source_kind' => 'operation', 'source_operation_id' => $original->id]))->toThrow(ValidationException::class);
|
||||
});
|
||||
|
||||
test('a corrected issue no longer satisfies a reservation and the same need can use a new actual issue', function () {
|
||||
test('coordination task returns when a corrected issue no longer satisfies a reservation and the same need can use a new actual issue', function () {
|
||||
$reservation = app(SupplyCoordination::class)->save($this->f['receiver'], 'reservation', supplyInput(['stock_item_id' => $this->f['stock']->id, 'warehouse_id' => $this->f['warehouse']->id,
|
||||
'quantity_milli' => 3000, 'needed_on' => today()->toDateString(), 'additional' => false, 'reference' => 'NEED-REV']));
|
||||
$issue = reversalStock($this);
|
||||
$fulfilled = app(SupplyCoordination::class)->save($this->f['receiver'], 'reservation', supplyInput(['stock_operation_id' => $issue->id, 'quantity_milli' => 3000], 'fulfil', $reservation));
|
||||
$pending = fn () => collect(app(WorkflowTaskInbox::class)->build($this->f['receiver'], 'procurement'))->where('task', 'reservation_followup')->count();
|
||||
expect($pending())->toBe(0);
|
||||
reverseSourceOperation($this, $issue);
|
||||
expect($pending())->toBe(1)->and(app(SupplyCoordination::class)->canManage($this->f['receiver'], $fulfilled, 'fulfil'))->toBeTrue();
|
||||
$this->get('/compras/fornecimento?view=reservations&issue_record_id='.$fulfilled->id.'&selected_issue_id='.$issue->id)
|
||||
->assertInertia(fn (Assert $page) => $page->has('operations', 0));
|
||||
$events = app(SupplyCoordination::class)->events($this->f['stock']->id, $this->f['warehouse']->id, today()->toDateString());
|
||||
expect($events['reservations'][0]['quantity'])->toBe(3000)->and($events['fulfilled_needs'])->toBe([])->and($events['consumed_today'])->toBe(0)
|
||||
->and($fulfilled->fresh()->state)->toBe('fulfilled')->and($fulfilled->data['applications'])->toHaveCount(1);
|
||||
@@ -106,6 +114,7 @@ test('a corrected issue no longer satisfies a reservation and the same need can
|
||||
$again = app(SupplyCoordination::class)->save($this->f['receiver'], 'reservation', supplyInput(['stock_operation_id' => $nextIssue->id, 'quantity_milli' => 3000], 'fulfil', $fulfilled));
|
||||
expect(app(SupplyCoordination::class)->remainingReservation($again))->toBe(0)->and($again->data['applications'])->toHaveCount(2);
|
||||
expect($again->snapshot['effective_allocated_milli'])->toBe(3000)->and($again->snapshot['reversed_operations'])->toContain($issue->id);
|
||||
expect($pending())->toBe(0);
|
||||
});
|
||||
|
||||
test('a reversal outside a closed history period invalidates its signature and corrects effective consumption', function () {
|
||||
@@ -285,3 +294,74 @@ test('voided receipt matching blocks new invoices while preserving invoice facts
|
||||
$this->get('/facturas')->assertOk();
|
||||
$this->actingAs($this->f['receiver'])->get('/recepcoes/'.$this->receipt->id)->assertOk();
|
||||
});
|
||||
test('reversal task links independent approval and exactly one physical correction while preserving originals', function (string $kind) {
|
||||
$operation = $kind === 'operation' ? reversalStock($this) : null;
|
||||
$source = $kind === 'operation' ? ['source_kind' => 'operation', 'source_operation_id' => $operation->id] : ['source_kind' => 'receipt', 'receipt_item_id' => $this->receiptItem->id];
|
||||
$original = $operation?->fresh()->getAttributes() ?? $this->receiptItem->fresh()->getAttributes();
|
||||
$draft = app(InventoryReversals::class)->save($this->f['receiver'], supplyInput(['reference' => 'TASK-'.Str::uuid(), ...$source]));
|
||||
$path = '/stock/estornos?record_id='.$draft->id;
|
||||
supplyGrant($this->f['receiver'], ['inventory_reversal_review']);
|
||||
supplyGrant($this->f['issuer'], ['warehouse']);
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
||||
$this->actingAs($this->f['issuer'])->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)
|
||||
->where('tasks.data.0.task', 'reversal_approve')->where('tasks.data.0.href', $path.'#reversal-'.$draft->id)->missing('tasks.data.0.snapshot'));
|
||||
$before = [SupplyRecord::query()->count(), StockMovement::query()->count(), DB::table('audit_logs')->count(), DB::table('invoices')->count()];
|
||||
$this->get($path)->assertHeader('Cache-Control', 'max-age=0, no-store, private')->assertInertia(fn (Assert $page) => $page->has('records', 1)->where('records.0.canApprove', true));
|
||||
expect([SupplyRecord::query()->count(), StockMovement::query()->count(), DB::table('audit_logs')->count(), DB::table('invoices')->count()])->toBe($before);
|
||||
$approved = app(InventoryReversals::class)->save($this->f['issuer'], supplyInput([], 'approve', $draft));
|
||||
$this->get($path)->assertInertia(fn (Assert $page) => $page->where('focusChanged', true)->where('records.0.id', $approved->id)->where('records.0.canExecute', false));
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
||||
$this->actingAs($this->f['receiver'])->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)->where('tasks.data.0.task', 'reversal_execute'));
|
||||
$command = supplyInput(['event_on' => today()->toDateString(), 'evidence' => 'Correcção integral confirmada no ensaio descartável.'], 'execute', $approved);
|
||||
$this->postJson('/stock/estornos', $command)->assertRedirect();
|
||||
$this->postJson('/stock/estornos', $command)->assertRedirect();
|
||||
expect(app(StockWorkflow::class)->balance($this->f['stock']->id, $this->f['warehouse']->id)['quantity'])->toBe($kind === 'operation' ? 10000 : 0)
|
||||
->and(StockMovement::query()->count())->toBe($before[1] + 1)->and(DB::table('invoices')->count())->toBe($before[3])
|
||||
->and($operation?->fresh()->getAttributes() ?? $this->receiptItem->fresh()->getAttributes())->toBe($original);
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
||||
})->with(['operation', 'receipt']);
|
||||
|
||||
test('reversal task does not withdraw unavailable receipt stock and still permits an authorised return', function () {
|
||||
$approved = approvedReversal($this, ['source_kind' => 'receipt', 'receipt_item_id' => $this->receiptItem->id]);
|
||||
reversalStock($this, '1.000');
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)->where('tasks.data.0.blocked', true));
|
||||
$this->postJson('/stock/estornos', supplyInput(['event_on' => today()->toDateString(), 'evidence' => 'O consumo posterior deve impedir uma retirada sem saldo.'], 'execute', $approved))->assertUnprocessable();
|
||||
expect(app(StockWorkflow::class)->balance($this->f['stock']->id, $this->f['warehouse']->id)['quantity'])->toBe(9000);
|
||||
$this->actingAs($this->f['issuer']);
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 1)->where('tasks.data.0.task', 'reversal_return')->where('tasks.data.0.blocked', true));
|
||||
$returned = app(InventoryReversals::class)->save($this->f['issuer'], supplyInput([], 'return', $approved));
|
||||
expect($returned->state)->toBe('returned')->and($returned->snapshot['source'])->toBe($approved->snapshot['source']);
|
||||
});
|
||||
|
||||
test('reversal task preserves older pending sources beyond the hundred row overview', function () {
|
||||
$operation = reversalStock($this);
|
||||
$first = app(InventoryReversals::class)->save($this->f['receiver'], supplyInput(['reference' => 'OLD-'.Str::uuid(), 'source_kind' => 'operation', 'source_operation_id' => $operation->id]));
|
||||
for ($index = 1; $index < 105; $index++) {
|
||||
app(InventoryReversals::class)->save($this->f['receiver'], supplyInput(['reference' => 'PENDING-'.Str::uuid(), 'source_kind' => 'operation', 'source_operation_id' => $operation->id]));
|
||||
}
|
||||
$this->actingAs($this->f['issuer'])->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 25)
|
||||
->where('tasks.data.0.id', 'reversal:'.$first->id.':approve'));
|
||||
$this->get('/requisicoes/tarefas?module=warehouse&page=5')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 105)->has('tasks.data', 5));
|
||||
$this->get('/stock/estornos')->assertInertia(fn (Assert $page) => $page->has('records', 25)->where('recordPagination.total', 105));
|
||||
$this->get('/stock/estornos?record_id='.$first->id)->assertInertia(fn (Assert $page) => $page->has('records', 1)->where('records.0.id', $first->id));
|
||||
});
|
||||
|
||||
test('reversal task preserves record kind company maintenance and assignment expiry boundaries', function () {
|
||||
$operation = reversalStock($this);
|
||||
$draft = app(InventoryReversals::class)->save($this->f['receiver'], supplyInput(['reference' => 'SCOPE-'.Str::uuid(), 'source_kind' => 'operation', 'source_operation_id' => $operation->id]));
|
||||
$path = '/stock/estornos?record_id='.$draft->id;
|
||||
$other = receiptFixture();
|
||||
supplyGrant($other['receiver'], ['warehouse']);
|
||||
$this->actingAs($other['receiver'])->getJson($path)->assertUnprocessable();
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
||||
$this->actingAs($this->f['receiver']);
|
||||
$disposition = app(InventoryDispositions::class)->save($this->f['receiver'], supplyInput(['kind' => 'write_off', 'reference' => 'OTHER-'.Str::uuid(),
|
||||
'stock_item_id' => $this->f['stock']->id, 'warehouse_id' => $this->f['warehouse']->id, 'quantity' => '1.000']));
|
||||
$this->getJson('/stock/estornos?record_id='.$disposition->id)->assertUnprocessable();
|
||||
DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['maintenance' => true, 'expires_at' => now()->addHour()]);
|
||||
$this->actingAs($this->f['issuer'])->get('/requisicoes/tarefas?module=warehouse')->assertInertia(fn (Assert $page) => $page->where('tasks.total', 0));
|
||||
$this->get($path)->assertForbidden();
|
||||
expect(app(InventoryReversals::class)->canAct($this->f['issuer'], $draft, 'approve'))->toBeFalse();
|
||||
DB::table('organization_memberships')->where('user_id', $this->f['issuer']->id)->update(['expires_at' => now()->subMinute()]);
|
||||
$this->get('/requisicoes/tarefas?module=warehouse')->assertForbidden();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user