fix some bug
This commit is contained in:
@@ -500,7 +500,13 @@ class WalletController extends Controller
|
||||
TableEngine::create($sumQuery, $request->all())
|
||||
->initSearch(['customer.name']);
|
||||
|
||||
$sumRows = (clone $sumQuery)->with('product:id,metal_id,unit,amount,group_type,trade_by_quantity,direct_price,title')->get();
|
||||
$walletTableWith = [
|
||||
'customer:id,name,mobile',
|
||||
'fund:id,title',
|
||||
'product:id,metal_id,unit,amount,group_type,trade_by_quantity,direct_price,title',
|
||||
];
|
||||
|
||||
$sumRows = (clone $sumQuery)->with($walletTableWith)->get();
|
||||
$metalBalances = $balanceService->buildWalletMetalSums($sumRows, $catalogMetalIds);
|
||||
$totalGold = array_sum(array_column($metalBalances, 'gold'));
|
||||
$totalGoldToCash = array_sum(array_column($metalBalances, 'goldToCash'));
|
||||
@@ -514,9 +520,9 @@ class WalletController extends Controller
|
||||
'metalBalances' => $metalBalances,
|
||||
];
|
||||
|
||||
$records = Wallet::with(['customer', 'product:id,metal_id,unit,amount,group_type,trade_by_quantity,direct_price,title']);
|
||||
$records = Wallet::with($walletTableWith);
|
||||
TableEngine::create($records, $request->all())
|
||||
->initSearch(['customer.name'])
|
||||
->initSearch(['customer.name', 'customer.mobile', 'product.title', 'fund.title'])
|
||||
->select(['*'])
|
||||
->export($records);
|
||||
|
||||
|
||||
@@ -234,9 +234,10 @@ class SingleOrderController extends Controller
|
||||
{
|
||||
|
||||
$response = [];
|
||||
$config = app(ConfigService::class);
|
||||
$feeFromGold = $config->feeDeductFromGold();
|
||||
$data['price'] += $data['difference'];
|
||||
$data['total_price'] = $data['amount'] * $data['price'];
|
||||
$data['payment_amount'] = $data['total_price'];
|
||||
|
||||
$this->enforceProductOrderLimits($data, (float) $data['total_price']);
|
||||
|
||||
@@ -248,7 +249,12 @@ class SingleOrderController extends Controller
|
||||
|
||||
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : $data['service_charge_percentage'] * $data['amount'];
|
||||
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
|
||||
$data['amount'] -= $data['service_charge'];
|
||||
if ($feeFromGold) {
|
||||
$data['payment_amount'] = $data['total_price'] + $data['service_charge_price'];
|
||||
} else {
|
||||
$data['payment_amount'] = $data['total_price'];
|
||||
$data['amount'] -= $data['service_charge'];
|
||||
}
|
||||
if ($wallet->cash < $data['payment_amount']) {
|
||||
throw new UnprocessableEntityHttpException('موجودی شما کمتر از میزان سفارش است');
|
||||
}
|
||||
@@ -420,22 +426,30 @@ class SingleOrderController extends Controller
|
||||
private function processSellTransaction(&$data, $wallet, $wallet_interface)
|
||||
{
|
||||
$response = [];
|
||||
$config = app(ConfigService::class);
|
||||
$feeFromGold = $config->feeDeductFromGold();
|
||||
$data['price'] -= $data['difference'];
|
||||
$grossTotal = $data['amount'] * $data['price'];
|
||||
$this->enforceProductOrderLimits($data, (float) $grossTotal);
|
||||
$data['service_charge'] = isset($data['fee_free']) && $data['fee_free'] === true ? 0 : $data['service_charge_percentage'] * $data['amount'];
|
||||
$data['service_charge_price'] = $data['service_charge'] * $data['price_original'];
|
||||
$data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price'];
|
||||
$data['payment_amount'] = $data['total_price'];
|
||||
$data['payment_amount'] -= $data['service_charge_price'];
|
||||
if ($wallet->gold < $data['amount']) {
|
||||
if ($feeFromGold) {
|
||||
$data['total_price'] = $data['amount'] * $data['price'];
|
||||
$data['payment_amount'] = $data['total_price'];
|
||||
$goldDebit = $data['amount'] + $data['service_charge'];
|
||||
} else {
|
||||
$data['total_price'] = $data['amount'] * $data['price'] - $data['service_charge_price'];
|
||||
$data['payment_amount'] = $data['total_price'];
|
||||
$goldDebit = $data['amount'];
|
||||
}
|
||||
if ($wallet->gold < $goldDebit) {
|
||||
throw new UnprocessableEntityHttpException($this->insufficientGoldBalanceMessage($data));
|
||||
}
|
||||
$data['order'] = $order = Order::create($data);
|
||||
$abshode = $this->resolveAbshodeProductContext($data);
|
||||
$productId = (int) ($data['product_id'] ?? $abshode['id'] ?? 0);
|
||||
$sellTransaction = [
|
||||
"gold" => $data['amount'],
|
||||
"gold" => $goldDebit,
|
||||
"cash" => $data['total_price'],
|
||||
'expire_at' => now(),
|
||||
'active' => true,
|
||||
|
||||
+11
-3
@@ -89,12 +89,13 @@ class Withdraw extends Model
|
||||
->where('note', 'like', '%برداشت%');
|
||||
})
|
||||
->orWhere(function ($deduct) {
|
||||
$deduct->where('cash', '<', 0);
|
||||
$deduct->where('cash', '<', 0)
|
||||
->orWhere('gold', '<', 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** واریز: ورود پول/طلا به کیف (درگاه، شارژ ادمین، پاداش) */
|
||||
/** واریز: ورود پول/طلا به کیف (درگاه، شارژ ادمین، پاداش) — بدون کسر ادمین */
|
||||
public static function scopeDeposits($query)
|
||||
{
|
||||
return $query->where(function ($q) {
|
||||
@@ -105,7 +106,14 @@ class Withdraw extends Model
|
||||
})->where(function ($excludeWithdrawNotes) {
|
||||
$excludeWithdrawNotes->whereNull('note')
|
||||
->orWhere('note', 'not like', '%برداشت%');
|
||||
})->whereNotBetween('sts', [50, 52]);
|
||||
})->whereNotBetween('sts', [50, 52])
|
||||
->where(function ($positive) {
|
||||
$positive->where(function ($cash) {
|
||||
$cash->whereNull('cash')->orWhere('cash', '>=', 0);
|
||||
})->where(function ($gold) {
|
||||
$gold->whereNull('gold')->orWhere('gold', '>=', 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Order;
|
||||
use App\Models\Price;
|
||||
use App\Models\Product;
|
||||
use Illuminate\Support\Collection;
|
||||
@@ -60,6 +61,13 @@ class AssetSummaryService
|
||||
$mainWalletProductIds
|
||||
);
|
||||
$totalRialValue = $cashWallet + $assetsRialValue;
|
||||
$portfolio = $this->buildPortfolioInsight(
|
||||
$customerId,
|
||||
$metalTotals,
|
||||
$cashWallet,
|
||||
$assetsRialValue,
|
||||
$totalRialValue
|
||||
);
|
||||
|
||||
return [
|
||||
'meta' => [
|
||||
@@ -78,6 +86,78 @@ class AssetSummaryService
|
||||
'metal_totals' => $metalTotals,
|
||||
'assets_rial_value' => round($assetsRialValue, 0),
|
||||
'total_rial_value' => round($totalRialValue, 0),
|
||||
'profit_loss' => $portfolio['unrealized_profit_rial'],
|
||||
'portfolio' => $portfolio,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* خلاصه دارایی برای داشبورد — ارزش کل، تفکیک نقد/فلز، سود/زیان تحققنیافته طلا.
|
||||
*
|
||||
* @return array{
|
||||
* total_rial_value: float,
|
||||
* cash_rial: float,
|
||||
* metal_rial_value: float,
|
||||
* gold_weight: float,
|
||||
* gold_rial_value: float,
|
||||
* average_buy_price: float|null,
|
||||
* cost_basis_rial: float|null,
|
||||
* unrealized_profit_rial: float|null,
|
||||
* unrealized_profit_percent: float|null,
|
||||
* live_gold_price: float
|
||||
* }
|
||||
*/
|
||||
private function buildPortfolioInsight(
|
||||
int $customerId,
|
||||
array $metalTotals,
|
||||
float $cashWallet,
|
||||
float $assetsRialValue,
|
||||
float $totalRialValue
|
||||
): array {
|
||||
$goldMetal = collect($metalTotals)->first(fn (array $row) => (int) ($row['metal_id'] ?? 0) === 1);
|
||||
$goldWeight = (float) ($goldMetal['total_weight'] ?? 0);
|
||||
$liveGoldPrice = $this->resolveLivePricePerUnit(1);
|
||||
$goldRialValue = $goldWeight > 0 && $liveGoldPrice > 0
|
||||
? round($goldWeight * $liveGoldPrice, 0)
|
||||
: 0.0;
|
||||
|
||||
$averageBuyPrice = null;
|
||||
$costBasisRial = null;
|
||||
$unrealizedProfitRial = null;
|
||||
$unrealizedProfitPercent = null;
|
||||
|
||||
if ($goldWeight > 0) {
|
||||
$buyOrders = Order::query()
|
||||
->where('customer_id', $customerId)
|
||||
->where('direction', 1)
|
||||
->where('sts', 2)
|
||||
->where('amount', '>', 0)
|
||||
->get(['amount', 'price']);
|
||||
|
||||
$totalBought = (float) $buyOrders->sum('amount');
|
||||
$weightedSum = (float) $buyOrders->sum(fn (Order $order) => (float) $order->price * (float) $order->amount);
|
||||
|
||||
if ($totalBought > 0 && $weightedSum > 0) {
|
||||
$averageBuyPrice = round($weightedSum / $totalBought, 0);
|
||||
$costBasisRial = round($averageBuyPrice * $goldWeight, 0);
|
||||
$unrealizedProfitRial = round($goldRialValue - $costBasisRial, 0);
|
||||
if ($costBasisRial > 0) {
|
||||
$unrealizedProfitPercent = round(($unrealizedProfitRial / $costBasisRial) * 100, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'total_rial_value' => round($totalRialValue, 0),
|
||||
'cash_rial' => round($cashWallet, 0),
|
||||
'metal_rial_value' => round($assetsRialValue, 0),
|
||||
'gold_weight' => round($goldWeight, 3),
|
||||
'gold_rial_value' => $goldRialValue,
|
||||
'average_buy_price' => $averageBuyPrice,
|
||||
'cost_basis_rial' => $costBasisRial,
|
||||
'unrealized_profit_rial' => $unrealizedProfitRial,
|
||||
'unrealized_profit_percent' => $unrealizedProfitPercent,
|
||||
'live_gold_price' => round($liveGoldPrice, 0),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,15 @@ class ConfigService
|
||||
return (float) config('fee.service_charge_percentage', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* روشن: کارمزد از مقدار طلا (فروش = واریز ریالی گرد دقیق، خرید = دریافت کامل طلا).
|
||||
* خاموش (پیشفرض): کارمزد از ریال در فروش و از طلا در خرید — رفتار قبلی.
|
||||
*/
|
||||
public function feeDeductFromGold(): bool
|
||||
{
|
||||
return $this->getBool('fee_deduct_from_gold', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* sell / buy / price_last — یک منبع حقیقت برای lastPrice و buildFromDatabase.
|
||||
*
|
||||
|
||||
@@ -242,12 +242,16 @@ class WalletBalanceService
|
||||
public function enrichWalletTableRow(mixed &$row, array $catalogMetalIds, int $mainWalletMetalId): void
|
||||
{
|
||||
$metalMap = $this->walletLineMetalGoldMap($row, $catalogMetalIds, $mainWalletMetalId);
|
||||
$meta = $this->buildWalletTableRowMeta($row, $mainWalletMetalId);
|
||||
|
||||
if (is_array($row)) {
|
||||
$row['metal_gold'] = $metalMap;
|
||||
foreach ($catalogMetalIds as $metalId) {
|
||||
$row['metal_gold_' . $metalId] = $metalMap[(string) $metalId] ?? 0.0;
|
||||
}
|
||||
foreach ($meta as $key => $value) {
|
||||
$row[$key] = $value;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -257,9 +261,60 @@ class WalletBalanceService
|
||||
foreach ($catalogMetalIds as $metalId) {
|
||||
$row->{'metal_gold_' . $metalId} = $metalMap[(string) $metalId] ?? 0.0;
|
||||
}
|
||||
foreach ($meta as $key => $value) {
|
||||
$row->{$key} = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function buildWalletTableRowMeta(mixed $row, int $mainWalletMetalId): array
|
||||
{
|
||||
$fundId = $this->lineValue($row, 'fund_id');
|
||||
$productId = $this->lineValue($row, 'product_id');
|
||||
$product = $this->lineProduct($row);
|
||||
$fund = $this->lineFund($row);
|
||||
$cash = (float) ($this->lineValue($row, 'cash') ?? 0);
|
||||
|
||||
if ($fundId !== null) {
|
||||
$kind = 'fund';
|
||||
$kindLabel = 'قلک (صندوق)';
|
||||
$fundTitle = trim((string) ($fund?->title ?? ''));
|
||||
$subject = $fundTitle !== '' ? "قلک «{$fundTitle}»" : 'قلک بدون عنوان';
|
||||
} elseif ($productId === null) {
|
||||
$kind = 'main';
|
||||
$kindLabel = 'کیف اصلی';
|
||||
$subject = 'کیف اصلی — ریال و طلای آبشده';
|
||||
} else {
|
||||
$kind = 'product';
|
||||
$kindLabel = 'کیف محصول';
|
||||
$title = trim((string) ($product?->title ?? ''));
|
||||
$subject = $title !== '' ? "محصول «{$title}»" : "محصول کد {$productId}";
|
||||
}
|
||||
|
||||
$metalId = $this->resolveWalletLineMetalId($row, $mainWalletMetalId);
|
||||
$goldGrams = $this->walletLineGoldGrams($row);
|
||||
$goldToCash = $goldGrams > 0 ? (int) round($goldGrams * $this->livePricePerGram($metalId)) : 0;
|
||||
|
||||
return [
|
||||
'wallet_kind' => $kind,
|
||||
'wallet_kind_label' => $kindLabel,
|
||||
'wallet_subject' => $subject,
|
||||
'product_title' => $product?->title ?? null,
|
||||
'product_group_type' => $product?->group_type ?? null,
|
||||
'product_unit' => $product?->unit ?? null,
|
||||
'product_amount' => $product?->amount ?? null,
|
||||
'fund_title' => $fund?->title ?? null,
|
||||
'wallet_metal_id' => $metalId,
|
||||
'wallet_metal_label' => $this->balanceReportService->metalLabel($metalId),
|
||||
'gold_to_cash' => $goldToCash,
|
||||
'gold_grams' => round($goldGrams, 3),
|
||||
'total_rial_value' => (int) round($cash + $goldToCash),
|
||||
];
|
||||
}
|
||||
|
||||
private function livePricePerGram(int $metalId): float
|
||||
{
|
||||
if ($metalId === 1) {
|
||||
@@ -303,6 +358,21 @@ class WalletBalanceService
|
||||
return null;
|
||||
}
|
||||
|
||||
private function lineFund(mixed $line): ?object
|
||||
{
|
||||
if (is_object($line) && isset($line->fund)) {
|
||||
return $line->fund;
|
||||
}
|
||||
|
||||
if (is_array($line) && isset($line['fund'])) {
|
||||
$fund = $line['fund'];
|
||||
|
||||
return is_object($fund) ? $fund : (object) $fund;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function walletLineGoldGrams(mixed $line): float
|
||||
{
|
||||
$gold = (float) ($this->lineValue($line, 'gold') ?? 0);
|
||||
|
||||
@@ -421,6 +421,10 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de
|
||||
SELECT 'fee_service_charge_percentage', '0.005', 'درصد کارمزد سفارش', 1, 1, NULL, 'number'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_service_charge_percentage' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'fee_deduct_from_gold', 'false', 'کارمزد از مقدار طلا', 1, 1, NULL, 'switch'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_deduct_from_gold' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'min_withdraw', '', 'حداقل برداشت (ریال)', 1, 1, NULL, 'price'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'min_withdraw' LIMIT 1);
|
||||
@@ -888,6 +892,7 @@ UPDATE `settings` SET `description` = 'اختلاف پایه (ریال) بین
|
||||
UPDATE `settings` SET `description` = 'مبلغ اضافه (ریال) روی قیمت پایه برای سمت خرید. خالی = استفاده از «اختلاف پایه».' WHERE `key` = 'buy_fee';
|
||||
UPDATE `settings` SET `description` = 'مبلغ کسر (ریال) از قیمت پایه برای سمت فروش. خالی = استفاده از «اختلاف پایه».' WHERE `key` = 'sell_fee';
|
||||
UPDATE `settings` SET `description` = 'درصد کارمزد هر سفارش از مبلغ معامله (اعشاری؛ مثلاً ۰٫۰۰۵ یعنی نیم در هزار).' WHERE `key` = 'fee_service_charge_percentage';
|
||||
UPDATE `settings` SET `description` = 'روشن: کارمزد از مقدار طلا کسر میشود — در فروش مبلغ ریالی دقیق واریز میشود. خاموش: کارمزد از ریال فروش کم میشود (رفتار قبلی).' WHERE `key` = 'fee_deduct_from_gold';
|
||||
UPDATE `settings` SET `description` = 'تعداد اولین سفارشهای هر کاربر که از کارمزد معاف میشوند. ۰ = غیرفعال.' WHERE `key` = 'fee_free';
|
||||
UPDATE `settings` SET `description` = 'حداقل مبلغ درخواست برداشت وجه (ریال). درخواستهای کمتر رد میشوند.' WHERE `key` = 'min_withdraw';
|
||||
UPDATE `settings` SET `description` = 'حداقل مبلغ اولین خرید هر کاربر (ریال). برای فعالسازی کیف یا احراز سطح اول استفاده میشود.' WHERE `key` = 'min_first_buy';
|
||||
@@ -1018,6 +1023,7 @@ UPDATE `settings` SET `title_fa` = 'فعالسازی API سفارش خارج
|
||||
UPDATE `settings` SET `title_fa` = 'کارمزد (قدیمی — درصد)' WHERE `key` = 'fee';
|
||||
UPDATE `settings` SET `title_fa` = 'تعداد سفارش اول بدون کارمزد' WHERE `key` = 'fee_free';
|
||||
UPDATE `settings` SET `title_fa` = 'درصد کارمزد سفارش' WHERE `key` = 'fee_service_charge_percentage';
|
||||
UPDATE `settings` SET `title_fa` = 'کارمزد از مقدار طلا' WHERE `key` = 'fee_deduct_from_gold';
|
||||
UPDATE `settings` SET `title_fa` = 'اختلاف پایه نمایش' WHERE `key` = 'fee_single';
|
||||
UPDATE `settings` SET `title_fa` = 'پاداش معرفشونده' WHERE `key` = 'gift_for_referral';
|
||||
UPDATE `settings` SET `title_fa` = 'پاداش معرف' WHERE `key` = 'gift_for_referrer';
|
||||
|
||||
@@ -419,6 +419,10 @@ INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `de
|
||||
SELECT 'fee_service_charge_percentage', '0.005', 'درصد کارمزد سفارش', 1, 1, NULL, 'number'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_service_charge_percentage' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'fee_deduct_from_gold', 'false', 'کارمزد از مقدار طلا', 1, 1, NULL, 'switch'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_deduct_from_gold' LIMIT 1);
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'min_withdraw', '', 'حداقل برداشت (ریال)', 1, 1, NULL, 'price'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'min_withdraw' LIMIT 1);
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- سوییچ «کارمزد از مقدار طلا» — برای نصبهای موجود (مثلاً abshodehrahimi)
|
||||
-- پس از deploy کد، این فایل را یکبار روی DB اجرا کنید؛ سپس در ادمین > تنظیمات > کارمزد روشن کنید.
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'fee_deduct_from_gold', 'false', 'کارمزد از مقدار طلا', 1, 1,
|
||||
'روشن: کارمزد از طلا — فروش = واریز ریالی دقیق. خاموش: کارمزد از ریال فروش (پیشفرض).',
|
||||
'switch'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'fee_deduct_from_gold' LIMIT 1);
|
||||
|
||||
UPDATE `settings`
|
||||
SET `title_fa` = 'کارمزد از مقدار طلا',
|
||||
`description` = 'روشن: کارمزد از مقدار طلا کسر میشود — در فروش مبلغ ریالی دقیق واریز میشود. خاموش: کارمزد از ریال فروش کم میشود (رفتار قبلی).',
|
||||
`type` = 'switch',
|
||||
`can_edit` = 1
|
||||
WHERE `key` = 'fee_deduct_from_gold';
|
||||
@@ -14,7 +14,7 @@ class Wallet extends Model
|
||||
use HasFactory;
|
||||
public function customer()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Customer','owner')->select(['id','name']);
|
||||
return $this->belongsTo('App\Models\Customer','owner')->select(['id','name','mobile']);
|
||||
}
|
||||
public function fund()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user