ultimatepos/Modules/AssetExchange/Services/ListingService.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;
}
}