2026-01-24 14:03:11 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Customer;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\SuperController;
|
|
|
|
|
use App\Models\Fund;
|
2026-08-01 17:30:14 +03:30
|
|
|
use App\Models\Product;
|
|
|
|
|
use App\Services\ConfigService;
|
|
|
|
|
use App\Services\WalletBalanceService;
|
2026-01-24 14:03:11 +00:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Rooyesh\Wallet\interfaces\TransactionInterface;
|
|
|
|
|
use Rooyesh\Wallet\interfaces\WalletInterface;
|
|
|
|
|
use Rooyesh\Wallet\models\Wallet;
|
|
|
|
|
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
|
|
|
|
|
|
2026-08-01 17:30:14 +03:30
|
|
|
/**
|
|
|
|
|
* قلک per-product — انتقال فقط TransactionInterface؛ بدون سند حسابداری.
|
|
|
|
|
*/
|
2026-01-24 14:03:11 +00:00
|
|
|
class FundController extends SuperController
|
|
|
|
|
{
|
|
|
|
|
public function __construct(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$model = Fund::class;
|
2026-08-01 17:30:14 +03:30
|
|
|
$this->with = ['wallet', 'product'];
|
2026-01-24 14:03:11 +00:00
|
|
|
$select = ['*'];
|
2026-08-01 17:30:14 +03:30
|
|
|
$search = ['title'];
|
|
|
|
|
$request->request->add(['customer_id' => auth('customer')->id()]);
|
|
|
|
|
$query = Fund::with($this->with)->where('customer_id', auth('customer')->id());
|
|
|
|
|
parent::__construct($model, $this->with, $select, null, $query, $search);
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-08-01 17:30:14 +03:30
|
|
|
|
|
|
|
|
public function store(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$this->assertProductFundsEnabled();
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
$data = $request->all();
|
2026-08-01 17:30:14 +03:30
|
|
|
$productId = (int) ($data['product_id'] ?? 0);
|
|
|
|
|
if ($productId <= 0) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('انتخاب محصول برای قلک الزامی است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product = Product::query()->find($productId);
|
|
|
|
|
if (! $product) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('محصول یافت نشد');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$title = trim((string) ($data['title'] ?? ''));
|
|
|
|
|
if ($title === '') {
|
|
|
|
|
throw new UnprocessableEntityHttpException('عنوان قلک الزامی است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$customerId = (int) auth('customer')->id();
|
|
|
|
|
$fund = Fund::create([
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'file_id' => $data['file_id'] ?? null,
|
|
|
|
|
'customer_id' => $customerId,
|
|
|
|
|
'product_id' => $productId,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// کیف قلک را از همان ابتدا با product_id بساز (بدون سند حسابداری)
|
|
|
|
|
new WalletInterface(
|
|
|
|
|
$customerId,
|
|
|
|
|
auth('customer')->user()->type,
|
|
|
|
|
$fund->id,
|
|
|
|
|
$productId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return response()->json($fund->load(['wallet', 'product']));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function update(Request $request, $id)
|
|
|
|
|
{
|
|
|
|
|
$this->assertProductFundsEnabled();
|
|
|
|
|
|
|
|
|
|
$fund = $this->ownedFund((int) $id);
|
|
|
|
|
$title = trim((string) ($request->input('title') ?? $fund->title));
|
|
|
|
|
if ($title === '') {
|
|
|
|
|
throw new UnprocessableEntityHttpException('عنوان قلک الزامی است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// product_id بعد از ساخت عوض نمیشود
|
|
|
|
|
$fund->update([
|
|
|
|
|
'title' => $title,
|
|
|
|
|
'file_id' => $request->input('file_id', $fund->file_id),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return response()->json($fund->fresh(['wallet', 'product']));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function deposit(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$this->assertProductFundsEnabled();
|
|
|
|
|
|
|
|
|
|
$data = $request->all();
|
|
|
|
|
$fund = $this->ownedFund((int) ($data['fund_id'] ?? 0));
|
|
|
|
|
$value = (float) ($data['value'] ?? 0);
|
|
|
|
|
if ($value <= 0) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('مقدار نامعتبر است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$customer = auth('customer')->user();
|
|
|
|
|
$customerId = (int) $customer->id;
|
|
|
|
|
$ownerType = (int) $customer->type;
|
|
|
|
|
$productId = (int) ($fund->product_id ?? 0);
|
|
|
|
|
|
|
|
|
|
$balanceService = app(WalletBalanceService::class);
|
|
|
|
|
$free = $productId > 0
|
|
|
|
|
? $balanceService->freeProductGold($customerId, $productId, $ownerType)
|
|
|
|
|
: $balanceService->effectiveMainWalletGold($customerId, $ownerType);
|
|
|
|
|
|
|
|
|
|
if ($free < $value) {
|
2026-01-24 14:03:11 +00:00
|
|
|
throw new UnprocessableEntityHttpException('موجودی کمتر از مقدار وارد شده است');
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 17:30:14 +03:30
|
|
|
[$sourceFundId, $sourceProductId] = $this->sourceWalletKeys($productId);
|
|
|
|
|
$source = new WalletInterface($customerId, $ownerType, $sourceFundId, $sourceProductId);
|
|
|
|
|
$dest = new WalletInterface($customerId, $ownerType, $fund->id, $productId > 0 ? $productId : null);
|
|
|
|
|
|
|
|
|
|
// فقط جابهجایی داخلی — بدون Accounting*
|
|
|
|
|
new TransactionInterface($source, [
|
|
|
|
|
'gold' => $value,
|
|
|
|
|
'fund_id' => $fund->id,
|
|
|
|
|
'product_id' => $productId > 0 ? $productId : null,
|
|
|
|
|
'expire_at' => now(),
|
|
|
|
|
'active' => true,
|
2026-01-24 14:03:11 +00:00
|
|
|
], 6, false);
|
2026-08-01 17:30:14 +03:30
|
|
|
|
|
|
|
|
new TransactionInterface($dest, [
|
|
|
|
|
'gold' => $value,
|
|
|
|
|
'fund_id' => $fund->id,
|
|
|
|
|
'product_id' => $productId > 0 ? $productId : null,
|
|
|
|
|
'expire_at' => now(),
|
|
|
|
|
'active' => true,
|
2026-01-24 14:03:11 +00:00
|
|
|
], 5, true);
|
2026-08-01 17:30:14 +03:30
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-01 17:30:14 +03:30
|
|
|
public function withdraw(Request $request)
|
|
|
|
|
{
|
|
|
|
|
$this->assertProductFundsEnabled();
|
|
|
|
|
|
|
|
|
|
$data = $request->all();
|
|
|
|
|
$fund = $this->ownedFund((int) ($data['fund_id'] ?? 0));
|
|
|
|
|
$value = (float) ($data['value'] ?? 0);
|
|
|
|
|
if ($value <= 0) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('مقدار نامعتبر است');
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
2026-08-01 17:30:14 +03:30
|
|
|
|
|
|
|
|
$customer = auth('customer')->user();
|
|
|
|
|
$customerId = (int) $customer->id;
|
|
|
|
|
$ownerType = (int) $customer->type;
|
|
|
|
|
$productId = (int) ($fund->product_id ?? 0);
|
|
|
|
|
|
|
|
|
|
$source = new WalletInterface($customerId, $ownerType, $fund->id, $productId > 0 ? $productId : null);
|
|
|
|
|
if ((float) $source->wallet->gold < $value) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('موجودی کمتر از مقدار وارد شده است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[$destFundId, $destProductId] = $this->sourceWalletKeys($productId);
|
|
|
|
|
$dest = new WalletInterface($customerId, $ownerType, $destFundId, $destProductId);
|
|
|
|
|
|
|
|
|
|
new TransactionInterface($source, [
|
|
|
|
|
'gold' => $value,
|
|
|
|
|
'fund_id' => $fund->id,
|
|
|
|
|
'product_id' => $productId > 0 ? $productId : null,
|
|
|
|
|
'expire_at' => now(),
|
|
|
|
|
'active' => true,
|
|
|
|
|
], 6, false);
|
|
|
|
|
|
|
|
|
|
new TransactionInterface($dest, [
|
|
|
|
|
'gold' => $value,
|
|
|
|
|
'fund_id' => $fund->id,
|
|
|
|
|
'product_id' => $productId > 0 ? $productId : null,
|
|
|
|
|
'expire_at' => now(),
|
|
|
|
|
'active' => true,
|
|
|
|
|
], 5, true);
|
|
|
|
|
|
2026-01-24 14:03:11 +00:00
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
2026-08-01 17:30:14 +03:30
|
|
|
|
|
|
|
|
public function show($id)
|
|
|
|
|
{
|
|
|
|
|
$fund = $this->ownedFund((int) $id);
|
|
|
|
|
|
|
|
|
|
return response()->json($fund->load(['wallet', 'product']));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function destroy($id)
|
|
|
|
|
{
|
|
|
|
|
$this->assertProductFundsEnabled();
|
|
|
|
|
|
|
|
|
|
$fund = $this->ownedFund((int) $id);
|
|
|
|
|
$wallet = Wallet::where('owner', auth('customer')->id())
|
|
|
|
|
->where('fund_id', $fund->id)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($wallet && (float) $wallet->gold > 0) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('به علت داشتن موجودی امکان حذف قلک وجود ندارد');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Fund::destroy($fund->id);
|
|
|
|
|
|
|
|
|
|
return response()->json(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function assertProductFundsEnabled(): void
|
|
|
|
|
{
|
|
|
|
|
if (! app(ConfigService::class)->isProductFundsEnabled()) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('قابلیت قلک محصول غیرفعال است');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function ownedFund(int $id): Fund
|
|
|
|
|
{
|
|
|
|
|
if ($id <= 0) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('قلک نامعتبر است');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$fund = Fund::with(['wallet', 'product'])
|
|
|
|
|
->where('customer_id', auth('customer')->id())
|
|
|
|
|
->find($id);
|
|
|
|
|
|
|
|
|
|
if (! $fund) {
|
|
|
|
|
throw new UnprocessableEntityHttpException('قلک یافت نشد');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $fund;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @return array{0: null, 1: int|null} */
|
|
|
|
|
private function sourceWalletKeys(int $productId): array
|
|
|
|
|
{
|
|
|
|
|
if ($productId <= 0) {
|
|
|
|
|
return [null, null];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (app(ConfigService::class)->isMainWalletProduct($productId)) {
|
|
|
|
|
return [null, null];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [null, $productId];
|
2026-01-24 14:03:11 +00:00
|
|
|
}
|
|
|
|
|
}
|