@porulle/core 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (289) hide show
  1. package/dist/auth/setup.d.ts.map +1 -1
  2. package/dist/auth/setup.js +1 -0
  3. package/dist/config/defaults.d.ts.map +1 -1
  4. package/dist/config/defaults.js +5 -0
  5. package/dist/config/types.d.ts +17 -1
  6. package/dist/config/types.d.ts.map +1 -1
  7. package/dist/generated/plugin-manifest.d.ts +3 -0
  8. package/dist/generated/plugin-manifest.d.ts.map +1 -1
  9. package/dist/generated/plugin-manifest.js +1 -0
  10. package/dist/hooks/checkout.d.ts.map +1 -1
  11. package/dist/hooks/checkout.js +16 -1
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +1 -0
  15. package/dist/interfaces/rest/index.d.ts.map +1 -1
  16. package/dist/interfaces/rest/index.js +12 -0
  17. package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
  18. package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
  19. package/dist/interfaces/rest/routes/admin/staff.js +172 -0
  20. package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
  21. package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
  22. package/dist/interfaces/rest/routes/analytics.js +24 -0
  23. package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
  24. package/dist/interfaces/rest/routes/carts.js +39 -2
  25. package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
  26. package/dist/interfaces/rest/routes/catalog.js +17 -1
  27. package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
  28. package/dist/interfaces/rest/routes/checkout.js +17 -0
  29. package/dist/interfaces/rest/routes/documents.d.ts +10 -0
  30. package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
  31. package/dist/interfaces/rest/routes/documents.js +40 -0
  32. package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
  33. package/dist/interfaces/rest/routes/orders.js +98 -1
  34. package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
  35. package/dist/interfaces/rest/routes/pricing.js +36 -1
  36. package/dist/interfaces/rest/routes/settings.d.ts +5 -0
  37. package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
  38. package/dist/interfaces/rest/routes/settings.js +50 -0
  39. package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
  40. package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
  41. package/dist/interfaces/rest/routes/shipping.js +68 -0
  42. package/dist/interfaces/rest/routes/tax.d.ts +5 -0
  43. package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
  44. package/dist/interfaces/rest/routes/tax.js +68 -0
  45. package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
  46. package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
  47. package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
  48. package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
  49. package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
  50. package/dist/interfaces/rest/schemas/analytics.js +33 -0
  51. package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
  52. package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
  53. package/dist/interfaces/rest/schemas/carts.js +38 -0
  54. package/dist/interfaces/rest/schemas/catalog.d.ts +1218 -1
  55. package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
  56. package/dist/interfaces/rest/schemas/catalog.js +48 -0
  57. package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
  58. package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
  59. package/dist/interfaces/rest/schemas/checkout.js +4 -0
  60. package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
  61. package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
  62. package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
  63. package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
  64. package/dist/interfaces/rest/schemas/documents.js +65 -0
  65. package/dist/interfaces/rest/schemas/orders.d.ts +3110 -43
  66. package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
  67. package/dist/interfaces/rest/schemas/orders.js +233 -0
  68. package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
  69. package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
  70. package/dist/interfaces/rest/schemas/pricing.js +70 -0
  71. package/dist/interfaces/rest/schemas/responses.d.ts +156 -0
  72. package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
  73. package/dist/interfaces/rest/schemas/search.d.ts +17 -0
  74. package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
  75. package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
  76. package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
  77. package/dist/interfaces/rest/schemas/settings.js +79 -0
  78. package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
  79. package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
  80. package/dist/interfaces/rest/schemas/shipping.js +146 -0
  81. package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
  82. package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
  83. package/dist/interfaces/rest/schemas/tax.js +132 -0
  84. package/dist/kernel/database/schema.d.ts +4 -0
  85. package/dist/kernel/database/schema.d.ts.map +1 -1
  86. package/dist/kernel/database/schema.js +8 -0
  87. package/dist/kernel/hooks/types.d.ts +1 -1
  88. package/dist/kernel/hooks/types.d.ts.map +1 -1
  89. package/dist/kernel/plugin/manifest.d.ts +23 -0
  90. package/dist/kernel/plugin/manifest.d.ts.map +1 -1
  91. package/dist/kernel/plugin/manifest.js +17 -2
  92. package/dist/modules/analytics/module.d.ts.map +1 -1
  93. package/dist/modules/analytics/module.js +6 -0
  94. package/dist/modules/analytics/reports.d.ts +58 -0
  95. package/dist/modules/analytics/reports.d.ts.map +1 -0
  96. package/dist/modules/analytics/reports.js +249 -0
  97. package/dist/modules/analytics/service.d.ts +10 -0
  98. package/dist/modules/analytics/service.d.ts.map +1 -1
  99. package/dist/modules/analytics/service.js +14 -0
  100. package/dist/modules/cart/repository/index.d.ts +7 -0
  101. package/dist/modules/cart/repository/index.d.ts.map +1 -1
  102. package/dist/modules/cart/repository/index.js +25 -1
  103. package/dist/modules/cart/schema.d.ts +17 -0
  104. package/dist/modules/cart/schema.d.ts.map +1 -1
  105. package/dist/modules/cart/schema.js +2 -0
  106. package/dist/modules/cart/schemas.d.ts +1 -0
  107. package/dist/modules/cart/schemas.d.ts.map +1 -1
  108. package/dist/modules/cart/schemas.js +1 -0
  109. package/dist/modules/cart/service.d.ts +32 -0
  110. package/dist/modules/cart/service.d.ts.map +1 -1
  111. package/dist/modules/cart/service.js +58 -0
  112. package/dist/modules/catalog/entity-service.d.ts +21 -0
  113. package/dist/modules/catalog/entity-service.d.ts.map +1 -1
  114. package/dist/modules/catalog/entity-service.js +132 -3
  115. package/dist/modules/catalog/schema.d.ts +34 -0
  116. package/dist/modules/catalog/schema.d.ts.map +1 -1
  117. package/dist/modules/catalog/schema.js +5 -0
  118. package/dist/modules/catalog/schemas.d.ts +3 -0
  119. package/dist/modules/catalog/schemas.d.ts.map +1 -1
  120. package/dist/modules/catalog/schemas.js +3 -0
  121. package/dist/modules/catalog/service.d.ts +36 -0
  122. package/dist/modules/catalog/service.d.ts.map +1 -1
  123. package/dist/modules/catalog/service.js +6 -0
  124. package/dist/modules/documents/module.d.ts +7 -0
  125. package/dist/modules/documents/module.d.ts.map +1 -0
  126. package/dist/modules/documents/module.js +12 -0
  127. package/dist/modules/documents/pdf.d.ts +25 -0
  128. package/dist/modules/documents/pdf.d.ts.map +1 -0
  129. package/dist/modules/documents/pdf.js +80 -0
  130. package/dist/modules/documents/render.d.ts +32 -0
  131. package/dist/modules/documents/render.d.ts.map +1 -0
  132. package/dist/modules/documents/render.js +190 -0
  133. package/dist/modules/documents/repository/index.d.ts +25 -0
  134. package/dist/modules/documents/repository/index.d.ts.map +1 -0
  135. package/dist/modules/documents/repository/index.js +47 -0
  136. package/dist/modules/documents/schema.d.ts +214 -0
  137. package/dist/modules/documents/schema.d.ts.map +1 -0
  138. package/dist/modules/documents/schema.js +42 -0
  139. package/dist/modules/documents/service.d.ts +51 -0
  140. package/dist/modules/documents/service.d.ts.map +1 -0
  141. package/dist/modules/documents/service.js +119 -0
  142. package/dist/modules/fulfillment/service.d.ts +18 -0
  143. package/dist/modules/fulfillment/service.d.ts.map +1 -1
  144. package/dist/modules/fulfillment/service.js +85 -1
  145. package/dist/modules/orders/repository/index.d.ts +18 -1
  146. package/dist/modules/orders/repository/index.d.ts.map +1 -1
  147. package/dist/modules/orders/repository/index.js +86 -1
  148. package/dist/modules/orders/schema.d.ts +374 -0
  149. package/dist/modules/orders/schema.d.ts.map +1 -1
  150. package/dist/modules/orders/schema.js +52 -1
  151. package/dist/modules/orders/service.d.ts +86 -1
  152. package/dist/modules/orders/service.d.ts.map +1 -1
  153. package/dist/modules/orders/service.js +447 -20
  154. package/dist/modules/pricing/repository/index.d.ts +5 -0
  155. package/dist/modules/pricing/repository/index.d.ts.map +1 -1
  156. package/dist/modules/pricing/repository/index.js +19 -0
  157. package/dist/modules/pricing/service.d.ts +18 -0
  158. package/dist/modules/pricing/service.d.ts.map +1 -1
  159. package/dist/modules/pricing/service.js +38 -0
  160. package/dist/modules/settings/module.d.ts +6 -0
  161. package/dist/modules/settings/module.d.ts.map +1 -0
  162. package/dist/modules/settings/module.js +11 -0
  163. package/dist/modules/settings/repository/index.d.ts +16 -0
  164. package/dist/modules/settings/repository/index.d.ts.map +1 -0
  165. package/dist/modules/settings/repository/index.js +41 -0
  166. package/dist/modules/settings/schema.d.ts +121 -0
  167. package/dist/modules/settings/schema.d.ts.map +1 -0
  168. package/dist/modules/settings/schema.js +24 -0
  169. package/dist/modules/settings/service.d.ts +26 -0
  170. package/dist/modules/settings/service.d.ts.map +1 -0
  171. package/dist/modules/settings/service.js +51 -0
  172. package/dist/modules/shipping/calculator.d.ts +5 -0
  173. package/dist/modules/shipping/calculator.d.ts.map +1 -1
  174. package/dist/modules/shipping/calculator.js +10 -0
  175. package/dist/modules/shipping/module.d.ts +5 -1
  176. package/dist/modules/shipping/module.d.ts.map +1 -1
  177. package/dist/modules/shipping/module.js +4 -1
  178. package/dist/modules/shipping/repository/index.d.ts +27 -2
  179. package/dist/modules/shipping/repository/index.d.ts.map +1 -1
  180. package/dist/modules/shipping/repository/index.js +105 -1
  181. package/dist/modules/shipping/schema.d.ts +419 -0
  182. package/dist/modules/shipping/schema.d.ts.map +1 -0
  183. package/dist/modules/shipping/schema.js +56 -0
  184. package/dist/modules/shipping/service.d.ts +54 -0
  185. package/dist/modules/shipping/service.d.ts.map +1 -1
  186. package/dist/modules/shipping/service.js +161 -2
  187. package/dist/modules/tax/adapter.d.ts +13 -0
  188. package/dist/modules/tax/adapter.d.ts.map +1 -1
  189. package/dist/modules/tax/module.d.ts +5 -1
  190. package/dist/modules/tax/module.d.ts.map +1 -1
  191. package/dist/modules/tax/module.js +4 -1
  192. package/dist/modules/tax/repository/index.d.ts +38 -2
  193. package/dist/modules/tax/repository/index.d.ts.map +1 -1
  194. package/dist/modules/tax/repository/index.js +138 -1
  195. package/dist/modules/tax/schema.d.ts +355 -0
  196. package/dist/modules/tax/schema.d.ts.map +1 -0
  197. package/dist/modules/tax/schema.js +55 -0
  198. package/dist/modules/tax/service.d.ts +60 -1
  199. package/dist/modules/tax/service.d.ts.map +1 -1
  200. package/dist/modules/tax/service.js +227 -2
  201. package/dist/runtime/kernel-modules.d.ts +15 -2
  202. package/dist/runtime/kernel-modules.d.ts.map +1 -1
  203. package/dist/runtime/kernel-modules.js +4 -0
  204. package/dist/runtime/kernel-types.d.ts +5 -1
  205. package/dist/runtime/kernel-types.d.ts.map +1 -1
  206. package/dist/runtime/kernel-types.js +2 -0
  207. package/dist/runtime/server.js +1 -1
  208. package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
  209. package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
  210. package/dist/test-utils/create-plugin-test-app.js +15 -3
  211. package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
  212. package/dist/test-utils/rest-api-test-utils.js +6 -0
  213. package/package.json +3 -3
  214. package/src/auth/setup.ts +1 -0
  215. package/src/config/defaults.ts +5 -0
  216. package/src/config/types.ts +17 -1
  217. package/src/generated/plugin-capabilities.d.ts +1 -0
  218. package/src/generated/plugin-manifest.ts +1 -0
  219. package/src/generated/plugin-repositories.d.ts +1 -0
  220. package/src/hooks/checkout.ts +25 -3
  221. package/src/index.ts +1 -0
  222. package/src/interfaces/rest/index.ts +12 -0
  223. package/src/interfaces/rest/routes/admin/staff.ts +218 -0
  224. package/src/interfaces/rest/routes/analytics.ts +33 -0
  225. package/src/interfaces/rest/routes/carts.ts +52 -2
  226. package/src/interfaces/rest/routes/catalog.ts +34 -0
  227. package/src/interfaces/rest/routes/checkout.ts +22 -0
  228. package/src/interfaces/rest/routes/documents.ts +61 -0
  229. package/src/interfaces/rest/routes/orders.ts +137 -1
  230. package/src/interfaces/rest/routes/pricing.ts +44 -1
  231. package/src/interfaces/rest/routes/settings.ts +61 -0
  232. package/src/interfaces/rest/routes/shipping.ts +92 -0
  233. package/src/interfaces/rest/routes/tax.ts +90 -0
  234. package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
  235. package/src/interfaces/rest/schemas/analytics.ts +39 -0
  236. package/src/interfaces/rest/schemas/carts.ts +40 -0
  237. package/src/interfaces/rest/schemas/catalog.ts +53 -0
  238. package/src/interfaces/rest/schemas/checkout.ts +4 -0
  239. package/src/interfaces/rest/schemas/documents.ts +72 -0
  240. package/src/interfaces/rest/schemas/orders.ts +257 -0
  241. package/src/interfaces/rest/schemas/pricing.ts +75 -0
  242. package/src/interfaces/rest/schemas/settings.ts +93 -0
  243. package/src/interfaces/rest/schemas/shipping.ts +164 -0
  244. package/src/interfaces/rest/schemas/tax.ts +148 -0
  245. package/src/kernel/database/schema.ts +12 -0
  246. package/src/kernel/hooks/types.ts +1 -0
  247. package/src/kernel/plugin/manifest.ts +38 -2
  248. package/src/modules/analytics/module.ts +10 -0
  249. package/src/modules/analytics/reports.ts +311 -0
  250. package/src/modules/analytics/service.ts +23 -0
  251. package/src/modules/cart/repository/index.ts +30 -1
  252. package/src/modules/cart/schema.ts +2 -0
  253. package/src/modules/cart/schemas.ts +1 -0
  254. package/src/modules/cart/service.ts +101 -0
  255. package/src/modules/catalog/entity-service.ts +168 -3
  256. package/src/modules/catalog/schema.ts +5 -0
  257. package/src/modules/catalog/schemas.ts +3 -0
  258. package/src/modules/catalog/service.ts +30 -0
  259. package/src/modules/documents/module.ts +19 -0
  260. package/src/modules/documents/pdf.ts +101 -0
  261. package/src/modules/documents/render.ts +231 -0
  262. package/src/modules/documents/repository/index.ts +68 -0
  263. package/src/modules/documents/schema.ts +44 -0
  264. package/src/modules/documents/service.ts +176 -0
  265. package/src/modules/fulfillment/service.ts +139 -1
  266. package/src/modules/orders/repository/index.ts +127 -1
  267. package/src/modules/orders/schema.ts +54 -1
  268. package/src/modules/orders/service.ts +654 -22
  269. package/src/modules/pricing/repository/index.ts +34 -0
  270. package/src/modules/pricing/service.ts +71 -0
  271. package/src/modules/settings/module.ts +18 -0
  272. package/src/modules/settings/repository/index.ts +59 -0
  273. package/src/modules/settings/schema.ts +25 -0
  274. package/src/modules/settings/service.ts +82 -0
  275. package/src/modules/shipping/calculator.ts +27 -0
  276. package/src/modules/shipping/module.ts +6 -2
  277. package/src/modules/shipping/repository/index.ts +136 -2
  278. package/src/modules/shipping/schema.ts +58 -0
  279. package/src/modules/shipping/service.ts +260 -1
  280. package/src/modules/tax/adapter.ts +9 -0
  281. package/src/modules/tax/module.ts +6 -2
  282. package/src/modules/tax/repository/index.ts +169 -2
  283. package/src/modules/tax/schema.ts +57 -0
  284. package/src/modules/tax/service.ts +313 -2
  285. package/src/runtime/kernel-modules.ts +4 -0
  286. package/src/runtime/kernel-types.ts +6 -0
  287. package/src/runtime/server.ts +1 -1
  288. package/src/test-utils/create-plugin-test-app.ts +17 -4
  289. package/src/test-utils/rest-api-test-utils.ts +6 -0
@@ -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":"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"}
@@ -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
+ }