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>
This commit is contained in:
2026-11-10 11:48:23 +03:30
co-authored by Cursor
parent 885e51a79b
commit 487d899ded
4 changed files with 105 additions and 1 deletions
@@ -29,6 +29,7 @@
- 2026-09-09 | رمز ادمین ≠ لاگین اپ | `Customer::setPasswordAttribute` + `AuthController` + `updateAdmin` | قبل از هش/لاگین ارقام فارسی→انگلیسی؛ jwt hash=false؛ آنلاین فقط password غیرخالی بفرست - 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 | خرید اقساطی دیجی‌پی | `DigipayInstallmentService` + جداول `digipay_credits`/`digipay_orders` + migration `2026_10_20_000002` | فلگ پیش‌فرض خاموش؛ اعتبار را ادمین ست می‌کند نه وب‌هوک دیجی‌پی
- 2026-10-20 | دسترسی صفحات مدیر | `users.page_access` + `AdminPageAccess` + ادمین `adminPageAccess.ts` / ویرایش مدیران | null=کامل؛ درخت مشاهده/ویرایش/حذف؛ ورود=اولین صفحه مجاز؛ منوی ممنوع=«شما به این صفحه دسترسی ندارید» - 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 | ویرایش/حذف کارت هدیه | `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 | دکمه ویرایش/حذف گیفت | `allowsPageFlag` + `giftCards/components/table.vue` | بدون edit/delete روی giftCardMain دکمه و دیالوگ باز نمی‌شود
- 2026-10-20 | دسترسی عملیات جدول | `PAGE_TABLE_ACTIONS` + `usePageActionAccess` + `AdminPageAccess::ACTIONS` | هر صفحه فقط عملیات ستون خودش؛ مشاهده جدا؛ رکورد قدیمی بدون actions ویرایش/حذف را از همان فیلد و بقیه را از مشاهده می‌گیرد - 2026-10-20 | دسترسی عملیات جدول | `PAGE_TABLE_ACTIONS` + `usePageActionAccess` + `AdminPageAccess::ACTIONS` | هر صفحه فقط عملیات ستون خودش؛ مشاهده جدا؛ رکورد قدیمی بدون actions ویرایش/حذف را از همان فیلد و بقیه را از مشاهده می‌گیرد
@@ -943,4 +943,106 @@ class GiftCardController extends Controller
return response()->json($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;
}
} }
+1 -1
View File
@@ -79,7 +79,7 @@ class AdminPageAccess
'accountingMain' => ['doc', 'register', 'send'], 'accountingMain' => ['doc', 'register', 'send'],
'appNotifications' => ['recipients', 'readers', 'edit', 'toggle', 'delete'], 'appNotifications' => ['recipients', 'readers', 'edit', 'toggle', 'delete'],
'automations' => ['preview', 'log', 'edit', 'toggle', 'delete'], 'automations' => ['preview', 'log', 'edit', 'toggle', 'delete'],
'giftCardMain' => ['info', 'codes', 'edit', 'delete'], 'giftCardMain' => ['info', 'codes', 'usageReport', 'edit', 'delete'],
'discounts' => ['edit'], 'discounts' => ['edit'],
'usersMain' => ['info', 'edit', 'delete'], 'usersMain' => ['info', 'edit', 'delete'],
]; ];
+1
View File
@@ -345,6 +345,7 @@ Route::middleware(['auth:user', 'admin.page'])->group(function (){
//Giftcard //Giftcard
Route::post('giftcard/generate/', [GiftCardController::class, 'redeem']); Route::post('giftcard/generate/', [GiftCardController::class, 'redeem']);
Route::post('giftcard/table/', [GiftCardController::class, 'table']); Route::post('giftcard/table/', [GiftCardController::class, 'table']);
Route::post('giftcard/codes-usage-report/', [GiftCardController::class, 'codesUsageReport']);
Route::put('giftcard/{id}', [GiftCardController::class, 'update']); Route::put('giftcard/{id}', [GiftCardController::class, 'update']);
Route::delete('giftcard/{id}', [GiftCardController::class, 'destroy']); Route::delete('giftcard/{id}', [GiftCardController::class, 'destroy']);