Files
boursecu_server/app/Http/Controllers/Admin/GiftCardController.php
T
safavimohadeseh78andCursor 487d899ded feat(admin): gift card codes usage report API
Expose codes-usage-report endpoint with per-code usage counts and filter-aware summary for admin reporting.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-10 11:48:23 +03:30

1049 lines
39 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Customer;
use App\Models\GiftCard;
use App\Models\GiftCardCodes;
use App\DB\Te\TableEngine;
use App\Models\Withdraw;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
use Rooyesh\Wallet\interfaces\TransactionInterface;
use Rooyesh\Wallet\interfaces\WalletInterface;
use App\Services\ConfigService;
use App\Services\PargarSyncService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class GiftCardController extends Controller
{
public function redeem(Request $request)
{
if (app(ConfigService::class)->isStore()) {
return $this->storeRedeem($request);
}
$user = auth()->user();
$count = (int) $request->count;
$amount = $request->amount;
if(!$user){
throw new UnprocessableEntityHttpException('auth failed!');
}
if((!is_numeric($request->count) || $count < 1) || (!is_numeric($amount) || $amount <= 0)){
return response()->json(['message' => 'تعداد کد را چک کنید!'], 400);
}
$expiryError = $this->validateGiftExpiryDate($request->expiry_date);
if ($expiryError !== null) {
return response()->json(['message' => $expiryError], 400);
}
$customer = $this->resolveGiftCardCustomerForCreate($request);
$gift = $this->createGiftCardWithUniqueCodes($request, $customer);
$createdCodes = $gift->giftcardcodes->pluck('code')->all();
return response()->json([
'customer' => $customer ?? null,
'order' => $gift,
'orders' => [$gift],
'codes' => $createdCodes,
'total' => $count * $amount,
]);
}
public function useGiftCard(Request $request){
$user = auth()->user();
if(!$user){
throw new UnprocessableEntityHttpException('auth failed!');
}
self::assertCustomerIdentityVerified($user instanceof Customer ? $user : null);
if (!app(ConfigService::class)->getBool('has_page_gift_card', false)) {
throw new UnprocessableEntityHttpException('قابلیت کارت هدیه غیرفعال است.');
}
$redeem = $request->code;
$giftcode = GiftCardCodes::where('code', $redeem)->first();
if(!$giftcode){
return response()->json(['message' => 'کد هدیه یافت نشد!'], 404);
}
$gift = GiftCard::find($giftcode->giftcard_id);
if(!$gift){
return response()->json(['message' => 'کد هدیه یافت نشد!'], 404);
}
if($gift->expiry_date && $gift->expiry_date <= now()){
$gift->sts = 3;
$giftcode->sts = 3;
$giftcode->save();
$gift->save();
return response()->json(['message' => 'کد هدیه منقضی شده است!'], 400);
}
if((int) $giftcode->sts !== 1){
return response()->json(['message' => 'کد هدیه قبلا استفاده شده است!'], 400);
}
if((int) $gift->count > 0 && (int) $gift->used_count >= (int) $gift->count){
return response()->json(['message' => 'کد هدیه قبلا استفاده شده است!'], 400);
}
$unit = (int) $giftcode->unit;
$message = null;
if($unit === 1){
//add IRR to user wallet
$Withdraw = Withdraw::create([
'customer_id' => auth('customer')->id(),
"cash" => $giftcode->amount,
"note" => "کارت هدیه",
'sts' => 1,
'order_id' => $giftcode->id,
]);
$Withdraw->tracked_number = $giftcode->code; // توکن تراکنش برابر کد گیفت کارت
if ($Withdraw->cash == $giftcode->amount) {
if ($Withdraw->order_id){
$this->verifyOrder($Withdraw->id);
}
$Withdraw->zify_code = $Withdraw->tracked_number;
$Withdraw->tracked_number = $giftcode->id;//gift card id
$Withdraw->sts = 6;
$Withdraw->save();
$wallet_interface_fund = new WalletInterface($user->id, $user->type);
$transaction = new TransactionInterface($wallet_interface_fund, $_inp = [
"cash" => $giftcode->amount,
'expire_at' => now(),
'withdraw_id' => $Withdraw->id,
'active' => true
], 3, false, true);
try {
app(PargarSyncService::class)->queueWalletDocument(
$user,
'cash',
(float) $giftcode->amount,
[
'withdraw_id' => $Withdraw->id,
'note' => 'کارت هدیه',
'source' => 'gift_card',
]
);
} catch (\Throwable $e) {
report($e);
}
}
$amount = number_format((float) $giftcode->amount);
$message = "$user->name عزیز
کیف پول شما به مقدار $amount ریال
به واسطه کارت هدیه از طرف $gift->name شارژ شد.
قلک ریال";
}elseif ($unit === 2){
// شارژ کیف فلزی (طلا / مس — فیلد gold کیف اصلی)
$goldAmount = (float) $giftcode->amount;
if ($goldAmount <= 0) {
return response()->json(['message' => 'مقدار کارت هدیه نامعتبر است!'], 400);
}
$configService = app(ConfigService::class);
$representative = $configService->mainWalletRepresentativeProduct();
$Withdraw = Withdraw::create([
'customer_id' => $user->id,
'gold' => $goldAmount,
'note' => 'کارت هدیه',
'sts' => 6,
'tracked_number' => $giftcode->id,
]);
$wallet_interface = new WalletInterface($user->id, $user->type);
new TransactionInterface($wallet_interface, [
'cash' => 0,
'gold' => $goldAmount,
'expire_at' => now(),
'note' => 'کارت هدیه',
'withdraw_id' => $Withdraw->id,
'product_id' => $representative?->id,
'active' => true,
], 7, true, false);
$syncPayload = [
'customer_id' => $user->id,
'value' => $goldAmount,
'direction' => 1,
'withdraw_id' => $Withdraw->id,
'note' => 'کارت هدیه',
];
if ($representative) {
$syncPayload['product_id'] = $representative->id;
$syncPayload['product'] = [
'id' => $representative->id,
'title' => $representative->title,
'group_type' => $representative->group_type,
'metal_id' => $representative->metal_id,
'amount' => $representative->amount,
'unit' => $representative->unit,
'trade_by_quantity' => $representative->trade_by_quantity,
];
}
if ($configService->isTahesabAccounting()) {
try {
app(TahesabController::class)->walletGold($syncPayload);
} catch (\Throwable $e) {
report($e);
}
}
if ($configService->isZarinAccounting()) {
try {
app(ZarinController::class)->walletGold($syncPayload);
} catch (\Throwable $e) {
report($e);
}
}
try {
app(PargarSyncService::class)->queueWalletDocument(
$user,
'gold',
$goldAmount,
[
'withdraw_id' => $Withdraw->id,
'note' => 'کارت هدیه',
'product_id' => $representative?->id,
'source' => 'gift_card',
]
);
} catch (\Throwable $e) {
report($e);
}
$weightUnit = self::tradeWeightUnit();
$metalLabel = self::mainWalletMetalLabel();
$formattedGold = self::formatSmsGoldWeight($goldAmount);
$message = "$user->name عزیز
کیف $metalLabel شما به مقدار $formattedGold $weightUnit
به واسطه کارت هدیه از طرف $gift->name شارژ شد.
قلک مس";
try {
app(\App\Services\CustomerAppNotifyService::class)->notifyCustomer(
(int) $user->id,
'واریز کارت هدیه',
"کیف $metalLabel شما به اندازه $formattedGold $weightUnit شارژ شد.",
'success'
);
} catch (\Throwable $e) {
report($e);
}
} else {
return response()->json(['message' => 'نوع کارت هدیه پشتیبانی نمی‌شود.'], 400);
}
if((int) $gift->used_count + 1 >= (int) $gift->count){
$gift->sts = 2;
}
$gift->used_count = (int) $gift->used_count + 1;
$gift->save();
$giftcode->assigned_to = $user->id;
$giftcode->sts = 2;
$giftcode->used_at = now();
$giftcode->save();
if ($message) {
$this->Send($user->mobile, $message);
}
return response()->json([
'message' => 'کد هدیه با موفقیت استفاده شد!',
'code' => $giftcode->code,
'amount' => $giftcode->amount,
'unit' => $giftcode->unit,
]);
}
/**
* لیست کدهای قابل‌استفاده (اختصاصی + عمومی تک‌کد) و تاریخچهٔ مصرف‌شدهٔ مشتری.
*/
public function mine(Request $request)
{
$user = auth()->user();
if (! $user) {
throw new UnprocessableEntityHttpException('auth failed!');
}
if (! app(ConfigService::class)->getBool('has_page_gift_card', false)) {
throw new UnprocessableEntityHttpException('قابلیت کارت هدیه غیرفعال است.');
}
$customerId = (int) $user->id;
$mobile = $user->mobile ?? null;
$available = [];
$seenCodes = [];
$personalGifts = GiftCard::query()
->where(function ($q) use ($customerId, $mobile) {
$q->where('customer_id', $customerId);
if ($mobile) {
$q->orWhere('mobile', $mobile);
}
})
->where(function ($q) {
$q->whereNull('sts')->orWhere('sts', '!=', 3);
})
->where(function ($q) {
$q->whereNull('expiry_date')->orWhere('expiry_date', '>', now());
})
->with(['giftcardcodes' => function ($q) {
$q->where('sts', 1);
}])
->orderByDesc('id')
->get();
foreach ($personalGifts as $gift) {
foreach ($gift->giftcardcodes as $code) {
if ($this->isCodeExpired($code, $gift)) {
continue;
}
if (isset($seenCodes[$code->code])) {
continue;
}
$seenCodes[$code->code] = true;
$available[] = $this->mapCustomerGiftItem($gift, $code, 'personal');
}
}
$publicGifts = GiftCard::query()
->where(function ($q) {
$q->whereNull('customer_id')->orWhere('customer_id', 0);
})
->where(function ($q) {
$q->whereNull('mobile')->orWhere('mobile', '');
})
->where('use_once', 0)
->where(function ($q) {
$q->whereNull('sts')->orWhere('sts', '!=', 3);
})
->where(function ($q) {
$q->whereNull('expiry_date')->orWhere('expiry_date', '>', now());
})
->with('giftcardcodes')
->orderByDesc('id')
->get();
foreach ($publicGifts as $gift) {
if ((int) $gift->count > 0 && (int) $gift->used_count >= (int) $gift->count) {
continue;
}
if (! $this->isPublicGiftVisibleInApp($gift)) {
continue;
}
$codes = $gift->giftcardcodes
->filter(function ($code) use ($gift) {
return (int) $code->sts !== 2
&& (int) $code->sts !== 3
&& ! $this->isCodeExpired($code, $gift);
})
->values();
foreach ($codes as $code) {
if ($this->customerAlreadyUsedCode($code, $customerId, $gift)) {
continue;
}
if (isset($seenCodes[$code->code])) {
continue;
}
$seenCodes[$code->code] = true;
$available[] = $this->mapCustomerGiftItem($gift, $code, 'public');
}
}
$history = [];
$historyCodes = GiftCardCodes::query()
->whereNotNull('assigned_to')
->where('assigned_to', '!=', '')
->where(function ($q) use ($customerId) {
$id = (string) $customerId;
$q->where('assigned_to', $customerId)
->orWhere('assigned_to', $id)
->orWhere('assigned_to', '['.$id.']')
->orWhere('assigned_to', 'like', '%"'.$id.'"%')
->orWhere('assigned_to', 'like', '%['.$id.',%')
->orWhere('assigned_to', 'like', '%,'.$id.']%')
->orWhere('assigned_to', 'like', '%,'.$id.',%');
})
->with('giftcard')
->orderByDesc('used_at')
->orderByDesc('updated_at')
->limit(100)
->get();
foreach ($historyCodes as $code) {
$gift = $code->giftcard;
if (! $gift || ! $this->customerAlreadyUsedCode($code, $customerId, $gift)) {
continue;
}
$history[] = $this->mapCustomerGiftItem($gift, $code, $this->resolveCodeScope($gift, $customerId, $mobile));
}
return response()->json([
'available' => $available,
'history' => $history,
]);
}
private function resolveGiftCardCustomer(Request $request): ?Customer
{
if ($request->filled('customer_id')) {
return Customer::select('id', 'name', 'mobile')->find((int) $request->customer_id);
}
if ($request->filled('mobile')) {
return Customer::select('id', 'name', 'mobile')->where('mobile', $request->mobile)->first();
}
return null;
}
/**
* کارت عمومی: show_in_app=1 → در لیست اپ همه؛ ۰ → فقط با وارد کردن دستی کد.
* سازگاری با رکوردهای قبل از ستون show_in_app: تک‌کد مشترک مثل قبل در اپ می‌آید.
*/
private function isPublicGiftVisibleInApp(GiftCard $gift): bool
{
if (! isset($gift->show_in_app)) {
$codesCount = $gift->relationLoaded('giftcardcodes')
? $gift->giftcardcodes->count()
: $gift->giftcardcodes()->count();
return $codesCount === 1 || ! empty($gift->unique_code);
}
return (int) $gift->show_in_app === 1;
}
private function resolveGiftCardCustomerForCreate(Request $request): ?Customer
{
$customer = $this->resolveGiftCardCustomer($request);
if ($request->filled('customer_id') && ! $customer) {
throw new UnprocessableEntityHttpException('مشتری انتخاب‌شده یافت نشد!');
}
return $customer;
}
/**
* یک رکورد giftcards + N کد یکتا در giftcard_codes (نه N رکورد جدا).
*/
private function createGiftCardWithUniqueCodes(Request $request, ?Customer $customer = null): GiftCard
{
if ($customer === null) {
$customer = $this->resolveGiftCardCustomer($request);
}
$count = (int) $request->count;
$amount = $request->amount;
$unit = (int) $request->unit;
$unitAmount = $unit === 3 ? $amount / 100 : $amount;
$giftData = [
'customer_id' => $customer?->id,
'use_once' => $request->use_once ? 1 : 0,
'count' => $count,
'amount' => $unitAmount,
'mobile' => $customer?->mobile ?? $request->mobile ?? null,
'name' => ($customer ? $customer->name : $request->name) ?? null,
'total' => $unit === 3 ? null : $count * $amount,
'sts' => 1,
'unit' => $request->unit,
'expiry_date' => $request->expiry_date,
'created_at' => now(),
'updated_at' => now(),
'description' => $request->description,
];
if (Schema::hasColumn('giftcards', 'show_in_app')) {
$giftData['show_in_app'] = $customer ? 0 : ($request->boolean('show_in_app') ? 1 : 0);
}
$gift = GiftCard::create($giftData);
$codeRows = [];
for ($i = 0; $i < $count; $i++) {
do {
$uniqueCode = strtoupper(bin2hex(random_bytes(4)));
} while (GiftCardCodes::where('code', $uniqueCode)->exists());
$codeRows[] = [
'giftcard_id' => $gift->id,
'use_once' => $request->use_once ? 1 : 0,
'code' => $uniqueCode,
'sts' => 1,
'unit' => $request->unit,
'amount' => $unitAmount,
'expiry_date' => $request->expiry_date,
'created_at' => now(),
];
}
GiftCardCodes::insert($codeRows);
if ($count === 1) {
$gift->unique_code = $codeRows[0]['code'];
$gift->save();
}
return $gift->load(['giftcardcodes', 'customer']);
}
/**
* تاریخ انقضا نباید قبل از امروز باشد (امروز مجاز است).
*/
private function validateGiftExpiryDate(mixed $expiryDate): ?string
{
if ($expiryDate === null || $expiryDate === '') {
return 'تاریخ انقضا الزامی است.';
}
try {
$expiryDay = \Carbon\Carbon::parse($expiryDate)->startOfDay();
} catch (\Throwable $e) {
return 'تاریخ انقضا نامعتبر است.';
}
if ($expiryDay->lt(now()->startOfDay())) {
return 'تاریخ انقضا نمی‌تواند قبل از امروز باشد.';
}
return null;
}
private function isCodeExpired(GiftCardCodes $code, GiftCard $gift): bool
{
if ($code->expiry_date && $code->expiry_date <= now()) {
return true;
}
if ($gift->expiry_date && $gift->expiry_date <= now()) {
return true;
}
return false;
}
private function customerAlreadyUsedCode(GiftCardCodes $code, int $customerId, GiftCard $gift): bool
{
$assigned = $code->assigned_to;
if ($assigned === null || $assigned === '') {
return false;
}
if ((int) $gift->unit === 3 || (is_string($assigned) && str_starts_with(trim($assigned), '['))) {
$ids = json_decode($assigned, true);
if (! is_array($ids)) {
return false;
}
return in_array($customerId, $ids, false) || in_array((string) $customerId, $ids, false);
}
return (string) $assigned === (string) $customerId;
}
private function resolveCodeScope(GiftCard $gift, int $customerId, ?string $mobile): string
{
if ((int) $gift->customer_id === $customerId) {
return 'personal';
}
if ($mobile && $gift->mobile && (string) $gift->mobile === (string) $mobile) {
return 'personal';
}
return 'public';
}
private function mapCustomerGiftItem(GiftCard $gift, GiftCardCodes $code, string $scope): array
{
$unit = (int) ($code->unit ?? $gift->unit);
return [
'id' => $code->id,
'giftcard_id' => $gift->id,
'code' => $code->code,
'name' => $gift->name,
'description' => $gift->description,
'amount' => $code->amount ?? $gift->amount,
'unit' => $unit,
'kind' => $unit === 3 ? 'discount' : 'gift',
'scope' => $scope,
'sts' => (int) $code->sts,
'expiry_date' => $code->expiry_date ?? $gift->expiry_date,
'used_at' => $code->used_at,
];
}
public function useDiscount(Request $request)
{
if (! app(ConfigService::class)->isStore()) {
return response()->json(['message' => 'این قابلیت فقط برای فروشگاه فعال است'], 403);
}
$result = $this->applyDiscount((string) $request->input('code'), auth('customer')->id());
if (! empty($result['message'])) {
return response()->json(['message' => $result['message']], 400);
}
return response()->json(['amount' => $result['amount']]);
}
public function validateDiscount(Request $request)
{
if (! app(ConfigService::class)->isStore()) {
return response()->json(['message' => 'این قابلیت فقط برای فروشگاه فعال است'], 403);
}
$giftcode = $this->verifyDiscount((string) $request->input('code'));
if (! empty($giftcode['message'])) {
return response()->json(['message' => $giftcode['message']], 400);
}
return response()->json(['giftcard' => $giftcode], 200);
}
private function storeRedeem(Request $request)
{
$user = auth()->user();
$count = (int) $request->count;
$amount = $request->amount;
if (! $user) {
throw new UnprocessableEntityHttpException('auth failed!');
}
if ((! is_numeric($request->count) || $count < 1) || (! is_numeric($amount) || $amount <= 0)) {
return response()->json(['message' => 'تعداد کد را چک کنید!'], 400);
}
$expiryError = $this->validateGiftExpiryDate($request->expiry_date);
if ($expiryError !== null) {
return response()->json(['message' => $expiryError], 400);
}
if (isset($request->code) && GiftCardCodes::where('code', $request->code)->where('unit', $request->unit)->exists()) {
return response()->json(['message' => 'کد تکراری میباشد!'], 400);
}
$unitAmount = $request->unit == 3 ? $amount / 100 : $amount;
$unit = (int) $request->unit;
// کارت هدیه (ریالی/فلزی): همیشه N کد یکتا؛ تخفیف با use_once=0 می‌تواند تک‌کد مشترک باشد
$createUniqueCodes = $request->use_once || $unit === 1 || $unit === 2;
if ($createUniqueCodes) {
try {
$customer = $this->resolveGiftCardCustomerForCreate($request);
} catch (UnprocessableEntityHttpException $e) {
return response()->json(['message' => $e->getMessage()], 400);
}
$gift = $this->createGiftCardWithUniqueCodes($request, $customer);
} else {
do {
$unique_code = strtoupper(bin2hex(random_bytes(4)));
} while (GiftCardCodes::where('code', $unique_code)->exists());
$gift = GiftCard::create([
'use_once' => $request->use_once ? 1 : 0,
'count' => $count,
'unique_code' => ! empty($request->code) ? $request->code : $unique_code,
'amount' => $unitAmount,
'name' => $request->name,
'total' => $request->unit == 3 ? null : $count * $amount,
'sts' => 1,
'unit' => $request->unit,
'expiry_date' => $request->expiry_date,
'created_at' => now(),
'updated_at' => now(),
'description' => $request->description,
]);
GiftCardCodes::insert([
'giftcard_id' => $gift->id,
'use_once' => $request->use_once ? 1 : 0,
'code' => ! empty($request->code) ? $request->code : $unique_code,
'sts' => 1,
'unit' => $request->unit,
'amount' => $unitAmount,
'expiry_date' => $request->expiry_date,
'created_at' => now(),
]);
}
return response()->json([
'order' => $gift,
'codes' => $gift->giftcardcodes,
'total' => $count * $amount,
]);
}
public function applyDiscount(string $code, $customerId): array
{
$giftcode = $this->verifyDiscount($code);
if (! empty($giftcode['message'])) {
return ['message' => $giftcode['message']];
}
$giftcode = GiftCardCodes::where('code', $code)->first();
$gift = GiftCard::find($giftcode->giftcard_id);
$idsArray = json_decode($giftcode->assigned_to, true);
if (! is_array($idsArray)) {
$idsArray = [];
}
$idsArray[] = $customerId;
if ($gift->use_once == 1) {
$giftcode->sts = 2;
}
$giftcode->assigned_to = json_encode($idsArray);
$giftcode->save();
$gift->used_count += 1;
if ($gift->used_count + 1 > $gift->count) {
$gift->sts = 2;
}
$gift->save();
return ['amount' => $giftcode->amount];
}
private function verifyDiscount(string $code)
{
$giftcode = GiftCardCodes::where('code', $code)->first();
if (! $giftcode) {
return ['message' => 'کد تخفیف یافت نشد!'];
}
$gift = GiftCard::find($giftcode->giftcard_id);
$customer_id = auth('customer')->id();
if ($gift->unit !== 3 || $giftcode->unit !== 3) {
return ['message' => 'کد تخفیف صحیح نمیباشد!'];
}
if ($gift->use_once === 0) {
$stored_ids = json_decode($giftcode->assigned_to, true);
if (is_array($stored_ids) && in_array($customer_id, $stored_ids)) {
return ['message' => 'شما قبلا از تخفیف خود استفاده کرده اید!'];
}
} elseif ($gift->use_once === 1) {
if (GiftCardCodes::where('giftcard_id', $gift->id)->where('assigned_to', '['.auth('customer')->id().']')->exists()) {
return ['message' => 'شما قبلا از تخفیف خود استفاده کرده اید!'];
}
}
if ($gift->expiry_date <= now() || $gift->sts === 3) {
$gift->sts = 3;
$giftcode->sts = 3;
$giftcode->save();
$gift->save();
return ['message' => 'کد تخفیف منقضی شده است!'];
}
if ($giftcode->sts === 2) {
return ['message' => 'کد تخفیف قبلا استفاده شده است!'];
}
if ($gift->used_count + 1 > $gift->count) {
return ['message' => 'تعداد استفاده کد تخفیف به پایان رسیده است!'];
}
return $giftcode;
}
public function update(Request $request, $id)
{
$user = auth()->user();
if (! $user) {
throw new UnprocessableEntityHttpException('auth failed!');
}
$gift = GiftCard::with('giftcardcodes')->findOrFail($id);
$count = (int) $request->count;
$amount = $request->amount;
if ((! is_numeric($request->count) || $count < 1) || (! is_numeric($amount) || $amount <= 0)) {
return response()->json(['message' => 'تعداد یا مقدار را چک کنید!'], 400);
}
$expiryError = $this->validateGiftExpiryDate($request->expiry_date);
if ($expiryError !== null) {
return response()->json(['message' => $expiryError], 400);
}
$unit = (int) $request->unit;
if (! in_array($unit, [1, 2, 3], true)) {
return response()->json(['message' => 'نوع کارت هدیه نامعتبر است.'], 400);
}
try {
$customer = $request->filled('customer_id')
? $this->resolveGiftCardCustomerForCreate($request)
: null;
} catch (UnprocessableEntityHttpException $e) {
return response()->json(['message' => $e->getMessage()], 400);
}
$requestedCustomer = $customer?->id ? (int) $customer->id : null;
$currentCustomer = $gift->customer_id ? (int) $gift->customer_id : null;
$structuralChange = $count !== (int) $gift->count
|| abs((float) $amount - (float) $gift->amount) > 0.0001
|| $unit !== (int) $gift->unit
|| $requestedCustomer !== $currentCustomer;
if ($this->giftCardIsUsed($gift) && $structuralChange) {
return response()->json([
'message' => 'این کارت استفاده شده است؛ فقط توضیحات، تاریخ انقضا و نمایش در اپ قابل ویرایش است.',
], 400);
}
$unitAmount = $unit === 3 ? $amount / 100 : $amount;
DB::transaction(function () use ($gift, $request, $customer, $count, $amount, $unit, $unitAmount, $structuralChange) {
$gift->description = $request->description;
$gift->expiry_date = $request->expiry_date;
if (Schema::hasColumn('giftcards', 'show_in_app')) {
$gift->show_in_app = $customer ? 0 : ($request->boolean('show_in_app') ? 1 : 0);
}
if (! $structuralChange) {
$gift->save();
GiftCardCodes::where('giftcard_id', $gift->id)
->where('sts', '!=', 2)
->update([
'expiry_date' => $request->expiry_date,
'updated_at' => now(),
]);
return;
}
$gift->customer_id = $customer?->id;
$gift->mobile = $customer?->mobile;
$gift->name = $customer?->name;
$gift->count = $count;
$gift->amount = $unitAmount;
$gift->unit = $unit;
$gift->total = $unit === 3 ? null : $count * $amount;
$gift->save();
$this->syncUnusedGiftCodes($gift, $count, $unit, $unitAmount, $request->expiry_date);
});
return response()->json($gift->fresh(['giftcardcodes', 'customer']));
}
public function destroy($id)
{
$user = auth()->user();
if (! $user) {
throw new UnprocessableEntityHttpException('auth failed!');
}
$gift = GiftCard::with('giftcardcodes')->findOrFail($id);
if ($this->giftCardIsUsed($gift)) {
return response()->json(['message' => 'این کارت هدیه استفاده شده و قابل حذف نیست.'], 400);
}
DB::transaction(function () use ($gift) {
GiftCardCodes::where('giftcard_id', $gift->id)->delete();
$gift->delete();
});
return response()->json(true);
}
private function giftCardIsUsed(GiftCard $gift): bool
{
if ((int) $gift->used_count > 0) {
return true;
}
return $gift->giftcardcodes->contains(function ($code) {
return (int) $code->sts === 2 || ! empty($code->used_at);
});
}
private function syncUnusedGiftCodes(GiftCard $gift, int $count, int $unit, $unitAmount, $expiryDate): void
{
$existing = GiftCardCodes::where('giftcard_id', $gift->id)->orderBy('id')->get();
$current = $existing->count();
if ($count < $current) {
$removeIds = $existing->slice($count)->pluck('id');
GiftCardCodes::whereIn('id', $removeIds)->delete();
} elseif ($count > $current) {
$rows = [];
for ($i = 0; $i < $count - $current; $i++) {
do {
$uniqueCode = strtoupper(bin2hex(random_bytes(4)));
} while (GiftCardCodes::where('code', $uniqueCode)->exists());
$rows[] = [
'giftcard_id' => $gift->id,
'use_once' => $gift->use_once ? 1 : 0,
'code' => $uniqueCode,
'sts' => 1,
'unit' => $unit,
'amount' => $unitAmount,
'expiry_date' => $expiryDate,
'created_at' => now(),
'updated_at' => now(),
];
}
if ($rows !== []) {
GiftCardCodes::insert($rows);
}
}
GiftCardCodes::where('giftcard_id', $gift->id)->update([
'unit' => $unit,
'amount' => $unitAmount,
'expiry_date' => $expiryDate,
'updated_at' => now(),
]);
$firstCode = GiftCardCodes::where('giftcard_id', $gift->id)->orderBy('id')->value('code');
$gift->unique_code = $count === 1 ? $firstCode : null;
$gift->save();
}
public function table(Request $request)
{
$user = auth()->user();
if(!$user){
throw new UnprocessableEntityHttpException('auth failed!');
}
$records = GiftCard::with([
'customer',
'giftcardcodes',
'giftcardcodes.assignedTo',
]);
TableEngine::create($records, $request->all())
->initSearch([])
->select([
'*', GiftCard::$SELECT_STS_STR
])->export($records);
return response()->json($records);
}
/** گزارش استفاده از هر کد هدیه/تخفیف — ردیف به‌ردیف giftcard_codes */
public function codesUsageReport(Request $request)
{
$user = auth()->user();
if (! $user) {
throw new UnprocessableEntityHttpException('auth failed!');
}
$records = GiftCardCodes::query()
->leftJoin('giftcards', 'giftcards.id', '=', 'giftcard_codes.giftcard_id')
->with(['giftcard.customer', 'assignedTo'])
->select('giftcard_codes.*');
$engine = TableEngine::create($records, $request->all());
$engine->initSearch([
'giftcard_codes.code',
'giftcards.name',
'giftcards.mobile',
]);
$summary = [
'total_codes' => (clone $records)->count(),
'used_codes' => (clone $records)->where(function ($q) {
$q->where('giftcard_codes.sts', 2)
->orWhereNotNull('giftcard_codes.used_at')
->orWhere(function ($q2) {
$q2->whereNotNull('giftcard_codes.assigned_to')
->where('giftcard_codes.assigned_to', '!=', '');
});
})->count(),
'active_codes' => (clone $records)->where('giftcard_codes.sts', 1)->count(),
'expired_codes' => (clone $records)->where('giftcard_codes.sts', 3)->count(),
];
$summary['unused_codes'] = max(0, $summary['total_codes'] - $summary['used_codes']);
$result = [];
$engine->select(['giftcard_codes.*'])->export($result);
if (! empty($result['rows'])) {
$mapped = [];
foreach ($result['rows'] as $row) {
if (! $row instanceof GiftCardCodes) {
continue;
}
$row->loadMissing(['giftcard', 'assignedTo']);
$gift = $row->giftcard;
$usageCount = $this->resolveGiftCodeUsageCount($row, $gift);
$mapped[] = [
'id' => $row->id,
'giftcard_id' => $row->giftcard_id,
'code' => $row->code,
'sts' => (int) $row->sts,
'sts_str' => GiftCardCodes::$STS[(int) $row->sts] ?? '-',
'unit' => (int) ($row->unit ?? $gift?->unit ?? 0),
'amount' => $row->amount ?? $gift?->amount,
'expiry_date' => $row->expiry_date ?? $gift?->expiry_date,
'used_at' => $row->used_at,
'usage_count' => $usageCount,
'campaign_name' => $gift?->name,
'campaign_count' => (int) ($gift?->count ?? 0),
'campaign_used_count' => (int) ($gift?->used_count ?? 0),
'assigned_to' => $row->assignedTo,
'giftcard' => $gift ? [
'id' => $gift->id,
'name' => $gift->name,
'customer' => $gift->customer,
] : null,
];
}
$result['rows'] = $mapped;
}
$result['summary'] = $summary;
return response()->json($result);
}
private function resolveGiftCodeUsageCount(GiftCardCodes $code, ?GiftCard $gift): int
{
if ($gift && (int) $gift->use_once === 0 && (int) $gift->count > 0) {
return (int) $gift->used_count;
}
if (is_string($code->assigned_to) && str_starts_with(trim($code->assigned_to), '[')) {
$ids = json_decode($code->assigned_to, true);
if (is_array($ids)) {
return count($ids);
}
}
if ((int) $code->sts === 2 || ! empty($code->used_at)) {
return 1;
}
if ($code->assigned_to !== null && $code->assigned_to !== '') {
return 1;
}
return 0;
}
}