Files
boursecu_server/app/Services/AccountingProductDocMapper.php
T
2026-07-29 14:58:33 +03:30

296 lines
8.8 KiB
PHP

<?php
namespace App\Services;
use App\Models\Product;
/**
* نگاشت محصول → نوع سند ته‌حساب/زرین بر اساس group_type و metal_id.
* مس و فلز ناشناخته → skip (بدون ارسال سند).
*/
class AccountingProductDocMapper
{
public const KIND_SEKEH = 'sekeh';
public const KIND_SHEMSH = 'shemsh';
public const KIND_KAR = 'kar';
public const KIND_WEIGHT = 'weight';
public const KIND_SKIP = 'skip';
private const SEKEH_GROUPS = ['سکه', 'سکه پارسیان'];
private const SHEMSH_GROUPS = ['شمش', 'شمش برنا', 'شمش ونوس'];
private const KAR_GROUPS = ['مکعب', 'گوی'];
private const SKIP_GROUPS = ['مس', 'مفتول مس'];
/**
* @param Product|array|null $product
*/
public function resolveKind($product): string
{
if (!$product) {
return self::KIND_WEIGHT;
}
$group = $this->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<string,mixed> $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<string,mixed> $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;
}
}