ultimatepos/Modules/AssetExchange/Services/PriceInquiryService.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,
]);
}
}