productUtil->setAndGetReferenceCount('sc_rfq'); $refNo = $data['ref_no'] ?? $this->productUtil->generateReferenceNumber('sc_rfq', $refCount); $rfq = RfqRequest::create([ 'business_id' => $businessId, 'ref_no' => $refNo, 'title' => $data['title'], 'status' => $data['status'] ?? 'draft', 'due_date' => $data['due_date'] ?? null, 'holding_entity_id' => $data['holding_entity_id'] ?? null, 'notes' => $data['notes'] ?? null, 'created_by' => $createdBy ?? auth()->id(), ]); foreach ($lines as $line) { RfqLine::create([ 'rfq_id' => $rfq->id, 'product_id' => $line['product_id'], 'variation_id' => $line['variation_id'] ?? null, 'qty' => $line['qty'], 'specs' => $line['specs'] ?? null, ]); } $this->inviteSuppliers($rfq, $supplierContactIds); return $rfq->fresh(['lines', 'responses.contact']); }); } public function update(RfqRequest $rfq, array $data, ?array $lines = null): RfqRequest { return DB::transaction(function () use ($rfq, $data, $lines) { $rfq->update([ 'title' => $data['title'] ?? $rfq->title, 'status' => $data['status'] ?? $rfq->status, 'due_date' => $data['due_date'] ?? $rfq->due_date, 'holding_entity_id' => $data['holding_entity_id'] ?? $rfq->holding_entity_id, 'notes' => $data['notes'] ?? $rfq->notes, ]); if ($lines !== null) { $rfq->lines()->delete(); foreach ($lines as $line) { RfqLine::create([ 'rfq_id' => $rfq->id, 'product_id' => $line['product_id'], 'variation_id' => $line['variation_id'] ?? null, 'qty' => $line['qty'], 'specs' => $line['specs'] ?? null, ]); } } return $rfq->fresh(['lines', 'responses.contact']); }); } public function inviteSuppliers(RfqRequest $rfq, ?array $contactIds = null): int { if ($contactIds === null) { $contactIds = Contact::where('business_id', $rfq->business_id) ->whereIn('type', ['supplier', 'both']) ->pluck('id') ->all(); } $created = 0; foreach ($contactIds as $contactId) { $response = RfqResponse::firstOrCreate( ['rfq_id' => $rfq->id, 'contact_id' => (int) $contactId], ['status' => 'pending'] ); if ($response->wasRecentlyCreated) { $created++; } } return $created; } public function submitResponse( RfqRequest $rfq, int $contactId, float $quotedPrice, int $leadDays, ?string $notes = null ): RfqResponse { return RfqResponse::updateOrCreate( ['rfq_id' => $rfq->id, 'contact_id' => $contactId], [ 'quoted_price' => $quotedPrice, 'lead_days' => $leadDays, 'status' => 'submitted', 'notes' => $notes, ] ); } public function award(RfqRequest $rfq, int $contactId): RfqRequest { return DB::transaction(function () use ($rfq, $contactId) { $rfq->responses()->update(['status' => 'rejected']); RfqResponse::where('rfq_id', $rfq->id) ->where('contact_id', $contactId) ->update(['status' => 'awarded']); $rfq->update(['status' => 'awarded']); return $rfq->fresh(['lines', 'responses.contact']); }); } public function open(RfqRequest $rfq): RfqRequest { $rfq->update(['status' => 'open']); return $rfq->fresh(['lines', 'responses.contact']); } public function close(RfqRequest $rfq): RfqRequest { $rfq->update(['status' => 'closed']); return $rfq->fresh(['lines', 'responses.contact']); } /** * Create RFQ from MRP shortage lines. * * @param array> $shortages */ public function createFromMrpShortages( int $businessId, array $shortages, ?string $title = null, ?array $supplierContactIds = null ): RfqRequest { $lines = []; foreach ($shortages as $shortage) { $productId = ! empty($shortage['product_id']) ? (int) $shortage['product_id'] : null; if (! $productId && ! empty($shortage['variation_id'])) { $productId = (int) \App\Variation::where('id', (int) $shortage['variation_id'])->value('product_id'); } if (empty($shortage['variation_id']) || ! $productId) { continue; } $lines[] = [ 'product_id' => $productId, 'variation_id' => (int) $shortage['variation_id'], 'qty' => (float) ($shortage['shortage_qty'] ?? $shortage['qty'] ?? 0), 'specs' => $shortage['item_name'] ?? null, ]; } return $this->create( $businessId, [ 'title' => $title ?? __('supplychain::lang.rfq_from_mrp'), 'status' => 'open', 'due_date' => now()->addDays(14)->toDateString(), ], $lines, $supplierContactIds ); } }