ultimatepos/Modules/AssetExchange/Services/DealService.php

236 lines
8.8 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\Commission;
use Modules\AssetExchange\Models\Deal;
use Modules\AssetExchange\Models\Listing;
use Modules\AssetExchange\Utils\AssetExchangeUtil;
class DealService
{
public const DEAL_TYPES = [
'company_purchase',
'brokerage',
'appraisal_only',
'renovation_contract',
];
public function __construct(
protected AssetExchangeUtil $aexUtil,
protected AssetExchangeErpBridgeService $erpBridge,
protected AssetExchangeCmmsBridgeService $cmmsBridge,
protected AssetExchangeCrmBridgeService $crmBridge,
protected PipelineService $pipelineService,
protected ListingService $listingService
) {
}
public function createDeal(Business $business, Listing $listing, string $dealType, array $data = [], ?User $user = null): Deal
{
if (! in_array($dealType, self::DEAL_TYPES, true)) {
throw new \InvalidArgumentException('نوع معامله نامعتبر است: '.$dealType);
}
$settings = $this->aexUtil->getAexSettings($business->id);
if (($settings['require_appraisal_before_deal'] ?? true) && ! in_array($listing->status, ['appraised', 'in_deal'], true)) {
throw new \RuntimeException('قبل از ایجاد معامله، کارشناسی عرضه باید تکمیل شود.');
}
$appraisal = null;
if (! empty($data['appraisal_id'])) {
$appraisal = \Modules\AssetExchange\Models\Appraisal::where('business_id', $business->id)
->where('listing_id', $listing->id)
->find($data['appraisal_id']);
} else {
$appraisal = $listing->appraisals()->where('status', 'approved')->latest('id')->first();
}
return DB::transaction(function () use ($business, $listing, $dealType, $data, $user, $settings, $appraisal) {
$agreedPrice = (int) ($data['agreed_price']
?? $appraisal?->recommended_buy_price
?? $listing->asking_price);
$commissionPercent = (float) ($data['commission_percent']
?? $settings['default_commission_percent']
?? 0);
$deal = Deal::create([
'business_id' => $business->id,
'listing_id' => $listing->id,
'appraisal_id' => $appraisal?->id,
'deal_code' => $data['deal_code'] ?? $this->aexUtil->generateDealCode($business->id),
'deal_type' => $dealType,
'buyer_contact_id' => $data['buyer_contact_id'] ?? null,
'status' => 'negotiating',
'agreed_price' => $agreedPrice,
'commission_percent' => $commissionPercent,
'commission_amount' => (int) round($agreedPrice * ($commissionPercent / 100)),
'notes' => $data['notes'] ?? null,
'created_by' => $user?->id,
]);
$listing->update(['status' => 'in_deal']);
$stage = match ($dealType) {
'company_purchase' => 'company_buy',
'brokerage' => 'broker_sale',
'renovation_contract' => 'renovation_contract',
default => 'deal_negotiation',
};
$this->pipelineService->advanceStage($business, $listing, 'deal_negotiation', $deal->id, Deal::class, $deal->id, $user);
$this->pipelineService->advanceStage($business, $listing, $stage, $deal->id, Deal::class, $deal->id, $user);
$this->pipelineService->logEvent(
$business,
Deal::class,
$deal->id,
'deal_created',
'معامله جدید ایجاد شد: '.$this->aexUtil->dealTypeLabel($dealType),
$user
);
$this->bootstrapDealRecords($business, $listing, $deal, $user);
return $deal->fresh(['listing', 'appraisal']);
});
}
public function updateStatus(Business $business, Deal $deal, string $status, ?User $user = null): Deal
{
$allowed = ['negotiating', 'quoted', 'contracted', 'in_progress', 'completed', 'cancelled'];
if (! in_array($status, $allowed, true)) {
throw new \InvalidArgumentException('وضعیت معامله نامعتبر است.');
}
$deal->update(['status' => $status]);
$this->pipelineService->logEvent(
$business,
Deal::class,
$deal->id,
'deal_status_changed',
'وضعیت معامله به «'.$this->aexUtil->statusLabel($status).'» تغییر کرد',
$user,
['status' => $status]
);
return $deal->fresh();
}
public function completeDeal(Business $business, Deal $deal, ?User $user = null): Deal
{
return DB::transaction(function () use ($business, $deal, $user) {
$deal->loadMissing('listing', 'appraisal');
$listing = $deal->listing;
$deal->update(['status' => 'completed']);
if ($listing) {
$listing->update(['status' => 'sold']);
$this->pipelineService->advanceStage($business, $listing, 'sold', $deal->id, Deal::class, $deal->id, $user);
}
if ($deal->deal_type === 'brokerage' && $deal->commission_amount > 0) {
$this->ensureBrokerCommission($deal, $user);
$this->crmBridge->syncCommissions($business, $deal);
}
if ($deal->deal_type === 'company_purchase' && ($this->aexUtil->getAexSettings($business->id)['auto_create_cmms_on_acquire'] ?? true)) {
try {
$this->cmmsBridge->createEquipmentFromListing($business, $listing, $user);
} catch (\Throwable) {
}
}
if ($deal->deal_type === 'renovation_contract') {
try {
$this->cmmsBridge->startRebuildFromDeal($business, $deal, $user);
if ($listing) {
$this->pipelineService->advanceStage($business, $listing, 'in_renovation', $deal->id, Deal::class, $deal->id, $user);
}
} catch (\Throwable) {
}
}
$this->pipelineService->logEvent(
$business,
Deal::class,
$deal->id,
'deal_completed',
'معامله تکمیل شد',
$user
);
return $deal->fresh(['listing', 'commissions']);
});
}
public function linkTransaction(Deal $deal, string $side, int $transactionId): Deal
{
if (! Schema::hasTable('transactions')) {
throw new \RuntimeException('جدول تراکنش‌ها موجود نیست.');
}
$field = match ($side) {
'purchase' => 'transaction_purchase_id',
'sell' => 'transaction_sell_id',
default => throw new \InvalidArgumentException('نوع تراکنش باید purchase یا sell باشد.'),
};
$deal->update([$field => $transactionId]);
return $deal->fresh();
}
protected function bootstrapDealRecords(Business $business, Listing $listing, Deal $deal, ?User $user): void
{
try {
match ($deal->deal_type) {
'company_purchase' => $this->erpBridge->createPurchaseOrder($business, $listing, $deal, null, $user),
'brokerage' => $this->erpBridge->createQuotation($business, $listing, $deal, null, $user),
'appraisal_only' => $this->erpBridge->createSellInvoice(
$business,
$listing,
$deal->fresh(['appraisal']),
null,
$user
),
'renovation_contract' => $this->erpBridge->createQuotation($business, $listing, $deal, null, $user),
default => null,
};
} catch (\Throwable) {
}
}
protected function ensureBrokerCommission(Deal $deal, ?User $user): void
{
if (! Schema::hasTable('aex_commissions')) {
return;
}
$brokerId = $deal->listing?->assigned_broker_user_id ?: $user?->id;
if (! $brokerId) {
return;
}
Commission::updateOrCreate(
[
'deal_id' => $deal->id,
'user_id' => $brokerId,
'role' => 'broker',
],
[
'business_id' => $deal->business_id,
'amount' => $deal->commission_amount,
'status' => 'pending',
]
);
}
}