make(Kernel::class)->bootstrap(); if (! app()->environment('testing') || DB::getDriverName() !== 'pgsql' || ! preg_match('/^compras_receipts_test_[a-f0-9]{12}$/D', DB::connection()->getDatabaseName())) { throw new LogicException('Worker apenas em base PostgreSQL descartável.'); } require_once dirname(__DIR__).'/Support/ReceiptFixture.php'; require_once dirname(__DIR__).'/Support/SupplyFixture.php'; if (($argv[1] ?? '') === 'prepare_reversal') { $f = receiptFixture('100.000'); supplyGrant($f['receiver'], ['warehouse', 'inventory_reversal_prepare']); supplyGrant($f['issuer'], ['inventory_reversal_review']); auth()->guard()->setUser($f['receiver']); app(ReceiptWorkflow::class)->confirm($f['receiver'], $f['order'], receiptPayload($f, '10.000')); $issue = app(StockWorkflow::class)->record($f['receiver'], ['kind' => 'issue', 'reference' => 'ISSUE', 'idempotency_key' => (string) Str::uuid(), 'warehouse_id' => $f['warehouse']->id, 'stock_item_id' => $f['stock']->id, 'responsible_id' => $f['receiver']->id, 'quantity' => '3.000', 'reason' => 'Saída original para disputa de estorno.']); $records = []; foreach ([1, 2] as $number) { auth()->guard()->setUser($f['receiver']); $draft = app(InventoryReversals::class)->save($f['receiver'], supplyInput(['reference' => 'REV-'.$number, 'source_kind' => 'operation', 'source_operation_id' => $issue->id])); auth()->guard()->setUser($f['issuer']); $approved = app(InventoryReversals::class)->save($f['issuer'], supplyInput([], 'approve', $draft)); $records[] = ['record_key' => $approved->record_key, 'version' => $approved->version]; } echo json_encode(['user' => $f['receiver']->id, 'tenant' => $f['tenant']->id, 'stock' => $f['stock']->id, 'warehouse' => $f['warehouse']->id, 'records' => $records], JSON_THROW_ON_ERROR); exit; } if (($argv[1] ?? '') === 'prepare_return') { $f = receiptFixture('100.000'); supplyGrant($f['receiver'], ['warehouse', 'inventory_disposition_prepare']); supplyGrant($f['issuer'], ['inventory_disposition_review']); auth()->guard()->setUser($f['receiver']); $receipt = app(ReceiptWorkflow::class)->confirm($f['receiver'], $f['order'], receiptPayload($f, '10.000')); app(ReceiptWorkflow::class)->confirm($f['receiver'], $f['order'], receiptPayload($f, '10.000')); $records = []; foreach ([1, 2] as $number) { auth()->guard()->setUser($f['receiver']); $draft = app(InventoryDispositions::class)->save($f['receiver'], supplyInput(['kind' => 'supplier_return', 'reference' => 'RET-'.$number, 'stock_item_id' => $f['stock']->id, 'warehouse_id' => $f['warehouse']->id, 'receipt_item_id' => $receipt->items->first()->id, 'quantity' => '7.000'])); auth()->guard()->setUser($f['issuer']); $approved = app(InventoryDispositions::class)->save($f['issuer'], supplyInput([], 'approve', $draft)); $records[] = ['record_key' => $approved->record_key, 'version' => $approved->version]; } echo json_encode(['user' => $f['receiver']->id, 'tenant' => $f['tenant']->id, 'stock' => $f['stock']->id, 'warehouse' => $f['warehouse']->id, 'records' => $records], JSON_THROW_ON_ERROR); exit; } if (in_array($argv[1] ?? '', ['prepare_lots', 'prepare_count', 'prepare_policy'], true)) { $f = receiptFixture('100.000'); supplyGrant($f['issuer'], ['inventory_lot_prepare', 'inventory_adjust_review']); supplyGrant($f['receiver'], ['warehouse', 'inventory_lot_review', 'inventory_lot_release']); auth()->guard()->setUser($f['receiver']); app(ReceiptWorkflow::class)->confirm($f['receiver'], $f['order'], receiptPayload($f, '10.000')); auth()->guard()->setUser($f['issuer']); $draft = app(InventoryLots::class)->save($f['issuer'], supplyInput(['stock_item_id' => $f['stock']->id, 'warehouse_id' => $f['warehouse']->id, 'requires_expiry' => true, 'minimum_remaining_days' => 0, 'lots' => [['code' => 'RACE', 'expires_on' => today()->addDays(10)->toDateString()]]], 'prepare')); auth()->guard()->setUser($f['receiver']); app(InventoryLots::class)->save($f['receiver'], supplyInput([], 'activate', $draft)); $lot = app(InventoryLots::class)->context($f['stock']->id, $f['warehouse']->id)['lots'][0]; $metadata = SupplyRecord::query()->findOrFail($lot['metadata_id']); $release = app(InventoryLots::class)->save($f['receiver'], supplyInput(['lot_id' => $lot['id'], 'warehouse_id' => $f['warehouse']->id, 'state_version' => 0], 'release', $metadata)); $destination = Warehouse::query()->create(['tenant_id' => $f['tenant']->id, 'created_by' => $f['receiver']->id, 'code' => 'DEST', 'name' => 'Destino da corrida']); auth()->guard()->setUser($f['issuer']); $destinationDraft = app(InventoryLots::class)->save($f['issuer'], supplyInput(['stock_item_id' => $f['stock']->id, 'warehouse_id' => $destination->id, 'requires_expiry' => true, 'minimum_remaining_days' => 0, 'lots' => []], 'prepare')); auth()->guard()->setUser($f['receiver']); app(InventoryLots::class)->save($f['receiver'], supplyInput([], 'activate', $destinationDraft)); $count = null; $policy = null; if ($argv[1] === 'prepare_policy') { $active = app(InventoryLots::class)->setup($f['stock']->id, $f['warehouse']->id); auth()->guard()->setUser($f['issuer']); $policy = app(InventoryLots::class)->save($f['issuer'], supplyInput([...$active->data, 'minimum_remaining_days' => 2, 'lots' => []], 'prepare', $active)); } if ($argv[1] === 'prepare_count') { $count = app(StockWorkflow::class)->record($f['receiver'], ['kind' => 'count', 'reference' => 'CNT-'.Str::uuid(), 'idempotency_key' => (string) Str::uuid(), 'warehouse_id' => $f['warehouse']->id, 'stock_item_id' => $f['stock']->id, 'responsible_id' => $f['receiver']->id, 'quantity' => '9.000', 'reason' => 'Contagem para disputa de ajuste físico.', 'ledger_version' => app(StockWorkflow::class)->balance($f['stock']->id, $f['warehouse']->id)['version'], 'lot_version' => app(InventoryCounts::class)->source($f['stock']->id, $f['warehouse']->id)['hash'], 'lot_counts' => [['lot_id' => $lot['id'], 'quantity' => '9.000']]]); } echo json_encode(['user' => $f['receiver']->id, 'preparer' => $f['issuer']->id, 'tenant' => $f['tenant']->id, 'stock' => $f['stock']->id, 'warehouse' => $f['warehouse']->id, 'policy_record_key' => $policy?->record_key, 'policy_version' => $policy?->version, 'lot_id' => $lot['id'], 'reviewer' => $f['issuer']->id, 'count_id' => $count?->id, 'destination' => $destination->id, 'record_key' => $metadata->record_key, 'version' => $metadata->version, 'state_version' => $release->version], JSON_THROW_ON_ERROR); exit; } if (($argv[1] ?? '') === 'prepare_suggestion') { require_once dirname(__DIR__).'/Support/SuggestionFixture.php'; $f = suggestionFixture(); auth()->guard()->setUser($f['issuer']); $draft = app(SupplyPlanning::class)->save($f['issuer'], 'suggestion', supplyInput(['policy_key' => $f['policy']->record_key])); auth()->guard()->setUser($f['receiver']); $approved = app(SupplyPlanning::class)->save($f['receiver'], 'suggestion', supplyInput([], 'approve', $draft)); echo json_encode(['user' => $f['issuer']->id, 'tenant' => $f['tenant']->id, 'record_key' => $approved->record_key, 'version' => $approved->version, 'cost_center_id' => $f['r']->cost_center_id], JSON_THROW_ON_ERROR); exit; } if (($argv[1] ?? '') === 'prepare') { $f = receiptFixture(); supplyGrant($f['receiver'], ['supply_reserve']); echo json_encode(['user' => $f['receiver']->id, 'stock' => $f['stock']->id, 'warehouse' => $f['warehouse']->id, 'tenant' => $f['tenant']->id], JSON_THROW_ON_ERROR); exit; } $input = json_decode($argv[2], true, 16, JSON_THROW_ON_ERROR); $actor = User::query()->findOrFail(in_array($argv[1] ?? '', ['hold_lot_adjust', 'lot_adjust'], true) ? $input['reviewer'] : (($argv[1] ?? '') === 'hold_lot_identify' ? $input['preparer'] : $input['user'])); auth()->guard()->setUser($actor); if (in_array($argv[1] ?? '', ['hold', 'hold_suggestion', 'hold_lot_issue', 'hold_lot_identify', 'hold_lot_transfer', 'hold_lot_adjust', 'hold_disposition', 'hold_stock_reversal'], true)) { $held = false; DB::listen(function (QueryExecuted $event) use (&$held) { if (! $held && str_contains($event->sql, '"tenants"') && str_contains($event->sql, 'for update')) { $held = true; echo "locked\n"; fflush(STDOUT); usleep(2000000); } }); } try { if (in_array($argv[1] ?? '', ['hold_stock_reversal', 'stock_reversal'], true)) { $source = $input['records'][$input['selected']]; $record = app(InventoryReversals::class)->save($actor, ['operation_key' => $input['operation_key'], ...$source, 'action' => 'execute', 'reason' => 'Estornos concorrentes ligados à mesma origem.', 'data' => ['event_on' => today()->toDateString(), 'evidence' => 'Comprovativo da correcção física no ensaio de concorrência.']]); } elseif (($argv[1] ?? '') === 'lot_activate') { $record = app(InventoryLots::class)->save($actor, ['operation_key' => $input['operation_key'], 'record_key' => $input['policy_record_key'], 'version' => $input['policy_version'], 'action' => 'activate', 'reason' => 'Conferência de impacto concorrente com saída real.', 'data' => []]); } elseif (in_array($argv[1] ?? '', ['hold_disposition', 'disposition'], true)) { $source = $input['records'][$input['selected']]; $record = app(InventoryDispositions::class)->save($actor, ['operation_key' => $input['operation_key'], ...$source, 'action' => 'execute', 'reason' => 'Execuções concorrentes de devoluções à mesma origem.', 'data' => ['event_on' => today()->toDateString(), 'evidence' => 'Guia de devolução assinada no ensaio de concorrência.']]); } elseif (in_array($argv[1] ?? '', ['hold_lot_adjust', 'lot_adjust'], true)) { $record = app(StockWorkflow::class)->record($actor, ['kind' => 'adjustment', 'reference' => 'ADJ-'.Str::uuid(), 'idempotency_key' => $input['operation_key'], 'responsible_id' => $actor->id, 'warehouse_id' => $input['warehouse'], 'stock_item_id' => $input['stock'], 'quantity' => '0.000', 'count_id' => $input['count_id'], 'reason' => 'Duas conferências concorrentes da mesma contagem.']); } elseif (in_array($argv[1] ?? '', ['hold_lot_issue', 'lot_issue', 'hold_lot_transfer', 'lot_transfer'], true)) { $transfer = in_array($argv[1], ['hold_lot_transfer', 'lot_transfer'], true); $record = app(StockWorkflow::class)->record($actor, ['kind' => $transfer ? 'transfer' : 'issue', ...($transfer ? ['destination_warehouse_id' => $input['destination']] : []), 'reference' => 'RACE-'.Str::uuid(), 'idempotency_key' => $input['operation_key'], 'responsible_id' => $actor->id, 'warehouse_id' => $input['warehouse'], 'stock_item_id' => $input['stock'], 'quantity' => '7.000', 'reason' => 'Saídas concorrentes do mesmo lote libertado.']); } elseif (in_array($argv[1] ?? '', ['hold_lot_identify', 'lot_release'], true)) { $record = app(InventoryLots::class)->save($actor, ['operation_key' => $input['operation_key'], 'record_key' => $input['record_key'], 'version' => $input['version'], 'action' => $argv[1] === 'hold_lot_identify' ? 'identify' : 'release', 'reason' => 'Conferência concorrente de identificação e libertação.', 'data' => $argv[1] === 'hold_lot_identify' ? ['code' => 'CORRECTED', 'expires_on' => today()->addDays(5)->toDateString()] : ['lot_id' => $input['lot_id'], 'warehouse_id' => $input['warehouse'], 'state_version' => $input['state_version']]]); } elseif (in_array($argv[1] ?? '', ['hold_suggestion', 'convert_suggestion'], true)) { $record = app(SupplyPlanning::class)->save($actor, 'suggestion', ['operation_key' => $input['operation_key'], 'record_key' => $input['record_key'], 'version' => $input['version'], 'action' => 'convert', 'reason' => 'Prova de conversão concorrente da sugestão.', 'data' => ['cost_center_id' => $input['cost_center_id']]]); } else { $record = app(SupplyPlanning::class)->save($actor, 'reservation', ['operation_key' => $input['operation_key'], 'version' => 0, 'action' => 'save', 'reason' => 'Prova de duas reservas concorrentes.', 'data' => ['stock_item_id' => $input['stock'], 'warehouse_id' => $input['warehouse'], 'quantity_milli' => 1000, 'needed_on' => today()->toDateString(), 'additional' => false, 'reference' => $input['reference']]]); } echo json_encode(['result' => 'saved', 'id' => $record->id], JSON_THROW_ON_ERROR); } catch (ValidationException $e) { echo json_encode(['result' => 'rejected', 'fields' => array_keys($e->errors())], JSON_THROW_ON_ERROR); }