@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
@@ -42,6 +42,11 @@ function quantityRangeWidth(min, max) {
42
42
  return Number.MAX_SAFE_INTEGER - 1;
43
43
  return Number.MAX_SAFE_INTEGER;
44
44
  }
45
+ function sameNaturalKeyDate(a, b) {
46
+ if (a == null || b == null)
47
+ return (a ?? null) === (b ?? null);
48
+ return a.getTime() === b.getTime();
49
+ }
45
50
  function compareBasePriceSpecificity(a, b, context) {
46
51
  const aVariant = context.variantId !== undefined && a.variantId === context.variantId
47
52
  ? 1
@@ -116,7 +121,20 @@ export class PricingService {
116
121
  validFrom: input.validFrom ?? null,
117
122
  validUntil: input.validUntil ?? null,
118
123
  };
119
- const record = await this.repo.createPrice(priceData, ctx);
124
+ // Upsert on the natural key. "Set base price" is idempotent per
125
+ // (org, entity, variant, currency, customerGroup, qty range, validity):
126
+ // a repeat call replaces the existing row's amount instead of appending a
127
+ // shadow row that the resolver would then tie-break on createdAt.
128
+ const existing = (await this.repo.findPricesByEntityId(orgId, input.entityId, ctx)).find((p) => p.currency === priceData.currency &&
129
+ p.variantId === (priceData.variantId ?? null) &&
130
+ p.customerGroupId === (priceData.customerGroupId ?? null) &&
131
+ p.minQuantity === (priceData.minQuantity ?? null) &&
132
+ p.maxQuantity === (priceData.maxQuantity ?? null) &&
133
+ sameNaturalKeyDate(p.validFrom, priceData.validFrom ?? null) &&
134
+ sameNaturalKeyDate(p.validUntil, priceData.validUntil ?? null));
135
+ const record = existing
136
+ ? (await this.repo.updatePrice(existing.id, { amount: priceData.amount, metadata: priceData.metadata }, ctx)) ?? existing
137
+ : await this.repo.createPrice(priceData, ctx);
120
138
  const afterHooks = this.deps.hooks.resolve("pricing.afterCreate");
121
139
  const hctx = hookContext(actor ?? ctx?.actor ?? null, this.deps.services, this.deps.database, ctx?.tx ?? null);
122
140
  await runAfterHooks(afterHooks, null, record, "create", hctx);
@@ -160,6 +178,44 @@ export class PricingService {
160
178
  await runAfterHooks(afterHooks, null, modifier, "create", hctx);
161
179
  return Ok(modifier);
162
180
  }
181
+ async listModifiers(filter, actor, ctx) {
182
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
183
+ const rows = await this.repo.findModifiers(orgId, {
184
+ ...(filter?.entityId !== undefined ? { entityId: filter.entityId } : {}),
185
+ ...(filter?.currency !== undefined
186
+ ? { currency: normalizeCurrency(filter.currency) }
187
+ : {}),
188
+ ...(filter?.active ? { activeAt: new Date() } : {}),
189
+ }, ctx);
190
+ return Ok(rows);
191
+ }
192
+ async updateModifier(id, patch, actor, ctx) {
193
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
194
+ const existing = await this.repo.findModifierById(orgId, id, ctx);
195
+ if (!existing) {
196
+ return Err(new CommerceNotFoundError("Price modifier not found."));
197
+ }
198
+ const updated = await this.repo.updateModifier(id, {
199
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
200
+ ...(patch.value !== undefined ? { value: patch.value } : {}),
201
+ ...(patch.priority !== undefined ? { priority: patch.priority } : {}),
202
+ ...(patch.validFrom !== undefined ? { validFrom: patch.validFrom } : {}),
203
+ ...(patch.validUntil !== undefined ? { validUntil: patch.validUntil } : {}),
204
+ ...(patch.minQuantity !== undefined ? { minQuantity: patch.minQuantity } : {}),
205
+ ...(patch.maxQuantity !== undefined ? { maxQuantity: patch.maxQuantity } : {}),
206
+ ...(patch.metadata !== undefined ? { metadata: patch.metadata } : {}),
207
+ }, ctx);
208
+ return Ok(updated);
209
+ }
210
+ async deleteModifier(id, actor, ctx) {
211
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
212
+ const existing = await this.repo.findModifierById(orgId, id, ctx);
213
+ if (!existing) {
214
+ return Err(new CommerceNotFoundError("Price modifier not found."));
215
+ }
216
+ await this.repo.deleteModifier(id, ctx);
217
+ return Ok({ deleted: true });
218
+ }
163
219
  async listPrices(filter, actor, ctx) {
164
220
  const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
165
221
  // Get all prices for the entity if specified, or filter after retrieval
@@ -0,0 +1,6 @@
1
+ import { storeSettings } from "./schema.js";
2
+ import { SettingsService } from "./service.js";
3
+ export declare const settingsModule: import("../../kernel/module/define.js").AppModule<{
4
+ storeSettings: typeof storeSettings;
5
+ }, SettingsService, Record<string, never>>;
6
+ //# sourceMappingURL=module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/module.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,eAAO,MAAM,cAAc;mBACR,OAAO,aAAa;0CAUrC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { defineModule } from "../../kernel/module/index.js";
2
+ import { SettingsRepository } from "./repository/index.js";
3
+ import { storeSettings } from "./schema.js";
4
+ import { SettingsService } from "./service.js";
5
+ export const settingsModule = defineModule({
6
+ id: "settings",
7
+ schema: () => ({ storeSettings }),
8
+ service: (deps) => new SettingsService({
9
+ repository: new SettingsRepository(deps.db.db),
10
+ }),
11
+ });
@@ -0,0 +1,16 @@
1
+ import type { TxContext } from "../../../kernel/database/tx-context.js";
2
+ import type { DrizzleDatabase } from "../../../kernel/database/drizzle-db.js";
3
+ import { storeSettings } from "../schema.js";
4
+ export type StoreSetting = typeof storeSettings.$inferSelect;
5
+ /**
6
+ * Persistence for org-scoped runtime settings (issue #49).
7
+ */
8
+ export declare class SettingsRepository {
9
+ private readonly db;
10
+ constructor(db: DrizzleDatabase);
11
+ private getDb;
12
+ findByGroup(orgId: string, group: string, ctx?: TxContext): Promise<StoreSetting | undefined>;
13
+ findAll(orgId: string, ctx?: TxContext): Promise<StoreSetting[]>;
14
+ upsert(orgId: string, group: string, value: Record<string, unknown>, ctx?: TxContext): Promise<StoreSetting>;
15
+ }
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/settings/repository/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAE7D;;GAEG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAEhD,OAAO,CAAC,KAAK;IAIP,WAAW,CACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAS9B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAQhE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,YAAY,CAAC;CAYzB"}
@@ -0,0 +1,41 @@
1
+ import { and, eq } from "drizzle-orm";
2
+ import { storeSettings } from "../schema.js";
3
+ /**
4
+ * Persistence for org-scoped runtime settings (issue #49).
5
+ */
6
+ export class SettingsRepository {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ getDb(ctx) {
12
+ return ctx?.tx ?? this.db;
13
+ }
14
+ async findByGroup(orgId, group, ctx) {
15
+ const db = this.getDb(ctx);
16
+ const rows = await db
17
+ .select()
18
+ .from(storeSettings)
19
+ .where(and(eq(storeSettings.organizationId, orgId), eq(storeSettings.group, group)));
20
+ return rows[0];
21
+ }
22
+ async findAll(orgId, ctx) {
23
+ const db = this.getDb(ctx);
24
+ return db
25
+ .select()
26
+ .from(storeSettings)
27
+ .where(eq(storeSettings.organizationId, orgId));
28
+ }
29
+ async upsert(orgId, group, value, ctx) {
30
+ const db = this.getDb(ctx);
31
+ const rows = await db
32
+ .insert(storeSettings)
33
+ .values({ organizationId: orgId, group, value })
34
+ .onConflictDoUpdate({
35
+ target: [storeSettings.organizationId, storeSettings.group],
36
+ set: { value, updatedAt: new Date() },
37
+ })
38
+ .returning();
39
+ return rows[0];
40
+ }
41
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Org-scoped runtime settings (issue #49).
3
+ *
4
+ * One row per (organization, group). Groups are shallow JSON objects —
5
+ * `general` (currency, timezone, locale), `branding` (receipt header/footer,
6
+ * store name, logo), `policies` (free-form policy knobs), plus any custom
7
+ * group a plugin namespaces for itself. PATCH semantics are shallow merge
8
+ * with `null` deleting a key.
9
+ */
10
+ export declare const storeSettings: import("drizzle-orm/pg-core").PgTableWithColumns<{
11
+ name: "store_settings";
12
+ schema: undefined;
13
+ columns: {
14
+ id: import("drizzle-orm/pg-core").PgColumn<{
15
+ name: "id";
16
+ tableName: "store_settings";
17
+ dataType: "string";
18
+ columnType: "PgUUID";
19
+ data: string;
20
+ driverParam: string;
21
+ notNull: true;
22
+ hasDefault: true;
23
+ isPrimaryKey: true;
24
+ isAutoincrement: false;
25
+ hasRuntimeDefault: false;
26
+ enumValues: undefined;
27
+ baseColumn: never;
28
+ identity: undefined;
29
+ generated: undefined;
30
+ }, {}, {}>;
31
+ organizationId: import("drizzle-orm/pg-core").PgColumn<{
32
+ name: "organization_id";
33
+ tableName: "store_settings";
34
+ dataType: "string";
35
+ columnType: "PgText";
36
+ data: string;
37
+ driverParam: string;
38
+ notNull: true;
39
+ hasDefault: false;
40
+ isPrimaryKey: false;
41
+ isAutoincrement: false;
42
+ hasRuntimeDefault: false;
43
+ enumValues: [string, ...string[]];
44
+ baseColumn: never;
45
+ identity: undefined;
46
+ generated: undefined;
47
+ }, {}, {}>;
48
+ group: import("drizzle-orm/pg-core").PgColumn<{
49
+ name: "group";
50
+ tableName: "store_settings";
51
+ dataType: "string";
52
+ columnType: "PgText";
53
+ data: string;
54
+ driverParam: string;
55
+ notNull: true;
56
+ hasDefault: false;
57
+ isPrimaryKey: false;
58
+ isAutoincrement: false;
59
+ hasRuntimeDefault: false;
60
+ enumValues: [string, ...string[]];
61
+ baseColumn: never;
62
+ identity: undefined;
63
+ generated: undefined;
64
+ }, {}, {}>;
65
+ value: import("drizzle-orm/pg-core").PgColumn<{
66
+ name: "value";
67
+ tableName: "store_settings";
68
+ dataType: "json";
69
+ columnType: "PgJsonb";
70
+ data: Record<string, unknown>;
71
+ driverParam: unknown;
72
+ notNull: true;
73
+ hasDefault: true;
74
+ isPrimaryKey: false;
75
+ isAutoincrement: false;
76
+ hasRuntimeDefault: false;
77
+ enumValues: undefined;
78
+ baseColumn: never;
79
+ identity: undefined;
80
+ generated: undefined;
81
+ }, {}, {
82
+ $type: Record<string, unknown>;
83
+ }>;
84
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
85
+ name: "created_at";
86
+ tableName: "store_settings";
87
+ dataType: "date";
88
+ columnType: "PgTimestamp";
89
+ data: Date;
90
+ driverParam: string;
91
+ notNull: true;
92
+ hasDefault: true;
93
+ isPrimaryKey: false;
94
+ isAutoincrement: false;
95
+ hasRuntimeDefault: false;
96
+ enumValues: undefined;
97
+ baseColumn: never;
98
+ identity: undefined;
99
+ generated: undefined;
100
+ }, {}, {}>;
101
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
102
+ name: "updated_at";
103
+ tableName: "store_settings";
104
+ dataType: "date";
105
+ columnType: "PgTimestamp";
106
+ data: Date;
107
+ driverParam: string;
108
+ notNull: true;
109
+ hasDefault: true;
110
+ isPrimaryKey: false;
111
+ isAutoincrement: false;
112
+ hasRuntimeDefault: false;
113
+ enumValues: undefined;
114
+ baseColumn: never;
115
+ identity: undefined;
116
+ generated: undefined;
117
+ }, {}, {}>;
118
+ };
119
+ dialect: "pg";
120
+ }>;
121
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/schema.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAYvB,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { index, jsonb, pgTable, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
2
+ import { organization } from "../../auth/auth-schema.js";
3
+ /**
4
+ * Org-scoped runtime settings (issue #49).
5
+ *
6
+ * One row per (organization, group). Groups are shallow JSON objects —
7
+ * `general` (currency, timezone, locale), `branding` (receipt header/footer,
8
+ * store name, logo), `policies` (free-form policy knobs), plus any custom
9
+ * group a plugin namespaces for itself. PATCH semantics are shallow merge
10
+ * with `null` deleting a key.
11
+ */
12
+ export const storeSettings = pgTable("store_settings", {
13
+ id: uuid("id").defaultRandom().primaryKey(),
14
+ organizationId: text("organization_id")
15
+ .notNull()
16
+ .references(() => organization.id, { onDelete: "cascade" }),
17
+ group: text("group").notNull(),
18
+ value: jsonb("value").$type().notNull().default({}),
19
+ createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
20
+ updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow().notNull(),
21
+ }, (table) => ({
22
+ orgGroupUnique: uniqueIndex("store_settings_org_group_unique").on(table.organizationId, table.group),
23
+ orgIdx: index("idx_store_settings_org").on(table.organizationId),
24
+ }));
@@ -0,0 +1,26 @@
1
+ import type { Actor } from "../../auth/types.js";
2
+ import type { TxContext } from "../../kernel/database/tx-context.js";
3
+ import { type Result } from "../../kernel/result.js";
4
+ import type { SettingsRepository } from "./repository/index.js";
5
+ interface SettingsServiceDeps {
6
+ repository: SettingsRepository;
7
+ }
8
+ /**
9
+ * Org-scoped runtime settings (issue #49).
10
+ *
11
+ * REST-facing methods resolve the org from the actor; `read()` is the
12
+ * plugin/hook-facing API — an orgId-keyed lookup with no actor requirement so
13
+ * policy values can be consumed at request time inside any service or hook.
14
+ */
15
+ export declare class SettingsService {
16
+ private repository;
17
+ constructor(deps: SettingsServiceDeps);
18
+ /** Runtime read for plugins/hooks. Unset groups read as `{}`. */
19
+ read(orgId: string, group: string, ctx?: TxContext): Promise<Record<string, unknown>>;
20
+ getGroup(group: string, actor?: Actor | null, ctx?: TxContext): Promise<Result<Record<string, unknown>>>;
21
+ getAll(actor?: Actor | null, ctx?: TxContext): Promise<Result<Record<string, Record<string, unknown>>>>;
22
+ /** Shallow merge: keys in `patch` overwrite, `null` deletes the key. */
23
+ updateGroup(group: string, patch: Record<string, unknown>, actor?: Actor | null, ctx?: TxContext): Promise<Result<Record<string, unknown>>>;
24
+ }
25
+ export {};
26
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../../src/modules/settings/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AAErE,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAIhE,UAAU,mBAAmB;IAC3B,UAAU,EAAE,kBAAkB,CAAC;CAChC;AAED;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,UAAU,CAAqB;gBAE3B,IAAI,EAAE,mBAAmB;IAIrC,iEAAiE;IAC3D,IAAI,CACR,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAK7B,QAAQ,CACZ,KAAK,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAKrC,MAAM,CACV,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAQ3D,wEAAwE;IAClE,WAAW,CACf,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAkB5C"}
@@ -0,0 +1,51 @@
1
+ import { resolveOrgId } from "../../auth/org.js";
2
+ import { CommerceValidationError } from "../../kernel/errors.js";
3
+ import { Err, Ok } from "../../kernel/result.js";
4
+ const GROUP_NAME_RE = /^[a-z][a-z0-9_-]*$/;
5
+ /**
6
+ * Org-scoped runtime settings (issue #49).
7
+ *
8
+ * REST-facing methods resolve the org from the actor; `read()` is the
9
+ * plugin/hook-facing API — an orgId-keyed lookup with no actor requirement so
10
+ * policy values can be consumed at request time inside any service or hook.
11
+ */
12
+ export class SettingsService {
13
+ repository;
14
+ constructor(deps) {
15
+ this.repository = deps.repository;
16
+ }
17
+ /** Runtime read for plugins/hooks. Unset groups read as `{}`. */
18
+ async read(orgId, group, ctx) {
19
+ const row = await this.repository.findByGroup(orgId, group, ctx);
20
+ return row?.value ?? {};
21
+ }
22
+ async getGroup(group, actor, ctx) {
23
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
24
+ return Ok(await this.read(orgId, group, ctx));
25
+ }
26
+ async getAll(actor, ctx) {
27
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
28
+ const rows = await this.repository.findAll(orgId, ctx);
29
+ const all = {};
30
+ for (const row of rows)
31
+ all[row.group] = row.value;
32
+ return Ok(all);
33
+ }
34
+ /** Shallow merge: keys in `patch` overwrite, `null` deletes the key. */
35
+ async updateGroup(group, patch, actor, ctx) {
36
+ if (!GROUP_NAME_RE.test(group)) {
37
+ return Err(new CommerceValidationError("Settings group must match [a-z][a-z0-9_-]* (e.g. general, branding, policies)."));
38
+ }
39
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
40
+ const current = await this.read(orgId, group, ctx);
41
+ const next = { ...current };
42
+ for (const [key, value] of Object.entries(patch)) {
43
+ if (value === null)
44
+ delete next[key];
45
+ else
46
+ next[key] = value;
47
+ }
48
+ const row = await this.repository.upsert(orgId, group, next, ctx);
49
+ return Ok(row.value);
50
+ }
51
+ }
@@ -34,6 +34,11 @@ export interface ShippingCalculationInput {
34
34
  address?: ShippingAddress;
35
35
  isFreeShipping: boolean;
36
36
  }
37
+ /**
38
+ * Total weight (grams) of the shippable line items — used by both the
39
+ * code-config strategy and runtime zone rates with weight bands.
40
+ */
41
+ export declare function computeShippableWeightGrams(config: CommerceConfig, catalogRepo: CatalogRepository, lineItems: ShippingLineItem[], ctx?: TxContext): Promise<number>;
37
42
  export declare function calculateShippingCost(config: CommerceConfig, catalogRepo: CatalogRepository, input: ShippingCalculationInput, ctx?: TxContext): Promise<{
38
43
  amount: number;
39
44
  strategy: string;
@@ -1 +1 @@
1
- {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../../src/modules/shipping/calculator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEN,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAiFD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,wBAAwB,EAC/B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CA+DpE"}
1
+ {"version":3,"file":"calculator.d.ts","sourceRoot":"","sources":["../../../src/modules/shipping/calculator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qCAAqC,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAExE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,GACD;IACE,IAAI,EAAE,cAAc,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC,CAAC;AAEN,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,cAAc,EAAE,OAAO,CAAC;CACzB;AAiFD;;;GAGG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,iBAAiB,EAC9B,SAAS,EAAE,gBAAgB,EAAE,EAC7B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,cAAc,EACtB,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,wBAAwB,EAC/B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,CAAC,CA+DpE"}
@@ -51,6 +51,16 @@ function resolveStrategy(config) {
51
51
  : {}),
52
52
  };
53
53
  }
54
+ /**
55
+ * Total weight (grams) of the shippable line items — used by both the
56
+ * code-config strategy and runtime zone rates with weight bands.
57
+ */
58
+ export async function computeShippableWeightGrams(config, catalogRepo, lineItems, ctx) {
59
+ const flags = await Promise.all(lineItems.map((lineItem) => isShippableEntity(config, catalogRepo, lineItem.entityId, ctx)));
60
+ const shippable = lineItems.filter((_, i) => flags[i]);
61
+ const weights = await Promise.all(shippable.map((lineItem) => resolveWeightGrams(catalogRepo, lineItem.entityId, lineItem.variantId, ctx)));
62
+ return shippable.reduce((sum, lineItem, i) => sum + (weights[i] ?? 0) * lineItem.quantity, 0);
63
+ }
54
64
  export async function calculateShippingCost(config, catalogRepo, input, ctx) {
55
65
  if (input.isFreeShipping) {
56
66
  return { amount: 0, strategy: "promotion:free_shipping", weightGrams: 0 };
@@ -1,8 +1,12 @@
1
1
  import type { CatalogServiceImpl } from "../catalog/service.js";
2
+ import { shippingZones, shippingRates } from "./schema.js";
2
3
  import { ShippingService } from "./service.js";
3
4
  type ShippingModuleDeps = {
4
5
  catalog: CatalogServiceImpl;
5
6
  };
6
- export declare const shippingModule: import("../../kernel/module/define.js").AppModule<Record<string, never>, ShippingService, ShippingModuleDeps>;
7
+ export declare const shippingModule: import("../../kernel/module/define.js").AppModule<{
8
+ shippingZones: typeof shippingZones;
9
+ shippingRates: typeof shippingRates;
10
+ }, ShippingService, ShippingModuleDeps>;
7
11
  export {};
8
12
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/shipping/module.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,cAAc,+GAazB,CAAC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/shipping/module.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,KAAK,kBAAkB,GAAG;IACxB,OAAO,EAAE,kBAAkB,CAAC;CAC7B,CAAC;AAEF,eAAO,MAAM,cAAc;mBACR,OAAO,aAAa;mBAAiB,OAAO,aAAa;uCAa1E,CAAC"}
@@ -1,11 +1,14 @@
1
1
  import { defineModule } from "../../kernel/module/index.js";
2
+ import { ShippingConfigRepository } from "./repository/index.js";
3
+ import { shippingZones, shippingRates } from "./schema.js";
2
4
  import { ShippingService } from "./service.js";
3
5
  export const shippingModule = defineModule({
4
6
  id: "shipping",
5
7
  dependencies: ["catalog"],
6
- schema: () => ({}),
8
+ schema: () => ({ shippingZones, shippingRates }),
7
9
  service: (deps) => new ShippingService({
8
10
  config: deps.config,
9
11
  catalogRepository: deps.services.catalog.repository,
12
+ repository: new ShippingConfigRepository(deps.db.db),
10
13
  }),
11
14
  });
@@ -1,5 +1,30 @@
1
1
  import type { TxContext } from "../../../kernel/database/tx-context.js";
2
- export interface ShippingRepository {
3
- ping(ctx: TxContext): Promise<void>;
2
+ import type { DrizzleDatabase } from "../../../kernel/database/drizzle-db.js";
3
+ import { shippingZones, shippingRates } from "../schema.js";
4
+ export type ShippingZone = typeof shippingZones.$inferSelect;
5
+ export type ShippingZoneInsert = typeof shippingZones.$inferInsert;
6
+ export type ShippingRate = typeof shippingRates.$inferSelect;
7
+ export type ShippingRateInsert = typeof shippingRates.$inferInsert;
8
+ /**
9
+ * Persistence for runtime shipping zones & rates (issue #45).
10
+ */
11
+ export declare class ShippingConfigRepository {
12
+ private readonly db;
13
+ constructor(db: DrizzleDatabase);
14
+ private getDb;
15
+ findZoneById(orgId: string, id: string, ctx?: TxContext): Promise<ShippingZone | undefined>;
16
+ findZones(orgId: string, ctx?: TxContext): Promise<ShippingZone[]>;
17
+ findActiveZones(orgId: string, ctx?: TxContext): Promise<ShippingZone[]>;
18
+ createZone(data: ShippingZoneInsert, ctx?: TxContext): Promise<ShippingZone>;
19
+ updateZone(id: string, data: Partial<Omit<ShippingZoneInsert, "id" | "organizationId">>, ctx?: TxContext): Promise<ShippingZone | undefined>;
20
+ deleteZone(id: string, ctx?: TxContext): Promise<boolean>;
21
+ findRateById(orgId: string, id: string, ctx?: TxContext): Promise<ShippingRate | undefined>;
22
+ findRates(orgId: string, filter?: {
23
+ zoneId?: string;
24
+ }, ctx?: TxContext): Promise<ShippingRate[]>;
25
+ findActiveRatesByZoneId(zoneId: string, ctx?: TxContext): Promise<ShippingRate[]>;
26
+ createRate(data: ShippingRateInsert, ctx?: TxContext): Promise<ShippingRate>;
27
+ updateRate(id: string, data: Partial<Omit<ShippingRateInsert, "id" | "organizationId">>, ctx?: TxContext): Promise<ShippingRate | undefined>;
28
+ deleteRate(id: string, ctx?: TxContext): Promise<boolean>;
4
29
  }
5
30
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/shipping/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/shipping/repository/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAC7D,MAAM,MAAM,kBAAkB,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AACnE,MAAM,MAAM,YAAY,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAC7D,MAAM,MAAM,kBAAkB,GAAG,OAAO,aAAa,CAAC,YAAY,CAAC;AAEnE;;GAEG;AACH,qBAAa,wBAAwB;IACvB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAEhD,OAAO,CAAC,KAAK;IAMP,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAS3F,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IASlE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IASxE,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC;IAM5E,UAAU,CACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAChE,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAU9B,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAQzD,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAS3F,SAAS,CACb,KAAK,EAAE,MAAM,EACb,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAC5B,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,YAAY,EAAE,CAAC;IAWpB,uBAAuB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IASjF,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC;IAM5E,UAAU,CACd,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAChE,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC;IAU9B,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;CAKhE"}
@@ -1 +1,105 @@
1
- export {};
1
+ import { eq, and, asc } from "drizzle-orm";
2
+ import { shippingZones, shippingRates } from "../schema.js";
3
+ /**
4
+ * Persistence for runtime shipping zones & rates (issue #45).
5
+ */
6
+ export class ShippingConfigRepository {
7
+ db;
8
+ constructor(db) {
9
+ this.db = db;
10
+ }
11
+ getDb(ctx) {
12
+ return ctx?.tx ?? this.db;
13
+ }
14
+ // ── Zones ──────────────────────────────────────────────────────────────
15
+ async findZoneById(orgId, id, ctx) {
16
+ const db = this.getDb(ctx);
17
+ const rows = await db
18
+ .select()
19
+ .from(shippingZones)
20
+ .where(and(eq(shippingZones.organizationId, orgId), eq(shippingZones.id, id)));
21
+ return rows[0];
22
+ }
23
+ async findZones(orgId, ctx) {
24
+ const db = this.getDb(ctx);
25
+ return db
26
+ .select()
27
+ .from(shippingZones)
28
+ .where(eq(shippingZones.organizationId, orgId))
29
+ .orderBy(asc(shippingZones.priority));
30
+ }
31
+ async findActiveZones(orgId, ctx) {
32
+ const db = this.getDb(ctx);
33
+ return db
34
+ .select()
35
+ .from(shippingZones)
36
+ .where(and(eq(shippingZones.organizationId, orgId), eq(shippingZones.isActive, true)))
37
+ .orderBy(asc(shippingZones.priority));
38
+ }
39
+ async createZone(data, ctx) {
40
+ const db = this.getDb(ctx);
41
+ const rows = await db.insert(shippingZones).values(data).returning();
42
+ return rows[0];
43
+ }
44
+ async updateZone(id, data, ctx) {
45
+ const db = this.getDb(ctx);
46
+ const rows = await db
47
+ .update(shippingZones)
48
+ .set({ ...data, updatedAt: new Date() })
49
+ .where(eq(shippingZones.id, id))
50
+ .returning();
51
+ return rows[0];
52
+ }
53
+ async deleteZone(id, ctx) {
54
+ const db = this.getDb(ctx);
55
+ const rows = await db.delete(shippingZones).where(eq(shippingZones.id, id)).returning();
56
+ return rows.length > 0;
57
+ }
58
+ // ── Rates ──────────────────────────────────────────────────────────────
59
+ async findRateById(orgId, id, ctx) {
60
+ const db = this.getDb(ctx);
61
+ const rows = await db
62
+ .select()
63
+ .from(shippingRates)
64
+ .where(and(eq(shippingRates.organizationId, orgId), eq(shippingRates.id, id)));
65
+ return rows[0];
66
+ }
67
+ async findRates(orgId, filter, ctx) {
68
+ const db = this.getDb(ctx);
69
+ const conditions = [eq(shippingRates.organizationId, orgId)];
70
+ if (filter?.zoneId)
71
+ conditions.push(eq(shippingRates.zoneId, filter.zoneId));
72
+ return db
73
+ .select()
74
+ .from(shippingRates)
75
+ .where(and(...conditions))
76
+ .orderBy(asc(shippingRates.amount));
77
+ }
78
+ async findActiveRatesByZoneId(zoneId, ctx) {
79
+ const db = this.getDb(ctx);
80
+ return db
81
+ .select()
82
+ .from(shippingRates)
83
+ .where(and(eq(shippingRates.zoneId, zoneId), eq(shippingRates.isActive, true)))
84
+ .orderBy(asc(shippingRates.amount));
85
+ }
86
+ async createRate(data, ctx) {
87
+ const db = this.getDb(ctx);
88
+ const rows = await db.insert(shippingRates).values(data).returning();
89
+ return rows[0];
90
+ }
91
+ async updateRate(id, data, ctx) {
92
+ const db = this.getDb(ctx);
93
+ const rows = await db
94
+ .update(shippingRates)
95
+ .set({ ...data, updatedAt: new Date() })
96
+ .where(eq(shippingRates.id, id))
97
+ .returning();
98
+ return rows[0];
99
+ }
100
+ async deleteRate(id, ctx) {
101
+ const db = this.getDb(ctx);
102
+ const rows = await db.delete(shippingRates).where(eq(shippingRates.id, id)).returning();
103
+ return rows.length > 0;
104
+ }
105
+ }