@porulle/core 0.7.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 (289) 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 +98 -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 +1218 -1
  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 +3110 -43
  66. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  67. package/dist/interfaces/rest/schemas/orders.js +233 -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/hooks/types.d.ts +1 -1
  88. package/dist/kernel/hooks/types.d.ts.map +1 -1
  89. package/dist/kernel/plugin/manifest.d.ts +23 -0
  90. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  91. package/dist/kernel/plugin/manifest.js +17 -2
  92. package/dist/modules/analytics/module.d.ts.map +1 -1
  93. package/dist/modules/analytics/module.js +6 -0
  94. package/dist/modules/analytics/reports.d.ts +58 -0
  95. package/dist/modules/analytics/reports.d.ts.map +1 -0
  96. package/dist/modules/analytics/reports.js +249 -0
  97. package/dist/modules/analytics/service.d.ts +10 -0
  98. package/dist/modules/analytics/service.d.ts.map +1 -1
  99. package/dist/modules/analytics/service.js +14 -0
  100. package/dist/modules/cart/repository/index.d.ts +7 -0
  101. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  102. package/dist/modules/cart/repository/index.js +25 -1
  103. package/dist/modules/cart/schema.d.ts +17 -0
  104. package/dist/modules/cart/schema.d.ts.map +1 -1
  105. package/dist/modules/cart/schema.js +2 -0
  106. package/dist/modules/cart/schemas.d.ts +1 -0
  107. package/dist/modules/cart/schemas.d.ts.map +1 -1
  108. package/dist/modules/cart/schemas.js +1 -0
  109. package/dist/modules/cart/service.d.ts +32 -0
  110. package/dist/modules/cart/service.d.ts.map +1 -1
  111. package/dist/modules/cart/service.js +58 -0
  112. package/dist/modules/catalog/entity-service.d.ts +21 -0
  113. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  114. package/dist/modules/catalog/entity-service.js +132 -3
  115. package/dist/modules/catalog/schema.d.ts +34 -0
  116. package/dist/modules/catalog/schema.d.ts.map +1 -1
  117. package/dist/modules/catalog/schema.js +5 -0
  118. package/dist/modules/catalog/schemas.d.ts +3 -0
  119. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  120. package/dist/modules/catalog/schemas.js +3 -0
  121. package/dist/modules/catalog/service.d.ts +36 -0
  122. package/dist/modules/catalog/service.d.ts.map +1 -1
  123. package/dist/modules/catalog/service.js +6 -0
  124. package/dist/modules/documents/module.d.ts +7 -0
  125. package/dist/modules/documents/module.d.ts.map +1 -0
  126. package/dist/modules/documents/module.js +12 -0
  127. package/dist/modules/documents/pdf.d.ts +25 -0
  128. package/dist/modules/documents/pdf.d.ts.map +1 -0
  129. package/dist/modules/documents/pdf.js +80 -0
  130. package/dist/modules/documents/render.d.ts +32 -0
  131. package/dist/modules/documents/render.d.ts.map +1 -0
  132. package/dist/modules/documents/render.js +190 -0
  133. package/dist/modules/documents/repository/index.d.ts +25 -0
  134. package/dist/modules/documents/repository/index.d.ts.map +1 -0
  135. package/dist/modules/documents/repository/index.js +47 -0
  136. package/dist/modules/documents/schema.d.ts +214 -0
  137. package/dist/modules/documents/schema.d.ts.map +1 -0
  138. package/dist/modules/documents/schema.js +42 -0
  139. package/dist/modules/documents/service.d.ts +51 -0
  140. package/dist/modules/documents/service.d.ts.map +1 -0
  141. package/dist/modules/documents/service.js +119 -0
  142. package/dist/modules/fulfillment/service.d.ts +18 -0
  143. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  144. package/dist/modules/fulfillment/service.js +85 -1
  145. package/dist/modules/orders/repository/index.d.ts +18 -1
  146. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  147. package/dist/modules/orders/repository/index.js +86 -1
  148. package/dist/modules/orders/schema.d.ts +374 -0
  149. package/dist/modules/orders/schema.d.ts.map +1 -1
  150. package/dist/modules/orders/schema.js +52 -1
  151. package/dist/modules/orders/service.d.ts +86 -1
  152. package/dist/modules/orders/service.d.ts.map +1 -1
  153. package/dist/modules/orders/service.js +447 -20
  154. package/dist/modules/pricing/repository/index.d.ts +5 -0
  155. package/dist/modules/pricing/repository/index.d.ts.map +1 -1
  156. package/dist/modules/pricing/repository/index.js +19 -0
  157. package/dist/modules/pricing/service.d.ts +18 -0
  158. package/dist/modules/pricing/service.d.ts.map +1 -1
  159. package/dist/modules/pricing/service.js +38 -0
  160. package/dist/modules/settings/module.d.ts +6 -0
  161. package/dist/modules/settings/module.d.ts.map +1 -0
  162. package/dist/modules/settings/module.js +11 -0
  163. package/dist/modules/settings/repository/index.d.ts +16 -0
  164. package/dist/modules/settings/repository/index.d.ts.map +1 -0
  165. package/dist/modules/settings/repository/index.js +41 -0
  166. package/dist/modules/settings/schema.d.ts +121 -0
  167. package/dist/modules/settings/schema.d.ts.map +1 -0
  168. package/dist/modules/settings/schema.js +24 -0
  169. package/dist/modules/settings/service.d.ts +26 -0
  170. package/dist/modules/settings/service.d.ts.map +1 -0
  171. package/dist/modules/settings/service.js +51 -0
  172. package/dist/modules/shipping/calculator.d.ts +5 -0
  173. package/dist/modules/shipping/calculator.d.ts.map +1 -1
  174. package/dist/modules/shipping/calculator.js +10 -0
  175. package/dist/modules/shipping/module.d.ts +5 -1
  176. package/dist/modules/shipping/module.d.ts.map +1 -1
  177. package/dist/modules/shipping/module.js +4 -1
  178. package/dist/modules/shipping/repository/index.d.ts +27 -2
  179. package/dist/modules/shipping/repository/index.d.ts.map +1 -1
  180. package/dist/modules/shipping/repository/index.js +105 -1
  181. package/dist/modules/shipping/schema.d.ts +419 -0
  182. package/dist/modules/shipping/schema.d.ts.map +1 -0
  183. package/dist/modules/shipping/schema.js +56 -0
  184. package/dist/modules/shipping/service.d.ts +54 -0
  185. package/dist/modules/shipping/service.d.ts.map +1 -1
  186. package/dist/modules/shipping/service.js +161 -2
  187. package/dist/modules/tax/adapter.d.ts +13 -0
  188. package/dist/modules/tax/adapter.d.ts.map +1 -1
  189. package/dist/modules/tax/module.d.ts +5 -1
  190. package/dist/modules/tax/module.d.ts.map +1 -1
  191. package/dist/modules/tax/module.js +4 -1
  192. package/dist/modules/tax/repository/index.d.ts +38 -2
  193. package/dist/modules/tax/repository/index.d.ts.map +1 -1
  194. package/dist/modules/tax/repository/index.js +138 -1
  195. package/dist/modules/tax/schema.d.ts +355 -0
  196. package/dist/modules/tax/schema.d.ts.map +1 -0
  197. package/dist/modules/tax/schema.js +55 -0
  198. package/dist/modules/tax/service.d.ts +60 -1
  199. package/dist/modules/tax/service.d.ts.map +1 -1
  200. package/dist/modules/tax/service.js +227 -2
  201. package/dist/runtime/kernel-modules.d.ts +15 -2
  202. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  203. package/dist/runtime/kernel-modules.js +4 -0
  204. package/dist/runtime/kernel-types.d.ts +5 -1
  205. package/dist/runtime/kernel-types.d.ts.map +1 -1
  206. package/dist/runtime/kernel-types.js +2 -0
  207. package/dist/runtime/server.js +1 -1
  208. package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
  209. package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
  210. package/dist/test-utils/create-plugin-test-app.js +15 -3
  211. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  212. package/dist/test-utils/rest-api-test-utils.js +6 -0
  213. package/package.json +3 -3
  214. package/src/auth/setup.ts +1 -0
  215. package/src/config/defaults.ts +5 -0
  216. package/src/config/types.ts +17 -1
  217. package/src/generated/plugin-capabilities.d.ts +1 -0
  218. package/src/generated/plugin-manifest.ts +1 -0
  219. package/src/generated/plugin-repositories.d.ts +1 -0
  220. package/src/hooks/checkout.ts +25 -3
  221. package/src/index.ts +1 -0
  222. package/src/interfaces/rest/index.ts +12 -0
  223. package/src/interfaces/rest/routes/admin/staff.ts +218 -0
  224. package/src/interfaces/rest/routes/analytics.ts +33 -0
  225. package/src/interfaces/rest/routes/carts.ts +52 -2
  226. package/src/interfaces/rest/routes/catalog.ts +34 -0
  227. package/src/interfaces/rest/routes/checkout.ts +22 -0
  228. package/src/interfaces/rest/routes/documents.ts +61 -0
  229. package/src/interfaces/rest/routes/orders.ts +137 -1
  230. package/src/interfaces/rest/routes/pricing.ts +44 -1
  231. package/src/interfaces/rest/routes/settings.ts +61 -0
  232. package/src/interfaces/rest/routes/shipping.ts +92 -0
  233. package/src/interfaces/rest/routes/tax.ts +90 -0
  234. package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
  235. package/src/interfaces/rest/schemas/analytics.ts +39 -0
  236. package/src/interfaces/rest/schemas/carts.ts +40 -0
  237. package/src/interfaces/rest/schemas/catalog.ts +53 -0
  238. package/src/interfaces/rest/schemas/checkout.ts +4 -0
  239. package/src/interfaces/rest/schemas/documents.ts +72 -0
  240. package/src/interfaces/rest/schemas/orders.ts +257 -0
  241. package/src/interfaces/rest/schemas/pricing.ts +75 -0
  242. package/src/interfaces/rest/schemas/settings.ts +93 -0
  243. package/src/interfaces/rest/schemas/shipping.ts +164 -0
  244. package/src/interfaces/rest/schemas/tax.ts +148 -0
  245. package/src/kernel/database/schema.ts +12 -0
  246. package/src/kernel/hooks/types.ts +1 -0
  247. package/src/kernel/plugin/manifest.ts +38 -2
  248. package/src/modules/analytics/module.ts +10 -0
  249. package/src/modules/analytics/reports.ts +311 -0
  250. package/src/modules/analytics/service.ts +23 -0
  251. package/src/modules/cart/repository/index.ts +30 -1
  252. package/src/modules/cart/schema.ts +2 -0
  253. package/src/modules/cart/schemas.ts +1 -0
  254. package/src/modules/cart/service.ts +101 -0
  255. package/src/modules/catalog/entity-service.ts +168 -3
  256. package/src/modules/catalog/schema.ts +5 -0
  257. package/src/modules/catalog/schemas.ts +3 -0
  258. package/src/modules/catalog/service.ts +30 -0
  259. package/src/modules/documents/module.ts +19 -0
  260. package/src/modules/documents/pdf.ts +101 -0
  261. package/src/modules/documents/render.ts +231 -0
  262. package/src/modules/documents/repository/index.ts +68 -0
  263. package/src/modules/documents/schema.ts +44 -0
  264. package/src/modules/documents/service.ts +176 -0
  265. package/src/modules/fulfillment/service.ts +139 -1
  266. package/src/modules/orders/repository/index.ts +127 -1
  267. package/src/modules/orders/schema.ts +54 -1
  268. package/src/modules/orders/service.ts +654 -22
  269. package/src/modules/pricing/repository/index.ts +34 -0
  270. package/src/modules/pricing/service.ts +71 -0
  271. package/src/modules/settings/module.ts +18 -0
  272. package/src/modules/settings/repository/index.ts +59 -0
  273. package/src/modules/settings/schema.ts +25 -0
  274. package/src/modules/settings/service.ts +82 -0
  275. package/src/modules/shipping/calculator.ts +27 -0
  276. package/src/modules/shipping/module.ts +6 -2
  277. package/src/modules/shipping/repository/index.ts +136 -2
  278. package/src/modules/shipping/schema.ts +58 -0
  279. package/src/modules/shipping/service.ts +260 -1
  280. package/src/modules/tax/adapter.ts +9 -0
  281. package/src/modules/tax/module.ts +6 -2
  282. package/src/modules/tax/repository/index.ts +169 -2
  283. package/src/modules/tax/schema.ts +57 -0
  284. package/src/modules/tax/service.ts +313 -2
  285. package/src/runtime/kernel-modules.ts +4 -0
  286. package/src/runtime/kernel-types.ts +6 -0
  287. package/src/runtime/server.ts +1 -1
  288. package/src/test-utils/create-plugin-test-app.ts +17 -4
  289. package/src/test-utils/rest-api-test-utils.ts +6 -0
@@ -5,9 +5,11 @@ import { type Result } from "../../kernel/result.js";
5
5
  import { type Pagination } from "../../utils/pagination.js";
6
6
  import type { TxContext } from "../../kernel/database/tx-context.js";
7
7
  import type { DatabaseAdapter } from "../../kernel/database/adapter.js";
8
- import { OrdersRepository, type Order, type OrderLineItem, type OrderStatusHistory } from "./repository/index.js";
8
+ import { OrdersRepository, type Order, type OrderLineItem, type OrderNote, type OrderRefund, type OrderStatusHistory } from "./repository/index.js";
9
9
  export interface CreateOrderInput {
10
10
  customerId?: string;
11
+ /** Client-supplied retry key — a repeat create with the same key returns the original order. */
12
+ idempotencyKey?: string | undefined;
11
13
  currency: string;
12
14
  subtotal: number;
13
15
  taxTotal: number;
@@ -78,6 +80,8 @@ export declare class OrderService {
78
80
  create(input: CreateOrderInput, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
79
81
  getById(id: string, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
80
82
  getByNumber(orderNumber: string, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
83
+ /** Returns the order previously created with this idempotency key, or null. */
84
+ getByIdempotencyKey(idempotencyKey: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder | null>>;
81
85
  list(params: ListOrdersParams, actor: Actor | null, ctx?: TxContext): Promise<Result<OrderListResult>>;
82
86
  listByCustomer(customerId: string, params: ListOrdersParams, actor?: Actor | null, ctx?: TxContext): Promise<Result<OrderListResult>>;
83
87
  /**
@@ -108,6 +112,62 @@ export declare class OrderService {
108
112
  refund(orderId: string, actor: Actor | null, reason?: string, ctx?: TxContext, opts?: {
109
113
  amount?: number;
110
114
  }): Promise<Result<HydratedOrder>>;
115
+ private refundPolicies;
116
+ /**
117
+ * Refunds specific line-item quantities (issue #52). Enforces per-line
118
+ * refundable quantity (`quantity - refundedQuantity`), the operator's daily
119
+ * refund cap (`policies.refundDailyCap`, 403 with the cap surfaced), moves
120
+ * money through the payment adapter when the order has a captured payment,
121
+ * and records an auditable `order_refunds` ledger row.
122
+ */
123
+ refundLines(orderId: string, input: {
124
+ lines: Array<{
125
+ lineItemId: string;
126
+ quantity: number;
127
+ }>;
128
+ reason?: string | undefined;
129
+ }, actor: Actor | null, ctx?: TxContext): Promise<Result<{
130
+ order: HydratedOrder;
131
+ refund: OrderRefund;
132
+ }>>;
133
+ /**
134
+ * Undoes a refund within the configured window
135
+ * (`policies.refundUndoWindowMinutes`, default 15). Restores line
136
+ * refundedQuantity and marks the ledger row `undone` — an audited,
137
+ * compensating ledger operation; re-collecting the money (cash back into
138
+ * the drawer) is the operator's side of the exchange.
139
+ */
140
+ undoRefund(orderId: string, refundId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<{
141
+ order: HydratedOrder;
142
+ refund: OrderRefund;
143
+ }>>;
144
+ listRefunds(orderId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<OrderRefund[]>>;
145
+ /** The acting operator's daily refund-cap status (issue #52). */
146
+ refundCapStatus(actor: Actor | null, ctx?: TxContext): Promise<Result<{
147
+ cap: number | null;
148
+ usedToday: number;
149
+ remaining: number | null;
150
+ }>>;
151
+ private requireOrderAccess;
152
+ addNote(orderId: string, input: {
153
+ body: string;
154
+ pinned?: boolean | undefined;
155
+ }, actor: Actor | null, ctx?: TxContext): Promise<Result<OrderNote>>;
156
+ listNotes(orderId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<OrderNote[]>>;
157
+ deleteNote(orderId: string, noteId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<{
158
+ deleted: true;
159
+ }>>;
160
+ /**
161
+ * One merged per-order activity view (issue #56): status history + operator
162
+ * notes + refund ledger events (both directions), newest first.
163
+ */
164
+ timeline(orderId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<Array<{
165
+ type: "status" | "note" | "refund";
166
+ at: Date;
167
+ actor: string;
168
+ summary: string;
169
+ data: Record<string, unknown>;
170
+ }>>>;
111
171
  /**
112
172
  * Capture an authorized payment for an order via the payment adapter and
113
173
  * record the captured amount. Does not transition order status — capture is a
@@ -117,6 +177,31 @@ export declare class OrderService {
117
177
  amount?: number;
118
178
  }, ctx?: TxContext): Promise<Result<HydratedOrder>>;
119
179
  getStatusHistory(orderId: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<OrderStatusHistory[]>>;
180
+ /** Line items may not be edited on terminal or fully-captured orders. */
181
+ private lineItemEditGuard;
182
+ /**
183
+ * Recomputes subtotal/taxTotal/grandTotal from the order's line items
184
+ * (shippingTotal and order-level discountTotal are preserved), persists
185
+ * them, and records an audit entry in the status history.
186
+ */
187
+ private recalcOrderTotals;
188
+ addLineItem(orderId: string, input: {
189
+ entityId: string;
190
+ entityType: string;
191
+ variantId?: string | undefined;
192
+ sku?: string | undefined;
193
+ title: string;
194
+ quantity: number;
195
+ unitPrice: number;
196
+ totalPrice?: number | undefined;
197
+ taxAmount?: number | undefined;
198
+ discountAmount?: number | undefined;
199
+ metadata?: Record<string, unknown> | undefined;
200
+ }, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
201
+ updateOrderLineItem(orderId: string, lineItemId: string, patch: {
202
+ quantity: number;
203
+ }, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
204
+ removeLineItem(orderId: string, lineItemId: string, actor: Actor | null, ctx?: TxContext): Promise<Result<HydratedOrder>>;
120
205
  updateOrder(orderId: string, data: {
121
206
  placedAt?: Date;
122
207
  metadata?: Record<string, unknown>;
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/orders/service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/orders/service.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { resolveOrgId } from "../../auth/org.js";
2
2
  import { assertOwnership, assertPermission } from "../../auth/permissions.js";
3
- import { CommerceInvalidTransitionError, CommerceNotFoundError, CommerceValidationError, toCommerceError, } from "../../kernel/errors.js";
3
+ import { CommerceForbiddenError, CommerceInvalidTransitionError, CommerceNotFoundError, CommerceValidationError, toCommerceError, } from "../../kernel/errors.js";
4
4
  import { runAfterHooks, runBeforeHooks } from "../../kernel/hooks/executor.js";
5
5
  import { createHookContext } from "../../kernel/hooks/create-context.js";
6
6
  import { canTransition, orderStateMachine, } from "../../kernel/state-machine/machine.js";
@@ -44,26 +44,47 @@ export class OrderService {
44
44
  const afterHooks = this.deps.hooks.resolve("orders.afterCreate");
45
45
  const hookCtx = context(actor, this.deps.services, this.deps.database, ctx?.tx);
46
46
  const processed = await runBeforeHooks(beforeHooks, input, "create", hookCtx);
47
- const orderNumber = await this.repo.getNextOrderNumber(ctx);
48
47
  const orgId = resolveOrgId(actor);
49
- const order = await this.repo.create({
50
- organizationId: orgId,
51
- orderNumber,
52
- status: "pending",
53
- currency: processed.currency,
54
- subtotal: processed.subtotal,
55
- taxTotal: processed.taxTotal,
56
- shippingTotal: processed.shippingTotal,
57
- discountTotal: processed.discountTotal ?? 0,
58
- grandTotal: processed.grandTotal,
59
- ...(processed.paymentIntentId != null ? { paymentIntentId: processed.paymentIntentId } : {}),
60
- ...(processed.paymentMethodId != null ? { paymentMethodId: processed.paymentMethodId } : {}),
61
- metadata: processed.metadata ?? {},
62
- placedAt: new Date(),
63
- ...(processed.customerId !== undefined
64
- ? { customerId: processed.customerId }
65
- : {}),
66
- }, ctx);
48
+ // Idempotent replay: offline POS queues and network retries re-submit the
49
+ // same create — return the original order instead of double-creating.
50
+ if (processed.idempotencyKey) {
51
+ const existing = await this.repo.findByIdempotencyKey(orgId, processed.idempotencyKey, ctx);
52
+ if (existing) {
53
+ return Ok(await this.hydrateOrder(existing, ctx));
54
+ }
55
+ }
56
+ const orderNumber = await this.repo.getNextOrderNumber(ctx);
57
+ let order;
58
+ try {
59
+ order = await this.repo.create({
60
+ organizationId: orgId,
61
+ orderNumber,
62
+ status: "pending",
63
+ currency: processed.currency,
64
+ subtotal: processed.subtotal,
65
+ taxTotal: processed.taxTotal,
66
+ shippingTotal: processed.shippingTotal,
67
+ discountTotal: processed.discountTotal ?? 0,
68
+ grandTotal: processed.grandTotal,
69
+ ...(processed.paymentIntentId != null ? { paymentIntentId: processed.paymentIntentId } : {}),
70
+ ...(processed.paymentMethodId != null ? { paymentMethodId: processed.paymentMethodId } : {}),
71
+ ...(processed.idempotencyKey != null ? { idempotencyKey: processed.idempotencyKey } : {}),
72
+ metadata: processed.metadata ?? {},
73
+ placedAt: new Date(),
74
+ ...(processed.customerId !== undefined
75
+ ? { customerId: processed.customerId }
76
+ : {}),
77
+ }, ctx);
78
+ }
79
+ catch (error) {
80
+ // Concurrent replay lost the unique-index race — return the winner.
81
+ if (processed.idempotencyKey) {
82
+ const winner = await this.repo.findByIdempotencyKey(orgId, processed.idempotencyKey, ctx);
83
+ if (winner)
84
+ return Ok(await this.hydrateOrder(winner, ctx));
85
+ }
86
+ throw error;
87
+ }
67
88
  const lineItemsData = processed.lineItems.map((item) => ({
68
89
  orderId: order.id,
69
90
  entityId: item.entityId,
@@ -127,6 +148,14 @@ export class OrderService {
127
148
  return Err(new CommerceNotFoundError("Order not found."));
128
149
  return this.getById(order.id, actor, ctx);
129
150
  }
151
+ /** Returns the order previously created with this idempotency key, or null. */
152
+ async getByIdempotencyKey(idempotencyKey, actor, ctx) {
153
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
154
+ const order = await this.repo.findByIdempotencyKey(orgId, idempotencyKey, ctx);
155
+ if (!order)
156
+ return Ok(null);
157
+ return Ok(await this.hydrateOrder(order, ctx));
158
+ }
130
159
  async list(params, actor, ctx) {
131
160
  try {
132
161
  assertPermission(actor, "orders:read");
@@ -360,6 +389,279 @@ export class OrderService {
360
389
  ...(opts?.amount != null ? { refundAmount: opts.amount } : {}),
361
390
  }, actor, ctx);
362
391
  }
392
+ // ── Line-level refund policy primitives (issue #52) ─────────────────────
393
+ async refundPolicies(orgId, ctx) {
394
+ const settings = this.deps.services.settings;
395
+ const policies = (await settings?.read(orgId, "policies", ctx)) ?? {};
396
+ const general = (await settings?.read(orgId, "general", ctx)) ?? {};
397
+ return {
398
+ cap: typeof policies.refundDailyCap === "number" ? policies.refundDailyCap : null,
399
+ undoWindowMinutes: typeof policies.refundUndoWindowMinutes === "number"
400
+ ? policies.refundUndoWindowMinutes
401
+ : 15,
402
+ timezone: typeof general.timezone === "string" ? general.timezone : "UTC",
403
+ };
404
+ }
405
+ /**
406
+ * Refunds specific line-item quantities (issue #52). Enforces per-line
407
+ * refundable quantity (`quantity - refundedQuantity`), the operator's daily
408
+ * refund cap (`policies.refundDailyCap`, 403 with the cap surfaced), moves
409
+ * money through the payment adapter when the order has a captured payment,
410
+ * and records an auditable `order_refunds` ledger row.
411
+ */
412
+ async refundLines(orderId, input, actor, ctx) {
413
+ try {
414
+ assertPermission(actor, "orders:update");
415
+ }
416
+ catch (error) {
417
+ return Err(toCommerceError(error));
418
+ }
419
+ const orgId = resolveOrgId(actor);
420
+ const found = await this.repo.findWithLineItems(orgId, orderId, ctx);
421
+ if (!found)
422
+ return Err(new CommerceNotFoundError("Order not found."));
423
+ if (input.lines.length === 0) {
424
+ return Err(new CommerceValidationError("At least one line is required."));
425
+ }
426
+ const byId = new Map(found.lineItems.map((li) => [li.id, li]));
427
+ const refundLines = [];
428
+ for (const line of input.lines) {
429
+ const lineItem = byId.get(line.lineItemId);
430
+ if (!lineItem) {
431
+ return Err(new CommerceNotFoundError(`Line item ${line.lineItemId} not found on this order.`));
432
+ }
433
+ if (!Number.isInteger(line.quantity) || line.quantity < 1) {
434
+ return Err(new CommerceValidationError("Refund quantity must be a positive integer."));
435
+ }
436
+ const refundable = lineItem.quantity - lineItem.refundedQuantity;
437
+ if (line.quantity > refundable) {
438
+ return Err(new CommerceValidationError(`Line "${lineItem.title}" has ${refundable} refundable unit(s); requested ${line.quantity}.`));
439
+ }
440
+ // Effective paid per unit: line total + tax − discount, split evenly.
441
+ const lineValue = lineItem.totalPrice + lineItem.taxAmount - lineItem.discountAmount;
442
+ const amount = Math.round((lineValue * line.quantity) / lineItem.quantity);
443
+ refundLines.push({ lineItemId: lineItem.id, quantity: line.quantity, amount });
444
+ }
445
+ const totalAmount = refundLines.reduce((sum, l) => sum + l.amount, 0);
446
+ const performedBy = actor?.userId ?? "system";
447
+ const policies = await this.refundPolicies(orgId, ctx);
448
+ if (policies.cap != null) {
449
+ const usedToday = await this.repo.sumRefundsByOperatorToday(orgId, performedBy, policies.timezone, ctx);
450
+ if (usedToday + totalAmount > policies.cap) {
451
+ return Err(new CommerceForbiddenError(`Daily refund cap exceeded: cap ${policies.cap}, used ${usedToday} today, requested ${totalAmount}.`));
452
+ }
453
+ }
454
+ // Move money if a captured payment exists — clamped to what remains.
455
+ if (found.order.paymentIntentId && (found.order.amountCaptured ?? 0) > 0) {
456
+ const payments = this.deps.services.payments;
457
+ if (payments?.refund) {
458
+ const priorRefunds = await this.repo.findRefundsByOrderId(orderId, ctx);
459
+ const alreadyRefunded = priorRefunds
460
+ .filter((r) => r.status === "completed")
461
+ .reduce((sum, r) => sum + r.amount, 0);
462
+ const refundable = Math.max(0, (found.order.amountCaptured ?? 0) - alreadyRefunded);
463
+ await payments.refund(found.order.paymentIntentId, Math.min(totalAmount, refundable), input.reason ?? "line_refund");
464
+ }
465
+ }
466
+ for (const line of refundLines) {
467
+ const lineItem = byId.get(line.lineItemId);
468
+ await this.repo.updateLineItem(line.lineItemId, { refundedQuantity: lineItem.refundedQuantity + line.quantity }, ctx);
469
+ }
470
+ const refund = await this.repo.createRefund({
471
+ organizationId: orgId,
472
+ orderId,
473
+ amount: totalAmount,
474
+ reason: input.reason ?? null,
475
+ lines: refundLines,
476
+ performedBy,
477
+ }, ctx);
478
+ await this.repo.createStatusHistory({
479
+ orderId,
480
+ fromStatus: found.order.status,
481
+ toStatus: found.order.status,
482
+ reason: `refund ${refund.id}: ${totalAmount} (${input.reason ?? "line refund"})`,
483
+ changedBy: performedBy,
484
+ }, ctx);
485
+ const hydrated = await this.hydrateOrder((await this.repo.findById(orgId, orderId, ctx)), ctx);
486
+ return Ok({ order: hydrated, refund });
487
+ }
488
+ /**
489
+ * Undoes a refund within the configured window
490
+ * (`policies.refundUndoWindowMinutes`, default 15). Restores line
491
+ * refundedQuantity and marks the ledger row `undone` — an audited,
492
+ * compensating ledger operation; re-collecting the money (cash back into
493
+ * the drawer) is the operator's side of the exchange.
494
+ */
495
+ async undoRefund(orderId, refundId, actor, ctx) {
496
+ try {
497
+ assertPermission(actor, "orders:update");
498
+ }
499
+ catch (error) {
500
+ return Err(toCommerceError(error));
501
+ }
502
+ const orgId = resolveOrgId(actor);
503
+ const refund = await this.repo.findRefundById(orgId, refundId, ctx);
504
+ if (!refund || refund.orderId !== orderId) {
505
+ return Err(new CommerceNotFoundError("Refund not found."));
506
+ }
507
+ if (refund.status !== "completed") {
508
+ return Err(new CommerceValidationError("Refund has already been undone."));
509
+ }
510
+ const policies = await this.refundPolicies(orgId, ctx);
511
+ const ageMs = Date.now() - refund.createdAt.getTime();
512
+ if (ageMs > policies.undoWindowMinutes * 60_000) {
513
+ return Err(new CommerceValidationError(`Refund undo window (${policies.undoWindowMinutes} minutes) has passed.`));
514
+ }
515
+ const performedBy = actor?.userId ?? "system";
516
+ const undone = await this.repo.markRefundUndone(refundId, performedBy, ctx);
517
+ if (!undone) {
518
+ return Err(new CommerceValidationError("Refund has already been undone."));
519
+ }
520
+ for (const line of refund.lines) {
521
+ const lineItem = await this.repo.findLineItemById(line.lineItemId, ctx);
522
+ if (lineItem) {
523
+ await this.repo.updateLineItem(line.lineItemId, { refundedQuantity: Math.max(0, lineItem.refundedQuantity - line.quantity) }, ctx);
524
+ }
525
+ }
526
+ await this.repo.createStatusHistory({
527
+ orderId,
528
+ fromStatus: "refund_completed",
529
+ toStatus: "refund_undone",
530
+ reason: `refund ${refundId} undone (${refund.amount})`,
531
+ changedBy: performedBy,
532
+ }, ctx);
533
+ const order = await this.repo.findById(orgId, orderId, ctx);
534
+ const hydrated = await this.hydrateOrder(order, ctx);
535
+ return Ok({ order: hydrated, refund: undone });
536
+ }
537
+ async listRefunds(orderId, actor, ctx) {
538
+ try {
539
+ assertPermission(actor, "orders:read");
540
+ }
541
+ catch (error) {
542
+ return Err(toCommerceError(error));
543
+ }
544
+ const orgId = resolveOrgId(actor);
545
+ const order = await this.repo.findById(orgId, orderId, ctx);
546
+ if (!order)
547
+ return Err(new CommerceNotFoundError("Order not found."));
548
+ return Ok(await this.repo.findRefundsByOrderId(orderId, ctx));
549
+ }
550
+ /** The acting operator's daily refund-cap status (issue #52). */
551
+ async refundCapStatus(actor, ctx) {
552
+ try {
553
+ assertPermission(actor, "orders:read");
554
+ }
555
+ catch (error) {
556
+ return Err(toCommerceError(error));
557
+ }
558
+ const orgId = resolveOrgId(actor);
559
+ const policies = await this.refundPolicies(orgId, ctx);
560
+ const usedToday = await this.repo.sumRefundsByOperatorToday(orgId, actor?.userId ?? "system", policies.timezone, ctx);
561
+ return Ok({
562
+ cap: policies.cap,
563
+ usedToday,
564
+ remaining: policies.cap != null ? Math.max(0, policies.cap - usedToday) : null,
565
+ });
566
+ }
567
+ // ── Order notes + activity timeline (issue #56) ─────────────────────────
568
+ async requireOrderAccess(orderId, actor, perm, ctx) {
569
+ try {
570
+ assertPermission(actor, perm);
571
+ }
572
+ catch (error) {
573
+ return Err(toCommerceError(error));
574
+ }
575
+ const orgId = resolveOrgId(actor);
576
+ const order = await this.repo.findById(orgId, orderId, ctx);
577
+ if (!order)
578
+ return Err(new CommerceNotFoundError("Order not found."));
579
+ return Ok(order);
580
+ }
581
+ async addNote(orderId, input, actor, ctx) {
582
+ const order = await this.requireOrderAccess(orderId, actor, "orders:update", ctx);
583
+ if (!order.ok)
584
+ return order;
585
+ if (!input.body.trim()) {
586
+ return Err(new CommerceValidationError("Note body must not be empty."));
587
+ }
588
+ const note = await this.repo.createNote({
589
+ organizationId: order.value.organizationId,
590
+ orderId,
591
+ author: actor?.userId ?? "system",
592
+ body: input.body,
593
+ pinned: input.pinned ?? false,
594
+ }, ctx);
595
+ return Ok(note);
596
+ }
597
+ async listNotes(orderId, actor, ctx) {
598
+ const order = await this.requireOrderAccess(orderId, actor, "orders:read", ctx);
599
+ if (!order.ok)
600
+ return order;
601
+ return Ok(await this.repo.findNotesByOrderId(orderId, ctx));
602
+ }
603
+ async deleteNote(orderId, noteId, actor, ctx) {
604
+ const order = await this.requireOrderAccess(orderId, actor, "orders:update", ctx);
605
+ if (!order.ok)
606
+ return order;
607
+ const deleted = await this.repo.deleteNote(order.value.organizationId, orderId, noteId, ctx);
608
+ if (!deleted)
609
+ return Err(new CommerceNotFoundError("Note not found."));
610
+ return Ok({ deleted: true });
611
+ }
612
+ /**
613
+ * One merged per-order activity view (issue #56): status history + operator
614
+ * notes + refund ledger events (both directions), newest first.
615
+ */
616
+ async timeline(orderId, actor, ctx) {
617
+ const order = await this.requireOrderAccess(orderId, actor, "orders:read", ctx);
618
+ if (!order.ok)
619
+ return order;
620
+ const [history, notes, refunds] = await Promise.all([
621
+ this.repo.findStatusHistoryByOrderId(orderId, ctx),
622
+ this.repo.findNotesByOrderId(orderId, ctx),
623
+ this.repo.findRefundsByOrderId(orderId, ctx),
624
+ ]);
625
+ const events = [];
626
+ for (const entry of history) {
627
+ events.push({
628
+ type: "status",
629
+ at: entry.changedAt,
630
+ actor: entry.changedBy,
631
+ summary: `Status ${entry.fromStatus} → ${entry.toStatus}${entry.reason ? ` (${entry.reason})` : ""}`,
632
+ data: { fromStatus: entry.fromStatus, toStatus: entry.toStatus, reason: entry.reason },
633
+ });
634
+ }
635
+ for (const note of notes) {
636
+ events.push({
637
+ type: "note",
638
+ at: note.createdAt,
639
+ actor: note.author,
640
+ summary: note.body,
641
+ data: { noteId: note.id, pinned: note.pinned },
642
+ });
643
+ }
644
+ for (const refund of refunds) {
645
+ events.push({
646
+ type: "refund",
647
+ at: refund.createdAt,
648
+ actor: refund.performedBy,
649
+ summary: `Refund of ${refund.amount}${refund.reason ? ` (${refund.reason})` : ""}`,
650
+ data: { refundId: refund.id, amount: refund.amount, lines: refund.lines, status: refund.status },
651
+ });
652
+ if (refund.status === "undone" && refund.undoneAt) {
653
+ events.push({
654
+ type: "refund",
655
+ at: refund.undoneAt,
656
+ actor: refund.undoneBy ?? "system",
657
+ summary: `Refund of ${refund.amount} undone`,
658
+ data: { refundId: refund.id, amount: refund.amount, undone: true },
659
+ });
660
+ }
661
+ }
662
+ events.sort((a, b) => b.at.getTime() - a.at.getTime());
663
+ return Ok(events);
664
+ }
363
665
  /**
364
666
  * Capture an authorized payment for an order via the payment adapter and
365
667
  * record the captured amount. Does not transition order status — capture is a
@@ -407,6 +709,131 @@ export class OrderService {
407
709
  items.sort((a, b) => a.changedAt.getTime() - b.changedAt.getTime());
408
710
  return Ok(items);
409
711
  }
712
+ /** Line items may not be edited on terminal or fully-captured orders. */
713
+ lineItemEditGuard(order) {
714
+ if (this.machine.terminal.includes(order.status)) {
715
+ return new CommerceValidationError(`Order in terminal status "${order.status}" cannot be edited.`);
716
+ }
717
+ if (order.amountCaptured != null &&
718
+ order.amountCaptured >= order.grandTotal) {
719
+ return new CommerceValidationError("Order payment is fully captured; line items cannot be edited.");
720
+ }
721
+ return null;
722
+ }
723
+ /**
724
+ * Recomputes subtotal/taxTotal/grandTotal from the order's line items
725
+ * (shippingTotal and order-level discountTotal are preserved), persists
726
+ * them, and records an audit entry in the status history.
727
+ */
728
+ async recalcOrderTotals(order, actor, reason, ctx) {
729
+ const lineItems = await this.repo.findLineItemsByOrderId(order.id, ctx);
730
+ const subtotal = lineItems.reduce((sum, li) => sum + li.totalPrice, 0);
731
+ const taxTotal = lineItems.reduce((sum, li) => sum + li.taxAmount, 0);
732
+ const grandTotal = subtotal + taxTotal + order.shippingTotal - order.discountTotal;
733
+ const updated = await this.repo.update(order.id, { subtotal, taxTotal, grandTotal }, ctx);
734
+ await this.repo.createStatusHistory({
735
+ orderId: order.id,
736
+ fromStatus: order.status,
737
+ toStatus: order.status,
738
+ reason,
739
+ changedBy: actor?.userId ?? "system",
740
+ }, ctx);
741
+ return { ...(updated ?? order), lineItems };
742
+ }
743
+ async addLineItem(orderId, input, actor, ctx) {
744
+ try {
745
+ assertPermission(actor, "orders:update");
746
+ }
747
+ catch (error) {
748
+ return Err(toCommerceError(error));
749
+ }
750
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
751
+ const order = await this.repo.findById(orgId, orderId, ctx);
752
+ if (!order)
753
+ return Err(new CommerceNotFoundError("Order not found."));
754
+ const guard = this.lineItemEditGuard(order);
755
+ if (guard)
756
+ return Err(guard);
757
+ await this.repo.createLineItems([
758
+ {
759
+ orderId: order.id,
760
+ entityId: input.entityId,
761
+ entityType: input.entityType,
762
+ title: input.title,
763
+ quantity: input.quantity,
764
+ unitPrice: input.unitPrice,
765
+ totalPrice: input.totalPrice ?? input.unitPrice * input.quantity,
766
+ taxAmount: input.taxAmount ?? 0,
767
+ discountAmount: input.discountAmount ?? 0,
768
+ fulfillmentStatus: "unfulfilled",
769
+ metadata: input.metadata ?? {},
770
+ ...(input.variantId !== undefined ? { variantId: input.variantId } : {}),
771
+ ...(input.sku !== undefined ? { sku: input.sku } : {}),
772
+ },
773
+ ], ctx);
774
+ return Ok(await this.recalcOrderTotals(order, actor, "line_item_added", ctx));
775
+ }
776
+ async updateOrderLineItem(orderId, lineItemId, patch, actor, ctx) {
777
+ try {
778
+ assertPermission(actor, "orders:update");
779
+ }
780
+ catch (error) {
781
+ return Err(toCommerceError(error));
782
+ }
783
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
784
+ const order = await this.repo.findById(orgId, orderId, ctx);
785
+ if (!order)
786
+ return Err(new CommerceNotFoundError("Order not found."));
787
+ const guard = this.lineItemEditGuard(order);
788
+ if (guard)
789
+ return Err(guard);
790
+ const line = await this.repo.findLineItemById(lineItemId, ctx);
791
+ if (!line || line.orderId !== order.id) {
792
+ return Err(new CommerceNotFoundError("Line item not found on this order."));
793
+ }
794
+ if (line.fulfillmentStatus !== "unfulfilled") {
795
+ return Err(new CommerceValidationError("Line items with fulfillment progress cannot be adjusted."));
796
+ }
797
+ if (patch.quantity < 1) {
798
+ return Err(new CommerceValidationError("Quantity must be at least 1."));
799
+ }
800
+ // Scale line totals with the quantity change (tax scales per-unit).
801
+ const perUnitTax = line.quantity > 0 ? line.taxAmount / line.quantity : 0;
802
+ await this.repo.updateLineItem(lineItemId, {
803
+ quantity: patch.quantity,
804
+ totalPrice: line.unitPrice * patch.quantity,
805
+ taxAmount: Math.round(perUnitTax * patch.quantity),
806
+ }, ctx);
807
+ return Ok(await this.recalcOrderTotals(order, actor, "line_item_updated", ctx));
808
+ }
809
+ async removeLineItem(orderId, lineItemId, actor, ctx) {
810
+ try {
811
+ assertPermission(actor, "orders:update");
812
+ }
813
+ catch (error) {
814
+ return Err(toCommerceError(error));
815
+ }
816
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
817
+ const order = await this.repo.findById(orgId, orderId, ctx);
818
+ if (!order)
819
+ return Err(new CommerceNotFoundError("Order not found."));
820
+ const guard = this.lineItemEditGuard(order);
821
+ if (guard)
822
+ return Err(guard);
823
+ const line = await this.repo.findLineItemById(lineItemId, ctx);
824
+ if (!line || line.orderId !== order.id) {
825
+ return Err(new CommerceNotFoundError("Line item not found on this order."));
826
+ }
827
+ if (line.fulfillmentStatus !== "unfulfilled") {
828
+ return Err(new CommerceValidationError("Line items with fulfillment progress cannot be removed."));
829
+ }
830
+ const existing = await this.repo.findLineItemsByOrderId(order.id, ctx);
831
+ if (existing.length <= 1) {
832
+ return Err(new CommerceValidationError("An order must keep at least one line item."));
833
+ }
834
+ await this.repo.deleteLineItem(lineItemId, ctx);
835
+ return Ok(await this.recalcOrderTotals(order, actor, "line_item_removed", ctx));
836
+ }
410
837
  async updateOrder(orderId, data, actor, ctx) {
411
838
  const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
412
839
  const order = await this.repo.findById(orgId, orderId, ctx);
@@ -25,6 +25,11 @@ export declare class PricingRepository {
25
25
  deletePricesByEntityId(entityId: string, ctx?: TxContext): Promise<void>;
26
26
  findModifierById(orgId: string, id: string, ctx?: TxContext): Promise<PriceModifier | undefined>;
27
27
  findModifiersByEntityId(orgId: string, entityId: string, ctx?: TxContext): Promise<PriceModifier[]>;
28
+ findModifiers(orgId: string, filter?: {
29
+ entityId?: string;
30
+ currency?: string;
31
+ activeAt?: Date;
32
+ }, ctx?: TxContext): Promise<PriceModifier[]>;
28
33
  findActiveModifiers(orgId: string, entityId: string, variantId?: string, customerGroupId?: string, currency?: string, quantity?: number, ctx?: TxContext): Promise<PriceModifier[]>;
29
34
  createModifier(data: PriceModifierInsert, ctx?: TxContext): Promise<PriceModifier>;
30
35
  updateModifier(id: string, data: Partial<Omit<PriceModifierInsert, "id">>, ctx?: TxContext): Promise<PriceModifier | undefined>;