@stacksjs/commerce 0.70.55 → 0.70.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (297) hide show
  1. package/package.json +10 -7
  2. package/src/carts/destroy.ts +0 -54
  3. package/src/carts/fetch.ts +0 -20
  4. package/src/carts/index.ts +0 -13
  5. package/src/carts/store.ts +0 -75
  6. package/src/carts/update.ts +0 -78
  7. package/src/coupons/destroy.ts +0 -49
  8. package/src/coupons/fetch.ts +0 -510
  9. package/src/coupons/index.ts +0 -30
  10. package/src/coupons/store.ts +0 -46
  11. package/src/coupons/update.ts +0 -147
  12. package/src/customers/destroy.ts +0 -62
  13. package/src/customers/fetch.ts +0 -20
  14. package/src/customers/index.ts +0 -17
  15. package/src/customers/store.ts +0 -45
  16. package/src/customers/update.ts +0 -44
  17. package/src/devices/destroy.ts +0 -54
  18. package/src/devices/export.ts +0 -98
  19. package/src/devices/fetch.ts +0 -125
  20. package/src/devices/index.ts +0 -31
  21. package/src/devices/store.ts +0 -82
  22. package/src/devices/update.ts +0 -125
  23. package/src/errors/destroy.ts +0 -54
  24. package/src/errors/fetch.ts +0 -197
  25. package/src/errors/index.ts +0 -28
  26. package/src/errors/update.ts +0 -116
  27. package/src/gift-cards/destroy.ts +0 -87
  28. package/src/gift-cards/fetch.ts +0 -431
  29. package/src/gift-cards/index.ts +0 -26
  30. package/src/gift-cards/store.ts +0 -56
  31. package/src/gift-cards/update.ts +0 -102
  32. package/src/index.ts +0 -78
  33. package/src/orders/destroy.ts +0 -111
  34. package/src/orders/events.ts +0 -121
  35. package/src/orders/export.ts +0 -158
  36. package/src/orders/fetch.ts +0 -379
  37. package/src/orders/guards.ts +0 -261
  38. package/src/orders/index.ts +0 -91
  39. package/src/orders/place-order.ts +0 -274
  40. package/src/orders/store.ts +0 -51
  41. package/src/orders/totals.ts +0 -190
  42. package/src/orders/update.ts +0 -164
  43. package/src/orders/webhook.ts +0 -312
  44. package/src/payments/destroy.ts +0 -56
  45. package/src/payments/fetch.ts +0 -288
  46. package/src/payments/index.ts +0 -16
  47. package/src/payments/store.ts +0 -63
  48. package/src/payments/update.ts +0 -52
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +0 -103
  51. package/src/products/categories/fetch.ts +0 -285
  52. package/src/products/categories/index.ts +0 -36
  53. package/src/products/categories/store.ts +0 -87
  54. package/src/products/categories/update.ts +0 -251
  55. package/src/products/index.ts +0 -35
  56. package/src/products/items/destroy.ts +0 -54
  57. package/src/products/items/fetch.ts +0 -282
  58. package/src/products/items/index.ts +0 -26
  59. package/src/products/items/store.ts +0 -84
  60. package/src/products/items/update.ts +0 -324
  61. package/src/products/manufacturers/destroy.ts +0 -56
  62. package/src/products/manufacturers/fetch.ts +0 -117
  63. package/src/products/manufacturers/index.ts +0 -24
  64. package/src/products/manufacturers/store.ts +0 -82
  65. package/src/products/manufacturers/update.ts +0 -113
  66. package/src/products/reviews/destroy.ts +0 -56
  67. package/src/products/reviews/fetch.ts +0 -63
  68. package/src/products/reviews/index.ts +0 -25
  69. package/src/products/reviews/store.ts +0 -46
  70. package/src/products/reviews/update.ts +0 -82
  71. package/src/products/units/destroy.ts +0 -56
  72. package/src/products/units/fetch.ts +0 -20
  73. package/src/products/units/index.ts +0 -13
  74. package/src/products/units/store.ts +0 -162
  75. package/src/products/units/update.ts +0 -159
  76. package/src/products/variants/destroy.ts +0 -56
  77. package/src/products/variants/fetch.ts +0 -20
  78. package/src/products/variants/index.ts +0 -21
  79. package/src/products/variants/store.ts +0 -136
  80. package/src/products/variants/update.ts +0 -104
  81. package/src/receipts/destroy.ts +0 -54
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +0 -222
  84. package/src/receipts/index.ts +0 -24
  85. package/src/receipts/printer.ts +0 -69
  86. package/src/receipts/store.ts +0 -69
  87. package/src/receipts/update.ts +0 -124
  88. package/src/shippings/delivery-routes/destroy.ts +0 -54
  89. package/src/shippings/delivery-routes/fetch.ts +0 -42
  90. package/src/shippings/delivery-routes/index.ts +0 -26
  91. package/src/shippings/delivery-routes/store.ts +0 -69
  92. package/src/shippings/delivery-routes/update.ts +0 -120
  93. package/src/shippings/digital-deliveries/destroy.ts +0 -111
  94. package/src/shippings/digital-deliveries/fetch.ts +0 -20
  95. package/src/shippings/digital-deliveries/index.ts +0 -26
  96. package/src/shippings/digital-deliveries/store.ts +0 -76
  97. package/src/shippings/digital-deliveries/update.ts +0 -129
  98. package/src/shippings/drivers/destroy.ts +0 -78
  99. package/src/shippings/drivers/fetch.ts +0 -20
  100. package/src/shippings/drivers/index.ts +0 -24
  101. package/src/shippings/drivers/store.ts +0 -69
  102. package/src/shippings/drivers/update.ts +0 -117
  103. package/src/shippings/index.ts +0 -39
  104. package/src/shippings/license-keys/destroy.ts +0 -111
  105. package/src/shippings/license-keys/fetch.ts +0 -20
  106. package/src/shippings/license-keys/index.ts +0 -26
  107. package/src/shippings/license-keys/store.ts +0 -77
  108. package/src/shippings/license-keys/update.ts +0 -117
  109. package/src/shippings/shipping-methods/destroy.ts +0 -111
  110. package/src/shippings/shipping-methods/fetch.ts +0 -62
  111. package/src/shippings/shipping-methods/index.ts +0 -28
  112. package/src/shippings/shipping-methods/store.ts +0 -123
  113. package/src/shippings/shipping-methods/update.ts +0 -120
  114. package/src/shippings/shipping-rates/destroy.ts +0 -102
  115. package/src/shippings/shipping-rates/fetch.ts +0 -320
  116. package/src/shippings/shipping-rates/index.ts +0 -36
  117. package/src/shippings/shipping-rates/store.ts +0 -102
  118. package/src/shippings/shipping-rates/update.ts +0 -139
  119. package/src/shippings/shipping-zones/destroy.ts +0 -111
  120. package/src/shippings/shipping-zones/fetch.ts +0 -96
  121. package/src/shippings/shipping-zones/index.ts +0 -26
  122. package/src/shippings/shipping-zones/store.ts +0 -74
  123. package/src/shippings/shipping-zones/update.ts +0 -156
  124. package/src/tax/destroy.ts +0 -54
  125. package/src/tax/fetch.ts +0 -20
  126. package/src/tax/index.ts +0 -24
  127. package/src/tax/store.ts +0 -69
  128. package/src/tax/update.ts +0 -119
  129. package/src/tests/coupons.test.ts +0 -90
  130. package/src/tests/customers.test.ts +0 -39
  131. package/src/tests/device.test.ts +0 -111
  132. package/src/tests/digitals.test.ts +0 -31
  133. package/src/tests/drivers.test.ts +0 -25
  134. package/src/tests/gift-cards.test.ts +0 -36
  135. package/src/tests/license.test.ts +0 -31
  136. package/src/tests/manufacturers.test.ts +0 -80
  137. package/src/tests/orders.test.ts +0 -25
  138. package/src/tests/payments.test.ts +0 -75
  139. package/src/tests/rates.test.ts +0 -367
  140. package/src/tests/receipts.test.ts +0 -287
  141. package/src/tests/restaurant.test.ts +0 -522
  142. package/src/tests/reviews.test.ts +0 -165
  143. package/src/tests/routes.test.ts +0 -51
  144. package/src/tests/setup.ts +0 -149
  145. package/src/tests/shipping.test.ts +0 -88
  146. package/src/tests/tax.test.ts +0 -25
  147. package/src/tests/units.test.ts +0 -110
  148. package/src/tests/variants.test.ts +0 -133
  149. package/src/tests/waitlist.test.ts +0 -303
  150. package/src/tests/zones.test.ts +0 -86
  151. package/src/types.ts +0 -312
  152. package/src/utils/typed-stats.ts +0 -67
  153. package/src/waitlists/index.ts +0 -19
  154. package/src/waitlists/products/destroy.ts +0 -65
  155. package/src/waitlists/products/export.ts +0 -113
  156. package/src/waitlists/products/fetch.ts +0 -362
  157. package/src/waitlists/products/index.ts +0 -33
  158. package/src/waitlists/products/store.ts +0 -112
  159. package/src/waitlists/products/update.ts +0 -125
  160. package/src/waitlists/restaurant/destroy.ts +0 -65
  161. package/src/waitlists/restaurant/export.ts +0 -107
  162. package/src/waitlists/restaurant/fetch.ts +0 -538
  163. package/src/waitlists/restaurant/index.ts +0 -43
  164. package/src/waitlists/restaurant/store.ts +0 -109
  165. package/src/waitlists/restaurant/update.ts +0 -198
  166. /package/dist/{src/coupons → coupons}/destroy.d.ts +0 -0
  167. /package/dist/{src/coupons → coupons}/fetch.d.ts +0 -0
  168. /package/dist/{src/coupons → coupons}/index.d.ts +0 -0
  169. /package/dist/{src/coupons → coupons}/store.d.ts +0 -0
  170. /package/dist/{src/coupons → coupons}/update.d.ts +0 -0
  171. /package/dist/{src/customers → customers}/destroy.d.ts +0 -0
  172. /package/dist/{src/customers → customers}/fetch.d.ts +0 -0
  173. /package/dist/{src/customers → customers}/index.d.ts +0 -0
  174. /package/dist/{src/customers → customers}/store.d.ts +0 -0
  175. /package/dist/{src/customers → customers}/update.d.ts +0 -0
  176. /package/dist/{src/devices → devices}/destroy.d.ts +0 -0
  177. /package/dist/{src/devices → devices}/export.d.ts +0 -0
  178. /package/dist/{src/devices → devices}/fetch.d.ts +0 -0
  179. /package/dist/{src/devices → devices}/index.d.ts +0 -0
  180. /package/dist/{src/devices → devices}/store.d.ts +0 -0
  181. /package/dist/{src/devices → devices}/update.d.ts +0 -0
  182. /package/dist/{src/errors → errors}/destroy.d.ts +0 -0
  183. /package/dist/{src/errors → errors}/fetch.d.ts +0 -0
  184. /package/dist/{src/errors → errors}/index.d.ts +0 -0
  185. /package/dist/{src/errors → errors}/update.d.ts +0 -0
  186. /package/dist/{src/gift-cards → gift-cards}/destroy.d.ts +0 -0
  187. /package/dist/{src/gift-cards → gift-cards}/fetch.d.ts +0 -0
  188. /package/dist/{src/gift-cards → gift-cards}/index.d.ts +0 -0
  189. /package/dist/{src/gift-cards → gift-cards}/store.d.ts +0 -0
  190. /package/dist/{src/gift-cards → gift-cards}/update.d.ts +0 -0
  191. /package/dist/{src/index.d.ts → index.d.ts} +0 -0
  192. /package/dist/{src/orders → orders}/destroy.d.ts +0 -0
  193. /package/dist/{src/orders → orders}/events.d.ts +0 -0
  194. /package/dist/{src/orders → orders}/export.d.ts +0 -0
  195. /package/dist/{src/orders → orders}/fetch.d.ts +0 -0
  196. /package/dist/{src/orders → orders}/guards.d.ts +0 -0
  197. /package/dist/{src/orders → orders}/index.d.ts +0 -0
  198. /package/dist/{src/orders → orders}/place-order.d.ts +0 -0
  199. /package/dist/{src/orders → orders}/store.d.ts +0 -0
  200. /package/dist/{src/orders → orders}/totals.d.ts +0 -0
  201. /package/dist/{src/orders → orders}/update.d.ts +0 -0
  202. /package/dist/{src/orders → orders}/webhook.d.ts +0 -0
  203. /package/dist/{src/payments → payments}/destroy.d.ts +0 -0
  204. /package/dist/{src/payments → payments}/fetch.d.ts +0 -0
  205. /package/dist/{src/payments → payments}/index.d.ts +0 -0
  206. /package/dist/{src/payments → payments}/store.d.ts +0 -0
  207. /package/dist/{src/payments → payments}/update.d.ts +0 -0
  208. /package/dist/{src/products → products}/categories/destroy.d.ts +0 -0
  209. /package/dist/{src/products → products}/categories/fetch.d.ts +0 -0
  210. /package/dist/{src/products → products}/categories/index.d.ts +0 -0
  211. /package/dist/{src/products → products}/categories/store.d.ts +0 -0
  212. /package/dist/{src/products → products}/categories/update.d.ts +0 -0
  213. /package/dist/{src/products → products}/index.d.ts +0 -0
  214. /package/dist/{src/products → products}/items/destroy.d.ts +0 -0
  215. /package/dist/{src/products → products}/items/fetch.d.ts +0 -0
  216. /package/dist/{src/products → products}/items/index.d.ts +0 -0
  217. /package/dist/{src/products → products}/items/store.d.ts +0 -0
  218. /package/dist/{src/products → products}/items/update.d.ts +0 -0
  219. /package/dist/{src/products → products}/manufacturers/destroy.d.ts +0 -0
  220. /package/dist/{src/products → products}/manufacturers/fetch.d.ts +0 -0
  221. /package/dist/{src/products → products}/manufacturers/index.d.ts +0 -0
  222. /package/dist/{src/products → products}/manufacturers/store.d.ts +0 -0
  223. /package/dist/{src/products → products}/manufacturers/update.d.ts +0 -0
  224. /package/dist/{src/products → products}/reviews/destroy.d.ts +0 -0
  225. /package/dist/{src/products → products}/reviews/fetch.d.ts +0 -0
  226. /package/dist/{src/products → products}/reviews/index.d.ts +0 -0
  227. /package/dist/{src/products → products}/reviews/store.d.ts +0 -0
  228. /package/dist/{src/products → products}/reviews/update.d.ts +0 -0
  229. /package/dist/{src/products → products}/units/destroy.d.ts +0 -0
  230. /package/dist/{src/products → products}/units/fetch.d.ts +0 -0
  231. /package/dist/{src/products → products}/units/index.d.ts +0 -0
  232. /package/dist/{src/products → products}/units/store.d.ts +0 -0
  233. /package/dist/{src/products → products}/units/update.d.ts +0 -0
  234. /package/dist/{src/products → products}/variants/destroy.d.ts +0 -0
  235. /package/dist/{src/products → products}/variants/fetch.d.ts +0 -0
  236. /package/dist/{src/products → products}/variants/index.d.ts +0 -0
  237. /package/dist/{src/products → products}/variants/store.d.ts +0 -0
  238. /package/dist/{src/products → products}/variants/update.d.ts +0 -0
  239. /package/dist/{src/receipts → receipts}/destroy.d.ts +0 -0
  240. /package/dist/{src/receipts → receipts}/fetch.d.ts +0 -0
  241. /package/dist/{src/receipts → receipts}/index.d.ts +0 -0
  242. /package/dist/{src/receipts → receipts}/store.d.ts +0 -0
  243. /package/dist/{src/receipts → receipts}/update.d.ts +0 -0
  244. /package/dist/{src/shippings → shippings}/delivery-routes/destroy.d.ts +0 -0
  245. /package/dist/{src/shippings → shippings}/delivery-routes/fetch.d.ts +0 -0
  246. /package/dist/{src/shippings → shippings}/delivery-routes/index.d.ts +0 -0
  247. /package/dist/{src/shippings → shippings}/delivery-routes/store.d.ts +0 -0
  248. /package/dist/{src/shippings → shippings}/delivery-routes/update.d.ts +0 -0
  249. /package/dist/{src/shippings → shippings}/digital-deliveries/destroy.d.ts +0 -0
  250. /package/dist/{src/shippings → shippings}/digital-deliveries/fetch.d.ts +0 -0
  251. /package/dist/{src/shippings → shippings}/digital-deliveries/index.d.ts +0 -0
  252. /package/dist/{src/shippings → shippings}/digital-deliveries/store.d.ts +0 -0
  253. /package/dist/{src/shippings → shippings}/digital-deliveries/update.d.ts +0 -0
  254. /package/dist/{src/shippings → shippings}/drivers/destroy.d.ts +0 -0
  255. /package/dist/{src/shippings → shippings}/drivers/fetch.d.ts +0 -0
  256. /package/dist/{src/shippings → shippings}/drivers/index.d.ts +0 -0
  257. /package/dist/{src/shippings → shippings}/drivers/store.d.ts +0 -0
  258. /package/dist/{src/shippings → shippings}/drivers/update.d.ts +0 -0
  259. /package/dist/{src/shippings → shippings}/index.d.ts +0 -0
  260. /package/dist/{src/shippings → shippings}/license-keys/destroy.d.ts +0 -0
  261. /package/dist/{src/shippings → shippings}/license-keys/fetch.d.ts +0 -0
  262. /package/dist/{src/shippings → shippings}/license-keys/index.d.ts +0 -0
  263. /package/dist/{src/shippings → shippings}/license-keys/store.d.ts +0 -0
  264. /package/dist/{src/shippings → shippings}/license-keys/update.d.ts +0 -0
  265. /package/dist/{src/shippings → shippings}/shipping-methods/destroy.d.ts +0 -0
  266. /package/dist/{src/shippings → shippings}/shipping-methods/fetch.d.ts +0 -0
  267. /package/dist/{src/shippings → shippings}/shipping-methods/index.d.ts +0 -0
  268. /package/dist/{src/shippings → shippings}/shipping-methods/store.d.ts +0 -0
  269. /package/dist/{src/shippings → shippings}/shipping-methods/update.d.ts +0 -0
  270. /package/dist/{src/shippings → shippings}/shipping-rates/destroy.d.ts +0 -0
  271. /package/dist/{src/shippings → shippings}/shipping-rates/fetch.d.ts +0 -0
  272. /package/dist/{src/shippings → shippings}/shipping-rates/index.d.ts +0 -0
  273. /package/dist/{src/shippings → shippings}/shipping-rates/store.d.ts +0 -0
  274. /package/dist/{src/shippings → shippings}/shipping-rates/update.d.ts +0 -0
  275. /package/dist/{src/shippings → shippings}/shipping-zones/destroy.d.ts +0 -0
  276. /package/dist/{src/shippings → shippings}/shipping-zones/fetch.d.ts +0 -0
  277. /package/dist/{src/shippings → shippings}/shipping-zones/index.d.ts +0 -0
  278. /package/dist/{src/shippings → shippings}/shipping-zones/store.d.ts +0 -0
  279. /package/dist/{src/shippings → shippings}/shipping-zones/update.d.ts +0 -0
  280. /package/dist/{src/tax → tax}/destroy.d.ts +0 -0
  281. /package/dist/{src/tax → tax}/fetch.d.ts +0 -0
  282. /package/dist/{src/tax → tax}/index.d.ts +0 -0
  283. /package/dist/{src/tax → tax}/store.d.ts +0 -0
  284. /package/dist/{src/tax → tax}/update.d.ts +0 -0
  285. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  286. /package/dist/{src/utils → utils}/typed-stats.d.ts +0 -0
  287. /package/dist/{src/waitlists → waitlists}/index.d.ts +0 -0
  288. /package/dist/{src/waitlists → waitlists}/products/destroy.d.ts +0 -0
  289. /package/dist/{src/waitlists → waitlists}/products/fetch.d.ts +0 -0
  290. /package/dist/{src/waitlists → waitlists}/products/index.d.ts +0 -0
  291. /package/dist/{src/waitlists → waitlists}/products/store.d.ts +0 -0
  292. /package/dist/{src/waitlists → waitlists}/products/update.d.ts +0 -0
  293. /package/dist/{src/waitlists → waitlists}/restaurant/destroy.d.ts +0 -0
  294. /package/dist/{src/waitlists → waitlists}/restaurant/fetch.d.ts +0 -0
  295. /package/dist/{src/waitlists → waitlists}/restaurant/index.d.ts +0 -0
  296. /package/dist/{src/waitlists → waitlists}/restaurant/store.d.ts +0 -0
  297. /package/dist/{src/waitlists → waitlists}/restaurant/update.d.ts +0 -0
@@ -1,147 +0,0 @@
1
- import type { StacksExpressionBuilder } from '@stacksjs/database'
2
- import { db } from '@stacksjs/database'
3
- import { HttpError } from '@stacksjs/error-handling'
4
- import { formatDate, isUniqueViolation } from '@stacksjs/orm'
5
- type CouponJsonResponse = ModelRow<typeof Coupon>
6
- type CouponUpdate = UpdateModelData<typeof Coupon>
7
- import { fetchById } from './fetch'
8
-
9
- /**
10
- * Discriminated result for `redeem` — `ok: true` on a successful
11
- * atomic redemption (the row's `usage_count` was bumped and the
12
- * limit wasn't already hit); `ok: false` with a `reason` when the
13
- * redemption failed (out of uses, expired, inactive, missing).
14
- * Callers branch on `reason` to surface a useful error to the user.
15
- */
16
- export type CouponRedemptionResult =
17
- | { ok: true, coupon: CouponJsonResponse }
18
- | { ok: false, reason: 'not-found' | 'inactive' | 'expired' | 'limit-reached' }
19
-
20
- /**
21
- * Update a coupon by ID
22
- *
23
- * @param id The ID of the coupon to update
24
- * @param data The updated coupon data
25
- * @returns The updated coupon record
26
- */
27
- export async function update(id: number, data: Omit<CouponUpdate, 'id'>): Promise<CouponJsonResponse | undefined> {
28
- // Check if coupon exists
29
- const existingCoupon = await fetchById(id)
30
- if (!existingCoupon) {
31
- throw new Error(`Coupon with ID ${id} not found`)
32
- }
33
-
34
- try {
35
- // Update the coupon
36
- await db
37
- .updateTable('coupons')
38
- .set({
39
- ...data,
40
- updated_at: formatDate(new Date()),
41
- })
42
- .where('id', '=', id)
43
- .execute()
44
-
45
- // Fetch and return the updated coupon
46
- const updatedCoupon = await fetchById(id)
47
-
48
- if (updatedCoupon) {
49
- // Convert SQLite integer boolean to actual boolean
50
- const c = updatedCoupon as Record<string, unknown>
51
- c.is_active = Boolean(c.is_active)
52
- }
53
-
54
- return updatedCoupon
55
- }
56
- catch (error) {
57
- if (error instanceof HttpError)
58
- throw error
59
- // Cross-dialect duplicate detection (#1957).
60
- if (isUniqueViolation(error))
61
- throw new HttpError(409, 'A coupon with this code already exists')
62
- if (error instanceof Error)
63
- throw new Error(`Failed to update coupon: ${error.message}`)
64
- throw error
65
- }
66
- }
67
-
68
- /**
69
- * Atomically redeem a coupon (stacksjs/stacks#1879 Co-5).
70
- *
71
- * Pre-fix: callers fetched the coupon, checked `usage_count <
72
- * max_uses`, then bumped `usage_count` via `update()`. Two concurrent
73
- * redemption requests against a coupon with `max_uses=1` both saw
74
- * `usage_count=0`, both incremented, both succeeded. The coupon
75
- * was redeemed twice — direct revenue loss equal to `discount_value`
76
- * times the over-redemption count.
77
- *
78
- * Post-fix: single conditional UPDATE that bumps `usage_count` and
79
- * enforces `max_uses` / `is_active` / expiry in the WHERE clause.
80
- * The database guarantees only one writer wins the race. Returns a
81
- * discriminated result so callers can surface a useful reason
82
- * ("limit reached" vs "expired" vs "inactive") instead of a generic
83
- * failure.
84
- *
85
- * @example
86
- * ```ts
87
- * const result = await redeem(couponId)
88
- * if (!result.ok) {
89
- * throw new HttpError(400, `Coupon cannot be redeemed: ${result.reason}`)
90
- * }
91
- * // Use result.coupon — it reflects the post-redemption state.
92
- * ```
93
- */
94
- export async function redeem(id: number): Promise<CouponRedemptionResult> {
95
- // Single atomic UPDATE — increment usage_count and enforce every
96
- // redemption precondition in the WHERE clause. `max_uses IS NULL`
97
- // means "unlimited" so coupons without an explicit cap aren't
98
- // accidentally rejected.
99
- const now = formatDate(new Date())
100
- const result = await db
101
- .updateTable('coupons')
102
- .set({
103
- usage_count: (db as any).raw(`COALESCE(usage_count, 0) + 1`),
104
- updated_at: now,
105
- })
106
- .where('id', '=', id)
107
- .where((eb: StacksExpressionBuilder) => eb.or([
108
- eb('max_uses', 'is', null),
109
- eb.cmpr('usage_count', '<', eb.ref('max_uses')),
110
- ]))
111
- .where('is_active', '=', 1)
112
- .where((eb: StacksExpressionBuilder) => eb.or([
113
- eb('start_date', 'is', null),
114
- eb.cmpr('start_date', '<=', now),
115
- ]))
116
- .where((eb: StacksExpressionBuilder) => eb.or([
117
- eb('end_date', 'is', null),
118
- eb.cmpr('end_date', '>=', now),
119
- ]))
120
- .executeTakeFirst()
121
-
122
- // bun-query-builder's executeTakeFirst returns metadata describing
123
- // affected rows. If the UPDATE matched zero rows, the redemption
124
- // failed (precondition mismatch); diagnose which one to give the
125
- // caller a useful reason.
126
- const affected = Number((result as any)?.numUpdatedRows ?? (result as any)?.affectedRows ?? 0)
127
- if (affected > 0) {
128
- const refreshed = await fetchById(id)
129
- if (refreshed) {
130
- // Convert SQLite integer boolean to actual boolean.
131
- const c = refreshed as Record<string, unknown>
132
- c.is_active = Boolean(c.is_active)
133
- return { ok: true, coupon: refreshed }
134
- }
135
- return { ok: false, reason: 'not-found' }
136
- }
137
-
138
- // UPDATE matched zero rows — figure out which precondition failed.
139
- const existing = await fetchById(id)
140
- if (!existing) return { ok: false, reason: 'not-found' }
141
- if (!existing.is_active) return { ok: false, reason: 'inactive' }
142
- const startOk = !existing.start_date || existing.start_date <= now
143
- const endOk = !existing.end_date || existing.end_date >= now
144
- if (!startOk || !endOk) return { ok: false, reason: 'expired' }
145
- // Fell through every other check → limit must have been reached.
146
- return { ok: false, reason: 'limit-reached' }
147
- }
@@ -1,62 +0,0 @@
1
- import { db } from '@stacksjs/database'
2
- import { fetchById } from './fetch'
3
-
4
- /**
5
- * Delete a customer by ID
6
- *
7
- * @param id The ID of the customer to delete
8
- * @returns True if the deletion was successful, false otherwise
9
- */
10
- export async function destroy(id: number): Promise<boolean> {
11
- try {
12
- // First check if the customer exists
13
- const customer = await fetchById(id)
14
-
15
- if (!customer) {
16
- throw new Error(`Customer with ID ${id} not found`)
17
- }
18
-
19
- // Delete the customer
20
- const result = await db
21
- .deleteFrom('customers')
22
- .where('id', '=', id)
23
- .executeTakeFirst()
24
-
25
- return result.numDeletedRows > 0
26
- }
27
- catch (error) {
28
- if (error instanceof Error) {
29
- throw new TypeError(`Failed to delete customer: ${error.message}`)
30
- }
31
-
32
- throw error
33
- }
34
- }
35
-
36
- /**
37
- * Bulk delete multiple customers
38
- *
39
- * @param ids Array of customer IDs to delete
40
- * @returns Number of customers successfully deleted
41
- */
42
- export async function bulkDestroy(ids: number[]): Promise<number> {
43
- if (!ids.length)
44
- return 0
45
-
46
- try {
47
- // Delete all customers in the array
48
- const result = await db
49
- .deleteFrom('customers')
50
- .where('id', 'in', ids)
51
- .executeTakeFirst()
52
-
53
- return Number(result.numDeletedRows) || 0
54
- }
55
- catch (error) {
56
- if (error instanceof Error) {
57
- throw new TypeError(`Failed to delete customers in bulk: ${error.message}`)
58
- }
59
-
60
- throw error
61
- }
62
- }
@@ -1,20 +0,0 @@
1
- type CustomerJsonResponse = ModelRow<typeof Customer>
2
- import { db } from '@stacksjs/database'
3
-
4
- /**
5
- * Fetch a customer by ID
6
- */
7
- export async function fetchById(id: number): Promise<CustomerJsonResponse | undefined> {
8
- return await db
9
- .selectFrom('customers')
10
- .where('id', '=', id)
11
- .selectAll()
12
- .executeTakeFirst() as CustomerJsonResponse | undefined
13
- }
14
-
15
- /**
16
- * Fetch all customers
17
- */
18
- export async function fetchAll(): Promise<CustomerJsonResponse[]> {
19
- return await db.selectFrom('customers').selectAll().execute() as CustomerJsonResponse[]
20
- }
@@ -1,17 +0,0 @@
1
- export {
2
- bulkDestroy,
3
- destroy,
4
- } from './destroy'
5
-
6
- export {
7
- fetchAll,
8
- fetchById,
9
- } from './fetch'
10
-
11
- export {
12
- store,
13
- } from './store'
14
-
15
- export {
16
- update,
17
- } from './update'
@@ -1,45 +0,0 @@
1
- type CustomerJsonResponse = ModelRow<typeof Customer>
2
- type NewCustomer = NewModelData<typeof Customer>
3
- import { db } from '@stacksjs/database'
4
- import { HttpError } from '@stacksjs/error-handling'
5
- import { isUniqueViolation } from '@stacksjs/orm'
6
- import { fetchById } from './fetch'
7
-
8
- /**
9
- * Create a new customer
10
- *
11
- * @param data The customer data to store
12
- * @returns The newly created customer record
13
- */
14
- export async function store(data: NewCustomer): Promise<CustomerJsonResponse> {
15
- try {
16
- const customerData = {
17
- ...data,
18
- avatar: data.avatar ?? 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400&fit=crop&q=60',
19
- }
20
-
21
- const result = await db
22
- .insertInto('customers')
23
- .values(customerData)
24
- .executeTakeFirst()
25
-
26
- if (!result)
27
- throw new Error('Failed to create customer')
28
-
29
- const insertId = result.insertId || Number(result.numInsertedOrUpdatedRows)
30
-
31
- const customerResult = await fetchById(Number(insertId)) as CustomerJsonResponse
32
-
33
- return customerResult
34
- }
35
- catch (error) {
36
- if (error instanceof HttpError)
37
- throw error
38
- // Cross-dialect duplicate detection (#1957).
39
- if (isUniqueViolation(error))
40
- throw new HttpError(409, 'A customer with this email already exists')
41
- if (error instanceof Error)
42
- throw new Error(`Failed to create customer: ${error.message}`)
43
- throw error
44
- }
45
- }
@@ -1,44 +0,0 @@
1
- import { db } from '@stacksjs/database'
2
- import { HttpError } from '@stacksjs/error-handling'
3
- import { formatDate, isUniqueViolation } from '@stacksjs/orm'
4
- type CustomerJsonResponse = ModelRow<typeof Customer>
5
- type CustomerUpdate = UpdateModelData<typeof Customer>
6
-
7
- /**
8
- * Update a customer
9
- *
10
- * @param id The ID of the customer to update
11
- * @param data The customer data to update
12
- * @returns The updated customer record
13
- */
14
- export async function update(id: number, data: Omit<CustomerUpdate, 'id'>): Promise<CustomerJsonResponse> {
15
- try {
16
- if (!id)
17
- throw new Error('Customer ID is required for update')
18
-
19
- const result = await db
20
- .updateTable('customers')
21
- .set({
22
- ...data,
23
- updated_at: formatDate(new Date()),
24
- })
25
- .where('id', '=', id)
26
- .returningAll()
27
- .executeTakeFirst()
28
-
29
- if (!result)
30
- throw new Error('Failed to update customer')
31
-
32
- return result as CustomerJsonResponse
33
- }
34
- catch (error) {
35
- if (error instanceof HttpError)
36
- throw error
37
- // Cross-dialect duplicate detection (#1957).
38
- if (isUniqueViolation(error))
39
- throw new HttpError(409, 'A customer with this email already exists')
40
- if (error instanceof Error)
41
- throw new Error(`Failed to update customer: ${error.message}`)
42
- throw error
43
- }
44
- }
@@ -1,54 +0,0 @@
1
- import { db } from '@stacksjs/database'
2
-
3
- /**
4
- * Delete a print device by ID
5
- *
6
- * @param id The ID of the print device to delete
7
- * @returns True if the print device was deleted, false otherwise
8
- */
9
- export async function destroy(id: number): Promise<boolean> {
10
- try {
11
- const result = await db
12
- .deleteFrom('print_devices')
13
- .where('id', '=', id)
14
- .executeTakeFirst()
15
-
16
- return Number(result.numDeletedRows) > 0
17
- }
18
- catch (error) {
19
- if (error instanceof Error) {
20
- throw new TypeError(`Failed to delete print device: ${error.message}`)
21
- }
22
-
23
- throw error
24
- }
25
- }
26
-
27
- /**
28
- * Delete multiple print devices by ID
29
- *
30
- * @param ids Array of print device IDs to delete
31
- * @returns Number of print devices deleted
32
- */
33
- export async function bulkDestroy(ids: number[]): Promise<number> {
34
- if (!ids.length)
35
- return 0
36
-
37
- try {
38
- // Perform the delete operation
39
- const result = await db
40
- .deleteFrom('print_devices')
41
- .where('id', 'in', ids)
42
- .executeTakeFirst()
43
-
44
- // Return the number of deleted rows
45
- return Number(result.numDeletedRows)
46
- }
47
- catch (error) {
48
- if (error instanceof Error) {
49
- throw new TypeError(`Failed to delete print devices: ${error.message}`)
50
- }
51
-
52
- throw error
53
- }
54
- }
@@ -1,98 +0,0 @@
1
- type PrintDeviceJsonResponse = ModelRow<typeof PrintDevice>
2
- import type { SpreadsheetWrapper } from 'ts-spreadsheets'
3
- import { createSpreadsheet } from 'ts-spreadsheets'
4
- import { fetchAll } from './fetch'
5
-
6
- /**
7
- * Represents the structure of an exported print device
8
- */
9
- export interface ExportedPrintDevice {
10
- 'Device ID': number
11
- 'Name': string
12
- 'MAC Address': string
13
- 'Location': string
14
- 'Terminal': string
15
- 'Status': string
16
- 'Last Ping': string
17
- 'Print Count': number
18
- }
19
-
20
- /**
21
- * Export print devices to a spreadsheet
22
- * @param format The format of the spreadsheet (default is CSV)
23
- * @returns Spreadsheet object ready for download or storage
24
- */
25
- export async function exportPrintDevices(format: 'csv' | 'excel' = 'csv'): Promise<SpreadsheetWrapper> {
26
- // Fetch all print devices
27
- const devices = await fetchAll()
28
-
29
- // Prepare data for spreadsheet
30
- const spreadsheetData = prepareDevicesForExport(devices)
31
-
32
- // Create and return spreadsheet
33
- return createSpreadsheet(spreadsheetData, { type: format })
34
- }
35
-
36
- /**
37
- * Prepare print devices data for spreadsheet export
38
- * @param devices Array of print device objects
39
- * @returns Spreadsheet data structure
40
- */
41
- function prepareDevicesForExport(devices: PrintDeviceJsonResponse[]) {
42
- // Define headings
43
- const headings: (keyof ExportedPrintDevice)[] = [
44
- 'Device ID',
45
- 'Name',
46
- 'MAC Address',
47
- 'Location',
48
- 'Terminal',
49
- 'Status',
50
- 'Last Ping',
51
- 'Print Count',
52
- ]
53
-
54
- // Transform devices into export format
55
- const data = devices.map((device) => {
56
- return [
57
- device.id,
58
- device.name,
59
- device.macAddress,
60
- device.location,
61
- device.terminal,
62
- device.status,
63
- new Date(device.lastPing).toLocaleString(),
64
- device.printCount,
65
- ]
66
- })
67
-
68
- return { headings, data }
69
- }
70
-
71
- /**
72
- * Export print devices and automatically download
73
- * @param format The format of the spreadsheet (default is CSV)
74
- * @param filename Optional filename for the download
75
- * @returns Download response
76
- */
77
- export async function downloadPrintDevices(format: 'csv' | 'excel' = 'csv', filename?: string): Promise<Response> {
78
- const spreadsheet = await exportPrintDevices(format)
79
-
80
- // Use default filename if not provided
81
- const defaultFilename = `print_devices_export_${new Date().toISOString().split('T')[0]}.${format}`
82
- return spreadsheet.download(filename || defaultFilename)
83
- }
84
-
85
- /**
86
- * Store print devices export to disk
87
- * @param format The format of the spreadsheet (default is CSV)
88
- * @param path Optional path to store the file
89
- * @returns Path where the file is stored
90
- */
91
- export async function storePrintDevicesExport(format: 'csv' | 'excel' = 'csv', path?: string): Promise<void> {
92
- const spreadsheet = await exportPrintDevices(format)
93
-
94
- // Use default path and filename if not provided
95
- const defaultPath = `print_devices_export_${new Date().toISOString().split('T')[0]}.${format}`
96
-
97
- spreadsheet.store(path || defaultPath)
98
- }
@@ -1,125 +0,0 @@
1
- type PrintDeviceJsonResponse = ModelRow<typeof PrintDevice>
2
- type ReceiptJsonResponse = ModelRow<typeof Receipt>
3
- import type { StacksExpressionBuilder } from '@stacksjs/database'
4
- import { db } from '@stacksjs/database'
5
-
6
- /**
7
- * Fetch a print device by ID
8
- */
9
- export async function fetchById(id: number): Promise<PrintDeviceJsonResponse | undefined> {
10
- return await db
11
- .selectFrom('print_devices')
12
- .where('id', '=', id)
13
- .selectAll()
14
- .executeTakeFirst() as PrintDeviceJsonResponse | undefined
15
- }
16
-
17
- /**
18
- * Fetch all print devices
19
- */
20
- export async function fetchAll(): Promise<PrintDeviceJsonResponse[]> {
21
- return await db.selectFrom('print_devices').selectAll().execute() as PrintDeviceJsonResponse[]
22
- }
23
-
24
- /**
25
- * Count all print devices
26
- */
27
- export async function countAll(): Promise<number> {
28
- const result = await db
29
- .selectFrom('print_devices')
30
- .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
31
- .executeTakeFirst() as { count: number } | undefined
32
- return result?.count ?? 0
33
- }
34
-
35
- /**
36
- * Count total prints across all print devices from receipts
37
- */
38
- export async function countTotalPrints(): Promise<number> {
39
- const result = await db
40
- .selectFrom('print_devices')
41
- .select('print_count' as any)
42
- .executeTakeFirst()
43
-
44
- return (result as any)?.print_count ?? 0
45
- }
46
-
47
- /**
48
- * Count total prints for a specific print device from receipts
49
- */
50
- export async function countPrintsByDeviceId(printDeviceId: number): Promise<number> {
51
- const result = await db
52
- .selectFrom('print_devices')
53
- .select('print_count' as any)
54
- .where('id', '=', printDeviceId)
55
- .executeTakeFirst()
56
-
57
- return (result as any)?.print_count ?? 0
58
- }
59
-
60
- /**
61
- * Calculate error rate percentage based on receipts
62
- */
63
- export async function calculateErrorRate(): Promise<number> {
64
- const result = await db
65
- .selectFrom('receipts')
66
- .select(((eb: StacksExpressionBuilder) => [
67
- eb.fn.count('id').as('total'),
68
- eb.fn.count('id').filterWhere('status', '=', 'error').as('error_count'),
69
- ]) as any)
70
- .executeTakeFirst()
71
-
72
- if (!(result as any)?.total || (result as any).total === 0) {
73
- return 0
74
- }
75
-
76
- return Number((((result as any).error_count ?? 0) / (result as any).total) * 100)
77
- }
78
-
79
- /**
80
- * Fetch all errors from receipts for a specific print device
81
- */
82
- export async function fetchErrorsByDeviceId(printDeviceId: number): Promise<ReceiptJsonResponse[]> {
83
- return await db
84
- .selectFrom('receipts')
85
- .where('print_device_id', '=', printDeviceId)
86
- .where('status', '=', 'error')
87
- .selectAll()
88
- .execute() as ReceiptJsonResponse[]
89
- }
90
-
91
- /**
92
- * Calculate printer health percentage based on online status
93
- */
94
- export async function calculatePrinterHealth(): Promise<number> {
95
- const result = await db
96
- .selectFrom('print_devices')
97
- .select(((eb: StacksExpressionBuilder) => [
98
- eb.fn.count('id').as('total'),
99
- eb.fn.count('id').filterWhere('status', '=', 'online').as('online_count'),
100
- ]) as any)
101
- .executeTakeFirst()
102
-
103
- if (!(result as any)?.total || (result as any).total === 0) {
104
- return 0
105
- }
106
-
107
- return Number((((result as any).online_count ?? 0) / (result as any).total) * 100)
108
- }
109
-
110
- /**
111
- * Get printer counts by status for visualization
112
- */
113
- export async function getPrinterStatusCounts(): Promise<Record<string, number>> {
114
- const result = await db
115
- .selectFrom('print_devices')
116
- .select(['status', (eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')] as any)
117
- .groupBy('status')
118
- .execute() as { status: string, count: number }[]
119
-
120
- // Convert array to object with status as key and count as value
121
- return result.reduce((acc: any, curr: any) => {
122
- acc[curr.status as string] = curr.count
123
- return acc
124
- }, {} as Record<string, number>)
125
- }
@@ -1,31 +0,0 @@
1
- // Functions from destroy.ts
2
- export {
3
- bulkDestroy,
4
- destroy,
5
- } from './destroy'
6
-
7
- // Functions from export.ts
8
- export {
9
- downloadPrintDevices,
10
- exportPrintDevices,
11
- storePrintDevicesExport,
12
- } from './export'
13
-
14
- // Functions from fetch.ts
15
- export {
16
- fetchAll,
17
- fetchById,
18
- } from './fetch'
19
-
20
- // Functions from store.ts
21
- export {
22
- bulkStore,
23
- store,
24
- } from './store'
25
-
26
- // Functions from update.ts
27
- export {
28
- update,
29
- updatePrintCount,
30
- updateStatus,
31
- } from './update'