feat(giftcard): add customer giftcard/mine endpoint; fix admin/app password digit normalization

This commit is contained in:
2026-09-09 16:57:20 +03:30
parent 741faa38fb
commit 0fa2b4d1f5
8 changed files with 243 additions and 8 deletions
@@ -194,6 +194,204 @@ class GiftCardController extends Controller
]);
}
/**
* لیست کدهای قابل‌استفاده (اختصاصی + عمومی تک‌کد) و تاریخچهٔ مصرف‌شدهٔ مشتری.
*/
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;
}
$codes = $gift->giftcardcodes
->filter(function ($code) use ($gift) {
return (int) $code->sts !== 2
&& (int) $code->sts !== 3
&& ! $this->isCodeExpired($code, $gift);
})
->values();
// عمومی: فقط پویش تک‌کد مشترک (جلوگیری از افشای بچ تصادفی)
if ($codes->count() !== 1 && empty($gift->unique_code)) {
continue;
}
$code = $codes->first();
if (! $code) {
continue;
}
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 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()) {