From c1e2bb5e9b85c333a00e6fb4ba01313405182d6e Mon Sep 17 00:00:00 2001 From: adonis Date: Sat, 29 Aug 2026 13:50:12 +0330 Subject: [PATCH] fix zarin duplicate hesab and convert mesghal unit price to gram before cash/weight. Reuse an existing Zarin hesab instead of a second addhesab, and stop rial-locked orders from treating mesghal as per-gram. Co-authored-by: Cursor --- .../Controllers/Admin/ZarinController.php | 119 +++++++++++++++--- .../Single/SingleOrderController.php | 41 ++++++ app/Services/ConfigService.php | 28 +++++ 3 files changed, 171 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Admin/ZarinController.php b/app/Http/Controllers/Admin/ZarinController.php index fc00cb7..27393b6 100644 --- a/app/Http/Controllers/Admin/ZarinController.php +++ b/app/Http/Controllers/Admin/ZarinController.php @@ -11,6 +11,7 @@ use App\Models\Zarin; use App\Services\AccountingProductDocMapper; use App\Services\ZarinSyncService; use Carbon\Carbon; +use Illuminate\Contracts\Cache\LockTimeoutException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; @@ -1146,6 +1147,79 @@ class ZarinController extends Controller return ''; } + /** کد حساب زرین از پاسخ addhesab / listhesabha — OK و Code هم مثل idHesab معتبرند */ + private function extractZarinHesabId(array $row): ?int + { + foreach (['idHesab', 'idhesab', 'Code', 'code', 'OK', 'id'] as $key) { + if (! array_key_exists($key, $row)) { + continue; + } + $value = $row[$key]; + if ($value === null || $value === '' || $value === false || $value === true) { + continue; + } + if (is_numeric($value) && (int) $value > 0) { + return (int) $value; + } + } + + return null; + } + + /** حساب از قبل ساخته‌شده در زرین — با موبایل، تا addhesab دوم نسازیم */ + private function findExistingZarinHesabId(?string $mobile): ?int + { + $want = Customer::normalizeMobile($mobile ?? ''); + if ($want === null) { + return null; + } + + $fetched = $this->fetchAllZarinCustomers(); + $rows = $fetched['rows'] ?? []; + if ($rows === []) { + return null; + } + + $groupFilter = $this->zarinCustomerGroupName(); + $sameGroup = null; + $any = null; + + foreach ($rows as $item) { + if (! is_array($item)) { + continue; + } + + $phone = $this->extractZarinPhone($item); + $got = Customer::normalizeMobile($phone); + if ($got === null || $got !== $want) { + continue; + } + + $id = $this->extractZarinHesabId($item); + if ($id === null) { + continue; + } + + $g = $this->extractZarinGroup($item); + if ($groupFilter !== '' && $g !== '' && $this->zarinGroupNamesMatch($g, $groupFilter)) { + $sameGroup = $id; + break; + } + + $any ??= $id; + } + + return $sameGroup ?? $any; + } + + private function bindCustomerZarinHesab(Customer $customer, int $idHesab): int + { + $customer->moshtari_code = $idHesab; + $customer->save(); + + return $idHesab; + } + public function syncPanelToZarin(): \Illuminate\Http\JsonResponse { $customers = Customer::whereNull('deleted_at') @@ -1450,7 +1524,7 @@ class ZarinController extends Controller $isColleagueLeg = ! empty($data['colleague_leg']) && $accountOverride !== ''; if (! $isColleagueLeg) { - if ($customer && ($customer->moshtari_code === null)) { + if ($customer && ! $customer->hasValidMoshtariCode()) { $this->createCustomerInZarin($customer->id); $customer = Customer::where('id', $data['customer_id'])->first(); } @@ -1472,6 +1546,9 @@ class ZarinController extends Controller $count = $mapper->accountingQty($product, $rawAmount, $data); $vazn = $mapper->accountingWeight($product, $rawAmount, $data); $isBuy = (int) ($data['direction'] ?? 0) === 1; + // زرین korf/parDar از دید حساب مشتری است، نه فروشگاه: + // خرید مشتری → k / +1 : طلا بستانکار (سوت اضافه) + ریال بدهکار (پول کم) + // فروش مشتری → f / −1 : طلا بدهکار + ریال بستانکار $orderId = is_object($data['order'] ?? null) ? ($data['order']->id ?? null) : ($data['order']['id'] ?? ($existing->order_id ?? null)); @@ -1530,7 +1607,7 @@ class ZarinController extends Controller $accountCustomer, $product, $count, - $isBuy ? -1 : 1, + $isBuy ? 1 : -1, $fi, $sharh, $this->zarinUid($uidKind, $uidSource ?? $zarin->id) @@ -1541,7 +1618,7 @@ class ZarinController extends Controller $data, $accountCustomer, $vazn, - $isBuy ? 'f' : 'k', + $isBuy ? 'k' : 'f', (float) ($data['price'] ?? 0), $sharh, $mapper->jensFelez($product) ?? 0, @@ -1576,7 +1653,7 @@ class ZarinController extends Controller $customer = Customer::where('id', $data['customer_id'])->first(); - if ($customer && ($customer->moshtari_code === null)) { + if ($customer && ! $customer->hasValidMoshtariCode()) { $this->createCustomerInZarin($customer->id); $customer = Customer::where('id', $data['customer_id'])->first(); } @@ -1698,7 +1775,7 @@ class ZarinController extends Controller return false; } - if ($customer->moshtari_code === null) { + if (! $customer->hasValidMoshtariCode()) { $this->createCustomerInZarin($customer->id); $customer = Customer::where('id', $data['customer_id'])->first(); } @@ -1806,7 +1883,7 @@ class ZarinController extends Controller $customer = Customer::where('id', $data['customer_id'])->first(); - if ($customer && ($customer->moshtari_code === null)) { + if ($customer && ! $customer->hasValidMoshtariCode()) { $this->createCustomerInZarin($customer->id); $customer = Customer::where('id', $data['customer_id'])->first(); } @@ -2066,16 +2143,18 @@ class ZarinController extends Controller { $customer = Customer::findOrFail($customer_id); - if ($customer->moshtari_code != null) { + if ($customer->hasValidMoshtariCode()) { return $customer->moshtari_code; } - $lock = Cache::lock('zarin:register-customer:' . $customer->id, 30); + $lock = Cache::lock('zarin:register-customer:' . $customer->id, 90); - if (!$lock->get()) { + try { + $lock->block(25); + } catch (LockTimeoutException $e) { $customer->refresh(); - if ($customer->moshtari_code != null) { + if ($customer->hasValidMoshtariCode()) { return $customer->moshtari_code; } @@ -2085,10 +2164,15 @@ class ZarinController extends Controller try { $customer->refresh(); - if ($customer->moshtari_code != null) { + if ($customer->hasValidMoshtariCode()) { return $customer->moshtari_code; } + $existingId = $this->findExistingZarinHesabId($customer->mobile); + if ($existingId !== null) { + return $this->bindCustomerZarinHesab($customer, $existingId); + } + $name = $customer->fname ?: ($customer->name ?? ('مشتری ' . $customer->id)); $family = $customer->lname ?: ''; $mobile = $customer->mobile ?? ''; @@ -2119,14 +2203,15 @@ class ZarinController extends Controller } $data = $this->extractResponseRow($response); - $idHesab = $data['idHesab'] ?? null; + $idHesab = $this->extractZarinHesabId($data); - if (!empty($idHesab)) { - $customer->moshtari_code = $idHesab; - $customer->save(); + if ($idHesab !== null) { + return $this->bindCustomerZarinHesab($customer, $idHesab); + } - // int برگردان تا با پیام خطای string اشتباه نشود - return is_numeric($idHesab) ? (0 + $idHesab) : $idHesab; + $existingId = $this->findExistingZarinHesabId($customer->mobile); + if ($existingId !== null) { + return $this->bindCustomerZarinHesab($customer, $existingId); } $detail = $data['msg'] ?? $data['message'] ?? $data['detail'] ?? null; diff --git a/app/Http/Controllers/Single/SingleOrderController.php b/app/Http/Controllers/Single/SingleOrderController.php index cf27006..d0268f4 100644 --- a/app/Http/Controllers/Single/SingleOrderController.php +++ b/app/Http/Controllers/Single/SingleOrderController.php @@ -99,6 +99,7 @@ class SingleOrderController extends Controller } else if (auth('user')->id()) {//admin $data['service_charge_percentage'] = 0; } + $this->ensureOrderUnitPriceIsPerGram($data); if($data['price'] === 0){ throw new UnprocessableEntityHttpException(' سفارشات بسته میباشد.'); } @@ -320,6 +321,7 @@ class SingleOrderController extends Controller $feeRate = isset($data['fee_free']) && $data['fee_free'] === true ? 0.0 : (float) ($data['service_charge_percentage'] ?? 0); + $this->ensureOrderUnitPriceIsPerGram($data); $data['price'] += $data['difference']; if ($rialLocked) { @@ -586,6 +588,7 @@ class SingleOrderController extends Controller $feeRate = isset($data['fee_free']) && $data['fee_free'] === true ? 0.0 : (float) ($data['service_charge_percentage'] ?? 0); + $this->ensureOrderUnitPriceIsPerGram($data); $data['price'] -= $data['difference']; if ($rialLocked) { @@ -1330,6 +1333,44 @@ class SingleOrderController extends Controller return round($amount, 10); } + /** مظنهٔ زندهٔ هر گرم (ریال) — برای تشخیص قیمت مثقال به‌جای گرم */ + private function liveGramQuoteRial(): float + { + $base = (float) app(ProductDisplayPriceService::class)->resolveAppBasePrice(); + if ($base > 0) { + return $base; + } + + $montzar = (float) app(ConfigService::class)->get('montzar_live_price', 0); + if ($montzar > 0) { + return $montzar; + } + + return (float) (Price::latest()->first()?->price ?? 0); + } + + /** + * اگر قیمت واحد سفارش حدود قیمت مثقال باشد، به قیمت هر گرم برگردان. + * جلوگیری از ۱۰ میلیون تومان → ۱۰۵ سوت وقتی مثقال در فیلد «هر گرم» نشسته. + */ + private function ensureOrderUnitPriceIsPerGram(array &$data): void + { + $live = $this->liveGramQuoteRial(); + $config = app(ConfigService::class); + $price = $config->gramUnitPriceIfMesghalQuoted((float) ($data['price'] ?? 0), $live); + if ($price <= 0 || abs($price - (float) ($data['price'] ?? 0)) < 1) { + return; + } + + $data['price'] = $price; + if (isset($data['price_original'])) { + $data['price_original'] = $config->gramUnitPriceIfMesghalQuoted( + (float) $data['price_original'], + $live + ); + } + } + private function resolveGoldPaymentProductId(array $data): ?int { if (!empty($data['payment_product_id'])) { diff --git a/app/Services/ConfigService.php b/app/Services/ConfigService.php index d06e4b1..29ab937 100644 --- a/app/Services/ConfigService.php +++ b/app/Services/ConfigService.php @@ -193,6 +193,34 @@ class ConfigService return $raw === '' ? 0.0 : (float) $raw; } + public const DEFAULT_CHANGE_MESGHAL = 4.3318; + + /** وزن هر مثقال به گرم — برای تشخیص قیمت مثقال به‌جای قیمت گرم */ + public function changeMesghalFactor(): float + { + $value = (float) $this->get('change_mesghal', self::DEFAULT_CHANGE_MESGHAL); + + return $value > 0 ? $value : self::DEFAULT_CHANGE_MESGHAL; + } + + /** + * اگر قیمت واحد حدود قیمت مثقال باشد (گرم زنده × ضریب)، به قیمت هر گرم برگردان. + */ + public function gramUnitPriceIfMesghalQuoted(float $unitPrice, float $liveGramPrice): float + { + $factor = $this->changeMesghalFactor(); + if ($unitPrice <= 0 || $liveGramPrice <= 0 || $factor <= 1) { + return $unitPrice; + } + + $ratio = $unitPrice / $liveGramPrice; + if ($ratio >= $factor * 0.92 && $ratio <= $factor * 1.08) { + return $unitPrice / $factor; + } + + return $unitPrice; + } + /** اختلاف فروش نمایش — فقط sell_fee (صفر یعنی بدون اختلاف) */ public function displaySellPriceDiffRial(): float {