99 lines
6.0 KiB
PHP
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;
|
|
}
|
|
}
|