user(); abort_unless($actor instanceof User && $actor->tenant_id !== null, 403); return User::query()->where('tenant_id', $actor->tenant_id)->findOrFail($actor->id); } /** @param Collection $selected * @return Collection */ public function receipts(Collection $selected, string $search = ''): Collection { $actor = $this->actor(); abort_unless(app(InventoryDispositions::class)->allows($actor, 'prepare') || app(InventoryReversals::class)->allows($actor, 'prepare'), 403); $query = GoodsReceiptItem::query()->whereNotNull('stock_item_id')->where('accepted_milli', '>', 0) ->whereNotIn('id', array_keys(app(InventoryReversals::class)->corrections('receipt'))); $recent = (clone $query)->when(trim($search) !== '', fn ($q) => $q->whereIn('goods_receipt_id', GoodsReceipt::query()->whereLike('number', '%'.trim($search).'%')->select('id')))->latest('id')->limit(200)->pluck('id'); return $query->where(fn ($q) => $q->whereIn('id', $recent)->orWhereIn('id', $selected->filter()->unique()))->latest('id')->get(); } /** @param Collection $selected * @return Collection */ public function operations(Collection $selected, string $search = ''): Collection { abort_unless(app(InventoryReversals::class)->allows($this->actor(), 'prepare'), 403); $query = StockOperation::query()->whereIn('kind', ['issue', 'transfer', 'adjustment', 'supplier_return', 'write_off']) ->whereNotIn('id', array_keys(app(InventoryReversals::class)->corrections('operation'))); $recent = (clone $query)->when(trim($search) !== '', fn ($q) => $q->whereLike('reference', '%'.trim($search).'%'))->latest('id')->limit(200)->pluck('id'); return $query->where(fn ($q) => $q->whereIn('id', $recent)->orWhereIn('id', $selected->filter()->unique()))->latest('id')->get(); } /** @return array{ready:bool,focus:SupplyRecord|null,records:Collection,filters:array{search:string,state:string},lookup:array,pagination:array|null} */ public function read(Request $request, User $actor, string $kind): array { abort_unless(auth()->id() === $actor->id && $actor->tenant_id !== null && in_array($kind, ['stock_event', 'stock_reversal'], true), 403); $actor = $this->actor(); $workflow = $kind === 'stock_event' ? app(InventoryDispositions::class) : app(InventoryReversals::class); abort_unless(collect(['read', 'prepare', 'review', 'execute'])->contains(fn ($capability) => $workflow->allows($actor, $capability)), 403); $filters = $request->validate([ 'record_id' => ['nullable', 'integer', 'min:1', Rule::exists('supply_records', 'id')->where('tenant_id', $actor->tenant_id)->where('kind', $kind)], 'records_page' => 'nullable|integer|min:1|max:1000000', 'search' => 'nullable|string|max:100', 'state' => 'nullable|in:draft,returned,approved,executed', 'source_search' => 'nullable|string|max:100', 'source_form_key' => 'nullable|string|max:80', 'selected_receipt_id' => ['nullable', 'integer', 'min:1', Rule::exists('goods_receipt_items', 'id')->where('tenant_id', $actor->tenant_id)], 'selected_operation_id' => ['nullable', 'integer', 'min:1', Rule::exists('stock_operations', 'id')->where('tenant_id', $actor->tenant_id)], ]); $ready = Schema::hasTable('supply_records'); $focus = isset($filters['record_id']) ? SupplyRecord::query()->where('tenant_id', $actor->tenant_id)->where('kind', $kind)->findOrFail((int) $filters['record_id']) : null; $query = $ready ? app(SupplyPlanning::class)->latest($kind)->where('tenant_id', $actor->tenant_id)->latest('id') : null; $search = trim($filters['search'] ?? ''); $state = $filters['state'] ?? ''; $page = $focus === null ? $query?->when($search !== '', fn ($q) => $q->whereLike('data->reference', '%'.$search.'%')) ->when($state !== '', fn ($q) => $q->where('state', $state))->paginate(25, ['*'], 'records_page')->withQueryString() : null; return ['ready' => $ready, 'focus' => $focus, 'lookup' => ['search' => trim($filters['source_search'] ?? ''), 'form' => $filters['source_form_key'] ?? null, 'receipt' => $filters['selected_receipt_id'] ?? null, 'operation' => $filters['selected_operation_id'] ?? null], 'records' => $focus !== null ? $query?->where('record_key', $focus->record_key)->get() ?? collect() : $page?->getCollection() ?? collect(), 'filters' => ['search' => $search, 'state' => $state], 'pagination' => $page === null ? null : ['current_page' => $page->currentPage(), 'last_page' => $page->lastPage(), 'total' => $page->total(), 'prev_page_url' => $page->previousPageUrl(), 'next_page_url' => $page->nextPageUrl()]]; } }