diff --git a/.cursor/skills/abshode-domain-lookup/learned-notes.md b/.cursor/skills/abshode-domain-lookup/learned-notes.md index 5e21d42..a271e19 100644 --- a/.cursor/skills/abshode-domain-lookup/learned-notes.md +++ b/.cursor/skills/abshode-domain-lookup/learned-notes.md @@ -29,6 +29,7 @@ - 2026-09-09 | رمز ادمین ≠ لاگین اپ | `Customer::setPasswordAttribute` + `AuthController` + `updateAdmin` | قبل از هش/لاگین ارقام فارسی→انگلیسی؛ jwt hash=false؛ آنلاین فقط password غیرخالی بفرست - 2026-10-20 | خرید اقساطی دیجی‌پی | `DigipayInstallmentService` + جداول `digipay_credits`/`digipay_orders` + migration `2026_10_20_000002` | فلگ پیش‌فرض خاموش؛ اعتبار را ادمین ست می‌کند نه وب‌هوک دیجی‌پی - 2026-10-20 | دسترسی صفحات مدیر | `users.page_access` + `AdminPageAccess` + ادمین `adminPageAccess.ts` / ویرایش مدیران | null=کامل؛ درخت مشاهده/ویرایش/حذف؛ ورود=اولین صفحه مجاز؛ منوی ممنوع=«شما به این صفحه دسترسی ندارید» +- 2026-11-10 | گزارش استفاده کدهای هدیه | `POST /admin/giftcard/codes-usage-report` + ادمین `GiftCardCodesUsageReportSheet.vue` | ردیف=giftcard_codes؛ usage_count؛ خلاصه کل/مصرف‌شده/فعال؛ اکسل از BaseTable - 2026-10-20 | ویرایش/حذف کارت هدیه | `GiftCardController::update/destroy` + `giftCards/components/table.vue` | PUT/DELETE `/admin/giftcard/{id}`؛ کارت استفاده‌شده حذف نمی‌شود و مقدار/تعدادش عوض نمی‌شود - 2026-10-20 | دکمه ویرایش/حذف گیفت | `allowsPageFlag` + `giftCards/components/table.vue` | بدون edit/delete روی giftCardMain دکمه و دیالوگ باز نمی‌شود - 2026-10-20 | دسترسی عملیات جدول | `PAGE_TABLE_ACTIONS` + `usePageActionAccess` + `AdminPageAccess::ACTIONS` | هر صفحه فقط عملیات ستون خودش؛ مشاهده جدا؛ رکورد قدیمی بدون actions ویرایش/حذف را از همان فیلد و بقیه را از مشاهده می‌گیرد diff --git a/app/Http/Controllers/Admin/GiftCardController.php b/app/Http/Controllers/Admin/GiftCardController.php index 170e4d8..15976c5 100644 --- a/app/Http/Controllers/Admin/GiftCardController.php +++ b/app/Http/Controllers/Admin/GiftCardController.php @@ -943,4 +943,106 @@ class GiftCardController extends Controller 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; + } + } diff --git a/app/Services/AdminPageAccess.php b/app/Services/AdminPageAccess.php index 16be897..1d7ffdd 100644 --- a/app/Services/AdminPageAccess.php +++ b/app/Services/AdminPageAccess.php @@ -79,7 +79,7 @@ class AdminPageAccess 'accountingMain' => ['doc', 'register', 'send'], 'appNotifications' => ['recipients', 'readers', 'edit', 'toggle', 'delete'], 'automations' => ['preview', 'log', 'edit', 'toggle', 'delete'], - 'giftCardMain' => ['info', 'codes', 'edit', 'delete'], + 'giftCardMain' => ['info', 'codes', 'usageReport', 'edit', 'delete'], 'discounts' => ['edit'], 'usersMain' => ['info', 'edit', 'delete'], ]; diff --git a/routes/admin.php b/routes/admin.php index 02b9742..2be952d 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -345,6 +345,7 @@ Route::middleware(['auth:user', 'admin.page'])->group(function (){ //Giftcard Route::post('giftcard/generate/', [GiftCardController::class, 'redeem']); Route::post('giftcard/table/', [GiftCardController::class, 'table']); + Route::post('giftcard/codes-usage-report/', [GiftCardController::class, 'codesUsageReport']); Route::put('giftcard/{id}', [GiftCardController::class, 'update']); Route::delete('giftcard/{id}', [GiftCardController::class, 'destroy']);