diff --git a/app/Http/Controllers/Admin/TahesabController.php b/app/Http/Controllers/Admin/TahesabController.php index 406bb01..a21226d 100644 --- a/app/Http/Controllers/Admin/TahesabController.php +++ b/app/Http/Controllers/Admin/TahesabController.php @@ -10,6 +10,7 @@ use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Cache; use App\Models\Tahesab; use App\Models\Customer; +use App\Services\AccountingProductDocMapper; use App\Services\TahesabSyncService; use Morilog\Jalali\Jalalian; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; @@ -421,6 +422,10 @@ class TahesabController extends Controller if (empty($data['customer_id']) && $existing && $existing->order_id) { $order = \App\Models\Order::find($existing->order_id); if ($order) { + $productPayload = ['metal_id' => $existing->type === 'طلا' ? 1 : 2]; + if ($order->product_id) { + $productPayload['id'] = (int) $order->product_id; + } $data = array_merge($data, [ 'customer_id' => $existing->customer_id, 'amount' => $existing->amount, @@ -428,8 +433,9 @@ class TahesabController extends Controller 'price' => $order->price, 'total_price' => $order->total_price, 'pay_at' => $existing->date, - 'product' => ['metal_id' => $existing->type === 'طلا' ? 1 : 2], - 'order' => ['id' => $existing->order_id], + 'product' => $productPayload, + 'product_id' => $order->product_id ? (int) $order->product_id : null, + 'order' => ['id' => $existing->order_id, 'product_id' => $order->product_id], ]); } } @@ -440,71 +446,91 @@ class TahesabController extends Controller : false; } -// $customer = auth()->user(); -// if (!$customer) { -// throw new UnprocessableEntityHttpException("Auth needed"); -// } + $mapper = app(AccountingProductDocMapper::class); + $product = $mapper->resolveProductFromPayload($data); + if ($product && $mapper->shouldSkip($product)) { + return false; + } + $customer = Customer::where('id', $data['customer_id'])->first(); if ($customer && ($customer->moshtari_code === null)) { $this->createCustomerInTahesab($customer->id); $customer = Customer::where('id', $data['customer_id'])->first(); } - $date = Verta::instance(Carbon::parse($data['pay_at']))->format('Y-m-d'); + + $kind = $mapper->resolveKind($product); + $rawAmount = (float) $data['amount']; + $count = $mapper->accountingQty($product, $rawAmount, $data); + $vazn = $mapper->accountingWeight($product, $rawAmount, $data); + $jens = $mapper->jensFelez($product) ?? 0; + $title = $mapper->productTitle($product); + $isBuy = (int) ($data['direction'] ?? 0) === 1; + $price = (float) ($data['price'] ?? 0); + $mablaghKol = $isBuy + ? (int) round($vazn * $price) + : (int) ($data['total_price'] ?? round($vazn * $price)); + // سند تعدادی: مظنه = فی هر عدد (نه لزوماً قیمت هر گرم) + $mazaneh = (int) $price; + if ($mapper->isCountDoc($product) && $count > 0) { + if (!empty($data['total_price'])) { + $mazaneh = (int) round(((float) $data['total_price']) / $count); + } elseif ($price > 0 && abs($vazn - $count) > 0.0001) { + // price احتمالاً هر گرم است + $unitW = $vazn / $count; + $mazaneh = (int) round($price * $unitW); + } + if ($mazaneh <= 0 && $mablaghKol > 0) { + $mazaneh = (int) round($mablaghKol / $count); + } + } + + $date = Verta::instance(Carbon::parse($data['pay_at'] ?? now()))->format('Y-m-d'); [$year, $month, $day] = explode('-', $date); - $amount = $data['amount']; - $isBuy = (int) ($data['direction'] ?? 0) === 1; - $mablaghKol = $isBuy - ? (int) round((float) $amount * (float) $data['price']) - : (int) ($data['total_price'] ?? round((float) $amount * (float) $data['price'])); + $action = $isBuy ? 'خرید' : 'فروش'; + $kindFa = $mapper->kindLabel($kind); + $sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}"); $data['handler'] = 'storeTrade'; if ($existing && empty($existing->data)) { $existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]); } $tahesab = $existing ?? Tahesab::create([ - "customer_id" => $customer->id, - "desc" => $data['direction'] == 1 ? "خرید آبشده" : "فروش آبشده", - "amount" => $amount, - "order_id" => $data['order']['id'] ?? null, - "date" => $data['pay_at'], - "type" => $data['product']['metal_id'] == 1 ? "طلا" : "نقره", - "created_at" => Carbon::now(), - "data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), + 'customer_id' => $customer->id, + 'desc' => $sharh, + 'amount' => $mapper->isCountDoc($product) ? $count : $vazn, + 'order_id' => $data['order']['id'] ?? null, + 'date' => $data['pay_at'] ?? Carbon::now(), + 'type' => $mapper->metalLabel($product), + 'created_at' => Carbon::now(), + 'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), ]); + if ($mapper->isCountDoc($product) && $title === '') { + $this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است'); + return false; + } + + $body = $this->buildBuySaleBody( + $kind, + (int) $customer->moshtari_code, + (int) $year, + (int) $month, + (int) $day, + $vazn, + $count, + $title, + $isBuy ? 1 : 0, + $mazaneh, + $mablaghKol, + $sharh, + $jens, + (string) Verta::instance($data['pay_at'] ?? now()) + ); + try { - $response = Http::withoutVerifying()->timeout(10)->withHeaders([ - 'DBName' => $this->Config('tahesab_db'), - 'Content-Type' => 'application/json', - 'Authorization' => "Bearer " . $this->Config('tahesab_token'), - ])->post($this->tahesabApiUrl(), [ - "DoNewSanadBuySaleGOLD" => [ - 1,//Sabte_Kol_Or_Movaghat_1_0 - (int)$customer->moshtari_code, //Moshtari_Code - -1, //Factor_Number - 1, //Radif_Number - (int)$year, //Shamsi_Year - (int)$month, //Shamsi_Month - (int)$day, //Shamsi_Day - (float)$amount, //Vazn - $data['product']['metal_id'] == 1 ? 750 : "", //Ayar - 0, - "", - $data['direction'] == 1 ? 1 : 0, //BuyOrSale_0_1 - (int)$data['price'], //Mazaneh - 1, //MazanehIsMesghalOrGeram_0_1 - 1, //IsMotefaregheOrAbshode_0_1 - $mablaghKol, //MablaghKol — خرید: وزن خالص × قیمت (بعد از کارمزد) - $data['direction'] == 1 ? "خرید آبشده" : "فروش آبشده", //sharh - -1, //Factor_Code — خودکار - -1, //HavalehBe_Mcode - -1, //MultiRadif - $data['product']['metal_id'] == 1 ? 0 : 2, //Jens_Felez - (string)Verta::instance($data['pay_at']), - ] - ])->json(); + $response = $this->postTahesab($body); } catch (\Exception $e) { $this->sync()->markFailed($tahesab, $e->getMessage()); return $e->getMessage(); @@ -517,7 +543,6 @@ class TahesabController extends Controller $this->sync()->markFailed($tahesab, $response, 'ثبت سند معامله در ته‌حساب ناموفق بود'); return false; - } // directon: 0 = daryaft az moshtari @@ -802,7 +827,7 @@ class TahesabController extends Controller return false; } - // direction: 1 = واریز طلا (خرید)، 0 = برداشت طلا (فروش) + // direction: 1 = واریز طلا (ورود)، 0 = برداشت طلا (خروج) — VKH نه BuySale public function walletGold($data, ?Tahesab $existing = null) { if (empty($data['customer_id']) && $existing) { @@ -812,6 +837,15 @@ class TahesabController extends Controller 'direction' => str_contains((string) $existing->desc, 'برداشت') ? 0 : 1, 'withdraw_id' => $existing->withdraw_id, ]); + $payload = json_decode((string) ($existing->data ?? ''), true); + if (is_array($payload)) { + if (!empty($payload['product_id'])) { + $data['product_id'] = (int) $payload['product_id']; + } + if (!empty($payload['product']) && is_array($payload['product'])) { + $data['product'] = $payload['product']; + } + } } if (!isset($data['customer_id'], $data['value'])) { @@ -820,6 +854,12 @@ class TahesabController extends Controller : false; } + $mapper = app(AccountingProductDocMapper::class); + $product = $mapper->resolveProductFromPayload($data); + if ($product && $mapper->shouldSkip($product)) { + return false; + } + $customer = Customer::where('id', $data['customer_id'])->first(); if ($customer && ($customer->moshtari_code === null)) { @@ -827,70 +867,65 @@ class TahesabController extends Controller $customer = Customer::where('id', $data['customer_id'])->first(); } - $amount = abs($data['value']); - $buyOrSale = (int) ($data['direction'] ?? 1) === 1 ? 1 : 0; - $isDeposit = $buyOrSale === 1; + $kind = $mapper->resolveKind($product); + $rawValue = (float) $data['value']; + $count = $mapper->accountingQty($product, $rawValue, $data); + $vazn = $mapper->accountingWeight($product, $rawValue, $data); + $jens = $mapper->jensFelez($product) ?? 0; + $title = $mapper->productTitle($product); + $isDeposit = (int) ($data['direction'] ?? 1) === 1; + $isVorood = $isDeposit ? 1 : 0; + $action = $isDeposit ? 'شارژ' : 'دشارژ'; + $kindFa = $mapper->kindLabel($kind); + $sharh = !empty($data['note']) + ? (string) $data['note'] + : (!empty($data['desc']) + ? (string) $data['desc'] + : trim($title !== '' ? "{$action} {$title}" : "{$action} {$kindFa}")); + $data['note'] = $sharh; $data['handler'] = 'walletGold'; + $tahesab = $existing ?? Tahesab::create([ - "customer_id" => $customer->id, - "desc" => $isDeposit ? "شارژ کیف پول آبشده" : "برداشت از کیف پول آبشده", - "amount" => $amount, - "date" => Carbon::now(), - "type" => "طلا", - "created_at" => Carbon::now(), - "withdraw_id" => $data['withdraw_id'] ?? null, - "data" => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), + 'customer_id' => $customer->id, + 'desc' => $sharh, + 'amount' => $mapper->isCountDoc($product) ? $count : $vazn, + 'date' => Carbon::now(), + 'type' => $mapper->metalLabel($product), + 'created_at' => Carbon::now(), + 'withdraw_id' => $data['withdraw_id'] ?? null, + 'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), ]); if ($existing && empty($existing->data)) { $existing->update(['data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)]); } + if ($mapper->isCountDoc($product) && $title === '') { + $this->sync()->markFailed($tahesab, null, 'نام محصول برای سند تعدادی ته‌حساب خالی است'); + return false; + } + $dateSource = ($existing !== null && !empty($existing->date)) ? Carbon::parse($existing->date) : Carbon::now(); $date = Verta::instance($dateSource)->format('Y-m-d'); [$year, $month, $day] = explode('-', $date); - $prices = $this->lastPriceData(); - - $mazaneh = $isDeposit ? (int) ($prices['buy'] ?? 0) : (int) ($prices['sell'] ?? 0); - $mablaghKol = (int) round($mazaneh * $amount); - $sharh = !empty($data['note']) - ? $data['note'] - : (!empty($data['desc']) ? $data['desc'] : ($isDeposit ? 'خرید آبشده' : 'فروش آبشده')); - $data['note'] = $sharh; + $body = $this->buildVkhBody( + $kind, + (int) $customer->moshtari_code, + (int) $year, + (int) $month, + (int) $day, + $vazn, + $count, + $title, + $isVorood, + $sharh, + $jens + ); try { - $response = Http::withoutVerifying()->timeout(10)->withHeaders([ - 'DBName' => $this->Config('tahesab_db'), - 'Content-Type' => 'application/json', - 'Authorization' => "Bearer " . $this->Config('tahesab_token'), - ])->post($this->tahesabApiUrl(), [ - "DoNewSanadBuySaleGOLD" => [ - 1,//Sabte_Kol_Or_Movaghat_1_0 - (int)$customer->moshtari_code, //Moshtari_Code - -1, //Factor_Number - 1, //Radif_Number - (int)$year, //Shamsi_Year - (int)$month, //Shamsi_Month - (int)$day, //Shamsi_Day - (float)$amount, //Vazn - 750, //Ayar - 0, - "", - $buyOrSale, //BuyOrSale_0_1 — 1=خرید، 0=فروش - $mazaneh, //Mazaneh - 1, //MazanehIsMesghalOrGeram_0_1 - 1, //IsMotefaregheOrAbshode_0_1 - $mablaghKol, //MablaghKol - $sharh,//Sharh - -1, //Factor_Code — خودکار - -1, //HavalehBe_Mcode - -1, //MultiRadif - 0, //Jens_Felez - (string)$date, - ] - ])->json(); + $response = $this->postTahesab($body); } catch (\Exception $e) { $this->sync()->markFailed($tahesab, $e->getMessage()); return $e->getMessage(); @@ -903,7 +938,141 @@ class TahesabController extends Controller $this->sync()->markFailed($tahesab, $response, 'ثبت سند طلایی کیف پول در ته‌حساب ناموفق بود'); return false; + } + private function postTahesab(array $body): array + { + $response = Http::withoutVerifying()->timeout(10)->withHeaders([ + 'DBName' => $this->Config('tahesab_db'), + 'Content-Type' => 'application/json', + 'Authorization' => 'Bearer ' . $this->Config('tahesab_token'), + ])->post($this->tahesabApiUrl(), $body)->json(); + + return is_array($response) ? $response : []; + } + + /** + * سند خرید/فروش — معامله. + */ + private function buildBuySaleBody( + string $kind, + int $moshtariCode, + int $year, + int $month, + int $day, + float $vazn, + float $count, + string $name, + int $buyOrSale, + int $mazaneh, + int $mablaghKol, + string $sharh, + int $jens, + string $zamanTasvie + ): array { + $ayar = $jens === 0 ? 750 : ''; + + if ($kind === AccountingProductDocMapper::KIND_SEKEH) { + return [ + 'DoNewSanadBuySaleSEKEH' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar, (float) $count, $name, + $buyOrSale, $mazaneh, $mablaghKol, $sharh, + -1, -1, -1, $jens, '', + ], + ]; + } + + if ($kind === AccountingProductDocMapper::KIND_SHEMSH) { + return [ + 'DoNewSanadBuySaleShemsh' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar, (float) $count, $name, + $buyOrSale, $mazaneh, $mablaghKol, $sharh, + -1, -1, -1, $jens, '', + ], + ]; + } + + if ($kind === AccountingProductDocMapper::KIND_KAR) { + return [ + 'DoNewSanadBuySaleKar' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar ?: 750, (float) $count, $name, + $buyOrSale, $mazaneh, 1, $mablaghKol, + 0, 0, 0, 0, 0, 0, 0, '', 0, $sharh, + -1, -1, -1, $jens, '', + ], + ]; + } + + // وزن / آبشده — همان payload قبلی با Jens_Felez و Sharh دقیق‌تر + return [ + 'DoNewSanadBuySaleGOLD' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar, 0, '', + $buyOrSale, $mazaneh, 1, 1, $mablaghKol, $sharh, + -1, -1, -1, $jens, $zamanTasvie, + ], + ]; + } + + /** + * سند ورود/خروج — شارژ، دشارژ، برداشت فیزیکی. + */ + private function buildVkhBody( + string $kind, + int $moshtariCode, + int $year, + int $month, + int $day, + float $vazn, + float $count, + string $name, + int $isVorood, + string $sharh, + int $jens + ): array { + $ayar = $jens === 0 ? 750 : ''; + + if ($kind === AccountingProductDocMapper::KIND_SEKEH) { + return [ + 'DoNewSanadVKHSEKEH' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar, (float) $count, $name, + $isVorood, $sharh, -1, -1, -1, $jens, + ], + ]; + } + + if ($kind === AccountingProductDocMapper::KIND_SHEMSH) { + return [ + 'DoNewSanadVKHShemsh' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar, (float) $count, $name, + $isVorood, $sharh, -1, -1, -1, $jens, + ], + ]; + } + + if ($kind === AccountingProductDocMapper::KIND_KAR) { + return [ + 'DoNewSanadVKHKar' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar ?: 750, (float) $count, $name, + $isVorood, 0, 0, 0, 0, 0, 0, '', 0, 0, $sharh, + -1, -1, -1, $jens, + ], + ]; + } + + return [ + 'DoNewSanadVKHGOLD' => [ + 1, $moshtariCode, -1, 1, $year, $month, $day, + (float) $vazn, $ayar ?: 750, '', '', + $isVorood, 1, $sharh, -1, -1, -1, $jens, 0, + ], + ]; } public function createCustomerInTahesab($customer_id) diff --git a/app/Http/Controllers/Admin/WalletController.php b/app/Http/Controllers/Admin/WalletController.php index 0d081e5..72accbc 100644 --- a/app/Http/Controllers/Admin/WalletController.php +++ b/app/Http/Controllers/Admin/WalletController.php @@ -315,6 +315,18 @@ class WalletController extends Controller try { $data['direction'] = $deposit ? 1 : 0; $data['withdraw_id'] = $Withdraw->id; + if ($representative) { + $data['product_id'] = $representative->id; + $data['product'] = [ + 'id' => $representative->id, + 'title' => $representative->title, + 'group_type' => $representative->group_type, + 'metal_id' => $representative->metal_id, + 'amount' => $representative->amount, + 'unit' => $representative->unit, + 'trade_by_quantity' => $representative->trade_by_quantity, + ]; + } app(TahesabController::class)->walletGold($data); } catch (\Exception $e) { report($e); @@ -325,6 +337,18 @@ class WalletController extends Controller try { $data['direction'] = $deposit ? 1 : 0; $data['withdraw_id'] = $Withdraw->id; + if ($representative) { + $data['product_id'] = $representative->id; + $data['product'] = [ + 'id' => $representative->id, + 'title' => $representative->title, + 'group_type' => $representative->group_type, + 'metal_id' => $representative->metal_id, + 'amount' => $representative->amount, + 'unit' => $representative->unit, + 'trade_by_quantity' => $representative->trade_by_quantity, + ]; + } app(\App\Http\Controllers\Admin\ZarinController::class)->walletGold($data); } catch (\Exception $e) { report($e); @@ -415,6 +439,9 @@ class WalletController extends Controller $label .= ' (غیرفعال)'; } + $payload = Controller::productUnitPayload($product); + $isCountBased = Controller::isCountBasedProduct($payload); + $targets[] = [ 'key' => 'product:' . $product->id, 'kind' => 'metal', @@ -424,6 +451,7 @@ class WalletController extends Controller 'metal_id' => max(1, (int) ($product->metal_id ?? 1)), 'uses_main_wallet' => $usesMainWallet, 'trade_by_quantity' => (bool) $product->trade_by_quantity, + 'allows_decimal' => !$isCountBased, 'group_type' => (string) ($product->group_type ?? ''), 'active' => (bool) $product->active, ]; @@ -453,7 +481,13 @@ class WalletController extends Controller $data = $request->all(); $product = Product::findOrFail($productId); $customer = Customer::findOrFail($data['customer_id']); - $deposit = ((float) ($data['value'] ?? 0)) > 0; + $value = (float) ($data['value'] ?? 0); + $deposit = $value > 0; + + $payload = Controller::productUnitPayload($product); + if (Controller::isCountBasedProduct($payload) && abs($value - round($value)) > 0.000001) { + throw new UnprocessableEntityHttpException('برای محصول تعدادی فقط عدد صحیح مجاز است'); + } $effectiveProductId = Controller::effectiveProductWalletId($productId); $wallet_interface = new WalletInterface( @@ -504,6 +538,16 @@ class WalletController extends Controller try { $data['direction'] = $deposit ? 1 : 0; $data['withdraw_id'] = $Withdraw->id; + $data['product_id'] = $productId; + $data['product'] = [ + 'id' => $product->id, + 'title' => $product->title, + 'group_type' => $product->group_type, + 'metal_id' => $product->metal_id, + 'amount' => $product->amount, + 'unit' => $product->unit, + 'trade_by_quantity' => $product->trade_by_quantity, + ]; app(TahesabController::class)->walletGold($data); } catch (\Exception $e) { report($e); @@ -514,6 +558,16 @@ class WalletController extends Controller try { $data['direction'] = $deposit ? 1 : 0; $data['withdraw_id'] = $Withdraw->id; + $data['product_id'] = $productId; + $data['product'] = [ + 'id' => $product->id, + 'title' => $product->title, + 'group_type' => $product->group_type, + 'metal_id' => $product->metal_id, + 'amount' => $product->amount, + 'unit' => $product->unit, + 'trade_by_quantity' => $product->trade_by_quantity, + ]; app(\App\Http\Controllers\Admin\ZarinController::class)->walletGold($data); } catch (\Exception $e) { report($e); @@ -576,19 +630,20 @@ class WalletController extends Controller { $payload = Controller::productUnitPayload($product); - if ( - Controller::usesQuantityTrade($payload) - && !Controller::isWeightBasedProductUnit($payload['unit'] ?? null) - ) { - $unit = trim((string) ($payload['unit'] ?? '')); - - return $unit !== '' ? $unit : 'عدد'; + // تعدادی: همیشه «عدد» — حتی اگر unit محصول وزن هر قطعه (گرم) باشد + if (Controller::isCountBasedProduct($payload)) { + return 'عدد'; } if (Controller::isWeightBasedProductUnit($payload['unit'] ?? null)) { return trim((string) $payload['unit']); } + $unit = trim((string) ($payload['unit'] ?? '')); + if ($unit !== '') { + return $unit; + } + return Controller::productStorageWeightUnit($product); } @@ -1102,6 +1157,26 @@ class WalletController extends Controller return $withdraw; }); + foreach ($data['wallets'] as $walletRow) { + $wallet = Wallet::with('product')->find($walletRow['wallet_id'] ?? 0); + $product = $wallet?->product; + if (!$product) { + continue; + } + try { + app(\App\Services\AccountingWalletPush::class)->pushProductWalletGold( + $customer, + $product, + (float) $walletRow['value'], + false, + (int) $withdraw->id, + 'برداشت فیزیکی ' . $product->title + ); + } catch (\Throwable $e) { + report($e); + } + } + $this->broadcastCustomerWalletAssets($customer); return response()->json(['id' => $withdraw->id]); diff --git a/app/Http/Controllers/Admin/ZarinController.php b/app/Http/Controllers/Admin/ZarinController.php index 71e33fd..4f2b83f 100644 --- a/app/Http/Controllers/Admin/ZarinController.php +++ b/app/Http/Controllers/Admin/ZarinController.php @@ -8,7 +8,7 @@ use App\Models\Customer; use App\Models\Order; use App\Models\Product; use App\Models\Zarin; -use App\Services\ConfigService; +use App\Services\AccountingProductDocMapper; use App\Services\ZarinSyncService; use Carbon\Carbon; use Illuminate\Http\Request; @@ -1088,7 +1088,8 @@ class ZarinController extends Controller 'id' => $order->product_id, 'metal_id' => $existing->type === 'طلا' ? 1 : 2, ], - 'order' => ['id' => $existing->order_id], + 'product_id' => $order->product_id ? (int) $order->product_id : null, + 'order' => ['id' => $existing->order_id, 'product_id' => $order->product_id], ]); } } @@ -1099,6 +1100,12 @@ class ZarinController extends Controller : false; } + $mapper = app(AccountingProductDocMapper::class); + $product = $mapper->resolveProductFromPayload($data); + if ($product && $mapper->shouldSkip($product)) { + return false; + } + $customer = Customer::where('id', $data['customer_id'])->first(); if ($customer && ($customer->moshtari_code === null)) { @@ -1110,9 +1117,19 @@ class ZarinController extends Controller return false; } - $amount = $data['amount']; + $rawAmount = (float) $data['amount']; + $count = $mapper->accountingQty($product, $rawAmount, $data); + $vazn = $mapper->accountingWeight($product, $rawAmount, $data); + $isBuy = (int) ($data['direction'] ?? 0) === 1; $orderId = $data['order']['id'] ?? ($existing->order_id ?? null); - $metalId = (int) ($data['product']['metal_id'] ?? 1); + $title = $mapper->productTitle($product); + $action = $isBuy ? 'خرید' : 'فروش'; + $sharh = trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}"); + $syncAmount = $mapper->isCountDoc($product) ? $count : $vazn; + $uidSource = $orderId ?? null; + if (!empty($data['line'])) { + $uidSource = ($orderId ?? '0') . 'L' . (int) $data['line']; + } $data['handler'] = 'storeTrade'; if ($existing && empty($existing->data)) { @@ -1120,84 +1137,55 @@ class ZarinController extends Controller } $zarin = $existing ?? Zarin::create([ 'customer_id' => $customer->id, - 'desc' => $data['direction'] == 1 ? 'خرید آبشده' : 'فروش آبشده', - 'amount' => $amount, + 'desc' => $sharh, + 'amount' => $syncAmount, 'order_id' => $orderId, 'date' => $data['pay_at'] ?? Carbon::now(), - 'type' => $metalId == 1 ? 'طلا' : 'نقره', + 'type' => $mapper->metalLabel($product), 'created_at' => Carbon::now(), 'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), ]); - $isStore = app(ConfigService::class)->isStore(); - $productId = $this->resolveProductId($data, $existing); - try { - if ($isStore && $productId) { - $product = Product::find($productId); - if (!$product) { - $this->sync()->markFailed($zarin, null, 'محصول برای ثبت سند زرین پیدا نشد'); - return false; + if ($mapper->isCountDoc($product)) { + $fi = (float) ($data['price'] ?? 0); + if ($count > 0) { + if (!empty($data['total_price'])) { + $fi = ((float) $data['total_price']) / $count; + } elseif ($fi > 0 && abs($vazn - $count) > 0.0001) { + $fi = $fi * ($vazn / $count); + } } - - $karResult = $this->ensureZarinKarForProduct($product); - if (empty($karResult['ok'])) { - $this->sync()->markFailed($zarin, $karResult, 'کد کار زرین برای محصول آماده نشد'); - return false; - } - - $params = [ - 'kind' => 'sabtSanadTedadi', - 'parDar' => ((int) ($data['direction'] ?? 0) === 1) ? -1 : 1, - 'idHesab' => $customer->moshtari_code, - 'idKar' => (int) $karResult['idKar'], - 'fi' => $data['price'] ?? 0, - 'tedad' => $amount, - 'sharh' => $data['direction'] == 1 ? 'خرید' : 'فروش', - 'uid' => $this->zarinUid('30', $orderId), - ]; + $result = $this->postZarinCountDoc( + $zarin, + $data, + $customer, + $product, + $count, + $isBuy ? -1 : 1, + $fi, + $sharh, + $this->zarinUid('10', $uidSource ?? $zarin->id) + ); } else { - $params = [ - 'kind' => 'pooli', - 'radif' => 1, - 'korf' => ((int) ($data['direction'] ?? 0) === 1) ? 'f' : 'k', - 'poolorvazn' => 'vazn', - 'meghdar' => $amount, - 'fi' => $data['price'] ?? 0, - 'hesab' => $customer->moshtari_code, - 'sharh' => $data['direction'] == 1 ? 'خرید آبشده' : 'فروش آبشده', - 'ayar' => '750', - 'movaghat' => '', - 'idFactor' => '', - 'uid' => $this->zarinUid('10', $orderId), - ]; - } - - $http = $this->zarinRequest($params); - if (!$http) { - $this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست'); - return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; - } - - [$response, $httpError] = $this->zarinDecodeResponse($http); - if ($httpError) { - $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); - return false; + $result = $this->postZarinWeightDoc( + $zarin, + $data, + $customer, + $vazn, + $isBuy ? 'f' : 'k', + (float) ($data['price'] ?? 0), + $sharh, + $mapper->jensFelez($product) ?? 0, + $this->zarinUid('10', $uidSource ?? $zarin->id) + ); } } catch (\Exception $e) { $this->sync()->markFailed($zarin, $e->getMessage()); return $e->getMessage(); } - $flat = $this->extractResponseRow(is_array($response) ? $response : null); - - if ($this->sync()->responseOk(is_array($response) ? $response : []) || $this->sync()->responseOk($flat)) { - $this->sync()->markSuccess($zarin, is_array($response) ? $response : $flat, $data); - return $data; - } - - $this->sync()->markFailed($zarin, $response, 'ثبت سند معامله در زرین ناموفق بود'); - return false; + return $result; } // direction: 0 = daryaft az moshtari, 1 = pardakht be moshtari @@ -1413,7 +1401,7 @@ class ZarinController extends Controller return false; } - // direction: 1 = واریز طلا (خرید)، 0 = برداشت طلا (فروش) + // direction: 1 = واریز، 0 = برداشت public function walletGold($data, ?Zarin $existing = null) { if (empty($data['customer_id']) && $existing) { @@ -1423,6 +1411,15 @@ class ZarinController extends Controller 'direction' => str_contains((string) $existing->desc, 'برداشت') ? 0 : 1, 'withdraw_id' => $existing->withdraw_id, ]); + $payload = json_decode((string) ($existing->data ?? ''), true); + if (is_array($payload)) { + if (!empty($payload['product_id'])) { + $data['product_id'] = (int) $payload['product_id']; + } + if (!empty($payload['product']) && is_array($payload['product'])) { + $data['product'] = $payload['product']; + } + } } if (!isset($data['customer_id'], $data['value'])) { @@ -1431,6 +1428,12 @@ class ZarinController extends Controller : false; } + $mapper = app(AccountingProductDocMapper::class); + $product = $mapper->resolveProductFromPayload($data); + if ($product && $mapper->shouldSkip($product)) { + return false; + } + $customer = Customer::where('id', $data['customer_id'])->first(); if ($customer && ($customer->moshtari_code === null)) { @@ -1442,16 +1445,27 @@ class ZarinController extends Controller return false; } - $amount = abs($data['value']); + $rawValue = (float) $data['value']; + $count = $mapper->accountingQty($product, $rawValue, $data); + $vazn = $mapper->accountingWeight($product, $rawValue, $data); $isDeposit = (int) ($data['direction'] ?? 1) === 1; - + $title = $mapper->productTitle($product); + $action = $isDeposit ? 'شارژ' : 'دشارژ'; + $sharh = !empty($data['note']) + ? (string) $data['note'] + : (!empty($data['desc']) + ? (string) $data['desc'] + : trim($title !== '' ? "{$action} {$title}" : "{$action} {$mapper->kindLabel($mapper->resolveKind($product))}")); + $data['note'] = $sharh; $data['handler'] = 'walletGold'; + $syncAmount = $mapper->isCountDoc($product) ? $count : $vazn; + $zarin = $existing ?? Zarin::create([ 'customer_id' => $customer->id, - 'desc' => $isDeposit ? 'شارژ کیف پول آبشده' : 'برداشت از کیف پول آبشده', - 'amount' => $amount, + 'desc' => $sharh, + 'amount' => $syncAmount, 'date' => Carbon::now(), - 'type' => 'طلا', + 'type' => $mapper->metalLabel($product), 'created_at' => Carbon::now(), 'withdraw_id' => $data['withdraw_id'] ?? null, 'data' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), @@ -1467,50 +1481,160 @@ class ZarinController extends Controller $mazaneh = 1; } - $sharh = !empty($data['note']) - ? $data['note'] - : (!empty($data['desc']) ? $data['desc'] : ($isDeposit ? 'خرید آبشده' : 'فروش آبشده')); - $data['note'] = $sharh; - $uidSource = $data['withdraw_id'] ?? $zarin->id; try { - $http = $this->zarinRequest([ - 'kind' => 'pooli', - 'radif' => 1, - 'korf' => $isDeposit ? 'f' : 'k', - 'poolorvazn' => 'vazn', - 'meghdar' => $amount, - 'fi' => $mazaneh, - 'hesab' => $customer->moshtari_code, - 'sharh' => $sharh, - 'ayar' => '750', - 'movaghat' => '', - 'idFactor' => '', - 'uid' => $this->zarinUid('21', $uidSource), - ]); + if ($mapper->isCountDoc($product)) { + $unitPrice = $mazaneh; + if ($product && (float) ($product->amount ?? 0) > 0) { + $unitPrice = (int) round($mazaneh * (float) $product->amount); + } + if ($unitPrice <= 0) { + $unitPrice = $mazaneh; + } - if (!$http) { - $this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست'); - return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; + return $this->postZarinCountDoc( + $zarin, + $data, + $customer, + $product, + $count, + $isDeposit ? -1 : 1, + (float) $unitPrice, + $sharh, + $this->zarinUid('21', $uidSource) + ); } - [$response, $httpError] = $this->zarinDecodeResponse($http); - if ($httpError) { - $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); - return false; - } + return $this->postZarinWeightDoc( + $zarin, + $data, + $customer, + $vazn, + $isDeposit ? 'f' : 'k', + (float) $mazaneh, + $sharh, + $mapper->jensFelez($product) ?? 0, + $this->zarinUid('21', $uidSource) + ); } catch (\Exception $e) { $this->sync()->markFailed($zarin, $e->getMessage()); return $e->getMessage(); } + } - if ($this->sync()->responseOk(is_array($response) ? $response : [])) { - $this->sync()->markSuccess($zarin, is_array($response) ? $response : [], $data); + /** + * سند تعدادی زرین (سکه / شمش / گوی / مکعب). + */ + private function postZarinCountDoc( + Zarin $zarin, + array $data, + Customer $customer, + $product, + float $tedad, + int $parDar, + float $fi, + string $sharh, + string $uid + ) { + if (!$product instanceof Product || empty($product->id)) { + $this->sync()->markFailed($zarin, null, 'محصول برای سند تعدادی زرین مشخص نیست'); + return false; + } + + $kar = $this->ensureZarinKarForProduct($product); + if (empty($kar['ok']) || empty($kar['idKar'])) { + $this->sync()->markFailed($zarin, $kar ?? null, $kar['message'] ?? 'کد کار زرین برای محصول یافت نشد'); + return false; + } + + if ($fi <= 0) { + $fi = 1; + } + + $http = $this->zarinRequest([ + 'kind' => 'sabtSanadTedadi', + 'idHesab' => $customer->moshtari_code, + 'idKar' => (int) $kar['idKar'], + 'fi' => $fi, + 'tedad' => $tedad, + 'parDar' => $parDar, + 'sharh' => $sharh, + 'uid' => $uid, + ]); + + if (!$http) { + $this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست'); + return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; + } + + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; + } + + $flat = $this->extractResponseRow(is_array($response) ? $response : null); + if ($this->sync()->responseOk(is_array($response) ? $response : []) || $this->sync()->responseOk($flat)) { + $this->sync()->markSuccess($zarin, is_array($response) ? $response : $flat, $data); return $data; } - $this->sync()->markFailed($zarin, $response, 'ثبت سند طلایی کیف پول در زرین ناموفق بود'); + $this->sync()->markFailed($zarin, $response, 'ثبت سند تعدادی در زرین ناموفق بود'); + return false; + } + + /** + * سند وزنی زرین (آبشده طلا/نقره). + */ + private function postZarinWeightDoc( + Zarin $zarin, + array $data, + Customer $customer, + float $meghdar, + string $korf, + float $fi, + string $sharh, + int $jensFelez, + string $uid + ) { + if ($fi <= 0) { + $fi = 1; + } + + $http = $this->zarinRequest([ + 'kind' => 'pooli', + 'radif' => 1, + 'korf' => $korf, + 'poolorvazn' => 'vazn', + 'meghdar' => $meghdar, + 'fi' => $fi, + 'hesab' => $customer->moshtari_code, + 'sharh' => $sharh, + 'ayar' => $jensFelez === 2 ? '' : '750', + 'movaghat' => '', + 'idFactor' => '', + 'uid' => $uid, + ]); + + if (!$http) { + $this->sync()->markFailed($zarin, null, 'تنظیمات زرین کامل نیست'); + return 'تنظیمات زرین کامل نیست. zarin_api یا zarin_token تنظیم نشده است.'; + } + + [$response, $httpError] = $this->zarinDecodeResponse($http); + if ($httpError) { + $this->sync()->markFailed($zarin, $response ?? $httpError, $httpError); + return false; + } + + $flat = $this->extractResponseRow(is_array($response) ? $response : null); + if ($this->sync()->responseOk(is_array($response) ? $response : []) || $this->sync()->responseOk($flat)) { + $this->sync()->markSuccess($zarin, is_array($response) ? $response : $flat, $data); + return $data; + } + + $this->sync()->markFailed($zarin, $response, 'ثبت سند وزنی در زرین ناموفق بود'); return false; } diff --git a/app/Http/Controllers/Customer/CartController.php b/app/Http/Controllers/Customer/CartController.php index 4f88b0f..e2b930e 100644 --- a/app/Http/Controllers/Customer/CartController.php +++ b/app/Http/Controllers/Customer/CartController.php @@ -353,6 +353,7 @@ class CartController extends Controller /** * ثبت سبد در دفترکل داخلی + حسابداری بیرونی (مستقل از is_store؛ فقط accounting_provider). + * ته‌حساب/زرین: per-line تا دستهٔ هر محصول درست برود. */ private function syncCartOrderAccounting(Order $order, Cart $cart): void { @@ -364,43 +365,62 @@ class CartController extends Controller report($e); } - $primaryProduct = Product::find($cart->items->first()?->product_id); - $metalId = max(1, (int) ($primaryProduct->metal_id ?? 1)); - - if ($config->isTahesabAccounting()) { - try { - app(TahesabController::class)->storeTrade([ - 'customer_id' => $order->customer_id, - 'amount' => $order->amount, - 'direction' => (int) ($order->direction ?? 1), - 'price' => $order->price, - 'total_price' => $order->total_price, - 'pay_at' => $order->pay_at ?? $order->created_at, - 'product' => ['metal_id' => $metalId], - 'order' => ['id' => $order->id], - ]); - } catch (\Throwable $e) { - report($e); + $lineIndex = 0; + foreach ($cart->items as $item) { + $product = Product::find($item->product_id); + if (!$product) { + continue; } - } - if ($config->isZarinAccounting()) { - try { - app(\App\Http\Controllers\Admin\ZarinController::class)->storeTrade([ - 'customer_id' => $order->customer_id, - 'amount' => $order->amount, - 'direction' => (int) ($order->direction ?? 1), - 'price' => $order->price, - 'total_price' => $order->total_price, - 'pay_at' => $order->pay_at ?? $order->created_at, - 'product' => [ - 'metal_id' => $metalId, - 'id' => $primaryProduct?->id, - ], - 'order' => ['id' => $order->id], - ]); - } catch (\Throwable $e) { - report($e); + $qty = max(1, (int) ($item->quantity ?? 1)); + $unitWeight = (float) ($product->amount ?? 0); + $lineWeight = $unitWeight > 0 ? $unitWeight * $qty : (float) $qty; + $unitPrice = (float) ($item->unit_price ?? 0); + if ($unitPrice <= 0) { + $unitPrice = (float) ($product->price ?? 0) + (float) ($product->price_profit ?? 0); + } + $lineTotal = (float) ($item->total_price ?? ($unitPrice * $qty)); + + $payload = [ + 'customer_id' => $order->customer_id, + 'amount' => $lineWeight, + 'number' => $qty, + 'direction' => (int) ($order->direction ?? 1), + 'price' => $unitPrice, + 'total_price' => $lineTotal, + 'pay_at' => $order->pay_at ?? $order->created_at, + 'product_id' => (int) $product->id, + 'product' => [ + 'id' => $product->id, + 'title' => $product->title, + 'group_type' => $product->group_type, + 'metal_id' => $product->metal_id, + 'amount' => $product->amount, + 'unit' => $product->unit, + 'trade_by_quantity' => $product->trade_by_quantity, + ], + 'line' => $lineIndex + 1, + 'order' => [ + 'id' => (int) $order->id, + 'product_id' => (int) $product->id, + ], + ]; + $lineIndex++; + + if ($config->isTahesabAccounting()) { + try { + app(TahesabController::class)->storeTrade($payload); + } catch (\Throwable $e) { + report($e); + } + } + + if ($config->isZarinAccounting()) { + try { + app(\App\Http\Controllers\Admin\ZarinController::class)->storeTrade($payload); + } catch (\Throwable $e) { + report($e); + } } } diff --git a/app/Http/Controllers/Customer/WalletController.php b/app/Http/Controllers/Customer/WalletController.php index bae1de8..02f4287 100644 --- a/app/Http/Controllers/Customer/WalletController.php +++ b/app/Http/Controllers/Customer/WalletController.php @@ -593,6 +593,29 @@ class WalletController extends Controller ], 6, false); } + foreach ($data['wallets'] as $walletRow) { + $product = $walletRow['wallet']['product'] ?? null; + if (!$product) { + continue; + } + $productModel = $product instanceof Product ? $product : Product::find($product['id'] ?? 0); + if (!$productModel) { + continue; + } + try { + app(\App\Services\AccountingWalletPush::class)->pushProductWalletGold( + $data['customer'], + $productModel, + (float) $walletRow['value'], + false, + (int) $data['withdraw']->id, + 'برداشت فیزیکی ' . $productModel->title + ); + } catch (\Throwable $e) { + report($e); + } + } + return response()->json($data['withdraw']['id']); } public function indexWithdraw(Request $request){ diff --git a/app/Services/AccountingProductDocMapper.php b/app/Services/AccountingProductDocMapper.php new file mode 100644 index 0000000..9831f59 --- /dev/null +++ b/app/Services/AccountingProductDocMapper.php @@ -0,0 +1,274 @@ +groupType($product); + $metalId = $this->metalId($product); + + if ($group !== '' && in_array($group, self::SKIP_GROUPS, true)) { + return self::KIND_SKIP; + } + if ($metalId === 3) { + return self::KIND_SKIP; + } + + if (in_array($group, self::SEKEH_GROUPS, true)) { + return self::KIND_SEKEH; + } + if (in_array($group, self::SHEMSH_GROUPS, true)) { + return self::KIND_SHEMSH; + } + if (in_array($group, self::KAR_GROUPS, true)) { + return self::KIND_KAR; + } + + // آبشده / کاتد / مقداری وزنی / نقره دسته‌ای → سند وزنی (فلز با Jens_Felez جدا می‌شود) + return self::KIND_WEIGHT; + } + + public function isCountDoc($product): bool + { + $kind = $this->resolveKind($product); + + return in_array($kind, [self::KIND_SEKEH, self::KIND_SHEMSH, self::KIND_KAR], true); + } + + public function shouldSkip($product): bool + { + return $this->resolveKind($product) === self::KIND_SKIP; + } + + /** + * Jens_Felez ته‌حساب: 0=طلا، 2=نقره. null = skip (مثلاً مس). + * + * @param Product|array|null $product + */ + public function jensFelez($product): ?int + { + if ($this->shouldSkip($product)) { + return null; + } + + $metalId = $this->metalId($product); + if ($metalId === 2) { + return 2; + } + if ($metalId === 3) { + return null; + } + + return 0; + } + + /** + * مقدار تعداد برای سند تعدادی. + * ترجیح: number/quantity/count از payload؛ وگرنه rawValue (شارژ کیف = تعداد). + * + * @param Product|array|null $product + * @param array $payload + */ + public function accountingQty($product, float $rawValue, array $payload = []): float + { + if (!$this->isCountDoc($product)) { + return abs($rawValue); + } + + foreach (['number', 'quantity', 'count', 'tedad'] as $key) { + if (isset($payload[$key]) && is_numeric($payload[$key]) && (float) $payload[$key] != 0.0) { + return abs((float) $payload[$key]); + } + } + + // سفارش‌های قدیمی: amount = number × وزن واحد → تعداد را از وزن برگردان + $unitWeight = $this->unitWeight($product); + if ($unitWeight > 0 && abs($rawValue) > $unitWeight + 0.000001) { + $derived = abs($rawValue) / $unitWeight; + if (abs($derived - round($derived)) < 0.0001) { + return round($derived); + } + } + + return abs($rawValue); + } + + /** + * وزن برای سند: تعدادی → count × amount؛ وزنی → همان مقدار. + * اگر rawValue از قبل وزن سفارش باشد (number×amount)، همان را برگردان. + * + * @param Product|array|null $product + * @param array $payload + */ + public function accountingWeight($product, float $rawValue, array $payload = []): float + { + $qty = abs($rawValue); + if (!$this->isCountDoc($product)) { + return $qty; + } + + $count = $this->accountingQty($product, $rawValue, $payload); + $unitWeight = $this->unitWeight($product); + if ($unitWeight > 0) { + // اگر rawValue تقریباً همان count×unit است، دوباره ضرب نکن + $expected = round($count * $unitWeight, 6); + if (abs($qty - $expected) < 0.0001 || abs($qty - $count) < 0.0001) { + return $expected; + } + // rawValue احتمالاً همان وزن سفارش است + if (abs($qty - $count * $unitWeight) < max(0.01, $unitWeight * 0.01)) { + return round($qty, 6); + } + + return $expected; + } + + return $qty; + } + + /** + * @param Product|array|null $product + */ + public function productTitle($product): string + { + if (is_object($product)) { + return trim((string) ($product->title ?? '')); + } + if (is_array($product)) { + return trim((string) ($product['title'] ?? '')); + } + + return ''; + } + + /** + * برچسب نوع سند برای desc جدول sync. + */ + public function kindLabel(string $kind): string + { + return match ($kind) { + self::KIND_SEKEH => 'سکه', + self::KIND_SHEMSH => 'شمش', + self::KIND_KAR => 'کارساخته', + self::KIND_WEIGHT => 'وزنی', + default => 'ردشده', + }; + } + + /** + * @param Product|array|null $product + */ + public function metalLabel($product): string + { + $jens = $this->jensFelez($product); + if ($jens === 2) { + return 'نقره'; + } + + return 'طلا'; + } + + /** + * Resolve Product model from storeTrade/walletGold payload. + */ + public function resolveProductFromPayload(array $data): ?Product + { + $productId = 0; + if (!empty($data['product_id'])) { + $productId = (int) $data['product_id']; + } elseif (!empty($data['product']['id'])) { + $productId = (int) $data['product']['id']; + } elseif (!empty($data['order']['product_id'])) { + $productId = (int) $data['order']['product_id']; + } + + if ($productId > 0) { + return Product::query()->withTrashed()->find($productId); + } + + // فقط metal_id در payload (retry قدیمی) — بدون group_type → وزن + if (isset($data['product']) && is_array($data['product'])) { + return new Product([ + 'title' => (string) ($data['product']['title'] ?? ''), + 'group_type' => (string) ($data['product']['group_type'] ?? 'آبشده'), + 'metal_id' => (int) ($data['product']['metal_id'] ?? 1), + 'amount' => (float) ($data['product']['amount'] ?? 0), + 'unit' => (string) ($data['product']['unit'] ?? 'گرم'), + 'trade_by_quantity' => (bool) ($data['product']['trade_by_quantity'] ?? false), + ]); + } + + return null; + } + + /** + * @param Product|array|null $product + */ + private function groupType($product): string + { + if (is_object($product)) { + return trim((string) ($product->group_type ?? '')); + } + if (is_array($product)) { + return trim((string) ($product['group_type'] ?? '')); + } + + return ''; + } + + /** + * @param Product|array|null $product + */ + private function metalId($product): int + { + if (is_object($product)) { + return max(1, (int) ($product->metal_id ?? 1)); + } + if (is_array($product)) { + return max(1, (int) ($product['metal_id'] ?? 1)); + } + + return 1; + } + + /** + * @param Product|array|null $product + */ + private function unitWeight($product): float + { + if (is_object($product)) { + return (float) ($product->amount ?? 0); + } + if (is_array($product)) { + return (float) ($product['amount'] ?? 0); + } + + return 0.0; + } +} diff --git a/app/Services/AccountingWalletPush.php b/app/Services/AccountingWalletPush.php new file mode 100644 index 0000000..8156165 --- /dev/null +++ b/app/Services/AccountingWalletPush.php @@ -0,0 +1,63 @@ +shouldSkip($product)) { + return; + } + + $config = app(ConfigService::class); + $payload = [ + 'customer_id' => $customer->id, + 'value' => abs($value), + 'direction' => $isDeposit ? 1 : 0, + 'withdraw_id' => $withdrawId, + 'note' => $note, + 'product_id' => (int) $product->id, + 'product' => [ + 'id' => $product->id, + 'title' => $product->title, + 'group_type' => $product->group_type, + 'metal_id' => $product->metal_id, + 'amount' => $product->amount, + 'unit' => $product->unit, + 'trade_by_quantity' => $product->trade_by_quantity, + ], + ]; + + if ($config->isTahesabAccounting()) { + try { + app(TahesabController::class)->walletGold($payload); + } catch (\Throwable $e) { + report($e); + } + } + + if ($config->isZarinAccounting()) { + try { + app(ZarinController::class)->walletGold($payload); + } catch (\Throwable $e) { + report($e); + } + } + } +}