ultimatepos/Modules/SupplyChain/Services/RfqService.php

198 lines
6.4 KiB
PHP

<?php
namespace Modules\SupplyChain\Services;
use App\Contact;
use App\Utils\ProductUtil;
use Illuminate\Support\Facades\DB;
use Modules\SupplyChain\Models\RfqLine;
use Modules\SupplyChain\Models\RfqRequest;
use Modules\SupplyChain\Models\RfqResponse;
class RfqService
{
public function __construct(
protected ProductUtil $productUtil
) {
}
public function create(
int $businessId,
array $data,
array $lines,
?array $supplierContactIds = null,
?int $createdBy = null
): RfqRequest {
return DB::transaction(function () use ($businessId, $data, $lines, $supplierContactIds, $createdBy) {
$refCount = $this->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<int, array<string, mixed>> $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
);
}
}