@porulle/core 0.7.0 → 0.9.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 (325) hide show
  1. package/dist/auth/middleware.d.ts.map +1 -1
  2. package/dist/auth/middleware.js +26 -7
  3. package/dist/auth/setup.d.ts.map +1 -1
  4. package/dist/auth/setup.js +8 -5
  5. package/dist/config/defaults.d.ts.map +1 -1
  6. package/dist/config/defaults.js +7 -0
  7. package/dist/config/types.d.ts +26 -1
  8. package/dist/config/types.d.ts.map +1 -1
  9. package/dist/generated/plugin-manifest.d.ts +3 -0
  10. package/dist/generated/plugin-manifest.d.ts.map +1 -1
  11. package/dist/generated/plugin-manifest.js +1 -0
  12. package/dist/hooks/checkout-completion.d.ts.map +1 -1
  13. package/dist/hooks/checkout-completion.js +2 -2
  14. package/dist/hooks/checkout.d.ts.map +1 -1
  15. package/dist/hooks/checkout.js +17 -2
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -0
  19. package/dist/interfaces/rest/index.d.ts.map +1 -1
  20. package/dist/interfaces/rest/index.js +12 -0
  21. package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
  22. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
  23. package/dist/interfaces/rest/routes/admin/staff.js +206 -0
  24. package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
  25. package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
  26. package/dist/interfaces/rest/routes/analytics.js +24 -0
  27. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  28. package/dist/interfaces/rest/routes/carts.js +39 -2
  29. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  30. package/dist/interfaces/rest/routes/catalog.js +17 -1
  31. package/dist/interfaces/rest/routes/checkout.d.ts +9 -0
  32. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  33. package/dist/interfaces/rest/routes/checkout.js +97 -21
  34. package/dist/interfaces/rest/routes/documents.d.ts +10 -0
  35. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
  36. package/dist/interfaces/rest/routes/documents.js +40 -0
  37. package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
  38. package/dist/interfaces/rest/routes/inventory.js +13 -0
  39. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  40. package/dist/interfaces/rest/routes/orders.js +111 -1
  41. package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
  42. package/dist/interfaces/rest/routes/pricing.js +36 -1
  43. package/dist/interfaces/rest/routes/settings.d.ts +5 -0
  44. package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
  45. package/dist/interfaces/rest/routes/settings.js +50 -0
  46. package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
  47. package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
  48. package/dist/interfaces/rest/routes/shipping.js +68 -0
  49. package/dist/interfaces/rest/routes/tax.d.ts +5 -0
  50. package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
  51. package/dist/interfaces/rest/routes/tax.js +68 -0
  52. package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
  53. package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
  54. package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
  55. package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
  56. package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
  57. package/dist/interfaces/rest/schemas/analytics.js +33 -0
  58. package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
  59. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  60. package/dist/interfaces/rest/schemas/carts.js +38 -0
  61. package/dist/interfaces/rest/schemas/catalog.d.ts +1218 -1
  62. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  63. package/dist/interfaces/rest/schemas/catalog.js +48 -0
  64. package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
  65. package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
  66. package/dist/interfaces/rest/schemas/checkout.js +4 -0
  67. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  68. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  69. package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
  70. package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
  71. package/dist/interfaces/rest/schemas/documents.js +65 -0
  72. package/dist/interfaces/rest/schemas/orders.d.ts +3110 -43
  73. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  74. package/dist/interfaces/rest/schemas/orders.js +234 -1
  75. package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
  76. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  77. package/dist/interfaces/rest/schemas/pricing.js +70 -0
  78. package/dist/interfaces/rest/schemas/responses.d.ts +190 -0
  79. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  80. package/dist/interfaces/rest/schemas/search.d.ts +17 -0
  81. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  82. package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
  83. package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
  84. package/dist/interfaces/rest/schemas/settings.js +79 -0
  85. package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
  86. package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
  87. package/dist/interfaces/rest/schemas/shipping.js +146 -0
  88. package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
  89. package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
  90. package/dist/interfaces/rest/schemas/tax.js +132 -0
  91. package/dist/kernel/database/schema.d.ts +4 -0
  92. package/dist/kernel/database/schema.d.ts.map +1 -1
  93. package/dist/kernel/database/schema.js +8 -0
  94. package/dist/kernel/database/scoped-db.d.ts +2 -1
  95. package/dist/kernel/database/scoped-db.d.ts.map +1 -1
  96. package/dist/kernel/database/scoped-db.js +62 -1
  97. package/dist/kernel/hooks/types.d.ts +1 -1
  98. package/dist/kernel/hooks/types.d.ts.map +1 -1
  99. package/dist/kernel/plugin/manifest.d.ts +26 -0
  100. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  101. package/dist/kernel/plugin/manifest.js +17 -2
  102. package/dist/modules/analytics/drizzle-adapter.d.ts.map +1 -1
  103. package/dist/modules/analytics/drizzle-adapter.js +16 -3
  104. package/dist/modules/analytics/module.d.ts.map +1 -1
  105. package/dist/modules/analytics/module.js +6 -0
  106. package/dist/modules/analytics/reports.d.ts +58 -0
  107. package/dist/modules/analytics/reports.d.ts.map +1 -0
  108. package/dist/modules/analytics/reports.js +249 -0
  109. package/dist/modules/analytics/service.d.ts +10 -0
  110. package/dist/modules/analytics/service.d.ts.map +1 -1
  111. package/dist/modules/analytics/service.js +14 -0
  112. package/dist/modules/cart/repository/index.d.ts +7 -0
  113. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  114. package/dist/modules/cart/repository/index.js +25 -1
  115. package/dist/modules/cart/schema.d.ts +17 -0
  116. package/dist/modules/cart/schema.d.ts.map +1 -1
  117. package/dist/modules/cart/schema.js +2 -0
  118. package/dist/modules/cart/schemas.d.ts +1 -0
  119. package/dist/modules/cart/schemas.d.ts.map +1 -1
  120. package/dist/modules/cart/schemas.js +1 -0
  121. package/dist/modules/cart/service.d.ts +32 -0
  122. package/dist/modules/cart/service.d.ts.map +1 -1
  123. package/dist/modules/cart/service.js +61 -2
  124. package/dist/modules/catalog/entity-service.d.ts +21 -0
  125. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  126. package/dist/modules/catalog/entity-service.js +166 -5
  127. package/dist/modules/catalog/schema.d.ts +34 -0
  128. package/dist/modules/catalog/schema.d.ts.map +1 -1
  129. package/dist/modules/catalog/schema.js +5 -0
  130. package/dist/modules/catalog/schemas.d.ts +3 -0
  131. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  132. package/dist/modules/catalog/schemas.js +3 -0
  133. package/dist/modules/catalog/service.d.ts +36 -0
  134. package/dist/modules/catalog/service.d.ts.map +1 -1
  135. package/dist/modules/catalog/service.js +6 -0
  136. package/dist/modules/customers/service.d.ts.map +1 -1
  137. package/dist/modules/customers/service.js +6 -0
  138. package/dist/modules/documents/module.d.ts +7 -0
  139. package/dist/modules/documents/module.d.ts.map +1 -0
  140. package/dist/modules/documents/module.js +12 -0
  141. package/dist/modules/documents/pdf.d.ts +25 -0
  142. package/dist/modules/documents/pdf.d.ts.map +1 -0
  143. package/dist/modules/documents/pdf.js +80 -0
  144. package/dist/modules/documents/render.d.ts +32 -0
  145. package/dist/modules/documents/render.d.ts.map +1 -0
  146. package/dist/modules/documents/render.js +190 -0
  147. package/dist/modules/documents/repository/index.d.ts +25 -0
  148. package/dist/modules/documents/repository/index.d.ts.map +1 -0
  149. package/dist/modules/documents/repository/index.js +47 -0
  150. package/dist/modules/documents/schema.d.ts +214 -0
  151. package/dist/modules/documents/schema.d.ts.map +1 -0
  152. package/dist/modules/documents/schema.js +42 -0
  153. package/dist/modules/documents/service.d.ts +51 -0
  154. package/dist/modules/documents/service.d.ts.map +1 -0
  155. package/dist/modules/documents/service.js +119 -0
  156. package/dist/modules/fulfillment/service.d.ts +18 -0
  157. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  158. package/dist/modules/fulfillment/service.js +85 -1
  159. package/dist/modules/orders/repository/index.d.ts +18 -1
  160. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  161. package/dist/modules/orders/repository/index.js +86 -1
  162. package/dist/modules/orders/schema.d.ts +391 -0
  163. package/dist/modules/orders/schema.d.ts.map +1 -1
  164. package/dist/modules/orders/schema.js +56 -1
  165. package/dist/modules/orders/service.d.ts +102 -3
  166. package/dist/modules/orders/service.d.ts.map +1 -1
  167. package/dist/modules/orders/service.js +653 -29
  168. package/dist/modules/orders/stale-order-cleanup.d.ts.map +1 -1
  169. package/dist/modules/orders/stale-order-cleanup.js +13 -1
  170. package/dist/modules/pricing/repository/index.d.ts +5 -0
  171. package/dist/modules/pricing/repository/index.d.ts.map +1 -1
  172. package/dist/modules/pricing/repository/index.js +19 -0
  173. package/dist/modules/pricing/service.d.ts +18 -0
  174. package/dist/modules/pricing/service.d.ts.map +1 -1
  175. package/dist/modules/pricing/service.js +43 -5
  176. package/dist/modules/settings/module.d.ts +6 -0
  177. package/dist/modules/settings/module.d.ts.map +1 -0
  178. package/dist/modules/settings/module.js +11 -0
  179. package/dist/modules/settings/repository/index.d.ts +16 -0
  180. package/dist/modules/settings/repository/index.d.ts.map +1 -0
  181. package/dist/modules/settings/repository/index.js +41 -0
  182. package/dist/modules/settings/schema.d.ts +121 -0
  183. package/dist/modules/settings/schema.d.ts.map +1 -0
  184. package/dist/modules/settings/schema.js +24 -0
  185. package/dist/modules/settings/service.d.ts +26 -0
  186. package/dist/modules/settings/service.d.ts.map +1 -0
  187. package/dist/modules/settings/service.js +51 -0
  188. package/dist/modules/shipping/calculator.d.ts +5 -0
  189. package/dist/modules/shipping/calculator.d.ts.map +1 -1
  190. package/dist/modules/shipping/calculator.js +10 -0
  191. package/dist/modules/shipping/module.d.ts +5 -1
  192. package/dist/modules/shipping/module.d.ts.map +1 -1
  193. package/dist/modules/shipping/module.js +4 -1
  194. package/dist/modules/shipping/repository/index.d.ts +27 -2
  195. package/dist/modules/shipping/repository/index.d.ts.map +1 -1
  196. package/dist/modules/shipping/repository/index.js +105 -1
  197. package/dist/modules/shipping/schema.d.ts +419 -0
  198. package/dist/modules/shipping/schema.d.ts.map +1 -0
  199. package/dist/modules/shipping/schema.js +56 -0
  200. package/dist/modules/shipping/service.d.ts +54 -0
  201. package/dist/modules/shipping/service.d.ts.map +1 -1
  202. package/dist/modules/shipping/service.js +161 -2
  203. package/dist/modules/tax/adapter.d.ts +13 -0
  204. package/dist/modules/tax/adapter.d.ts.map +1 -1
  205. package/dist/modules/tax/module.d.ts +5 -1
  206. package/dist/modules/tax/module.d.ts.map +1 -1
  207. package/dist/modules/tax/module.js +4 -1
  208. package/dist/modules/tax/repository/index.d.ts +38 -2
  209. package/dist/modules/tax/repository/index.d.ts.map +1 -1
  210. package/dist/modules/tax/repository/index.js +138 -1
  211. package/dist/modules/tax/schema.d.ts +355 -0
  212. package/dist/modules/tax/schema.d.ts.map +1 -0
  213. package/dist/modules/tax/schema.js +55 -0
  214. package/dist/modules/tax/service.d.ts +60 -1
  215. package/dist/modules/tax/service.d.ts.map +1 -1
  216. package/dist/modules/tax/service.js +227 -2
  217. package/dist/runtime/kernel-modules.d.ts +15 -2
  218. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  219. package/dist/runtime/kernel-modules.js +4 -0
  220. package/dist/runtime/kernel-types.d.ts +5 -1
  221. package/dist/runtime/kernel-types.d.ts.map +1 -1
  222. package/dist/runtime/kernel-types.js +2 -0
  223. package/dist/runtime/server.js +1 -1
  224. package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
  225. package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
  226. package/dist/test-utils/create-plugin-test-app.js +15 -3
  227. package/dist/test-utils/create-test-config.d.ts.map +1 -1
  228. package/dist/test-utils/create-test-config.js +2 -0
  229. package/dist/test-utils/order-test-helpers.d.ts +12 -0
  230. package/dist/test-utils/order-test-helpers.d.ts.map +1 -0
  231. package/dist/test-utils/order-test-helpers.js +18 -0
  232. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  233. package/dist/test-utils/rest-api-test-utils.js +7 -0
  234. package/dist/test-utils/test-actors.js +1 -1
  235. package/dist/testing.d.ts +1 -0
  236. package/dist/testing.d.ts.map +1 -1
  237. package/dist/testing.js +1 -0
  238. package/package.json +3 -3
  239. package/src/auth/middleware.ts +26 -7
  240. package/src/auth/setup.ts +11 -4
  241. package/src/config/defaults.ts +7 -0
  242. package/src/config/types.ts +26 -1
  243. package/src/generated/plugin-capabilities.d.ts +1 -0
  244. package/src/generated/plugin-manifest.ts +1 -0
  245. package/src/generated/plugin-repositories.d.ts +1 -0
  246. package/src/hooks/checkout-completion.ts +22 -16
  247. package/src/hooks/checkout.ts +36 -11
  248. package/src/index.ts +1 -0
  249. package/src/interfaces/rest/index.ts +12 -0
  250. package/src/interfaces/rest/routes/admin/staff.ts +260 -0
  251. package/src/interfaces/rest/routes/analytics.ts +33 -0
  252. package/src/interfaces/rest/routes/carts.ts +52 -2
  253. package/src/interfaces/rest/routes/catalog.ts +34 -0
  254. package/src/interfaces/rest/routes/checkout.ts +114 -27
  255. package/src/interfaces/rest/routes/documents.ts +61 -0
  256. package/src/interfaces/rest/routes/inventory.ts +14 -0
  257. package/src/interfaces/rest/routes/orders.ts +148 -1
  258. package/src/interfaces/rest/routes/pricing.ts +44 -1
  259. package/src/interfaces/rest/routes/settings.ts +61 -0
  260. package/src/interfaces/rest/routes/shipping.ts +92 -0
  261. package/src/interfaces/rest/routes/tax.ts +90 -0
  262. package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
  263. package/src/interfaces/rest/schemas/analytics.ts +39 -0
  264. package/src/interfaces/rest/schemas/carts.ts +40 -0
  265. package/src/interfaces/rest/schemas/catalog.ts +53 -0
  266. package/src/interfaces/rest/schemas/checkout.ts +4 -0
  267. package/src/interfaces/rest/schemas/documents.ts +72 -0
  268. package/src/interfaces/rest/schemas/orders.ts +258 -1
  269. package/src/interfaces/rest/schemas/pricing.ts +75 -0
  270. package/src/interfaces/rest/schemas/settings.ts +93 -0
  271. package/src/interfaces/rest/schemas/shipping.ts +164 -0
  272. package/src/interfaces/rest/schemas/tax.ts +148 -0
  273. package/src/kernel/database/schema.ts +12 -0
  274. package/src/kernel/database/scoped-db.ts +70 -1
  275. package/src/kernel/hooks/types.ts +1 -0
  276. package/src/kernel/plugin/manifest.ts +41 -2
  277. package/src/modules/analytics/drizzle-adapter.ts +19 -3
  278. package/src/modules/analytics/module.ts +10 -0
  279. package/src/modules/analytics/reports.ts +311 -0
  280. package/src/modules/analytics/service.ts +23 -0
  281. package/src/modules/cart/repository/index.ts +30 -1
  282. package/src/modules/cart/schema.ts +2 -0
  283. package/src/modules/cart/schemas.ts +1 -0
  284. package/src/modules/cart/service.ts +104 -2
  285. package/src/modules/catalog/entity-service.ts +196 -4
  286. package/src/modules/catalog/schema.ts +5 -0
  287. package/src/modules/catalog/schemas.ts +3 -0
  288. package/src/modules/catalog/service.ts +30 -0
  289. package/src/modules/customers/service.ts +6 -0
  290. package/src/modules/documents/module.ts +19 -0
  291. package/src/modules/documents/pdf.ts +101 -0
  292. package/src/modules/documents/render.ts +231 -0
  293. package/src/modules/documents/repository/index.ts +68 -0
  294. package/src/modules/documents/schema.ts +44 -0
  295. package/src/modules/documents/service.ts +176 -0
  296. package/src/modules/fulfillment/service.ts +139 -1
  297. package/src/modules/orders/repository/index.ts +127 -1
  298. package/src/modules/orders/schema.ts +58 -1
  299. package/src/modules/orders/service.ts +1041 -65
  300. package/src/modules/orders/stale-order-cleanup.ts +18 -4
  301. package/src/modules/pricing/repository/index.ts +34 -0
  302. package/src/modules/pricing/service.ts +76 -6
  303. package/src/modules/settings/module.ts +18 -0
  304. package/src/modules/settings/repository/index.ts +59 -0
  305. package/src/modules/settings/schema.ts +25 -0
  306. package/src/modules/settings/service.ts +82 -0
  307. package/src/modules/shipping/calculator.ts +27 -0
  308. package/src/modules/shipping/module.ts +6 -2
  309. package/src/modules/shipping/repository/index.ts +136 -2
  310. package/src/modules/shipping/schema.ts +58 -0
  311. package/src/modules/shipping/service.ts +260 -1
  312. package/src/modules/tax/adapter.ts +9 -0
  313. package/src/modules/tax/module.ts +6 -2
  314. package/src/modules/tax/repository/index.ts +169 -2
  315. package/src/modules/tax/schema.ts +57 -0
  316. package/src/modules/tax/service.ts +313 -2
  317. package/src/runtime/kernel-modules.ts +4 -0
  318. package/src/runtime/kernel-types.ts +6 -0
  319. package/src/runtime/server.ts +1 -1
  320. package/src/test-utils/create-plugin-test-app.ts +17 -4
  321. package/src/test-utils/create-test-config.ts +2 -0
  322. package/src/test-utils/order-test-helpers.ts +26 -0
  323. package/src/test-utils/rest-api-test-utils.ts +7 -0
  324. package/src/test-utils/test-actors.ts +1 -1
  325. package/src/testing.ts +1 -0
@@ -1,5 +1,7 @@
1
- import { Ok } from "../../kernel/result.js";
2
- import { calculateShippingCost, } from "./calculator.js";
1
+ import { resolveOrgId } from "../../auth/org.js";
2
+ import { CommerceNotFoundError, CommerceValidationError } from "../../kernel/errors.js";
3
+ import { Err, Ok } from "../../kernel/result.js";
4
+ import { calculateShippingCost, computeShippableWeightGrams, } from "./calculator.js";
3
5
  export class ShippingService {
4
6
  deps;
5
7
  catalogRepo;
@@ -8,6 +10,13 @@ export class ShippingService {
8
10
  this.catalogRepo = deps.catalogRepository;
9
11
  }
10
12
  async calculate(input, ctx) {
13
+ // Runtime zones (issue #45) take precedence when they exist and the
14
+ // destination is known; the code-config strategy remains the fallback.
15
+ if (!input.isFreeShipping && input.orgId && input.address) {
16
+ const zoneResult = await this.calculateFromZones(input, ctx);
17
+ if (zoneResult)
18
+ return Ok(zoneResult);
19
+ }
11
20
  const result = await calculateShippingCost(this.deps.config, this.catalogRepo, {
12
21
  lineItems: input.lineItems,
13
22
  subtotalAfterDiscount: input.subtotalAfterDiscount,
@@ -17,4 +26,154 @@ export class ShippingService {
17
26
  }, ctx);
18
27
  return Ok(result);
19
28
  }
29
+ zoneMatches(zone, address) {
30
+ const country = address.country?.toUpperCase();
31
+ const countries = zone.countries.map((c) => c.toUpperCase());
32
+ if (!countries.includes("*") && (!country || !countries.includes(country))) {
33
+ return false;
34
+ }
35
+ if (zone.states.length > 0) {
36
+ const state = address.state?.toUpperCase();
37
+ if (!state || !zone.states.map((s) => s.toUpperCase()).includes(state)) {
38
+ return false;
39
+ }
40
+ }
41
+ return true;
42
+ }
43
+ async calculateFromZones(input, ctx) {
44
+ const zones = await this.deps.repository.findActiveZones(input.orgId, ctx);
45
+ if (zones.length === 0)
46
+ return null;
47
+ let weightGrams = null;
48
+ for (const zone of zones) {
49
+ if (!this.zoneMatches(zone, input.address))
50
+ continue;
51
+ const rates = (await this.deps.repository.findActiveRatesByZoneId(zone.id, ctx)).filter((rate) => rate.currency === input.currency);
52
+ for (const rate of rates) {
53
+ const sub = input.subtotalAfterDiscount;
54
+ if (rate.minSubtotal != null && sub < rate.minSubtotal)
55
+ continue;
56
+ if (rate.maxSubtotal != null && sub > rate.maxSubtotal)
57
+ continue;
58
+ if (rate.minWeightGrams != null || rate.maxWeightGrams != null) {
59
+ weightGrams ??= await computeShippableWeightGrams(this.deps.config, this.catalogRepo, input.lineItems, ctx);
60
+ if (rate.minWeightGrams != null && weightGrams < rate.minWeightGrams)
61
+ continue;
62
+ if (rate.maxWeightGrams != null && weightGrams > rate.maxWeightGrams)
63
+ continue;
64
+ }
65
+ if (rate.freeShippingThreshold != null && sub >= rate.freeShippingThreshold) {
66
+ return {
67
+ amount: 0,
68
+ strategy: `zone:${zone.name}:free_shipping`,
69
+ weightGrams: weightGrams ?? 0,
70
+ };
71
+ }
72
+ return {
73
+ amount: Math.max(0, Math.round(rate.amount)),
74
+ strategy: `zone:${zone.name}:${rate.name}`,
75
+ weightGrams: weightGrams ?? 0,
76
+ };
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ // ── Runtime zone & rate management (issue #45) ─────────────────────────
82
+ async createZone(input, actor, ctx) {
83
+ if (input.countries.length === 0) {
84
+ return Err(new CommerceValidationError("A shipping zone requires at least one country (\"*\" matches any)."));
85
+ }
86
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
87
+ const zone = await this.deps.repository.createZone({
88
+ organizationId: orgId,
89
+ name: input.name,
90
+ countries: input.countries.map((c) => c.toUpperCase()),
91
+ states: (input.states ?? []).map((s) => s.toUpperCase()),
92
+ priority: input.priority ?? 100,
93
+ isActive: input.isActive ?? true,
94
+ }, ctx);
95
+ return Ok(zone);
96
+ }
97
+ async listZones(actor, ctx) {
98
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
99
+ return Ok(await this.deps.repository.findZones(orgId, ctx));
100
+ }
101
+ async updateZone(id, patch, actor, ctx) {
102
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
103
+ const existing = await this.deps.repository.findZoneById(orgId, id, ctx);
104
+ if (!existing)
105
+ return Err(new CommerceNotFoundError("Shipping zone not found."));
106
+ const updated = await this.deps.repository.updateZone(id, {
107
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
108
+ ...(patch.countries !== undefined
109
+ ? { countries: patch.countries.map((c) => c.toUpperCase()) }
110
+ : {}),
111
+ ...(patch.states !== undefined
112
+ ? { states: patch.states.map((s) => s.toUpperCase()) }
113
+ : {}),
114
+ ...(patch.priority !== undefined ? { priority: patch.priority } : {}),
115
+ ...(patch.isActive !== undefined ? { isActive: patch.isActive } : {}),
116
+ }, ctx);
117
+ return Ok(updated);
118
+ }
119
+ async deleteZone(id, actor, ctx) {
120
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
121
+ const existing = await this.deps.repository.findZoneById(orgId, id, ctx);
122
+ if (!existing)
123
+ return Err(new CommerceNotFoundError("Shipping zone not found."));
124
+ await this.deps.repository.deleteZone(id, ctx);
125
+ return Ok({ deleted: true });
126
+ }
127
+ async createRate(input, actor, ctx) {
128
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
129
+ const zone = await this.deps.repository.findZoneById(orgId, input.zoneId, ctx);
130
+ if (!zone)
131
+ return Err(new CommerceNotFoundError("Shipping zone not found."));
132
+ const rate = await this.deps.repository.createRate({
133
+ organizationId: orgId,
134
+ zoneId: input.zoneId,
135
+ name: input.name,
136
+ amount: input.amount,
137
+ currency: input.currency ?? "USD",
138
+ minSubtotal: input.minSubtotal ?? null,
139
+ maxSubtotal: input.maxSubtotal ?? null,
140
+ minWeightGrams: input.minWeightGrams ?? null,
141
+ maxWeightGrams: input.maxWeightGrams ?? null,
142
+ freeShippingThreshold: input.freeShippingThreshold ?? null,
143
+ isActive: input.isActive ?? true,
144
+ }, ctx);
145
+ return Ok(rate);
146
+ }
147
+ async listRates(filter, actor, ctx) {
148
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
149
+ return Ok(await this.deps.repository.findRates(orgId, filter?.zoneId !== undefined ? { zoneId: filter.zoneId } : undefined, ctx));
150
+ }
151
+ async updateRate(id, patch, actor, ctx) {
152
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
153
+ const existing = await this.deps.repository.findRateById(orgId, id, ctx);
154
+ if (!existing)
155
+ return Err(new CommerceNotFoundError("Shipping rate not found."));
156
+ const updated = await this.deps.repository.updateRate(id, {
157
+ ...(patch.name !== undefined ? { name: patch.name } : {}),
158
+ ...(patch.amount !== undefined ? { amount: patch.amount } : {}),
159
+ ...(patch.currency !== undefined ? { currency: patch.currency } : {}),
160
+ ...(patch.minSubtotal !== undefined ? { minSubtotal: patch.minSubtotal } : {}),
161
+ ...(patch.maxSubtotal !== undefined ? { maxSubtotal: patch.maxSubtotal } : {}),
162
+ ...(patch.minWeightGrams !== undefined ? { minWeightGrams: patch.minWeightGrams } : {}),
163
+ ...(patch.maxWeightGrams !== undefined ? { maxWeightGrams: patch.maxWeightGrams } : {}),
164
+ ...(patch.freeShippingThreshold !== undefined
165
+ ? { freeShippingThreshold: patch.freeShippingThreshold }
166
+ : {}),
167
+ ...(patch.isActive !== undefined ? { isActive: patch.isActive } : {}),
168
+ }, ctx);
169
+ return Ok(updated);
170
+ }
171
+ async deleteRate(id, actor, ctx) {
172
+ const orgId = resolveOrgId(actor ?? ctx?.actor ?? null);
173
+ const existing = await this.deps.repository.findRateById(orgId, id, ctx);
174
+ if (!existing)
175
+ return Err(new CommerceNotFoundError("Shipping rate not found."));
176
+ await this.deps.repository.deleteRate(id, ctx);
177
+ return Ok({ deleted: true });
178
+ }
20
179
  }
@@ -9,6 +9,8 @@ export interface TaxAddress {
9
9
  export interface TaxLineItem {
10
10
  id: string;
11
11
  entityId: string;
12
+ /** Variant reference — a variant-level taxClass overrides the entity's (issue #57). */
13
+ variantId?: string;
12
14
  description: string;
13
15
  quantity: number;
14
16
  unitPrice: number;
@@ -22,6 +24,11 @@ export interface TaxCalculationParams {
22
24
  fromAddress?: TaxAddress;
23
25
  toAddress?: TaxAddress;
24
26
  shippingAmount: number;
27
+ /**
28
+ * Cart-level discount not already reflected in per-line `discount` —
29
+ * pro-rated across lines before class-based tax (issue #57).
30
+ */
31
+ orderDiscount?: number;
25
32
  lineItems: TaxLineItem[];
26
33
  }
27
34
  export interface TaxCalculationResult {
@@ -29,6 +36,12 @@ export interface TaxCalculationResult {
29
36
  taxableAmount: number;
30
37
  rate: number;
31
38
  breakdown?: Record<string, unknown>;
39
+ /** Per-line results when product tax classes computed the tax (issue #57). */
40
+ lines?: Array<{
41
+ id: string;
42
+ taxClass: string | null;
43
+ taxAmount: number;
44
+ }>;
32
45
  }
33
46
  export interface TaxReportParams {
34
47
  transactionId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/modules/tax/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,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,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAClF,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACvF,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACpF"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/modules/tax/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAErD,MAAM,WAAW,UAAU;IACzB,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,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,oBAAoB;IACnC,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3E;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,UAAU,CAAC;IACzB,SAAS,CAAC,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAClF,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IACvF,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACpF"}
@@ -1,3 +1,7 @@
1
+ import { taxClasses, taxRates } from "./schema.js";
1
2
  import { TaxService } from "./service.js";
2
- export declare const taxModule: import("../../kernel/module/define.js").AppModule<Record<string, never>, TaxService, Record<string, never>>;
3
+ export declare const taxModule: import("../../kernel/module/define.js").AppModule<{
4
+ taxRates: typeof taxRates;
5
+ taxClasses: typeof taxClasses;
6
+ }, TaxService, Record<string, never>>;
3
7
  //# sourceMappingURL=module.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/tax/module.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,eAAO,MAAM,SAAS,6GAWpB,CAAC"}
1
+ {"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../../../src/modules/tax/module.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,eAAO,MAAM,SAAS;cACR,OAAO,QAAQ;gBAAc,OAAO,UAAU;qCAW1D,CAAC"}
@@ -1,9 +1,12 @@
1
1
  import { defineModule } from "../../kernel/module/index.js";
2
+ import { TaxRatesRepository } from "./repository/index.js";
3
+ import { taxClasses, taxRates } from "./schema.js";
2
4
  import { TaxService } from "./service.js";
3
5
  export const taxModule = defineModule({
4
6
  id: "tax",
5
- schema: () => ({}),
7
+ schema: () => ({ taxRates, taxClasses }),
6
8
  service: (deps) => new TaxService({
7
9
  adapter: deps.config.tax?.adapter,
10
+ repository: new TaxRatesRepository(deps.db.db),
8
11
  }),
9
12
  });
@@ -1,5 +1,41 @@
1
1
  import type { TxContext } from "../../../kernel/database/tx-context.js";
2
- export interface TaxRepository {
3
- ping(ctx: TxContext): Promise<void>;
2
+ import type { DrizzleDatabase } from "../../../kernel/database/drizzle-db.js";
3
+ import { taxClasses, taxRates } from "../schema.js";
4
+ export type TaxRate = typeof taxRates.$inferSelect;
5
+ export type TaxRateInsert = typeof taxRates.$inferInsert;
6
+ export type TaxClass = typeof taxClasses.$inferSelect;
7
+ export type TaxClassInsert = typeof taxClasses.$inferInsert;
8
+ /**
9
+ * Persistence for runtime tax rates (issue #45).
10
+ */
11
+ export declare class TaxRatesRepository {
12
+ private readonly db;
13
+ constructor(db: DrizzleDatabase);
14
+ private getDb;
15
+ findById(orgId: string, id: string, ctx?: TxContext): Promise<TaxRate | undefined>;
16
+ findAll(orgId: string, ctx?: TxContext): Promise<TaxRate[]>;
17
+ findActive(orgId: string, ctx?: TxContext): Promise<TaxRate[]>;
18
+ create(data: TaxRateInsert, ctx?: TxContext): Promise<TaxRate>;
19
+ update(id: string, data: Partial<Omit<TaxRateInsert, "id" | "organizationId">>, ctx?: TxContext): Promise<TaxRate | undefined>;
20
+ delete(id: string, ctx?: TxContext): Promise<boolean>;
21
+ findActiveClasses(orgId: string, ctx?: TxContext): Promise<TaxClass[]>;
22
+ findAllClasses(orgId: string, ctx?: TxContext): Promise<TaxClass[]>;
23
+ findClassById(orgId: string, id: string, ctx?: TxContext): Promise<TaxClass | undefined>;
24
+ createClass(data: TaxClassInsert, ctx?: TxContext): Promise<TaxClass>;
25
+ updateClass(id: string, data: Partial<Omit<TaxClassInsert, "id" | "organizationId">>, ctx?: TxContext): Promise<TaxClass | undefined>;
26
+ deleteClass(id: string, ctx?: TxContext): Promise<boolean>;
27
+ clearDefaultClass(orgId: string, ctx?: TxContext): Promise<void>;
28
+ /**
29
+ * Resolves the taxClass for catalog references: variant.taxClass overrides
30
+ * entity.taxClass. Returns maps keyed by id so callers can pick
31
+ * variant-first.
32
+ */
33
+ resolveCatalogTaxClasses(refs: Array<{
34
+ entityId: string;
35
+ variantId?: string | undefined;
36
+ }>, ctx?: TxContext): Promise<{
37
+ byEntity: Map<string, string | null>;
38
+ byVariant: Map<string, string | null>;
39
+ }>;
4
40
  }
5
41
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/tax/repository/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAExE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/modules/tax/repository/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AACxE,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,MAAM,OAAO,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AACnD,MAAM,MAAM,aAAa,GAAG,OAAO,QAAQ,CAAC,YAAY,CAAC;AACzD,MAAM,MAAM,QAAQ,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AACtD,MAAM,MAAM,cAAc,GAAG,OAAO,UAAU,CAAC,YAAY,CAAC;AAE5D;;GAEG;AACH,qBAAa,kBAAkB;IACjB,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,eAAe;IAEhD,OAAO,CAAC,KAAK;IAIP,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IASlF,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAS3D,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAS9D,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAM9D,MAAM,CACV,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAC3D,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAUzB,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrD,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IAStE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC;IASnE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IASxF,WAAW,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC;IAMrE,WAAW,CACf,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,GAAG,gBAAgB,CAAC,CAAC,EAC5D,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IAU1B,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC;IAM1D,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtE;;;;OAIG;IACG,wBAAwB,CAC5B,IAAI,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC,EACjE,GAAG,CAAC,EAAE,SAAS,GACd,OAAO,CAAC;QAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;QAAC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;CAuB5F"}
@@ -1 +1,138 @@
1
- export {};
1
+ import { eq, and, asc, inArray } from "drizzle-orm";
2
+ import { sellableEntities, variants } from "../../catalog/schema.js";
3
+ import { taxClasses, taxRates } from "../schema.js";
4
+ /**
5
+ * Persistence for runtime tax rates (issue #45).
6
+ */
7
+ export class TaxRatesRepository {
8
+ db;
9
+ constructor(db) {
10
+ this.db = db;
11
+ }
12
+ getDb(ctx) {
13
+ return ctx?.tx ?? this.db;
14
+ }
15
+ async findById(orgId, id, ctx) {
16
+ const db = this.getDb(ctx);
17
+ const rows = await db
18
+ .select()
19
+ .from(taxRates)
20
+ .where(and(eq(taxRates.organizationId, orgId), eq(taxRates.id, id)));
21
+ return rows[0];
22
+ }
23
+ async findAll(orgId, ctx) {
24
+ const db = this.getDb(ctx);
25
+ return db
26
+ .select()
27
+ .from(taxRates)
28
+ .where(eq(taxRates.organizationId, orgId))
29
+ .orderBy(asc(taxRates.priority));
30
+ }
31
+ async findActive(orgId, ctx) {
32
+ const db = this.getDb(ctx);
33
+ return db
34
+ .select()
35
+ .from(taxRates)
36
+ .where(and(eq(taxRates.organizationId, orgId), eq(taxRates.isActive, true)))
37
+ .orderBy(asc(taxRates.priority));
38
+ }
39
+ async create(data, ctx) {
40
+ const db = this.getDb(ctx);
41
+ const rows = await db.insert(taxRates).values(data).returning();
42
+ return rows[0];
43
+ }
44
+ async update(id, data, ctx) {
45
+ const db = this.getDb(ctx);
46
+ const rows = await db
47
+ .update(taxRates)
48
+ .set({ ...data, updatedAt: new Date() })
49
+ .where(eq(taxRates.id, id))
50
+ .returning();
51
+ return rows[0];
52
+ }
53
+ async delete(id, ctx) {
54
+ const db = this.getDb(ctx);
55
+ const rows = await db.delete(taxRates).where(eq(taxRates.id, id)).returning();
56
+ return rows.length > 0;
57
+ }
58
+ // ── Product tax classes (issue #57) ─────────────────────────────────────
59
+ async findActiveClasses(orgId, ctx) {
60
+ const db = this.getDb(ctx);
61
+ return db
62
+ .select()
63
+ .from(taxClasses)
64
+ .where(and(eq(taxClasses.organizationId, orgId), eq(taxClasses.isActive, true)))
65
+ .orderBy(asc(taxClasses.name));
66
+ }
67
+ async findAllClasses(orgId, ctx) {
68
+ const db = this.getDb(ctx);
69
+ return db
70
+ .select()
71
+ .from(taxClasses)
72
+ .where(eq(taxClasses.organizationId, orgId))
73
+ .orderBy(asc(taxClasses.name));
74
+ }
75
+ async findClassById(orgId, id, ctx) {
76
+ const db = this.getDb(ctx);
77
+ const rows = await db
78
+ .select()
79
+ .from(taxClasses)
80
+ .where(and(eq(taxClasses.organizationId, orgId), eq(taxClasses.id, id)));
81
+ return rows[0];
82
+ }
83
+ async createClass(data, ctx) {
84
+ const db = this.getDb(ctx);
85
+ const rows = await db.insert(taxClasses).values(data).returning();
86
+ return rows[0];
87
+ }
88
+ async updateClass(id, data, ctx) {
89
+ const db = this.getDb(ctx);
90
+ const rows = await db
91
+ .update(taxClasses)
92
+ .set({ ...data, updatedAt: new Date() })
93
+ .where(eq(taxClasses.id, id))
94
+ .returning();
95
+ return rows[0];
96
+ }
97
+ async deleteClass(id, ctx) {
98
+ const db = this.getDb(ctx);
99
+ const rows = await db.delete(taxClasses).where(eq(taxClasses.id, id)).returning();
100
+ return rows.length > 0;
101
+ }
102
+ async clearDefaultClass(orgId, ctx) {
103
+ const db = this.getDb(ctx);
104
+ await db
105
+ .update(taxClasses)
106
+ .set({ isDefault: false, updatedAt: new Date() })
107
+ .where(and(eq(taxClasses.organizationId, orgId), eq(taxClasses.isDefault, true)));
108
+ }
109
+ /**
110
+ * Resolves the taxClass for catalog references: variant.taxClass overrides
111
+ * entity.taxClass. Returns maps keyed by id so callers can pick
112
+ * variant-first.
113
+ */
114
+ async resolveCatalogTaxClasses(refs, ctx) {
115
+ const db = this.getDb(ctx);
116
+ const entityIds = [...new Set(refs.map((r) => r.entityId))];
117
+ const variantIds = [...new Set(refs.flatMap((r) => (r.variantId ? [r.variantId] : [])))];
118
+ const byEntity = new Map();
119
+ const byVariant = new Map();
120
+ if (entityIds.length > 0) {
121
+ const rows = await db
122
+ .select({ id: sellableEntities.id, taxClass: sellableEntities.taxClass })
123
+ .from(sellableEntities)
124
+ .where(inArray(sellableEntities.id, entityIds));
125
+ for (const row of rows)
126
+ byEntity.set(row.id, row.taxClass);
127
+ }
128
+ if (variantIds.length > 0) {
129
+ const rows = await db
130
+ .select({ id: variants.id, taxClass: variants.taxClass })
131
+ .from(variants)
132
+ .where(inArray(variants.id, variantIds));
133
+ for (const row of rows)
134
+ byVariant.set(row.id, row.taxClass);
135
+ }
136
+ return { byEntity, byVariant };
137
+ }
138
+ }