167 lines
13 KiB
PHP
167 lines
13 KiB
PHP
<?php
|
|
|
|
use App\Models\SupplyRecord;
|
|
use App\Models\User;
|
|
use App\Models\Warehouse;
|
|
use App\Services\InventoryCounts;
|
|
use App\Services\InventoryDispositions;
|
|
use App\Services\InventoryLots;
|
|
use App\Services\InventoryReversals;
|
|
use App\Services\ReceiptWorkflow;
|
|
use App\Services\StockWorkflow;
|
|
use App\Services\SupplyPlanning;
|
|
use Illuminate\Contracts\Console\Kernel;
|
|
use Illuminate\Database\Events\QueryExecuted;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
require dirname(__DIR__, 2).'/vendor/autoload.php';
|
|
$app = require dirname(__DIR__, 2).'/bootstrap/app.php';
|
|
$app->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);
|
|
}
|