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 'طلا'; } /** * برچسب ستون type جدول sync (ته‌حساب / زرین): * تعدادی → سکه / شمش / کارساخته؛ وزنی → طلا / نقره. * * @param Product|array|null $product */ public function syncTypeLabel($product): string { if ($this->isCountDoc($product)) { return $this->kindLabel($this->resolveKind($product)); } return $this->metalLabel($product); } /** @deprecated از syncTypeLabel استفاده کنید */ public function zarinSyncTypeLabel($product): string { return $this->syncTypeLabel($product); } /** * 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; } }