Skip to content

Commit a9667c5

Browse files
chore: wip
1 parent 3cf3d2c commit a9667c5

File tree

1 file changed

+266
-0
lines changed
  • storage/framework/core/commerce/src/gift-cards

1 file changed

+266
-0
lines changed
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
import type { GiftCardJsonResponse } from '../../../../orm/src/models/GiftCard'
2+
import { db } from '@stacksjs/database'
3+
4+
export interface FetchGiftCardsOptions {
5+
page?: number
6+
limit?: number
7+
search?: string
8+
status?: string
9+
is_active?: boolean
10+
is_digital?: boolean
11+
is_reloadable?: boolean
12+
sortBy?: string
13+
sortOrder?: 'asc' | 'desc'
14+
purchaser_id?: string
15+
from_date?: string
16+
to_date?: string
17+
min_balance?: number
18+
max_balance?: number
19+
}
20+
21+
export interface GiftCardResponse {
22+
data: GiftCardJsonResponse[]
23+
paging: {
24+
total_records: number
25+
page: number
26+
total_pages: number
27+
}
28+
next_cursor: number | null
29+
}
30+
31+
export interface GiftCardStats {
32+
total: number
33+
active: number
34+
by_status: Array<{ status: string, count: number }>
35+
by_balance: {
36+
low: number // Count of cards with less than 25% of initial balance
37+
medium: number // Count of cards with 25-75% of initial balance
38+
high: number // Count of cards with more than 75% of initial balance
39+
}
40+
expiring_soon: GiftCardJsonResponse[]
41+
recently_used: GiftCardJsonResponse[]
42+
}
43+
44+
/**
45+
* Fetch all gift cards from the database
46+
*/
47+
export async function fetchAll(): Promise<GiftCardJsonResponse[]> {
48+
const giftCards = await db
49+
.selectFrom('gift_cards')
50+
.selectAll()
51+
.execute()
52+
53+
return giftCards
54+
}
55+
56+
/**
57+
* Fetch gift cards with pagination, sorting, and filtering options
58+
*/
59+
export async function fetchPaginated(options: FetchGiftCardsOptions = {}): Promise<GiftCardResponse> {
60+
// Set default values
61+
const page = options.page || 1
62+
const limit = options.limit || 10
63+
64+
// Start building the query
65+
let query = db.selectFrom('gift_cards')
66+
let countQuery = db.selectFrom('gift_cards')
67+
68+
if (options.max_balance !== undefined) {
69+
query = query.where('current_balance', '<=', options.max_balance)
70+
countQuery = countQuery.where('current_balance', '<=', options.max_balance)
71+
}
72+
73+
// Get total count for pagination
74+
const countResult = await countQuery
75+
.select(eb => eb.fn.count('id').as('total'))
76+
.executeTakeFirst()
77+
78+
const total = Number(countResult?.total || 0)
79+
80+
// Apply pagination
81+
const giftCards = await query
82+
.selectAll()
83+
.limit(limit)
84+
.offset((page - 1) * limit)
85+
.execute()
86+
87+
// Calculate pagination info
88+
const totalPages = Math.ceil(total / limit)
89+
90+
return {
91+
data: giftCards,
92+
paging: {
93+
total_records: total,
94+
page,
95+
total_pages: totalPages,
96+
},
97+
next_cursor: page < totalPages ? page + 1 : null,
98+
}
99+
}
100+
101+
/**
102+
* Fetch a gift card by ID
103+
*/
104+
export async function fetchById(id: number): Promise<GiftCardJsonResponse | undefined> {
105+
return await db
106+
.selectFrom('gift_cards')
107+
.where('id', '=', id)
108+
.selectAll()
109+
.executeTakeFirst()
110+
}
111+
112+
/**
113+
* Fetch a gift card by code
114+
*/
115+
export async function fetchByCode(code: string): Promise<GiftCardJsonResponse | undefined> {
116+
return await db
117+
.selectFrom('gift_cards')
118+
.where('code', '=', code)
119+
.selectAll()
120+
.executeTakeFirst()
121+
}
122+
123+
/**
124+
* Fetch active gift cards (is_active = true and not expired)
125+
*/
126+
export async function fetchActive(options: FetchGiftCardsOptions = {}): Promise<GiftCardResponse> {
127+
const currentDate = new Date().toISOString()
128+
129+
return fetchPaginated({
130+
...options,
131+
is_active: true,
132+
status: 'ACTIVE',
133+
})
134+
}
135+
136+
/**
137+
* Get gift card statistics
138+
*/
139+
export async function fetchStats(): Promise<GiftCardStats> {
140+
// Total gift cards
141+
const totalGiftCards = await db
142+
.selectFrom('gift_cards')
143+
.select(eb => eb.fn.count('id').as('count'))
144+
.executeTakeFirst()
145+
146+
// Active gift cards
147+
const currentDate = new Date().toISOString().split('T')[0]
148+
const activeGiftCards = await db
149+
.selectFrom('gift_cards')
150+
.where('is_active', '=', true)
151+
.where('status', '=', 'ACTIVE')
152+
.where(eb => eb.or([
153+
eb('expiry_date', '>=', currentDate),
154+
eb('expiry_date', 'is', null),
155+
]))
156+
.select(eb => eb.fn.count('id').as('count'))
157+
.executeTakeFirst()
158+
159+
// Gift cards by status
160+
const giftCardsByStatus = await db
161+
.selectFrom('gift_cards')
162+
.select(['status', eb => eb.fn.count('id').as('count')])
163+
.groupBy('status')
164+
.execute()
165+
166+
// Calculate balance distribution counts - separate queries for better reliability
167+
const lowBalanceCount = await db
168+
.selectFrom('gift_cards')
169+
.where(eb => eb.raw('current_balance / initial_balance < 0.25'))
170+
.select(eb => eb.fn.count('id').as('count'))
171+
.executeTakeFirst()
172+
173+
const mediumBalanceCount = await db
174+
.selectFrom('gift_cards')
175+
.where(eb => eb.and([
176+
eb.raw('current_balance / initial_balance >= 0.25'),
177+
eb.raw('current_balance / initial_balance <= 0.75'),
178+
]))
179+
.select(eb => eb.fn.count('id').as('count'))
180+
.executeTakeFirst()
181+
182+
const highBalanceCount = await db
183+
.selectFrom('gift_cards')
184+
.where(eb => eb.raw('current_balance / initial_balance > 0.75'))
185+
.select(eb => eb.fn.count('id').as('count'))
186+
.executeTakeFirst()
187+
188+
// Expiring soon gift cards (next 30 days)
189+
const thirtyDaysFromNow = new Date()
190+
thirtyDaysFromNow.setDate(thirtyDaysFromNow.getDate() + 30)
191+
192+
const expiringGiftCards = await db
193+
.selectFrom('gift_cards')
194+
.where('is_active', '=', true)
195+
.where('status', '=', 'ACTIVE')
196+
.where('expiry_date', '<=', thirtyDaysFromNow.toISOString().split('T')[0])
197+
.where('expiry_date', '>=', currentDate)
198+
.select(['id', 'code', 'current_balance', 'expiry_date', 'recipient_name', 'recipient_email'])
199+
.limit(5)
200+
.execute()
201+
202+
// Recently used gift cards
203+
const recentlyUsedGiftCards = await db
204+
.selectFrom('gift_cards')
205+
.where('last_used_date', 'is not', null)
206+
.select(['id', 'code', 'current_balance', 'initial_balance', 'last_used_date', 'recipient_name'])
207+
.limit(5)
208+
.execute()
209+
210+
return {
211+
total: Number(totalGiftCards?.count || 0),
212+
active: Number(activeGiftCards?.count || 0),
213+
by_status: giftCardsByStatus.map(item => ({
214+
status: item.status,
215+
count: Number(item.count),
216+
})),
217+
by_balance: {
218+
low: Number(lowBalanceCount?.count || 0),
219+
medium: Number(mediumBalanceCount?.count || 0),
220+
high: Number(highBalanceCount?.count || 0),
221+
},
222+
expiring_soon: expiringGiftCards,
223+
recently_used: recentlyUsedGiftCards,
224+
}
225+
}
226+
227+
/**
228+
* Check gift card balance by code
229+
*/
230+
export async function checkBalance(code: string): Promise<{ valid: boolean, balance?: number, currency?: string, message?: string }> {
231+
const giftCard = await fetchByCode(code)
232+
233+
if (!giftCard) {
234+
return { valid: false, message: 'Gift card not found' }
235+
}
236+
237+
if (!giftCard.is_active || giftCard.status !== 'ACTIVE') {
238+
return {
239+
valid: false,
240+
balance: giftCard.current_balance,
241+
currency: giftCard.currency,
242+
message: `Gift card is ${giftCard.status.toLowerCase()}`,
243+
}
244+
}
245+
246+
// Check if expired
247+
if (giftCard.expiry_date) {
248+
const expiryDate = new Date(giftCard.expiry_date)
249+
const currentDate = new Date()
250+
251+
if (expiryDate < currentDate) {
252+
return {
253+
valid: false,
254+
balance: giftCard.current_balance,
255+
currency: giftCard.currency,
256+
message: 'Gift card has expired',
257+
}
258+
}
259+
}
260+
261+
return {
262+
valid: true,
263+
balance: giftCard.current_balance,
264+
currency: giftCard.currency,
265+
}
266+
}

0 commit comments

Comments
 (0)