55 lines
3.8 KiB
PHP
55 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Purchasing;
|
|
|
|
use App\Http\Requests\Sgc\StoreInvoiceRequest;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\{Crypt, DB};
|
|
use Tempo\Finance\Payables\Payables;
|
|
|
|
/** Stateless transport to the existing pending-invoice workflow; no second business writer. */
|
|
final class SgcInvoiceIngress
|
|
{
|
|
public function receive(StoreInvoiceRequest $request)
|
|
{
|
|
$body = $request->validated(); $id = (int)$body['order_id'];
|
|
$key = strtolower($request->header('X-Idempotency-Key'));
|
|
$controller = app(InvoiceController::class); $runtime = app(ReceiptRuntime::class);
|
|
$host = DB::connection();
|
|
abort_unless($host->getDriverName() === 'pgsql', 503);
|
|
return $host->transaction(function () use ($request, $body, $id, $key, $controller, $runtime, $host) {
|
|
// Serialize the complete retry check + native commit, including different orders using one key.
|
|
$host->statement("SET LOCAL lock_timeout = '5s'");
|
|
$host->select('SELECT pg_advisory_xact_lock(hashtextextended(?, 0))', ['sgc.invoice:'.$key]);
|
|
$controller->show($id, $runtime); // exact origin, current visibility and native policies
|
|
if ($body['phase'] === 'confirm') {
|
|
try { $cut = json_decode(Crypt::decryptString($body['preview']), true, 128, JSON_THROW_ON_ERROR); }
|
|
catch (\Throwable) { abort(409, 'Prévia inválida.'); }
|
|
abort_unless(($cut['action'] ?? null) === 'register' && ($cut['order_id'] ?? null) === $id
|
|
&& ($cut['operation_key'] ?? null) === $key, 409, 'Prévia de outra operação.');
|
|
$data = $cut['data']; $reason = $cut['reason'];
|
|
} else { $cut = null; $data = $body['data']; $reason = $body['reason']; }
|
|
$hash = hash('sha256', Payables::canonical([[$id, 'register', $data, $reason]]));
|
|
$generation = hash('sha256', Payables::canonical([$request->session()->get('company_context')]));
|
|
$old = $runtime->run('purchasing_invoices.prepare', function ($actor, $binding, $fingerprint) use ($key, $id, $hash, $cut, $generation) {
|
|
$actor = app(\Tempo\Purchasing\Embedded\Services\CurrentActor::class)->lock($actor, ['finance']);
|
|
abort_if(app(\Tempo\Purchasing\Embedded\Services\OrganizationAccess::class)->maintenanceOnly($actor), 403);
|
|
if ($cut) abort_unless(($cut['actor'] ?? null) === $actor->id && ($cut['binding'] ?? null) === $fingerprint
|
|
&& ($cut['generation'] ?? null) === $generation, 409, 'Contexto da prévia alterado.');
|
|
$receipt = \Tempo\Purchasing\Embedded\Models\AuditLog::where('user_id', $actor->id)
|
|
->where('action', 'sgf.invoice.command')->where('after->operation_key', $key)->first();
|
|
if (!$receipt) return null;
|
|
abort_unless(($receipt->after['result']['order_id'] ?? null) === $id && ($receipt->after['request_hash'] ?? null) === $hash
|
|
&& ($receipt->after['binding_revision'] ?? null) === $fingerprint, 409, 'Chave usada com outro conteúdo ou origem.');
|
|
return $receipt->after['result'] + ['replayed'=>true];
|
|
});
|
|
if ($old) return response()->json($old);
|
|
$input = $body['phase'] === 'confirm' ? ['preview'=>$body['preview']] :
|
|
['operation_key'=>$key, 'revision'=>$body['revision'], 'action'=>'register', 'reason'=>$reason, 'data'=>$data];
|
|
$native = Request::create('/api/purchasing-invoices/'.$id, 'POST', $input, [], [], ['HTTP_ACCEPT'=>'application/json']);
|
|
$native->setLaravelSession($request->session()); $native->setUserResolver($request->getUserResolver());
|
|
return $body['phase'] === 'confirm' ? $controller->confirm($id, $native, $runtime) : $controller->preview($id, $native, $runtime);
|
|
});
|
|
}
|
|
}
|