248 lines
9.9 KiB
PHP
248 lines
9.9 KiB
PHP
<?php
|
|
|
|
namespace Modules\AssetExchange\Services;
|
|
|
|
use App\Business;
|
|
use App\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\AssetExchange\Models\AppraisalLine;
|
|
use Modules\AssetExchange\Models\Listing;
|
|
use Modules\AssetExchange\Models\PriceInquiry;
|
|
use Modules\AssetExchange\Models\PriceInquiryLine;
|
|
use Modules\AssetExchange\Models\PriceInquiryRecipient;
|
|
use Modules\AssetExchange\Utils\AssetExchangeUtil;
|
|
|
|
class PriceInquiryService
|
|
{
|
|
public function __construct(
|
|
protected AssetExchangeUtil $aexUtil,
|
|
protected PipelineService $pipelineService,
|
|
protected AssetExchangeNotificationService $notificationService
|
|
) {
|
|
}
|
|
|
|
public function create(Business $business, Listing $listing, array $data, ?User $user = null): PriceInquiry
|
|
{
|
|
if (! Schema::hasTable('aex_price_inquiries')) {
|
|
throw new \RuntimeException('جدول استعلام قیمت موجود نیست.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($business, $listing, $data, $user) {
|
|
$inquiry = PriceInquiry::create([
|
|
'business_id' => $business->id,
|
|
'listing_id' => $listing->id,
|
|
'appraisal_id' => $data['appraisal_id'] ?? null,
|
|
'inquiry_code' => $data['inquiry_code'] ?? $this->aexUtil->generateInquiryCode($business->id),
|
|
'title' => $data['title'] ?? ('استعلام قیمت — '.$listing->title),
|
|
'status' => 'draft',
|
|
'inquiry_type' => $data['inquiry_type'] ?? 'mixed',
|
|
'notes' => $data['notes'] ?? null,
|
|
'due_date' => ! empty($data['due_date'])
|
|
? $this->aexUtil->parseInputDate($data['due_date'])
|
|
: null,
|
|
'created_by' => $user?->id,
|
|
]);
|
|
|
|
if (! empty($data['lines']) && is_array($data['lines'])) {
|
|
$this->syncLines($business, $inquiry, $data['lines']);
|
|
} elseif (! empty($data['from_appraisal']) && $inquiry->appraisal_id) {
|
|
$this->importLinesFromAppraisal($business, $inquiry);
|
|
} else {
|
|
$this->addDefaultMachineLine($business, $inquiry, $listing);
|
|
}
|
|
|
|
if (! empty($data['supplier_contact_ids'])) {
|
|
foreach ((array) $data['supplier_contact_ids'] as $contactId) {
|
|
$this->addRecipient($business, $inquiry, 'supplier', (int) $contactId, null);
|
|
}
|
|
}
|
|
|
|
if (! empty($data['internal_user_ids'])) {
|
|
foreach ((array) $data['internal_user_ids'] as $userId) {
|
|
$this->addRecipient($business, $inquiry, 'internal_user', null, (int) $userId);
|
|
}
|
|
}
|
|
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
PriceInquiry::class,
|
|
$inquiry->id,
|
|
'price_inquiry_created',
|
|
'استعلام قیمت ایجاد شد',
|
|
$user
|
|
);
|
|
|
|
return $inquiry->fresh(['lines', 'recipients']);
|
|
});
|
|
}
|
|
|
|
public function send(PriceInquiry $inquiry, ?User $user = null): PriceInquiry
|
|
{
|
|
$inquiry->loadMissing('business', 'recipients', 'listing');
|
|
$inquiry->update(['status' => 'sent']);
|
|
|
|
PriceInquiryRecipient::where('price_inquiry_id', $inquiry->id)
|
|
->where('status', 'pending')
|
|
->update(['sent_at' => now(), 'notified_at' => now()]);
|
|
|
|
try {
|
|
$this->notificationService->notifyInquiryRecipients($inquiry->business, $inquiry);
|
|
} catch (\Throwable) {
|
|
}
|
|
|
|
$this->pipelineService->logEvent(
|
|
$inquiry->business,
|
|
PriceInquiry::class,
|
|
$inquiry->id,
|
|
'price_inquiry_sent',
|
|
'استعلام قیمت برای گیرندگان ارسال شد',
|
|
$user
|
|
);
|
|
|
|
return $inquiry->fresh(['recipients']);
|
|
}
|
|
|
|
public function recordQuote(PriceInquiryRecipient $recipient, array $data, ?User $user = null): PriceInquiryRecipient
|
|
{
|
|
$recipient->loadMissing('inquiry.listing', 'contact');
|
|
$recipient->update([
|
|
'status' => 'responded',
|
|
'quoted_price' => (int) ($data['quoted_price'] ?? 0),
|
|
'currency' => $data['currency'] ?? 'IRR',
|
|
'quote_notes' => $data['quote_notes'] ?? null,
|
|
'responded_at' => now(),
|
|
]);
|
|
|
|
$inquiry = $recipient->inquiry;
|
|
if ($inquiry) {
|
|
$pending = $inquiry->recipients()->where('status', 'pending')->count();
|
|
$inquiry->update(['status' => $pending > 0 ? 'partial' : 'completed']);
|
|
|
|
try {
|
|
$inquiry->loadMissing('creator', 'listing.broker');
|
|
$notifiedUserIds = [];
|
|
$supplierName = $recipient->contact?->name ?: 'تأمینکننده';
|
|
$message = $supplierName.' برای استعلام '.$inquiry->inquiry_code.' قیمت ثبت کرد.';
|
|
|
|
$business = Business::find($inquiry->business_id);
|
|
if ($business && $inquiry->creator) {
|
|
$this->notificationService->notify(
|
|
$business,
|
|
'aex_supplier_quote_submitted',
|
|
'ثبت قیمت از سمت تأمینکننده',
|
|
$message,
|
|
$inquiry->creator,
|
|
null,
|
|
['inquiry_id' => $inquiry->id, 'url' => action([\Modules\AssetExchange\Http\Controllers\PriceInquiryController::class, 'show'], [$inquiry->id])],
|
|
'high'
|
|
);
|
|
$notifiedUserIds[] = $inquiry->creator->id;
|
|
}
|
|
|
|
$broker = $inquiry->listing?->broker;
|
|
if ($business && $broker && ! in_array($broker->id, $notifiedUserIds, true)) {
|
|
$this->notificationService->notify(
|
|
$business,
|
|
'aex_supplier_quote_submitted',
|
|
'ثبت قیمت از سمت تأمینکننده',
|
|
$message,
|
|
$broker,
|
|
null,
|
|
['inquiry_id' => $inquiry->id, 'url' => action([\Modules\AssetExchange\Http\Controllers\PriceInquiryController::class, 'show'], [$inquiry->id])],
|
|
'high'
|
|
);
|
|
}
|
|
} catch (\Throwable) {
|
|
}
|
|
}
|
|
|
|
return $recipient->fresh();
|
|
}
|
|
|
|
public function markRecipientViewed(PriceInquiryRecipient $recipient): PriceInquiryRecipient
|
|
{
|
|
if (! $recipient->viewed_at) {
|
|
$recipient->update(['viewed_at' => now()]);
|
|
}
|
|
|
|
return $recipient->fresh();
|
|
}
|
|
|
|
public function addRecipient(Business $business, PriceInquiry $inquiry, string $type, ?int $contactId, ?int $userId): PriceInquiryRecipient
|
|
{
|
|
return PriceInquiryRecipient::create([
|
|
'business_id' => $business->id,
|
|
'price_inquiry_id' => $inquiry->id,
|
|
'recipient_type' => $type,
|
|
'contact_id' => $contactId,
|
|
'user_id' => $userId,
|
|
'status' => 'pending',
|
|
]);
|
|
}
|
|
|
|
protected function syncLines(Business $business, PriceInquiry $inquiry, array $lines): void
|
|
{
|
|
$sort = 0;
|
|
foreach ($lines as $line) {
|
|
if (empty($line['part_name']) && empty($line['description'])) {
|
|
continue;
|
|
}
|
|
PriceInquiryLine::create([
|
|
'business_id' => $business->id,
|
|
'price_inquiry_id' => $inquiry->id,
|
|
'line_kind' => $line['line_kind'] ?? 'part',
|
|
'part_name' => $line['part_name'] ?? null,
|
|
'description' => $line['description'] ?? null,
|
|
'quantity' => (float) ($line['quantity'] ?? 1),
|
|
'target_condition' => $line['target_condition'] ?? 'either',
|
|
'ie_item_master_id' => $line['ie_item_master_id'] ?? null,
|
|
'appraisal_line_id' => $line['appraisal_line_id'] ?? null,
|
|
'reference_price' => isset($line['reference_price']) ? (int) $line['reference_price'] : null,
|
|
'notes' => $line['notes'] ?? null,
|
|
'sort_order' => $sort++,
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function importLinesFromAppraisal(Business $business, PriceInquiry $inquiry): void
|
|
{
|
|
$lines = AppraisalLine::where('appraisal_id', $inquiry->appraisal_id)
|
|
->whereIn('renovation_need', ['minor', 'major', 'replace'])
|
|
->orderBy('sort_order')
|
|
->get();
|
|
|
|
$sort = 0;
|
|
foreach ($lines as $line) {
|
|
PriceInquiryLine::create([
|
|
'business_id' => $business->id,
|
|
'price_inquiry_id' => $inquiry->id,
|
|
'line_kind' => 'part',
|
|
'part_name' => $line->name,
|
|
'description' => $line->condition_notes,
|
|
'quantity' => 1,
|
|
'target_condition' => $line->renovation_need === 'replace' ? 'new' : 'either',
|
|
'appraisal_line_id' => $line->id,
|
|
'ie_item_master_id' => $line->ie_item_master_id,
|
|
'reference_price' => $line->replacement_cost ?: null,
|
|
'sort_order' => $sort++,
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function addDefaultMachineLine(Business $business, PriceInquiry $inquiry, Listing $listing): void
|
|
{
|
|
PriceInquiryLine::create([
|
|
'business_id' => $business->id,
|
|
'price_inquiry_id' => $inquiry->id,
|
|
'line_kind' => 'machine',
|
|
'part_name' => $listing->title,
|
|
'description' => trim(($listing->manufacturer ?? '').' '.($listing->model ?? '')),
|
|
'quantity' => 1,
|
|
'target_condition' => str_contains($listing->listing_type, 'new') ? 'new' : 'used',
|
|
'reference_price' => $listing->asking_price ?: null,
|
|
'sort_order' => 0,
|
|
]);
|
|
}
|
|
}
|