Files
boursecu_server/app/Services/BalanceReportService.php
T

876 lines
29 KiB
PHP
Raw Normal View History

2026-06-23 20:35:13 +03:30
<?php
namespace App\Services;
2026-06-23 21:21:04 +03:30
use App\Http\Controllers\Controller;
2026-06-23 20:35:13 +03:30
use App\Models\Order;
use App\Models\Product;
class BalanceReportService
{
public const METAL_LABELS = [
1 => 'طلا',
2 => 'نقره',
3 => 'مس',
];
public function __construct(
private readonly ConfigService $configService
) {
}
2026-06-23 20:35:13 +03:30
/** @return list<int> */
public function catalogMetalIds(): array
{
return Product::query()
->where('active', 1)
->whereNotNull('metal_id')
->where('metal_id', '>', 0)
->distinct()
->orderBy('metal_id')
->pluck('metal_id')
->map(static fn ($id) => (int) $id)
->values()
->all();
}
public function metalLabel(int $metalId): string
{
return self::METAL_LABELS[$metalId] ?? "فلز {$metalId}";
}
2026-06-23 21:04:29 +03:30
/** واحد نمایش موجودی/تراز هر فلز در ادمین — مس کیلوگرم، بقیه گرم */
public function metalDisplayUnit(int $metalId): string
{
return (int) $metalId === 3 ? 'کیلوگرم' : 'گرم';
}
public function gramsToMetalDisplay(float $grams, int $metalId): float
{
return (int) $metalId === 3 ? $grams / 1000.0 : $grams;
}
/**
* @return array<int, string>
*/
public function catalogMetalsMeta(): array
{
$items = [];
foreach ($this->catalogMetalIds() as $metalId) {
$metalId = (int) $metalId;
$items[] = [
'metal_id' => $metalId,
'label' => $this->metalLabel($metalId),
'display_unit' => $this->metalDisplayUnit($metalId),
];
}
return $items;
}
2026-06-23 20:35:13 +03:30
/**
* @return array<int, array{gold: float, cash: float}>
*/
public function resolveOrderMetalSlices(Order $order): array
{
$order->loadMissing(['transactions.product']);
$slices = [];
$txWithProduct = $order->transactions->filter(
static fn ($tx) => $tx->product_id && abs((float) ($tx->gold ?? 0)) > 0
);
if ($txWithProduct->isNotEmpty()) {
foreach ($txWithProduct as $tx) {
$metalId = (int) ($tx->product?->metal_id ?? 1);
if ($metalId <= 0) {
$metalId = 1;
}
$gold = abs((float) ($tx->gold ?? 0));
if ($gold <= 0) {
continue;
}
$slices[$metalId] = ($slices[$metalId] ?? 0.0) + $gold;
}
$totalGold = array_sum($slices);
$result = [];
foreach ($slices as $metalId => $gold) {
$cashShare = $totalGold > 0
? (float) $order->total_price * ($gold / $totalGold)
: 0.0;
$result[(int) $metalId] = ['gold' => $gold, 'cash' => $cashShare];
}
return $result;
}
$amount = (float) ($order->amount ?? 0);
$totalPrice = (float) ($order->total_price ?? 0);
if ($amount <= 0) {
return [];
}
$metalId = 1;
$tx = $order->transactions->first(static fn ($t) => $t->product_id);
if ($tx?->product) {
$metalId = max(1, (int) ($tx->product->metal_id ?? 1));
}
return [$metalId => ['gold' => $amount, 'cash' => $totalPrice]];
}
/**
* @param list<int> $metalIds
* @return array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}>
*/
public function initMetalBalanceBuckets(array $metalIds): array
{
$buckets = [];
foreach ($metalIds as $metalId) {
$buckets[(int) $metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
return $buckets;
}
/**
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $buckets
*/
public function applyOrderToBuckets(array &$buckets, Order $order): void
{
$slices = $this->resolveOrderMetalSlices($order);
foreach ($slices as $metalId => $slice) {
$metalId = (int) $metalId;
if (! isset($buckets[$metalId])) {
$buckets[$metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
$gold = (float) $slice['gold'];
$cash = (float) $slice['cash'];
if ((int) $order->direction === 1) {
$buckets[$metalId]['gold'] += $gold;
$buckets[$metalId]['cash'] -= $cash;
} elseif ((int) $order->direction === 2) {
$buckets[$metalId]['gold'] -= $gold;
$buckets[$metalId]['cash'] += $cash;
}
$buckets[$metalId]['gold_abs'] += $gold;
$buckets[$metalId]['cash_abs'] += $cash;
}
}
/**
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $limanBuckets
* @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $kalhorBuckets
*/
public function buildMetalBalancesResponse(array $limanBuckets, array $kalhorBuckets): array
{
$metalIds = array_values(array_unique(array_merge(
array_keys($limanBuckets),
array_keys($kalhorBuckets)
)));
sort($metalIds);
$items = [];
$totalGoldBalance = 0.0;
foreach ($metalIds as $metalId) {
$metalId = (int) $metalId;
$liman = $limanBuckets[$metalId] ?? [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
$kalhor = $kalhorBuckets[$metalId] ?? [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
$goldBalance = -$liman['gold'] + $kalhor['gold'];
$cashBalance = -$liman['cash'] + $kalhor['cash'];
$goldBalanceOfLiman = -$liman['gold'];
$goldBalanceOfKalhor = $kalhor['gold'];
$items[] = [
'metal_id' => $metalId,
'label' => $this->metalLabel($metalId),
'balance' => round($goldBalance, 4),
'goldBalance' => round($goldBalance, 4),
'cashBalance' => round($cashBalance, 2),
'goldBalanceOfLiman' => round($goldBalanceOfLiman, 4),
'goldBalanceOfKalhor' => round($goldBalanceOfKalhor, 4),
];
$totalGoldBalance += $goldBalance;
}
return [
'items' => $items,
'totalGoldBalance' => round($totalGoldBalance, 4),
];
}
/** @param array<int, array{gold: float, cash: float, gold_abs: float, cash_abs: float}> $buckets */
public function applyKalhorSlice(
array &$buckets,
float $gold,
float $cash,
int $direction,
int $metalId = 1
): void {
if (! isset($buckets[$metalId])) {
$buckets[$metalId] = [
'gold' => 0.0,
'cash' => 0.0,
'gold_abs' => 0.0,
'cash_abs' => 0.0,
];
}
if ($direction === 2) {
$buckets[$metalId]['gold'] += $gold;
$buckets[$metalId]['cash'] -= $cash;
} elseif ($direction === 1) {
$buckets[$metalId]['gold'] -= $gold;
$buckets[$metalId]['cash'] += $cash;
}
$buckets[$metalId]['gold_abs'] += $gold;
$buckets[$metalId]['cash_abs'] += $cash;
}
2026-06-23 21:21:04 +03:30
public function resolveTransactionMetalId(mixed $transaction): int
{
2026-07-21 22:23:09 +03:30
$product = $this->resolveTransactionProduct($transaction);
2026-06-23 21:21:04 +03:30
if ($product !== null) {
$metalId = (int) ($product->metal_id ?? 0);
return $metalId > 0 ? $metalId : 1;
}
if ($this->isMainWalletMetalTransaction($transaction)) {
return $this->configService->mainWalletMetalId();
}
2026-06-23 21:21:04 +03:30
return 1;
}
public function resolveTransactionDisplayTypeStr(mixed $transaction, ?int $metalId = null): ?string
{
2026-06-24 01:32:32 +03:30
$type = $this->resolveTransactionType($transaction);
if (in_array($type, [3, 4, 8, 9], true)) {
return null;
}
2026-06-24 01:32:32 +03:30
$productLabel = $this->resolveTransactionProductLabel($transaction);
2026-06-24 01:44:38 +03:30
if ($productLabel === null) {
2026-06-24 01:32:32 +03:30
return null;
}
$prefix = $this->resolveTransactionActionPrefix($transaction)
2026-06-24 01:44:38 +03:30
?? $this->inferActionPrefixFromLabel(trim((string) ($this->lineValue($transaction, 'type_str') ?? '')))
2026-06-24 01:32:32 +03:30
?? $this->inferActionPrefixFromGold($transaction);
2026-06-24 01:44:38 +03:30
2026-06-24 01:32:32 +03:30
if ($prefix === null) {
return null;
}
2026-06-24 01:44:38 +03:30
return trim($prefix . ' ' . $productLabel);
2026-06-24 01:32:32 +03:30
}
private function resolveTransactionType(mixed $transaction): int
{
$type = $this->lineValue($transaction, 'type');
if ($type !== null && $type !== '' && is_numeric($type)) {
return (int) $type;
}
2026-06-24 01:44:38 +03:30
if (is_object($transaction) && method_exists($transaction, 'getAttributes')) {
$attrs = $transaction->getAttributes();
if (isset($attrs['type']) && is_numeric($attrs['type'])) {
return (int) $attrs['type'];
}
if (isset($attrs['type_str']) && is_numeric($attrs['type_str'])) {
return (int) $attrs['type_str'];
}
}
2026-06-24 01:32:32 +03:30
return 0;
}
private function resolveTransactionActionPrefix(mixed $transaction): ?string
{
$type = $this->resolveTransactionType($transaction);
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
return match (true) {
$type === 1 => 'خرید',
$type === 2 => 'فروش',
$type === 51 => 'اصلاح',
in_array($type, [6, 10], true) => 'برداشت',
$type === 5 => 'واریز',
$type === 7 => $gold >= 0 ? 'واریز' : 'برداشت',
$type === 12 => 'برداشت وجه',
abs($gold) > 0 => $gold > 0 ? 'واریز' : 'برداشت',
default => null,
};
}
2026-06-24 01:32:32 +03:30
private function inferActionPrefixFromGold(mixed $transaction): ?string
{
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
if (abs($gold) < 0.0000001) {
return null;
}
return $gold > 0 ? 'واریز' : 'برداشت';
}
private function inferActionPrefixFromLabel(string $label): ?string
{
if (preg_match('/^خرید/u', $label)) {
return 'خرید';
}
if (preg_match('/^فروش/u', $label)) {
return 'فروش';
}
if (preg_match('/^برداشت/u', $label)) {
return 'برداشت';
}
if (preg_match('/^(واریز|موجودی)/u', $label)) {
return 'واریز';
}
return null;
}
private function shouldReplaceGenericTransactionLabel(string $label): bool
{
2026-06-24 01:44:38 +03:30
if ($label === 'تعریف نشده' || $label === '') {
2026-06-24 01:32:32 +03:30
return true;
}
$generic = [
'واریز',
'برداشت',
'واریز طلایی',
'برداشت طلایی',
'خرید طلا',
'فروش طلا',
];
return in_array($label, $generic, true);
}
public function resolveTransactionProductLabel(mixed $transaction): ?string
{
2026-07-21 22:23:09 +03:30
$product = $this->resolveTransactionProduct($transaction);
2026-06-24 01:32:32 +03:30
if ($product !== null && !empty($product->title)) {
return trim((string) $product->title);
}
if ($this->isMainWalletMetalTransaction($transaction)) {
$labels = $this->configService->mainWalletProductLabels();
return $labels !== '' ? $labels : null;
}
2026-06-24 01:44:38 +03:30
return $this->parseProductLabelFromNote($transaction);
}
private function parseProductLabelFromNote(mixed $transaction): ?string
{
$note = trim((string) ($this->lineValue($transaction, 'note') ?? ''));
if ($note === '') {
return null;
}
if (preg_match('/(?:برداشت|واریز)\s+محصول\s+(.+?)\s+شماره\s+سند/u', $note, $matches)) {
return trim($matches[1]);
}
2026-06-24 01:32:32 +03:30
return null;
}
2026-06-24 01:44:38 +03:30
private function resolveTransactionTitleLabel(mixed $transaction): ?string
{
$title = trim((string) ($this->lineValue($transaction, 'title') ?? ''));
if ($title === '' || $title === 'تعریف نشده') {
return null;
}
return $title;
}
2026-06-23 21:21:04 +03:30
public function transactionGoldGrams(mixed $transaction): float
{
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
if (abs($gold) < 0.0000001) {
return 0.0;
}
$product = $this->transactionProduct($transaction);
if ($product !== null) {
return Controller::productLineWeightToGrams($product, $gold);
}
return Controller::weightToGrams($gold);
}
/**
* @param list<int> $catalogMetalIds
* @return array<string, float>
*/
public function buildTransactionMetalDisplayMap(mixed $transaction, array $catalogMetalIds): array
{
$map = [];
foreach ($catalogMetalIds as $metalId) {
$map[(string) (int) $metalId] = 0.0;
}
$type = (int) ($this->lineValue($transaction, 'type') ?? 0);
if (in_array($type, [3, 4], true)) {
return $map;
}
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
if (abs($gold) < 0.0000001) {
return $map;
}
$metalId = $this->resolveTransactionMetalId($transaction);
$grams = abs($this->transactionGoldGrams($transaction));
$display = $this->gramsToMetalDisplay($grams, $metalId);
if ($gold < 0) {
$display *= -1;
}
$map[(string) $metalId] = round($display, 3);
return $map;
}
/**
* @param list<int> $catalogMetalIds
*/
public function enrichTransactionTableRow(mixed &$row, array $catalogMetalIds): void
{
$metalMap = $this->buildTransactionMetalDisplayMap($row, $catalogMetalIds);
$metalId = $this->resolveTransactionMetalId($row);
$metalLabel = $this->metalLabel($metalId);
2026-06-24 01:32:32 +03:30
$currentTypeStr = trim((string) ($this->lineValue($row, 'type_str') ?? ''));
2026-07-21 22:23:09 +03:30
$titleLabel = $this->resolveTransactionTitleLabel($row);
$productAware = $this->resolveTransactionDisplayTypeStr($row, $metalId);
$product = $this->resolveTransactionProduct($row);
$productTitle = $this->resolveTransactionProductLabel($row);
$productId = (int) ($this->lineValue($row, 'product_id') ?? 0);
if ($productId <= 0 && $product !== null) {
$productId = (int) ($product->id ?? 0);
2026-06-24 01:32:32 +03:30
}
2026-06-23 21:21:04 +03:30
2026-07-21 22:23:09 +03:30
// عنوان ژنریک مثل «خرید طلا» را با «خرید {نام محصول}» عوض کن
if ($productAware !== null && (
$titleLabel === null
|| $this->shouldReplaceGenericTransactionLabel($titleLabel)
|| $this->shouldReplaceGenericTransactionLabel($currentTypeStr)
)) {
$displayType = $productAware;
} else {
$displayType = $titleLabel;
}
$productPayload = $this->productTablePayload($product);
2026-06-23 21:21:04 +03:30
if (is_array($row)) {
$row['metal_gold'] = $metalMap;
$row['transaction_metal_id'] = $metalId;
$row['transaction_metal_label'] = $metalLabel;
2026-07-21 22:23:09 +03:30
$row['product_id'] = $productId > 0 ? $productId : null;
$row['product_title'] = $productTitle;
if ($productPayload !== null && empty($row['product'])) {
$row['product'] = $productPayload;
}
if ($displayType !== null) {
$row['type_str'] = $displayType;
}
2026-06-23 21:21:04 +03:30
foreach ($catalogMetalIds as $mid) {
$row['metal_gold_' . $mid] = $metalMap[(string) $mid] ?? 0.0;
}
return;
}
if (is_object($row)) {
$row->metal_gold = $metalMap;
$row->transaction_metal_id = $metalId;
$row->transaction_metal_label = $metalLabel;
2026-07-21 22:23:09 +03:30
$row->product_id = $productId > 0 ? $productId : null;
$row->product_title = $productTitle;
if ($product instanceof Product && empty($this->lineValue($row, 'product'))) {
$row->setRelation('product', $product);
}
if ($displayType !== null) {
2026-06-24 01:44:38 +03:30
$row->setAttribute('type_str', $displayType);
}
2026-06-23 21:21:04 +03:30
foreach ($catalogMetalIds as $mid) {
$row->{'metal_gold_' . $mid} = $metalMap[(string) $mid] ?? 0.0;
}
}
}
private function transactionProduct(mixed $transaction): ?object
{
2026-07-21 22:23:09 +03:30
if (is_object($transaction) && isset($transaction->product) && $transaction->product) {
2026-06-23 21:21:04 +03:30
return $transaction->product;
}
2026-07-21 22:23:09 +03:30
if (is_array($transaction) && isset($transaction['product']) && $transaction['product']) {
2026-06-23 21:21:04 +03:30
$product = $transaction['product'];
return is_object($product) ? $product : (object) $product;
}
return null;
}
2026-07-21 22:23:09 +03:30
private function resolveTransactionProduct(mixed $transaction): ?object
{
$product = $this->transactionProduct($transaction);
if ($product !== null && ! empty($product->title ?? null)) {
return $product;
}
$walletProduct = $this->transactionWalletProduct($transaction);
if ($walletProduct !== null && ! empty($walletProduct->title ?? null)) {
return $walletProduct;
}
$productId = (int) ($this->lineValue($transaction, 'product_id') ?? 0);
if ($productId > 0) {
return Product::withTrashed()
->select(['id', 'title', 'group_type', 'unit', 'direct_price', 'amount', 'trade_by_quantity', 'metal_id', 'file_id'])
->find($productId);
}
return $product ?? $walletProduct;
}
/**
* @return array<string, mixed>|null
*/
private function productTablePayload(?object $product): ?array
{
if ($product === null) {
return null;
}
$id = (int) ($product->id ?? 0);
if ($id <= 0) {
return null;
}
return [
'id' => $id,
'title' => $product->title ?? null,
'group_type' => $product->group_type ?? null,
'unit' => $product->unit ?? null,
'amount' => $product->amount ?? null,
'trade_by_quantity' => (bool) ($product->trade_by_quantity ?? false),
'direct_price' => $product->direct_price ?? null,
'metal_id' => isset($product->metal_id) ? (int) $product->metal_id : null,
];
}
private function transactionWalletProduct(mixed $transaction): ?object
{
$wallet = $this->lineValue($transaction, 'wallet');
if ($wallet === null) {
return null;
}
if (is_object($wallet)) {
return isset($wallet->product) ? $wallet->product : null;
}
if (is_array($wallet) && isset($wallet['product'])) {
$product = $wallet['product'];
return is_object($product) ? $product : (object) $product;
}
return null;
}
private function isMainWalletMetalTransaction(mixed $transaction): bool
{
$wallet = $this->lineValue($transaction, 'wallet');
if ($wallet === null) {
return true;
}
if (is_object($wallet)) {
2026-06-24 01:32:32 +03:30
if ($wallet->product_id !== null || $wallet->fund_id !== null) {
return false;
}
} elseif (is_array($wallet)) {
if (($wallet['product_id'] ?? null) !== null || ($wallet['fund_id'] ?? null) !== null) {
return false;
}
} else {
return true;
}
2026-06-24 01:32:32 +03:30
$type = $this->resolveTransactionType($transaction);
$gold = (float) ($this->lineValue($transaction, 'gold') ?? 0);
2026-06-24 01:32:32 +03:30
return in_array($type, [1, 2, 5, 6, 7, 10, 51], true) || abs($gold) > 0;
}
2026-06-23 21:40:33 +03:30
public function resolveOrderMetalId(mixed $order): int
{
$product = $this->resolveOrderProduct($order);
if ($product !== null) {
$metalId = (int) ($product->metal_id ?? 0);
return $metalId > 0 ? $metalId : 1;
}
return 1;
}
/**
* @param list<int> $catalogMetalIds
*/
public function enrichOrderTableRow(mixed &$row, array $catalogMetalIds = []): void
2026-06-23 21:28:49 +03:30
{
$product = $this->resolveOrderProduct($row);
$unit = $this->resolveOrderAmountUnit($product);
$amount = (float) ($this->lineValue($row, 'amount') ?? 0);
$displayAmount = $this->resolveOrderDisplayAmount($amount, $product);
2026-06-23 21:40:33 +03:30
$metalId = $this->resolveOrderMetalId($row);
$metalLabel = $this->metalLabel($metalId);
$price = (float) ($this->lineValue($row, 'price') ?? 0);
2026-07-18 17:37:41 +03:30
$fee = (float) ($this->lineValue($row, 'fee') ?? 0);
$totalFee = (float) ($this->lineValue($row, 'total_fee') ?? 0);
$direction = (int) ($this->lineValue($row, 'direction') ?? 0);
$productId = (int) ($this->lineValue($row, 'product_id') ?? 0);
if ($productId <= 0 && $product !== null) {
$productId = (int) ($product->id ?? 0);
}
$productTitle = $product !== null ? trim((string) ($product->title ?? '')) : '';
2026-07-21 22:23:09 +03:30
$productPayload = $this->productTablePayload($product);
2026-07-18 17:37:41 +03:30
$storedSource = trim((string) ($this->lineValue($row, 'fee_source') ?? ''));
$storedLabel = trim((string) ($this->lineValue($row, 'fee_source_label') ?? ''));
$storedKey = trim((string) ($this->lineValue($row, 'fee_source_key') ?? ''));
$storedBreakdown = $this->lineValue($row, 'fee_breakdown');
if (is_string($storedBreakdown) && $storedBreakdown !== '') {
$decoded = json_decode($storedBreakdown, true);
$storedBreakdown = is_array($decoded) ? $decoded : null;
} elseif (! is_array($storedBreakdown)) {
$storedBreakdown = null;
}
$feeSource = $storedSource !== '' ? $storedSource : $this->resolveOrderFeeSourceKey($product);
$feeSourceLabel = $storedLabel !== ''
? $storedLabel
: $this->resolveOrderFeeSourceLabel($direction, $feeSource);
$feeSourceKey = $storedKey !== '' ? $storedKey : null;
2026-06-23 21:40:33 +03:30
$metalPrices = [];
foreach ($catalogMetalIds as $mid) {
$mid = (int) $mid;
$metalPrices[(string) $mid] = $mid === $metalId ? $price : 0.0;
}
$payload = [
'amount_unit' => $unit,
'amount_display' => $displayAmount,
'order_metal_id' => $metalId,
'order_metal_label' => $metalLabel,
'metal_prices' => $metalPrices,
2026-07-18 17:37:41 +03:30
'product_id' => $productId > 0 ? $productId : null,
'product_title' => $productTitle !== '' ? $productTitle : null,
'fee_source' => $feeSource,
'fee_source_key' => $feeSourceKey,
'fee_source_label' => $feeSourceLabel,
'fee_breakdown' => $storedBreakdown,
'fee_display' => abs($fee),
'total_fee_display' => abs($totalFee),
2026-06-23 21:40:33 +03:30
];
foreach ($catalogMetalIds as $mid) {
$payload['metal_price_' . (int) $mid] = $metalPrices[(string) (int) $mid] ?? 0.0;
}
2026-06-23 21:28:49 +03:30
if (is_array($row)) {
2026-06-23 21:40:33 +03:30
foreach ($payload as $key => $value) {
$row[$key] = $value;
}
2026-07-21 22:23:09 +03:30
if ($productPayload !== null && empty($row['product'])) {
$row['product'] = $productPayload;
}
2026-06-23 21:28:49 +03:30
return;
}
if (is_object($row)) {
2026-06-23 21:40:33 +03:30
foreach ($payload as $key => $value) {
$row->{$key} = $value;
}
2026-07-21 22:23:09 +03:30
if ($productPayload !== null && empty($this->lineValue($row, 'product'))) {
if ($product instanceof Product) {
$row->setRelation('product', $product);
} else {
$row->product = $productPayload;
}
}
2026-06-23 21:28:49 +03:30
}
}
2026-07-18 17:37:41 +03:30
/** settings | product | app+product — هم‌تراز resolveCashPriceDiffMeta در SingleOrderController */
public function resolveOrderFeeSourceKey(?object $product = null): string
{
if ($product instanceof Product) {
if (app(ProductDisplayPriceService::class)->usesAppPrice($product)) {
return 'app+product';
}
return 'product';
}
$provider = $this->configService->priceProvider();
if (in_array($provider, ['zaarbox', 'main_product'], true)) {
return 'settings';
}
return 'product';
}
public function resolveOrderFeeSourceLabel(int $direction, ?string $feeSource = null): string
{
$source = $feeSource ?? $this->resolveOrderFeeSourceKey();
if ($source === 'app+product') {
return $direction === 2
? 'قیمت اپ (sell_fee) + تب اختلاف محصول'
: 'قیمت اپ (buy_fee) + تب اختلاف محصول';
}
if ($source === 'settings') {
return $direction === 2
? 'تنظیمات سراسری (sell_fee / fee_single)'
: 'تنظیمات سراسری (buy_fee / fee_single)';
}
return $direction === 2
? 'هزینه فروش محصول (cash_fee_sell)'
: 'هزینه خرید محصول (cash_fee_buy)';
}
2026-06-23 21:28:49 +03:30
public function resolveOrderAmountUnit(?object $product): string
{
if ($product === null) {
return Controller::tradeWeightUnit();
}
return Controller::productQuantityUnit(Controller::productUnitPayload($product));
}
private function resolveOrderDisplayAmount(float $amount, ?object $product): float
{
if ($product === null || abs($amount) < 0.0000001) {
return round($amount, 3);
}
$payload = Controller::productUnitPayload($product);
$productUnit = trim((string) ($payload['unit'] ?? ''));
if (!Controller::isWeightBasedProductUnit($productUnit)) {
return round($amount, 3);
}
$tradeUnit = Controller::tradeWeightUnit();
if ($productUnit !== $tradeUnit) {
return round(Controller::convertWeight($amount, $tradeUnit, $productUnit), 3);
}
return round($amount, 3);
}
private function resolveOrderProduct(mixed $order): ?object
{
2026-07-18 17:37:41 +03:30
$embedded = $this->lineValue($order, 'product');
if (is_object($embedded) && ! empty($embedded->title ?? null)) {
return $embedded;
}
if (is_array($embedded) && ! empty($embedded['title'])) {
return (object) $embedded;
}
$productId = (int) ($this->lineValue($order, 'product_id') ?? 0);
if ($productId > 0) {
2026-07-21 22:23:09 +03:30
$byId = Product::withTrashed()
2026-07-18 17:37:41 +03:30
->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price'])
->find($productId);
if ($byId) {
return $byId;
}
}
2026-06-23 21:28:49 +03:30
$transactions = $this->lineValue($order, 'transactions');
if ($transactions instanceof \Illuminate\Support\Collection) {
$transactions = $transactions->all();
}
if (!is_array($transactions)) {
return null;
}
foreach ($transactions as $tx) {
$product = is_object($tx) ? ($tx->product ?? null) : ($tx['product'] ?? null);
if ($product) {
return is_object($product) ? $product : (object) $product;
}
2026-07-21 22:23:09 +03:30
$txProductId = (int) ($this->lineValue($tx, 'product_id') ?? 0);
if ($txProductId > 0) {
$byTx = Product::withTrashed()
->select(['id', 'title', 'unit', 'metal_id', 'group_type', 'amount', 'trade_by_quantity', 'direct_price'])
->find($txProductId);
if ($byTx) {
return $byTx;
}
}
2026-06-23 21:28:49 +03:30
}
return null;
}
2026-06-23 21:21:04 +03:30
private function lineValue(mixed $line, string $key): mixed
{
if (is_array($line)) {
return $line[$key] ?? null;
}
if (is_object($line)) {
return $line->{$key} ?? null;
}
return null;
}
2026-06-23 20:35:13 +03:30
}