254 lines
9.4 KiB
PHP
254 lines
9.4 KiB
PHP
<?php
|
|
|
|
namespace Modules\AssetExchange\Services;
|
|
|
|
use App\Business;
|
|
use App\User;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\AssetExchange\Models\Listing;
|
|
use Modules\AssetExchange\Utils\AssetExchangeUtil;
|
|
|
|
class ListingService
|
|
{
|
|
public function __construct(
|
|
protected AssetExchangeUtil $aexUtil,
|
|
protected PipelineService $pipelineService
|
|
) {
|
|
}
|
|
|
|
public function create(Business $business, array $data, ?User $user = null): Listing
|
|
{
|
|
if (! Schema::hasTable('aex_listings')) {
|
|
throw new \RuntimeException('جدول عرضهها هنوز ایجاد نشده است.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($business, $data, $user) {
|
|
$listing = Listing::create([
|
|
'business_id' => $business->id,
|
|
'listing_code' => $data['listing_code'] ?? $this->aexUtil->generateListingCode($business->id),
|
|
'seller_contact_id' => $data['seller_contact_id'] ?? null,
|
|
'listing_type' => $data['listing_type'] ?? 'machine_used',
|
|
'scope_type' => $data['scope_type'] ?? 'whole',
|
|
'title' => $data['title'],
|
|
'description' => $data['description'] ?? null,
|
|
'location_text' => $data['location_text'] ?? null,
|
|
'site_country' => $data['site_country'] ?? null,
|
|
'year_manufactured' => $data['year_manufactured'] ?? null,
|
|
'condition_grade' => $data['condition_grade'] ?? null,
|
|
'asking_price' => (int) ($data['asking_price'] ?? 0),
|
|
'currency_id' => $data['currency_id'] ?? null,
|
|
'price_negotiable' => $data['price_negotiable'] ?? true,
|
|
'visibility' => $data['visibility'] ?? 'internal',
|
|
'status' => 'draft',
|
|
'ie_line_template_id' => $data['ie_line_template_id'] ?? null,
|
|
'ie_line_revision_id' => $data['ie_line_revision_id'] ?? null,
|
|
'ie_bom_id' => $data['ie_bom_id'] ?? null,
|
|
'ie_installed_asset_id' => $data['ie_installed_asset_id'] ?? null,
|
|
'maintenance_equipment_id' => $data['maintenance_equipment_id'] ?? null,
|
|
'asset_id' => $data['asset_id'] ?? null,
|
|
'parent_listing_id' => $data['parent_listing_id'] ?? null,
|
|
'photos' => $data['photos'] ?? null,
|
|
'documents' => $data['documents'] ?? null,
|
|
'created_by' => $user?->id,
|
|
]);
|
|
|
|
$this->pipelineService->advanceStage($business, $listing, 'draft_listing', null, null, null, $user);
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'listing_created',
|
|
'عرضه جدید ثبت شد: '.$listing->title,
|
|
$user
|
|
);
|
|
|
|
return $listing->fresh();
|
|
});
|
|
}
|
|
|
|
public function update(Listing $listing, array $data, ?User $user = null): Listing
|
|
{
|
|
if (in_array($listing->status, ['sold', 'rejected', 'withdrawn'], true)) {
|
|
throw new \RuntimeException('عرضه در وضعیت فعلی قابل ویرایش نیست.');
|
|
}
|
|
|
|
$listing->update(collect($data)->only($listing->getFillable())->toArray());
|
|
|
|
$this->pipelineService->logEvent(
|
|
$listing->business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'listing_updated',
|
|
'عرضه بهروزرسانی شد',
|
|
$user
|
|
);
|
|
|
|
return $listing->fresh();
|
|
}
|
|
|
|
public function submit(Business $business, Listing $listing, ?User $user = null): Listing
|
|
{
|
|
if ($listing->status !== 'draft') {
|
|
throw new \RuntimeException('فقط عرضههای پیشنویس قابل ارسال هستند.');
|
|
}
|
|
|
|
if (empty($listing->title)) {
|
|
throw new \RuntimeException('عنوان عرضه الزامی است.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($business, $listing, $user) {
|
|
$listing->update([
|
|
'status' => 'submitted',
|
|
'submitted_at' => now(),
|
|
]);
|
|
|
|
$this->pipelineService->advanceStage($business, $listing, 'submitted', null, null, null, $user);
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'listing_submitted',
|
|
'عرضه برای بررسی ارسال شد',
|
|
$user
|
|
);
|
|
|
|
return $listing->fresh();
|
|
});
|
|
}
|
|
|
|
public function assignBroker(Business $business, Listing $listing, int $brokerUserId, ?User $user = null): Listing
|
|
{
|
|
if (! in_array($listing->status, ['submitted', 'under_review'], true)) {
|
|
throw new \RuntimeException('تخصیص کارشناس فقط برای عرضههای ارسالشده یا در حال بررسی مجاز است.');
|
|
}
|
|
|
|
return DB::transaction(function () use ($business, $listing, $brokerUserId, $user) {
|
|
$listing->update([
|
|
'assigned_broker_user_id' => $brokerUserId,
|
|
'status' => 'under_review',
|
|
]);
|
|
|
|
$this->pipelineService->advanceStage($business, $listing, 'under_review', null, null, null, $user);
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'broker_assigned',
|
|
'کارشناس/واسط اختصاص داده شد',
|
|
$user,
|
|
['broker_user_id' => $brokerUserId]
|
|
);
|
|
|
|
return $listing->fresh();
|
|
});
|
|
}
|
|
|
|
public function reject(Business $business, Listing $listing, ?string $reason = null, ?User $user = null): Listing
|
|
{
|
|
return DB::transaction(function () use ($business, $listing, $reason, $user) {
|
|
$listing->update(['status' => 'rejected']);
|
|
|
|
$this->pipelineService->advanceStage($business, $listing, 'rejected', null, null, null, $user);
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'listing_rejected',
|
|
$reason ?: 'عرضه رد شد',
|
|
$user
|
|
);
|
|
|
|
return $listing->fresh();
|
|
});
|
|
}
|
|
|
|
public function markAppraised(Business $business, Listing $listing, ?User $user = null): Listing
|
|
{
|
|
return DB::transaction(function () use ($business, $listing, $user) {
|
|
$listing->update([
|
|
'status' => 'appraised',
|
|
'appraised_at' => now(),
|
|
]);
|
|
|
|
$this->pipelineService->advanceStage($business, $listing, 'appraised', null, null, null, $user);
|
|
$this->pipelineService->logEvent(
|
|
$business,
|
|
Listing::class,
|
|
$listing->id,
|
|
'listing_appraised',
|
|
'کارشناسی عرضه تکمیل شد',
|
|
$user
|
|
);
|
|
|
|
return $listing->fresh();
|
|
});
|
|
}
|
|
|
|
public function listQuery(int $businessId, array $filters = []): Builder
|
|
{
|
|
$query = Listing::query()
|
|
->where('business_id', $businessId)
|
|
->with(['seller', 'broker', 'appraisals', 'deals']);
|
|
|
|
if (! empty($filters['status'])) {
|
|
$query->where('status', $filters['status']);
|
|
}
|
|
|
|
if (! empty($filters['listing_type'])) {
|
|
$query->where('listing_type', $filters['listing_type']);
|
|
}
|
|
|
|
if (! empty($filters['scope_type'])) {
|
|
$query->where('scope_type', $filters['scope_type']);
|
|
}
|
|
|
|
if (! empty($filters['seller_contact_id'])) {
|
|
$query->where('seller_contact_id', (int) $filters['seller_contact_id']);
|
|
}
|
|
|
|
if (! empty($filters['assigned_broker_user_id'])) {
|
|
$query->where('assigned_broker_user_id', (int) $filters['assigned_broker_user_id']);
|
|
}
|
|
|
|
if (! empty($filters['visibility'])) {
|
|
$query->where('visibility', $filters['visibility']);
|
|
}
|
|
|
|
if (! empty($filters['search'])) {
|
|
$term = '%'.$filters['search'].'%';
|
|
$query->where(function ($q) use ($term) {
|
|
$q->where('title', 'like', $term)
|
|
->orWhere('listing_code', 'like', $term)
|
|
->orWhere('location_text', 'like', $term);
|
|
});
|
|
}
|
|
|
|
if (! empty($filters['submitted_from'])) {
|
|
$query->whereDate('submitted_at', '>=', $this->aexUtil->parseInputDate($filters['submitted_from']));
|
|
}
|
|
|
|
if (! empty($filters['submitted_to'])) {
|
|
$query->whereDate('submitted_at', '<=', $this->aexUtil->parseInputDate($filters['submitted_to']));
|
|
}
|
|
|
|
if (! empty($filters['has_ie_bom'])) {
|
|
$query->whereNotNull('ie_bom_id');
|
|
}
|
|
|
|
if (! empty($filters['has_equipment'])) {
|
|
$query->whereNotNull('maintenance_equipment_id');
|
|
}
|
|
|
|
$sortBy = $filters['sort_by'] ?? 'created_at';
|
|
$sortDir = $filters['sort_dir'] ?? 'desc';
|
|
$allowedSort = ['created_at', 'submitted_at', 'asking_price', 'title', 'status'];
|
|
if (in_array($sortBy, $allowedSort, true)) {
|
|
$query->orderBy($sortBy, $sortDir === 'asc' ? 'asc' : 'desc');
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
}
|