variation_id) { $stock = VariationLocationDetails::join('business_locations as bl', 'variation_location_details.location_id', '=', 'bl.id') ->where('variation_location_details.variation_id', $item->variation_id) ->where('bl.business_id', $businessId) ->select( 'bl.business_id', 'bl.id as location_id', 'bl.name as location_name', 'variation_location_details.qty_available' ) ->get(); foreach ($stock as $row) { if ((float) $row->qty_available > 0) { $results->push([ 'source' => 'internal_stock', 'business_id' => $row->business_id, 'location_id' => $row->location_id, 'location_name' => $row->location_name, 'quantity' => (float) $row->qty_available, ]); } } } $equivalents = PartEquivalent::where('primary_item_id', $itemMasterId) ->where('is_approved', true) ->with('equivalentItem') ->orderBy('priority') ->get(); foreach ($equivalents as $equiv) { $alt = $equiv->equivalentItem; if ($alt?->variation_id) { $altStock = VariationLocationDetails::join('business_locations as bl', 'variation_location_details.location_id', '=', 'bl.id') ->where('variation_location_details.variation_id', $alt->variation_id) ->where('bl.business_id', $businessId) ->where('variation_location_details.qty_available', '>', 0) ->select('bl.name as location_name', 'variation_location_details.qty_available') ->get(); foreach ($altStock as $s) { $results->push([ 'source' => 'equivalent_stock', 'item_code' => $alt->item_code, 'item_name' => $alt->name, 'location_name' => $s->location_name, 'quantity' => (float) $s->qty_available, 'equivalence_type' => $equiv->equivalence_type, ]); } } } return $results; } /** * Find approved suppliers for an item. */ public function findSuppliers(int $itemMasterId): Collection { return SupplierPartNumber::with('supplier') ->where('item_master_id', $itemMasterId) ->where('is_approved', true) ->orderByDesc('is_preferred') ->get() ->map(fn ($spn) => [ 'supplier_id' => $spn->supplier_id, 'supplier_name' => $spn->supplier?->name, 'supplier_part_number' => $spn->supplier_part_number, 'unit_price' => (float) $spn->unit_price, 'lead_time_days' => $spn->lead_time_days, 'is_preferred' => $spn->is_preferred, ]); } /** * Create sourcing case and procurement tasks when part not available. */ public function createSourcingCase( int $businessId, int $itemMasterId, float $quantity, ?int $installedAssetId = null, ?string $neededBy = null, string $priority = 'medium' ): SupplierSourcingCase { return DB::transaction(function () use ($businessId, $itemMasterId, $quantity, $installedAssetId, $neededBy, $priority) { $caseNumber = 'SRC-'.now()->format('Ymd').'-'.str_pad((string) (SupplierSourcingCase::forBusiness($businessId)->count() + 1), 4, '0', STR_PAD_LEFT); $case = SupplierSourcingCase::create([ 'business_id' => $businessId, 'case_number' => $caseNumber, 'item_master_id' => $itemMasterId, 'installed_asset_id' => $installedAssetId, 'required_quantity' => $quantity, 'needed_by' => $neededBy, 'priority' => $priority, 'status' => 'open', 'assigned_to' => auth()->id(), ]); $stock = $this->findStock($businessId, $itemMasterId); if ($stock->isEmpty()) { ProcurementTask::create([ 'business_id' => $businessId, 'sourcing_case_id' => $case->id, 'item_master_id' => $itemMasterId, 'task_type' => 'find_supplier', 'title' => 'Find supplier for '.$case->itemMaster?->name, 'status' => 'pending', 'assigned_to' => auth()->id(), 'due_date' => $neededBy, ]); } else { ProcurementTask::create([ 'business_id' => $businessId, 'sourcing_case_id' => $case->id, 'item_master_id' => $itemMasterId, 'task_type' => 'internal_transfer', 'title' => 'Internal transfer for '.$case->itemMaster?->name, 'status' => 'pending', 'assigned_to' => auth()->id(), 'due_date' => $neededBy, ]); } return $case->fresh(['itemMaster', 'procurementTasks']); }); } /** * Resolve item cost from variation purchase price or standard cost. */ public function resolveItemCost(ItemMaster $item): float { if ((float) $item->standard_cost > 0) { return (float) $item->standard_cost; } if ($item->variation_id) { $variation = Variation::find($item->variation_id); return (float) ($variation?->dpp_inc_tax ?? 0); } $spn = SupplierPartNumber::where('item_master_id', $item->id) ->where('is_preferred', true) ->first(); return (float) ($spn?->unit_price ?? 0); } }