$data * @return array{applied: int, skipped: int, errors: list, logs: list} */ public function applyDocuments(Customer $customer, array $data, bool $broadcast = true): array { $transactions = is_array($data['transactions'] ?? null) ? $data['transactions'] : []; Log::warning('Pargar applyDocuments blocked (frozen)', [ 'customer_id' => $customer->id, 'tx_count' => count($transactions), ]); return [ 'applied' => 0, 'skipped' => count($transactions), 'errors' => ['اعمال اسناد پرگار روی کیف موقتاً غیرفعال است'], 'logs' => [], ]; } /** * @return array{ * documents: array{applied: int, skipped: int, errors: list, logs: list}, * mande: array{applied: bool, gold_delta?: float, rial_delta?: float, message: string} * } */ public function syncWalletFromPargar(Customer $customer, bool $forceMande = false): array { if (self::isApplyFrozen() && ! $forceMande) { return [ 'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => [], 'logs' => []], 'mande' => [ 'applied' => false, 'message' => 'اعمال کیف از پرگار یخ‌زده است — ابتدا با دستور pargar:wallet-freeze off آزاد کنید', ], ]; } $lock = Cache::lock('pargar-wallet-sync:'.$customer->id, 90); if (! $lock->get()) { return [ 'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => ['همگام‌سازی در حال اجراست'], 'logs' => []], 'mande' => ['applied' => false, 'message' => 'همگام‌سازی دیگری برای این مشتری در حال اجراست — لطفاً صبر کنید'], ]; } try { return [ 'documents' => ['applied' => 0, 'skipped' => 0, 'errors' => [], 'logs' => []], 'mande' => $this->applyLatestMandeDifference($customer, force: $forceMande), ]; } finally { $lock->release(); } } /** * @return array{applied: bool, gold_delta?: float, rial_delta?: float, message: string} */ public function applyLatestMandeDifference(Customer $customer, bool $force = false): array { if (self::isApplyFrozen() && ! $force) { return [ 'applied' => false, 'message' => 'اعمال کیف از پرگار یخ‌زده است', ]; } if ($this->hasPendingOutboundWalletSync((int) $customer->id)) { return [ 'applied' => false, 'message' => 'سند ارسالی از پنل به پرگار هنوز تأیید نشده — برای جلوگیری از برگشت شارژ، ماندهٔ پرگار اعمال نشد', ]; } $log = PargarSyncLog::query() ->where('customer_id', $customer->id) ->where('type', PargarSyncService::TYPE_MANDE) ->orderByDesc('id') ->first(); if ($log === null) { return ['applied' => false, 'message' => 'لاگ ماندهٔ پرگار برای این مشتری یافت نشد']; } $data = json_decode((string) $log->data, true) ?: []; if (! $force && ! empty($data['wallet_applied_from_mande'])) { return ['applied' => false, 'message' => 'ماندهٔ پرگار قبلاً روی کیف اعمال شده است']; } $goldInv = $this->parseNumber($data['GoldInv'] ?? 0); $rialInv = $this->parseNumber($data['RialInv'] ?? 0); $ownerType = (int) ($customer->type ?? 2); $walletInterface = new WalletInterface((int) $customer->id, $ownerType); $wallet = $walletInterface->wallet; $goldDiff = $goldInv - (float) ($wallet->gold ?? 0); $rialDiff = $rialInv - (float) ($wallet->cash ?? 0); if (abs($goldDiff) < 0.01 && abs($rialDiff) < 1) { $data['wallet_applied_from_mande'] = true; $log->data = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); $log->save(); return ['applied' => false, 'message' => 'اختلافی بین ماندهٔ پرگار و کیف اپ نیست']; } $input = [ 'title' => 'تطبیق ماندهٔ پرگار', 'note' => 'mande-log:'.$log->id, 'expire_at' => now(), 'active' => true, ]; if (abs($goldDiff) >= 0.01) { $input['gold'] = abs($goldDiff); } if (abs($rialDiff) >= 1) { $input['cash'] = abs($rialDiff); } new TransactionInterface( $walletInterface, $input, self::TX_TYPE_PARGAR, $goldDiff > 0, $rialDiff > 0 ); $data['wallet_applied_from_mande'] = true; $data['applied_gold_delta'] = $goldDiff; $data['applied_rial_delta'] = $rialDiff; $data['applied_at'] = now()->toDateTimeString(); $log->data = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT); $log->desc = 'اعمال ماندهٔ پرگار روی کیف اپ'; $log->save(); $this->walletBroadcast->broadcastForCustomer((int) $customer->id, $ownerType); return [ 'applied' => true, 'gold_delta' => $goldDiff, 'rial_delta' => $rialDiff, 'message' => 'ماندهٔ پرگار روی کیف اعمال شد', ]; } private function hasPendingOutboundWalletSync(int $customerId): bool { return PargarSyncLog::query() ->where('customer_id', $customerId) ->whereIn('type', [PargarSyncService::TYPE_WALLET_CASH, PargarSyncService::TYPE_WALLET_GOLD]) ->where('sync_status', PargarSyncService::STATUS_PENDING) ->exists(); } private function parseNumber(mixed $value): float { if ($value === null || $value === '') { return 0.0; } return (float) str_replace('/', '.', (string) $value); } }