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', ] ); } }