@porulle/core 0.6.0 → 0.8.0

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 (301) hide show
  1. package/dist/auth/setup.d.ts.map +1 -1
  2. package/dist/auth/setup.js +1 -0
  3. package/dist/config/defaults.d.ts.map +1 -1
  4. package/dist/config/defaults.js +5 -0
  5. package/dist/config/types.d.ts +17 -1
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/generated/plugin-manifest.d.ts +3 -0
  8. package/dist/generated/plugin-manifest.d.ts.map +1 -1
  9. package/dist/generated/plugin-manifest.js +1 -0
  10. package/dist/hooks/checkout.d.ts.map +1 -1
  11. package/dist/hooks/checkout.js +16 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/interfaces/rest/index.d.ts.map +1 -1
  16. package/dist/interfaces/rest/index.js +12 -0
  17. package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
  18. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
  19. package/dist/interfaces/rest/routes/admin/staff.js +172 -0
  20. package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
  21. package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
  22. package/dist/interfaces/rest/routes/analytics.js +24 -0
  23. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  24. package/dist/interfaces/rest/routes/carts.js +39 -2
  25. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  26. package/dist/interfaces/rest/routes/catalog.js +17 -1
  27. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  28. package/dist/interfaces/rest/routes/checkout.js +17 -0
  29. package/dist/interfaces/rest/routes/documents.d.ts +10 -0
  30. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
  31. package/dist/interfaces/rest/routes/documents.js +40 -0
  32. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  33. package/dist/interfaces/rest/routes/orders.js +122 -1
  34. package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
  35. package/dist/interfaces/rest/routes/pricing.js +36 -1
  36. package/dist/interfaces/rest/routes/settings.d.ts +5 -0
  37. package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
  38. package/dist/interfaces/rest/routes/settings.js +50 -0
  39. package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
  40. package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
  41. package/dist/interfaces/rest/routes/shipping.js +68 -0
  42. package/dist/interfaces/rest/routes/tax.d.ts +5 -0
  43. package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
  44. package/dist/interfaces/rest/routes/tax.js +68 -0
  45. package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
  46. package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
  47. package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
  48. package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
  49. package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
  50. package/dist/interfaces/rest/schemas/analytics.js +33 -0
  51. package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
  52. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  53. package/dist/interfaces/rest/schemas/carts.js +38 -0
  54. package/dist/interfaces/rest/schemas/catalog.d.ts +1230 -2
  55. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  56. package/dist/interfaces/rest/schemas/catalog.js +48 -0
  57. package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
  58. package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
  59. package/dist/interfaces/rest/schemas/checkout.js +4 -0
  60. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  61. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  62. package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
  63. package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
  64. package/dist/interfaces/rest/schemas/documents.js +65 -0
  65. package/dist/interfaces/rest/schemas/orders.d.ts +4467 -7
  66. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  67. package/dist/interfaces/rest/schemas/orders.js +330 -0
  68. package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
  69. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  70. package/dist/interfaces/rest/schemas/pricing.js +70 -0
  71. package/dist/interfaces/rest/schemas/responses.d.ts +156 -0
  72. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  73. package/dist/interfaces/rest/schemas/search.d.ts +17 -0
  74. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  75. package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
  76. package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
  77. package/dist/interfaces/rest/schemas/settings.js +79 -0
  78. package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
  79. package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
  80. package/dist/interfaces/rest/schemas/shipping.js +146 -0
  81. package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
  82. package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
  83. package/dist/interfaces/rest/schemas/tax.js +132 -0
  84. package/dist/kernel/database/schema.d.ts +4 -0
  85. package/dist/kernel/database/schema.d.ts.map +1 -1
  86. package/dist/kernel/database/schema.js +8 -0
  87. package/dist/kernel/error-mapper.d.ts.map +1 -1
  88. package/dist/kernel/error-mapper.js +1 -0
  89. package/dist/kernel/errors.d.ts +5 -0
  90. package/dist/kernel/errors.d.ts.map +1 -1
  91. package/dist/kernel/errors.js +9 -0
  92. package/dist/kernel/hooks/types.d.ts +1 -1
  93. package/dist/kernel/hooks/types.d.ts.map +1 -1
  94. package/dist/kernel/plugin/manifest.d.ts +23 -0
  95. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  96. package/dist/kernel/plugin/manifest.js +17 -2
  97. package/dist/modules/analytics/module.d.ts.map +1 -1
  98. package/dist/modules/analytics/module.js +6 -0
  99. package/dist/modules/analytics/reports.d.ts +58 -0
  100. package/dist/modules/analytics/reports.d.ts.map +1 -0
  101. package/dist/modules/analytics/reports.js +249 -0
  102. package/dist/modules/analytics/service.d.ts +10 -0
  103. package/dist/modules/analytics/service.d.ts.map +1 -1
  104. package/dist/modules/analytics/service.js +14 -0
  105. package/dist/modules/cart/repository/index.d.ts +7 -0
  106. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  107. package/dist/modules/cart/repository/index.js +25 -1
  108. package/dist/modules/cart/schema.d.ts +17 -0
  109. package/dist/modules/cart/schema.d.ts.map +1 -1
  110. package/dist/modules/cart/schema.js +2 -0
  111. package/dist/modules/cart/schemas.d.ts +1 -0
  112. package/dist/modules/cart/schemas.d.ts.map +1 -1
  113. package/dist/modules/cart/schemas.js +1 -0
  114. package/dist/modules/cart/service.d.ts +32 -0
  115. package/dist/modules/cart/service.d.ts.map +1 -1
  116. package/dist/modules/cart/service.js +58 -0
  117. package/dist/modules/catalog/entity-service.d.ts +21 -0
  118. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  119. package/dist/modules/catalog/entity-service.js +151 -6
  120. package/dist/modules/catalog/schema.d.ts +34 -0
  121. package/dist/modules/catalog/schema.d.ts.map +1 -1
  122. package/dist/modules/catalog/schema.js +5 -0
  123. package/dist/modules/catalog/schemas.d.ts +15 -1
  124. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  125. package/dist/modules/catalog/schemas.js +17 -1
  126. package/dist/modules/catalog/service.d.ts +43 -1
  127. package/dist/modules/catalog/service.d.ts.map +1 -1
  128. package/dist/modules/catalog/service.js +6 -0
  129. package/dist/modules/documents/module.d.ts +7 -0
  130. package/dist/modules/documents/module.d.ts.map +1 -0
  131. package/dist/modules/documents/module.js +12 -0
  132. package/dist/modules/documents/pdf.d.ts +25 -0
  133. package/dist/modules/documents/pdf.d.ts.map +1 -0
  134. package/dist/modules/documents/pdf.js +80 -0
  135. package/dist/modules/documents/render.d.ts +32 -0
  136. package/dist/modules/documents/render.d.ts.map +1 -0
  137. package/dist/modules/documents/render.js +190 -0
  138. package/dist/modules/documents/repository/index.d.ts +25 -0
  139. package/dist/modules/documents/repository/index.d.ts.map +1 -0
  140. package/dist/modules/documents/repository/index.js +47 -0
  141. package/dist/modules/documents/schema.d.ts +214 -0
  142. package/dist/modules/documents/schema.d.ts.map +1 -0
  143. package/dist/modules/documents/schema.js +42 -0
  144. package/dist/modules/documents/service.d.ts +51 -0
  145. package/dist/modules/documents/service.d.ts.map +1 -0
  146. package/dist/modules/documents/service.js +119 -0
  147. package/dist/modules/fulfillment/service.d.ts +18 -0
  148. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  149. package/dist/modules/fulfillment/service.js +85 -1
  150. package/dist/modules/media/service.d.ts +18 -0
  151. package/dist/modules/media/service.d.ts.map +1 -1
  152. package/dist/modules/media/service.js +26 -0
  153. package/dist/modules/orders/repository/index.d.ts +18 -1
  154. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  155. package/dist/modules/orders/repository/index.js +86 -1
  156. package/dist/modules/orders/schema.d.ts +374 -0
  157. package/dist/modules/orders/schema.d.ts.map +1 -1
  158. package/dist/modules/orders/schema.js +52 -1
  159. package/dist/modules/orders/service.d.ts +102 -2
  160. package/dist/modules/orders/service.d.ts.map +1 -1
  161. package/dist/modules/orders/service.js +494 -23
  162. package/dist/modules/pricing/repository/index.d.ts +5 -0
  163. package/dist/modules/pricing/repository/index.d.ts.map +1 -1
  164. package/dist/modules/pricing/repository/index.js +19 -0
  165. package/dist/modules/pricing/service.d.ts +18 -0
  166. package/dist/modules/pricing/service.d.ts.map +1 -1
  167. package/dist/modules/pricing/service.js +57 -1
  168. package/dist/modules/settings/module.d.ts +6 -0
  169. package/dist/modules/settings/module.d.ts.map +1 -0
  170. package/dist/modules/settings/module.js +11 -0
  171. package/dist/modules/settings/repository/index.d.ts +16 -0
  172. package/dist/modules/settings/repository/index.d.ts.map +1 -0
  173. package/dist/modules/settings/repository/index.js +41 -0
  174. package/dist/modules/settings/schema.d.ts +121 -0
  175. package/dist/modules/settings/schema.d.ts.map +1 -0
  176. package/dist/modules/settings/schema.js +24 -0
  177. package/dist/modules/settings/service.d.ts +26 -0
  178. package/dist/modules/settings/service.d.ts.map +1 -0
  179. package/dist/modules/settings/service.js +51 -0
  180. package/dist/modules/shipping/calculator.d.ts +5 -0
  181. package/dist/modules/shipping/calculator.d.ts.map +1 -1
  182. package/dist/modules/shipping/calculator.js +10 -0
  183. package/dist/modules/shipping/module.d.ts +5 -1
  184. package/dist/modules/shipping/module.d.ts.map +1 -1
  185. package/dist/modules/shipping/module.js +4 -1
  186. package/dist/modules/shipping/repository/index.d.ts +27 -2
  187. package/dist/modules/shipping/repository/index.d.ts.map +1 -1
  188. package/dist/modules/shipping/repository/index.js +105 -1
  189. package/dist/modules/shipping/schema.d.ts +419 -0
  190. package/dist/modules/shipping/schema.d.ts.map +1 -0
  191. package/dist/modules/shipping/schema.js +56 -0
  192. package/dist/modules/shipping/service.d.ts +54 -0
  193. package/dist/modules/shipping/service.d.ts.map +1 -1
  194. package/dist/modules/shipping/service.js +161 -2
  195. package/dist/modules/tax/adapter.d.ts +13 -0
  196. package/dist/modules/tax/adapter.d.ts.map +1 -1
  197. package/dist/modules/tax/module.d.ts +5 -1
  198. package/dist/modules/tax/module.d.ts.map +1 -1
  199. package/dist/modules/tax/module.js +4 -1
  200. package/dist/modules/tax/repository/index.d.ts +38 -2
  201. package/dist/modules/tax/repository/index.d.ts.map +1 -1
  202. package/dist/modules/tax/repository/index.js +138 -1
  203. package/dist/modules/tax/schema.d.ts +355 -0
  204. package/dist/modules/tax/schema.d.ts.map +1 -0
  205. package/dist/modules/tax/schema.js +55 -0
  206. package/dist/modules/tax/service.d.ts +60 -1
  207. package/dist/modules/tax/service.d.ts.map +1 -1
  208. package/dist/modules/tax/service.js +227 -2
  209. package/dist/runtime/kernel-modules.d.ts +15 -2
  210. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  211. package/dist/runtime/kernel-modules.js +4 -0
  212. package/dist/runtime/kernel-types.d.ts +5 -1
  213. package/dist/runtime/kernel-types.d.ts.map +1 -1
  214. package/dist/runtime/kernel-types.js +2 -0
  215. package/dist/runtime/server.d.ts.map +1 -1
  216. package/dist/runtime/server.js +35 -3
  217. package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
  218. package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
  219. package/dist/test-utils/create-plugin-test-app.js +15 -3
  220. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  221. package/dist/test-utils/rest-api-test-utils.js +6 -0
  222. package/package.json +3 -3
  223. package/src/auth/setup.ts +1 -0
  224. package/src/config/defaults.ts +5 -0
  225. package/src/config/types.ts +17 -1
  226. package/src/generated/plugin-capabilities.d.ts +1 -0
  227. package/src/generated/plugin-manifest.ts +1 -0
  228. package/src/generated/plugin-repositories.d.ts +1 -0
  229. package/src/hooks/checkout.ts +25 -3
  230. package/src/index.ts +1 -0
  231. package/src/interfaces/rest/index.ts +12 -0
  232. package/src/interfaces/rest/routes/admin/staff.ts +218 -0
  233. package/src/interfaces/rest/routes/analytics.ts +33 -0
  234. package/src/interfaces/rest/routes/carts.ts +52 -2
  235. package/src/interfaces/rest/routes/catalog.ts +34 -0
  236. package/src/interfaces/rest/routes/checkout.ts +22 -0
  237. package/src/interfaces/rest/routes/documents.ts +61 -0
  238. package/src/interfaces/rest/routes/orders.ts +172 -1
  239. package/src/interfaces/rest/routes/pricing.ts +44 -1
  240. package/src/interfaces/rest/routes/settings.ts +61 -0
  241. package/src/interfaces/rest/routes/shipping.ts +92 -0
  242. package/src/interfaces/rest/routes/tax.ts +90 -0
  243. package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
  244. package/src/interfaces/rest/schemas/analytics.ts +39 -0
  245. package/src/interfaces/rest/schemas/carts.ts +40 -0
  246. package/src/interfaces/rest/schemas/catalog.ts +53 -0
  247. package/src/interfaces/rest/schemas/checkout.ts +4 -0
  248. package/src/interfaces/rest/schemas/documents.ts +72 -0
  249. package/src/interfaces/rest/schemas/orders.ts +361 -0
  250. package/src/interfaces/rest/schemas/pricing.ts +75 -0
  251. package/src/interfaces/rest/schemas/settings.ts +93 -0
  252. package/src/interfaces/rest/schemas/shipping.ts +164 -0
  253. package/src/interfaces/rest/schemas/tax.ts +148 -0
  254. package/src/kernel/database/schema.ts +12 -0
  255. package/src/kernel/error-mapper.ts +1 -0
  256. package/src/kernel/errors.ts +11 -0
  257. package/src/kernel/hooks/types.ts +1 -0
  258. package/src/kernel/plugin/manifest.ts +38 -2
  259. package/src/modules/analytics/module.ts +10 -0
  260. package/src/modules/analytics/reports.ts +311 -0
  261. package/src/modules/analytics/service.ts +23 -0
  262. package/src/modules/cart/repository/index.ts +30 -1
  263. package/src/modules/cart/schema.ts +2 -0
  264. package/src/modules/cart/schemas.ts +1 -0
  265. package/src/modules/cart/service.ts +101 -0
  266. package/src/modules/catalog/entity-service.ts +195 -6
  267. package/src/modules/catalog/schema.ts +5 -0
  268. package/src/modules/catalog/schemas.ts +18 -1
  269. package/src/modules/catalog/service.ts +32 -2
  270. package/src/modules/documents/module.ts +19 -0
  271. package/src/modules/documents/pdf.ts +101 -0
  272. package/src/modules/documents/render.ts +231 -0
  273. package/src/modules/documents/repository/index.ts +68 -0
  274. package/src/modules/documents/schema.ts +44 -0
  275. package/src/modules/documents/service.ts +176 -0
  276. package/src/modules/fulfillment/service.ts +139 -1
  277. package/src/modules/media/service.ts +40 -0
  278. package/src/modules/orders/repository/index.ts +127 -1
  279. package/src/modules/orders/schema.ts +54 -1
  280. package/src/modules/orders/service.ts +739 -24
  281. package/src/modules/pricing/repository/index.ts +34 -0
  282. package/src/modules/pricing/service.ts +100 -1
  283. package/src/modules/settings/module.ts +18 -0
  284. package/src/modules/settings/repository/index.ts +59 -0
  285. package/src/modules/settings/schema.ts +25 -0
  286. package/src/modules/settings/service.ts +82 -0
  287. package/src/modules/shipping/calculator.ts +27 -0
  288. package/src/modules/shipping/module.ts +6 -2
  289. package/src/modules/shipping/repository/index.ts +136 -2
  290. package/src/modules/shipping/schema.ts +58 -0
  291. package/src/modules/shipping/service.ts +260 -1
  292. package/src/modules/tax/adapter.ts +9 -0
  293. package/src/modules/tax/module.ts +6 -2
  294. package/src/modules/tax/repository/index.ts +169 -2
  295. package/src/modules/tax/schema.ts +57 -0
  296. package/src/modules/tax/service.ts +313 -2
  297. package/src/runtime/kernel-modules.ts +4 -0
  298. package/src/runtime/kernel-types.ts +6 -0
  299. package/src/runtime/server.ts +36 -3
  300. package/src/test-utils/create-plugin-test-app.ts +17 -4
  301. package/src/test-utils/rest-api-test-utils.ts +6 -0
@@ -28,6 +28,16 @@ export interface AttachMediaInput {
28
28
  sortOrder?: number;
29
29
  }
30
30
 
31
+ export interface AttachedMedia {
32
+ mediaAssetId: string;
33
+ role: string;
34
+ sortOrder: number;
35
+ variantId: string | null;
36
+ url: string;
37
+ alt: string | null;
38
+ contentType: string;
39
+ }
40
+
31
41
  interface MediaServiceDeps {
32
42
  repository: MediaRepository;
33
43
  catalogRepository: CatalogRepository;
@@ -234,4 +244,34 @@ export class MediaService {
234
244
 
235
245
  return Ok(undefined);
236
246
  }
247
+
248
+ /**
249
+ * Media attached to an entity, resolved to public URLs and ordered by
250
+ * sortOrder. Powers catalog `?include=media` hydration. Returns entity-level
251
+ * links only unless a specific variantId is requested.
252
+ */
253
+ async listEntityMedia(
254
+ entityId: string,
255
+ opts?: { variantId?: string; orgId?: string },
256
+ ctx?: TxContext,
257
+ ): Promise<Result<AttachedMedia[]>> {
258
+ const links = await this.repo.findEntityMedia(entityId, opts?.variantId, ctx);
259
+ const out: AttachedMedia[] = [];
260
+ for (const link of links) {
261
+ const asset = await this.repo.findAssetById(link.mediaAssetId, ctx, opts?.orgId);
262
+ if (!asset) continue;
263
+ const urlResult = await this.deps.storage.getUrl(asset.storageKey);
264
+ out.push({
265
+ mediaAssetId: link.mediaAssetId,
266
+ role: link.role,
267
+ sortOrder: link.sortOrder,
268
+ variantId: link.variantId ?? null,
269
+ url: urlResult.ok ? urlResult.value : "",
270
+ alt: asset.alt ?? null,
271
+ contentType: asset.contentType,
272
+ });
273
+ }
274
+ out.sort((a, b) => a.sortOrder - b.sortOrder);
275
+ return Ok(out);
276
+ }
237
277
  }
@@ -4,7 +4,7 @@ import type {
4
4
  DrizzleDatabase,
5
5
  DbOrTx,
6
6
  } from "../../../kernel/database/drizzle-db.js";
7
- import { orders, orderLineItems, orderStatusHistory } from "../schema.js";
7
+ import { orders, orderLineItems, orderNotes, orderRefunds, orderStatusHistory } from "../schema.js";
8
8
  import { customers } from "../../customers/schema.js";
9
9
 
10
10
  export interface OrderLookupRow {
@@ -26,6 +26,10 @@ export type OrderLineItem = typeof orderLineItems.$inferSelect;
26
26
  export type OrderLineItemInsert = typeof orderLineItems.$inferInsert;
27
27
  export type OrderStatusHistory = typeof orderStatusHistory.$inferSelect;
28
28
  export type OrderStatusHistoryInsert = typeof orderStatusHistory.$inferInsert;
29
+ export type OrderRefund = typeof orderRefunds.$inferSelect;
30
+ export type OrderRefundInsert = typeof orderRefunds.$inferInsert;
31
+ export type OrderNote = typeof orderNotes.$inferSelect;
32
+ export type OrderNoteInsert = typeof orderNotes.$inferInsert;
29
33
 
30
34
  /**
31
35
  * OrdersRepository provides type-safe database operations for orders.
@@ -159,6 +163,24 @@ export class OrdersRepository {
159
163
  return query;
160
164
  }
161
165
 
166
+ async findByIdempotencyKey(
167
+ orgId: string,
168
+ idempotencyKey: string,
169
+ ctx?: TxContext,
170
+ ): Promise<Order | undefined> {
171
+ const db = this.getDb(ctx);
172
+ const rows = await db
173
+ .select()
174
+ .from(orders)
175
+ .where(
176
+ and(
177
+ eq(orders.organizationId, orgId),
178
+ eq(orders.idempotencyKey, idempotencyKey),
179
+ ),
180
+ );
181
+ return rows[0];
182
+ }
183
+
162
184
  async create(data: OrderInsert, ctx?: TxContext): Promise<Order> {
163
185
  const db = this.getDb(ctx);
164
186
  const rows = await db.insert(orders).values(data).returning();
@@ -328,6 +350,110 @@ export class OrdersRepository {
328
350
  return { order, lineItems };
329
351
  }
330
352
 
353
+ // ─────────────────────────────────────────────────────────────────────────────
354
+ // Line-level refunds (issue #52)
355
+ // ─────────────────────────────────────────────────────────────────────────────
356
+
357
+ async createRefund(data: OrderRefundInsert, ctx?: TxContext): Promise<OrderRefund> {
358
+ const db = this.getDb(ctx);
359
+ const rows = await db.insert(orderRefunds).values(data).returning();
360
+ return rows[0]!;
361
+ }
362
+
363
+ async findRefundById(
364
+ orgId: string,
365
+ id: string,
366
+ ctx?: TxContext,
367
+ ): Promise<OrderRefund | undefined> {
368
+ const db = this.getDb(ctx);
369
+ const rows = await db
370
+ .select()
371
+ .from(orderRefunds)
372
+ .where(and(eq(orderRefunds.organizationId, orgId), eq(orderRefunds.id, id)));
373
+ return rows[0];
374
+ }
375
+
376
+ async findRefundsByOrderId(orderId: string, ctx?: TxContext): Promise<OrderRefund[]> {
377
+ const db = this.getDb(ctx);
378
+ return db
379
+ .select()
380
+ .from(orderRefunds)
381
+ .where(eq(orderRefunds.orderId, orderId))
382
+ .orderBy(desc(orderRefunds.createdAt));
383
+ }
384
+
385
+ async markRefundUndone(
386
+ id: string,
387
+ undoneBy: string,
388
+ ctx?: TxContext,
389
+ ): Promise<OrderRefund | undefined> {
390
+ const db = this.getDb(ctx);
391
+ const rows = await db
392
+ .update(orderRefunds)
393
+ .set({ status: "undone", undoneAt: new Date(), undoneBy })
394
+ .where(and(eq(orderRefunds.id, id), eq(orderRefunds.status, "completed")))
395
+ .returning();
396
+ return rows[0];
397
+ }
398
+
399
+ /**
400
+ * Sum of completed refunds performed by an operator for the local calendar
401
+ * day in the given timezone — the daily-cap accounting query.
402
+ */
403
+ async sumRefundsByOperatorToday(
404
+ orgId: string,
405
+ performedBy: string,
406
+ timezone: string,
407
+ ctx?: TxContext,
408
+ ): Promise<number> {
409
+ const db = this.getDb(ctx);
410
+ const result = await db.execute(sql`
411
+ SELECT COALESCE(sum(amount), 0)::int AS "total"
412
+ FROM order_refunds
413
+ WHERE organization_id = ${orgId}
414
+ AND performed_by = ${performedBy}
415
+ AND status = 'completed'
416
+ AND (created_at AT TIME ZONE ${timezone})::date = (now() AT TIME ZONE ${timezone})::date
417
+ `);
418
+ const rows = Array.isArray(result)
419
+ ? (result as Record<string, unknown>[])
420
+ : ((result as { rows?: Record<string, unknown>[] }).rows ?? []);
421
+ const total = rows[0]?.total;
422
+ return typeof total === "bigint" ? Number(total) : ((total as number) ?? 0);
423
+ }
424
+
425
+ // ─────────────────────────────────────────────────────────────────────────────
426
+ // Order notes (issue #56)
427
+ // ─────────────────────────────────────────────────────────────────────────────
428
+
429
+ async createNote(data: OrderNoteInsert, ctx?: TxContext): Promise<OrderNote> {
430
+ const db = this.getDb(ctx);
431
+ const rows = await db.insert(orderNotes).values(data).returning();
432
+ return rows[0]!;
433
+ }
434
+
435
+ async findNotesByOrderId(orderId: string, ctx?: TxContext): Promise<OrderNote[]> {
436
+ const db = this.getDb(ctx);
437
+ return db
438
+ .select()
439
+ .from(orderNotes)
440
+ .where(eq(orderNotes.orderId, orderId))
441
+ .orderBy(desc(orderNotes.pinned), desc(orderNotes.createdAt));
442
+ }
443
+
444
+ async deleteNote(orgId: string, orderId: string, noteId: string, ctx?: TxContext): Promise<boolean> {
445
+ const db = this.getDb(ctx);
446
+ const rows = await db
447
+ .delete(orderNotes)
448
+ .where(and(
449
+ eq(orderNotes.id, noteId),
450
+ eq(orderNotes.orderId, orderId),
451
+ eq(orderNotes.organizationId, orgId),
452
+ ))
453
+ .returning();
454
+ return rows.length > 0;
455
+ }
456
+
331
457
  // ─────────────────────────────────────────────────────────────────────────────
332
458
  // Order Number Generation
333
459
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1,4 +1,5 @@
1
- import { index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
1
+ import { sql } from "drizzle-orm";
2
+ import { boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
2
3
  import { organization } from "../../auth/auth-schema.js";
3
4
  import { sellableEntities, variants } from "../catalog/schema.js";
4
5
 
@@ -17,6 +18,9 @@ export const orders = pgTable("orders", {
17
18
  amountCaptured: integer("amount_captured"),
18
19
  paymentIntentId: text("payment_intent_id"),
19
20
  paymentMethodId: text("payment_method_id"),
21
+ // Client-supplied key making order creation safely retryable (offline POS
22
+ // queues, network retries) — replays return the original order.
23
+ idempotencyKey: text("idempotency_key"),
20
24
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
21
25
  placedAt: timestamp("placed_at", { withTimezone: true }).defaultNow().notNull(),
22
26
  fulfilledAt: timestamp("fulfilled_at", { withTimezone: true }),
@@ -28,6 +32,9 @@ export const orders = pgTable("orders", {
28
32
  customerIdIdx: index("idx_orders_customer_id").on(table.customerId),
29
33
  placedAtIdx: index("idx_orders_placed_at").on(table.placedAt),
30
34
  paymentIntentIdx: index("idx_orders_payment_intent").on(table.paymentIntentId),
35
+ orgIdempotencyKeyUnique: uniqueIndex("orders_org_idempotency_key_unique")
36
+ .on(table.organizationId, table.idempotencyKey)
37
+ .where(sql`idempotency_key IS NOT NULL`),
31
38
  }));
32
39
 
33
40
  export const orderLineItems = pgTable("order_line_items", {
@@ -45,6 +52,9 @@ export const orderLineItems = pgTable("order_line_items", {
45
52
  totalPrice: integer("total_price").notNull(),
46
53
  taxAmount: integer("tax_amount").notNull().default(0),
47
54
  discountAmount: integer("discount_amount").notNull().default(0),
55
+ // Units already refunded on this line (issue #52) — line-level refund REST
56
+ // rejects refunds beyond `quantity - refundedQuantity`.
57
+ refundedQuantity: integer("refunded_quantity").notNull().default(0),
48
58
  fulfillmentStatus: text("fulfillment_status").notNull().default("unfulfilled"),
49
59
  metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
50
60
  }, (table) => [
@@ -52,6 +62,49 @@ export const orderLineItems = pgTable("order_line_items", {
52
62
  index("idx_order_line_items_entity_id").on(table.entityId),
53
63
  ]);
54
64
 
65
+ /**
66
+ * Line-level refunds (issue #52). One row per refund operation — the ledger
67
+ * behind the daily refund cap and the undo window. `lines` records which
68
+ * line items and quantities the refund covered so undo can restore them.
69
+ */
70
+ export const orderRefunds = pgTable("order_refunds", {
71
+ id: uuid("id").defaultRandom().primaryKey(),
72
+ organizationId: text("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
73
+ orderId: uuid("order_id")
74
+ .references(() => orders.id, { onDelete: "cascade" })
75
+ .notNull(),
76
+ amount: integer("amount").notNull(),
77
+ reason: text("reason"),
78
+ lines: jsonb("lines").$type<Array<{ lineItemId: string; quantity: number; amount: number }>>().notNull(),
79
+ performedBy: text("performed_by").notNull(),
80
+ status: text("status", { enum: ["completed", "undone"] }).notNull().default("completed"),
81
+ undoneAt: timestamp("undone_at", { withTimezone: true }),
82
+ undoneBy: text("undone_by"),
83
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
84
+ }, (table) => [
85
+ index("idx_order_refunds_order_id").on(table.orderId),
86
+ index("idx_order_refunds_org_performed_by").on(table.organizationId, table.performedBy, table.createdAt),
87
+ ]);
88
+
89
+ /**
90
+ * Operator annotations on orders (issue #56) — "customer will pick up
91
+ * Thursday". Merged into GET /orders/{id}/timeline alongside status history
92
+ * and refund ledger events.
93
+ */
94
+ export const orderNotes = pgTable("order_notes", {
95
+ id: uuid("id").defaultRandom().primaryKey(),
96
+ organizationId: text("organization_id").notNull().references(() => organization.id, { onDelete: "cascade" }),
97
+ orderId: uuid("order_id")
98
+ .references(() => orders.id, { onDelete: "cascade" })
99
+ .notNull(),
100
+ author: text("author").notNull(),
101
+ body: text("body").notNull(),
102
+ pinned: boolean("pinned").notNull().default(false),
103
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
104
+ }, (table) => [
105
+ index("idx_order_notes_order_id").on(table.orderId),
106
+ ]);
107
+
55
108
  export const orderStatusHistory = pgTable("order_status_history", {
56
109
  id: uuid("id").defaultRandom().primaryKey(),
57
110
  orderId: uuid("order_id")