Files
compras/app/Services/InternalSupplyTrace.php
T
2026-11-02 20:27:11 +02:00

99 lines
6.0 KiB
PHP

<?php
namespace App\Services;
use App\Models\PurchaseOrder;
use App\Models\Requisition;
use App\Models\SupplyRecord;
use App\Models\User;
use App\Models\WarehouseRequest;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
/** Internal links over authoritative SGC documents; no copy or workflow transition. */
class InternalSupplyTrace
{
/** @param array<string,mixed> $progress
* @return array<string,mixed>
*/
public function forRequest(User $actor, WarehouseRequest $request, array $progress): array
{
abort_unless(auth()->id() === $actor->id && app(WarehouseWorkdesk::class)->canView($actor, $request), 403);
if (! Schema::hasTable('supply_records')) {
return ['groups' => [], 'ready' => false];
}
// Last version that actually mentioned this request: future shared purchases
// must not be retroactively attributed to a request already fulfilled.
$matched = SupplyRecord::query()->where('kind', 'auto_need')->where('warehouse_id', $request->warehouse_id);
if (DB::getDriverName() === 'pgsql') {
$matched->whereRaw("EXISTS (SELECT 1 FROM jsonb_array_elements(supply_records.snapshot::jsonb->'origins') o WHERE o->>'warehouse_request_id' = ?)", [(string) $request->id]);
} else {
$matched->whereRaw("EXISTS (SELECT 1 FROM json_each(supply_records.snapshot, '$.origins') o WHERE json_extract(o.value, '$.warehouse_request_id') = ?)", [$request->id]);
}
$records = SupplyRecord::query()->whereIn('id', $matched->selectRaw('MAX(id)')->groupBy('record_key'))->orderBy('stock_item_id')->get();
$current = app(SupplyPlanning::class)->latest('auto_need')->whereIn('record_key', $records->pluck('record_key'))->get()->keyBy('record_key');
$corrections = app(InventoryReversals::class)->corrections('receipt');
$groups = [];
foreach ($records as $record) {
$indexes = [];
foreach ($record->snapshot['origins'] as $origin) {
$index = $origin['line_index'];
if ($origin['warehouse_request_id'] === $request->id && isset($request->snapshot['lines'][$index])
&& $request->snapshot['lines'][$index]['item']['id'] === $record->stock_item_id) {
$indexes[] = $index;
}
}
if ($indexes === []) {
continue;
}
$latest = $current->get($record->record_key) ?? $record;
$ids = array_values(array_unique($record->data['requisition_ids'] ?? []));
$purchases = Requisition::query()->visibleTo($actor)->whereIn('id', $ids)->with('steps')->orderBy('id')->get();
$rows = [];
foreach ($purchases as $purchase) {
$orders = [];
foreach (PurchaseOrder::query()->visibleTo($actor)->where('requisition_id', $purchase->id)->orderBy('id')->get() as $order) {
$receipts = [];
foreach (DB::table('goods_receipt_items as i')->join('goods_receipts as g', 'g.id', '=', 'i.goods_receipt_id')
->where('i.tenant_id', $actor->tenant_id)->where('g.tenant_id', $actor->tenant_id)->where('g.purchase_order_id', $order->id)
->where('g.warehouse_id', $request->warehouse_id)->where('i.stock_item_id', $record->stock_item_id)
->orderBy('i.id')->get(['i.id', 'g.id as receipt_id', 'g.received_on', 'i.accepted_milli']) as $receipt) {
$receipts[] = ['id' => $receipt->receipt_id, 'item_id' => $receipt->id, 'received_on' => $receipt->received_on,
'accepted_milli' => (int) $receipt->accepted_milli, 'reversed' => isset($corrections[$receipt->id])];
}
$orders[] = ['id' => $order->id, 'number' => $order->number,
'href' => $actor->hasFunctions(['admin', 'procurement', 'finance', 'department_head']) ? '/ordens/'.$order->id : null,
'receipts' => $receipts, 'effective_received_milli' => array_sum(array_map(fn ($row) => $row['reversed'] ? 0 : $row['accepted_milli'], $receipts))];
}
$next = $purchase->status === 'pending' ? $purchase->steps->firstWhere('decision', 'pending') : null;
$rows[] = ['id' => $purchase->id, 'number' => $purchase->number, 'status' => $purchase->status, 'href' => '/requisicoes/'.$purchase->id,
'next_task' => $next === null ? null : (OrganizationAccess::PERMISSIONS[$next->role] ?? $next->role), 'orders' => $orders];
}
$unreserved = array_sum(array_map(fn ($index) => max(0, $progress['remaining'][$index] - $progress['reserved'][$index]), $indexes));
$groups[] = ['item' => $request->snapshot['lines'][$indexes[0]]['item'], 'line_indexes' => array_values(array_unique($indexes)),
'origin_need_id' => $record->id, 'current_need_id' => $latest->id, 'need_state' => $latest->state,
'message' => $latest->data['message'] ?? '', 'unreserved_milli' => $unreserved,
'purchases' => $rows, 'restricted' => $purchases->count() !== count($ids)];
}
return ['ready' => true, 'groups' => $groups];
}
/** @return array<string,mixed>|null */
public function purchaseOrigin(User $actor, Requisition $purchase): ?array
{
$origin = app(AutomaticPurchases::class)->origin($purchase);
if ($origin === null) {
return null;
}
$sources = WarehouseRequest::query()->whereIn('id', array_column($origin['origins'], 'warehouse_request_id'))->get()->keyBy('id');
$origin['origins'] = array_map(function ($row) use ($sources, $actor) {
$request = $sources->get($row['warehouse_request_id']);
return [...$row, 'href' => $request !== null && app(WarehouseWorkdesk::class)->canView($actor, $request) ? '/stock/requisicoes/'.$request->id : null];
}, $origin['origins']);
return $origin;
}
}