Files
boursecu_server/app/Services/AssetSummaryService.php
T
2026-06-10 13:28:35 +03:30

441 lines
14 KiB
PHP

<?php
namespace App\Services;
use App\Http\Controllers\Controller;
use App\Models\Product;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Rooyesh\Wallet\models\Wallet;
class AssetSummaryService
{
private const METAL_LABELS = [
1 => 'طلا',
2 => 'نقره',
3 => 'مس',
];
public function __construct(
private readonly ConfigService $configService
) {
}
public function buildForCustomer(int $customerId, int $ownerType): array
{
$catalog = Product::query()
->where('active', 1)
->with('file')
->get();
$userWallets = Wallet::query()
->with(['product.file'])
->where('owner', $customerId)
->where('owner_type', $ownerType)
->get();
$mainWallet = $userWallets->first(fn (Wallet $w) => $w->product_id === null);
$metalIds = $this->catalogMetalIds($catalog);
$weightUnit = Controller::tradeWeightUnit();
$hasCountBased = $this->catalogHasCountBased($catalog);
$hasWeightBased = $this->catalogHasWeightBased($catalog);
$inventoryCards = array_merge(
$this->buildMeltedInventoryCards($catalog, $mainWallet, $metalIds, $userWallets),
$this->buildInventoryCards($userWallets)
);
$metalTotals = $this->buildMetalTotals($userWallets, $mainWallet, $metalIds, $weightUnit, $catalog, $inventoryCards);
$cashWallet = (float) ($mainWallet->cash ?? 0);
$assetsRialValue = $this->sumAssetsRialValue($userWallets);
$totalRialValue = $cashWallet + $assetsRialValue;
return [
'meta' => [
'metal_ids' => $metalIds,
'metal_count' => count($metalIds),
'has_count_based_products' => $hasCountBased,
'has_weight_based_products' => $hasWeightBased,
'weight_unit' => $weightUnit,
'single_metal' => count($metalIds) === 1,
],
'cash_wallet' => round($cashWallet, 0),
'inventory_cards' => $inventoryCards,
'count_products' => $hasCountBased
? array_values(array_filter($inventoryCards, fn (array $row) => ($row['display_type'] ?? '') === 'count'))
: [],
'metal_totals' => $metalTotals,
'assets_rial_value' => round($assetsRialValue, 0),
'total_rial_value' => round($totalRialValue, 0),
];
}
private function catalogMetalIds(Collection $catalog): array
{
return $catalog
->pluck('metal_id')
->filter(fn ($id) => (int) $id > 0)
->map(fn ($id) => (int) $id)
->unique()
->sort()
->values()
->all();
}
private function catalogHasCountBased(Collection $catalog): bool
{
return $catalog->contains(fn (Product $product) => $this->isCountBasedProduct($product));
}
private function catalogHasWeightBased(Collection $catalog): bool
{
return $catalog->contains(fn (Product $product) => !$this->isCountBasedProduct($product));
}
private function buildMeltedInventoryCards(
Collection $catalog,
?Wallet $mainWallet,
array $metalIds,
Collection $userWallets
): array {
$cards = [];
$mainWalletGrams = Controller::weightToGrams((float) ($mainWallet->gold ?? 0));
$assignedMetalIds = [];
$metalsWithProductWallets = $this->metalIdsWithVisibleProductWallet($userWallets);
foreach ($this->resolveMeltedProducts($catalog) as $product) {
if (!$this->shouldShowInAssets($product)) {
continue;
}
$metalId = (int) ($product->metal_id ?? 0);
if (
$metalId <= 0
|| in_array($metalId, $assignedMetalIds, true)
|| in_array($metalId, $metalsWithProductWallets, true)
) {
continue;
}
$meltedGrams = $this->meltedGramsForMetal($mainWalletGrams, $metalId, $metalIds, $catalog);
if ($meltedGrams <= 0) {
continue;
}
$assignedMetalIds[] = $metalId;
$productPayload = $this->productPayload($product);
$cards[] = [
'product_id' => (int) $product->id,
'title' => (string) $product->title,
'group_type' => $this->meltedGroupTypeLabel($product),
'metal_id' => $metalId,
'quantity' => round(Controller::gramsToTradeUnit($meltedGrams), 3),
'unit' => Controller::productQuantityUnit($productPayload),
'display_type' => 'weight',
'unit_weight' => $this->productUnitWeightLabel($product),
'show_in_assets' => $this->shouldShowInAssets($product),
'is_melted' => true,
'file' => $this->filePayload($product),
];
}
return $cards;
}
/**
* محصولات آبشده: دسته «آبشده»/«کاتد مس» + محصول اصلی تنظیمات (main_product).
*/
private function resolveMeltedProducts(Collection $catalog): Collection
{
$mainProductId = $this->configService->mainProductId();
return $catalog
->filter(fn (Product $product) => $this->isMeltedCatalogProduct($product, $mainProductId))
->sortBy(fn (Product $product) => $this->isMainMeltedProduct($product, $mainProductId) ? 0 : 1)
->values();
}
private function isMeltedCatalogProduct(Product $product, ?int $mainProductId): bool
{
if ($mainProductId && (int) $product->id === (int) $mainProductId) {
return true;
}
return $this->isMeltedTradeProduct($product);
}
private function isMainMeltedProduct(Product $product, ?int $mainProductId): bool
{
return $mainProductId !== null && (int) $product->id === (int) $mainProductId;
}
private function isMeltedTradeProduct(Product $product): bool
{
$group = trim((string) ($product->group_type ?? ''));
return in_array($group, ['آبشده', 'کاتد مس'], true);
}
private function meltedGroupTypeLabel(Product $product): string
{
$group = trim((string) ($product->group_type ?? ''));
return $group !== '' ? $group : 'آبشده';
}
private function hasVisibleMeltedProductForMetal(Collection $catalog, int $metalId): bool
{
return $this->resolveMeltedProducts($catalog)->contains(
fn (Product $product) => (int) ($product->metal_id ?? 0) === (int) $metalId
&& $this->shouldShowInAssets($product)
);
}
/** فلزهایی که کاربر موجودی روی کیف محصول دارد — کارت آبشده جدا لازم نیست. */
private function metalIdsWithVisibleProductWallet(Collection $userWallets): array
{
$metalIds = [];
foreach ($userWallets as $walletLine) {
if ($walletLine->product_id === null || (float) $walletLine->gold <= 0) {
continue;
}
$product = $walletLine->product;
if (!$product || !$this->shouldShowInAssets($product)) {
continue;
}
$metalId = (int) ($product->metal_id ?? 0);
if ($metalId > 0) {
$metalIds[] = $metalId;
}
}
return array_values(array_unique($metalIds));
}
private function buildInventoryCards(Collection $userWallets): array
{
$cards = [];
foreach ($userWallets as $walletLine) {
if ($walletLine->product_id === null || (float) $walletLine->gold <= 0) {
continue;
}
$product = $walletLine->product;
if (!$product || !$this->shouldShowInAssets($product)) {
continue;
}
$productPayload = $this->productPayload($product);
$isCountBased = $this->isCountBasedProduct($product);
$cards[] = [
'product_id' => (int) $product->id,
'title' => (string) $product->title,
'group_type' => (string) ($product->group_type ?? ''),
'metal_id' => (int) ($product->metal_id ?? 0),
'quantity' => round((float) $walletLine->gold, 3),
'unit' => Controller::productQuantityUnit($productPayload),
'display_type' => $isCountBased ? 'count' : 'weight',
'unit_weight' => $this->productUnitWeightLabel($product),
'show_in_assets' => $this->shouldShowInAssets($product),
'file' => $this->filePayload($product),
];
}
return $cards;
}
private function buildMetalTotals(
Collection $userWallets,
?Wallet $mainWallet,
array $metalIds,
string $weightUnit,
Collection $catalog,
array $inventoryCards
): array {
if ($metalIds === []) {
return [];
}
$mainWalletGrams = Controller::weightToGrams((float) ($mainWallet->gold ?? 0));
$metalsWithProductWallets = $this->metalIdsWithVisibleProductWallet($userWallets);
$totals = [];
foreach ($metalIds as $metalId) {
$productGrams = 0.0;
foreach ($userWallets as $walletLine) {
if ($walletLine->product_id === null || !$walletLine->product) {
continue;
}
$product = $walletLine->product;
if (!$this->shouldShowInAssets($product)) {
continue;
}
if ((int) $product->metal_id !== (int) $metalId) {
continue;
}
if ((string) ($product->group_type ?? '') === 'سکه') {
continue;
}
$productGrams += Controller::productLineWeightToGrams(
$product,
$walletLine->gold
);
}
$meltedGrams = $this->meltedGramsForMetal($mainWalletGrams, $metalId, $metalIds, $catalog);
if (in_array((int) $metalId, $metalsWithProductWallets, true)) {
$meltedGrams = 0.0;
}
$productWeight = round(Controller::gramsToTradeUnit($productGrams), 3);
$meltedWeight = round(Controller::gramsToTradeUnit($meltedGrams), 3);
$visibleCardWeight = round($this->visibleCardWeightForMetal($inventoryCards, (int) $metalId), 3);
$totalWeight = $visibleCardWeight > 0
? $visibleCardWeight
: round($productWeight + $meltedWeight, 3);
if ($totalWeight <= 0) {
continue;
}
$totals[] = [
'metal_id' => (int) $metalId,
'metal_label' => self::METAL_LABELS[$metalId] ?? 'فلز',
'label' => 'کل دارایی ' . (self::METAL_LABELS[$metalId] ?? 'فلز'),
'product_weight' => $productWeight,
'melted_weight' => $meltedWeight,
'total_weight' => $totalWeight,
'unit' => $weightUnit,
];
}
return $totals;
}
private function visibleCardWeightForMetal(array $inventoryCards, int $metalId): float
{
$total = 0.0;
foreach ($inventoryCards as $card) {
if ((int) ($card['metal_id'] ?? 0) !== $metalId) {
continue;
}
$total += (float) ($card['quantity'] ?? 0);
}
return $total;
}
private function meltedGramsForMetal(
float $mainWalletGrams,
int $metalId,
array $catalogMetalIds,
Collection $catalog
): float {
if ($mainWalletGrams <= 0) {
return 0.0;
}
if (!$this->hasVisibleMeltedProductForMetal($catalog, $metalId)) {
return 0.0;
}
if (count($catalogMetalIds) === 1) {
return (int) $catalogMetalIds[0] === (int) $metalId ? $mainWalletGrams : 0.0;
}
return (int) $metalId === 1 ? $mainWalletGrams : 0.0;
}
private function sumAssetsRialValue(Collection $userWallets): float
{
$total = 0.0;
foreach ($userWallets as $walletLine) {
if ($walletLine->product_id === null || !$walletLine->product) {
continue;
}
$product = $walletLine->product;
if (!$this->shouldShowInAssets($product)) {
continue;
}
$qty = (float) $walletLine->gold;
$metalId = (int) ($product->metal_id ?? 0);
if ($metalId === 1) {
$total += (float) ($product->price ?? 0) * $qty;
} elseif ($metalId === 2 || $metalId === 3) {
$total += ((float) ($product->price ?? 0) * (float) ($product->amount ?? 1)) * $qty;
}
}
return $total;
}
private function shouldShowInAssets(Product $product): bool
{
if (!Product::hasShowInAssetsColumn()) {
return false;
}
return (bool) ($product->show_in_assets ?? false);
}
private function isCountBasedProduct(Product $product): bool
{
return Controller::isCountBasedProduct($this->productPayload($product));
}
private function productPayload(Product $product): array
{
return [
'unit' => $product->unit ?? null,
'direct_price' => $product->direct_price ?? null,
'group_type' => $product->group_type ?? null,
'amount' => $product->amount ?? null,
];
}
private function productUnitWeightLabel(Product $product): ?string
{
if (!Controller::hasProductUnitWeight($this->productPayload($product))) {
return null;
}
$amount = (float) ($product->amount ?? 0);
$unit = Controller::productStorageWeightUnit($product);
return trim($amount . ' ' . $unit);
}
private function filePayload(Product $product): ?array
{
if (!$product->relationLoaded('file') || !$product->file) {
return null;
}
$fileModel = $product->file;
$src = (string) ($fileModel->src ?? '');
return [
'id' => $fileModel->id,
'src' => $src,
'full_src' => $src,
'url' => $fileModel->url ?? null,
];
}
}