@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.
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +26 -7
- package/dist/auth/setup.d.ts.map +1 -1
- package/dist/auth/setup.js +8 -5
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +7 -0
- package/dist/config/types.d.ts +26 -1
- package/dist/config/types.d.ts.map +1 -1
- package/dist/generated/plugin-manifest.d.ts +3 -0
- package/dist/generated/plugin-manifest.d.ts.map +1 -1
- package/dist/generated/plugin-manifest.js +1 -0
- package/dist/hooks/checkout-completion.d.ts.map +1 -1
- package/dist/hooks/checkout-completion.js +2 -2
- package/dist/hooks/checkout.d.ts.map +1 -1
- package/dist/hooks/checkout.js +17 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interfaces/rest/index.d.ts.map +1 -1
- package/dist/interfaces/rest/index.js +12 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts +12 -0
- package/dist/interfaces/rest/routes/admin/staff.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/admin/staff.js +206 -0
- package/dist/interfaces/rest/routes/analytics.d.ts +5 -0
- package/dist/interfaces/rest/routes/analytics.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/analytics.js +24 -0
- package/dist/interfaces/rest/routes/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/carts.js +39 -2
- package/dist/interfaces/rest/routes/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/catalog.js +17 -1
- package/dist/interfaces/rest/routes/checkout.d.ts +9 -0
- package/dist/interfaces/rest/routes/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/checkout.js +97 -21
- package/dist/interfaces/rest/routes/documents.d.ts +10 -0
- package/dist/interfaces/rest/routes/documents.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/documents.js +40 -0
- package/dist/interfaces/rest/routes/inventory.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/inventory.js +13 -0
- package/dist/interfaces/rest/routes/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/orders.js +111 -1
- package/dist/interfaces/rest/routes/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/routes/pricing.js +36 -1
- package/dist/interfaces/rest/routes/settings.d.ts +5 -0
- package/dist/interfaces/rest/routes/settings.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/settings.js +50 -0
- package/dist/interfaces/rest/routes/shipping.d.ts +5 -0
- package/dist/interfaces/rest/routes/shipping.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/shipping.js +68 -0
- package/dist/interfaces/rest/routes/tax.d.ts +5 -0
- package/dist/interfaces/rest/routes/tax.d.ts.map +1 -0
- package/dist/interfaces/rest/routes/tax.js +68 -0
- package/dist/interfaces/rest/schemas/admin-staff.d.ts +899 -0
- package/dist/interfaces/rest/schemas/admin-staff.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/admin-staff.js +98 -0
- package/dist/interfaces/rest/schemas/analytics.d.ts +252 -0
- package/dist/interfaces/rest/schemas/analytics.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/analytics.js +33 -0
- package/dist/interfaces/rest/schemas/carts.d.ts +656 -0
- package/dist/interfaces/rest/schemas/carts.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/carts.js +38 -0
- package/dist/interfaces/rest/schemas/catalog.d.ts +1218 -1
- package/dist/interfaces/rest/schemas/catalog.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/catalog.js +48 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts +2 -0
- package/dist/interfaces/rest/schemas/checkout.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/checkout.js +4 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts +35 -0
- package/dist/interfaces/rest/schemas/customer-portal.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/documents.d.ts +509 -0
- package/dist/interfaces/rest/schemas/documents.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/documents.js +65 -0
- package/dist/interfaces/rest/schemas/orders.d.ts +3110 -43
- package/dist/interfaces/rest/schemas/orders.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/orders.js +234 -1
- package/dist/interfaces/rest/schemas/pricing.d.ts +405 -0
- package/dist/interfaces/rest/schemas/pricing.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/pricing.js +70 -0
- package/dist/interfaces/rest/schemas/responses.d.ts +190 -0
- package/dist/interfaces/rest/schemas/responses.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/search.d.ts +17 -0
- package/dist/interfaces/rest/schemas/search.d.ts.map +1 -1
- package/dist/interfaces/rest/schemas/settings.d.ts +397 -0
- package/dist/interfaces/rest/schemas/settings.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/settings.js +79 -0
- package/dist/interfaces/rest/schemas/shipping.d.ts +1093 -0
- package/dist/interfaces/rest/schemas/shipping.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/shipping.js +146 -0
- package/dist/interfaces/rest/schemas/tax.d.ts +1074 -0
- package/dist/interfaces/rest/schemas/tax.d.ts.map +1 -0
- package/dist/interfaces/rest/schemas/tax.js +132 -0
- package/dist/kernel/database/schema.d.ts +4 -0
- package/dist/kernel/database/schema.d.ts.map +1 -1
- package/dist/kernel/database/schema.js +8 -0
- package/dist/kernel/database/scoped-db.d.ts +2 -1
- package/dist/kernel/database/scoped-db.d.ts.map +1 -1
- package/dist/kernel/database/scoped-db.js +62 -1
- package/dist/kernel/hooks/types.d.ts +1 -1
- package/dist/kernel/hooks/types.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.d.ts +26 -0
- package/dist/kernel/plugin/manifest.d.ts.map +1 -1
- package/dist/kernel/plugin/manifest.js +17 -2
- package/dist/modules/analytics/drizzle-adapter.d.ts.map +1 -1
- package/dist/modules/analytics/drizzle-adapter.js +16 -3
- package/dist/modules/analytics/module.d.ts.map +1 -1
- package/dist/modules/analytics/module.js +6 -0
- package/dist/modules/analytics/reports.d.ts +58 -0
- package/dist/modules/analytics/reports.d.ts.map +1 -0
- package/dist/modules/analytics/reports.js +249 -0
- package/dist/modules/analytics/service.d.ts +10 -0
- package/dist/modules/analytics/service.d.ts.map +1 -1
- package/dist/modules/analytics/service.js +14 -0
- package/dist/modules/cart/repository/index.d.ts +7 -0
- package/dist/modules/cart/repository/index.d.ts.map +1 -1
- package/dist/modules/cart/repository/index.js +25 -1
- package/dist/modules/cart/schema.d.ts +17 -0
- package/dist/modules/cart/schema.d.ts.map +1 -1
- package/dist/modules/cart/schema.js +2 -0
- package/dist/modules/cart/schemas.d.ts +1 -0
- package/dist/modules/cart/schemas.d.ts.map +1 -1
- package/dist/modules/cart/schemas.js +1 -0
- package/dist/modules/cart/service.d.ts +32 -0
- package/dist/modules/cart/service.d.ts.map +1 -1
- package/dist/modules/cart/service.js +61 -2
- package/dist/modules/catalog/entity-service.d.ts +21 -0
- package/dist/modules/catalog/entity-service.d.ts.map +1 -1
- package/dist/modules/catalog/entity-service.js +166 -5
- package/dist/modules/catalog/schema.d.ts +34 -0
- package/dist/modules/catalog/schema.d.ts.map +1 -1
- package/dist/modules/catalog/schema.js +5 -0
- package/dist/modules/catalog/schemas.d.ts +3 -0
- package/dist/modules/catalog/schemas.d.ts.map +1 -1
- package/dist/modules/catalog/schemas.js +3 -0
- package/dist/modules/catalog/service.d.ts +36 -0
- package/dist/modules/catalog/service.d.ts.map +1 -1
- package/dist/modules/catalog/service.js +6 -0
- package/dist/modules/customers/service.d.ts.map +1 -1
- package/dist/modules/customers/service.js +6 -0
- package/dist/modules/documents/module.d.ts +7 -0
- package/dist/modules/documents/module.d.ts.map +1 -0
- package/dist/modules/documents/module.js +12 -0
- package/dist/modules/documents/pdf.d.ts +25 -0
- package/dist/modules/documents/pdf.d.ts.map +1 -0
- package/dist/modules/documents/pdf.js +80 -0
- package/dist/modules/documents/render.d.ts +32 -0
- package/dist/modules/documents/render.d.ts.map +1 -0
- package/dist/modules/documents/render.js +190 -0
- package/dist/modules/documents/repository/index.d.ts +25 -0
- package/dist/modules/documents/repository/index.d.ts.map +1 -0
- package/dist/modules/documents/repository/index.js +47 -0
- package/dist/modules/documents/schema.d.ts +214 -0
- package/dist/modules/documents/schema.d.ts.map +1 -0
- package/dist/modules/documents/schema.js +42 -0
- package/dist/modules/documents/service.d.ts +51 -0
- package/dist/modules/documents/service.d.ts.map +1 -0
- package/dist/modules/documents/service.js +119 -0
- package/dist/modules/fulfillment/service.d.ts +18 -0
- package/dist/modules/fulfillment/service.d.ts.map +1 -1
- package/dist/modules/fulfillment/service.js +85 -1
- package/dist/modules/orders/repository/index.d.ts +18 -1
- package/dist/modules/orders/repository/index.d.ts.map +1 -1
- package/dist/modules/orders/repository/index.js +86 -1
- package/dist/modules/orders/schema.d.ts +391 -0
- package/dist/modules/orders/schema.d.ts.map +1 -1
- package/dist/modules/orders/schema.js +56 -1
- package/dist/modules/orders/service.d.ts +102 -3
- package/dist/modules/orders/service.d.ts.map +1 -1
- package/dist/modules/orders/service.js +653 -29
- package/dist/modules/orders/stale-order-cleanup.d.ts.map +1 -1
- package/dist/modules/orders/stale-order-cleanup.js +13 -1
- package/dist/modules/pricing/repository/index.d.ts +5 -0
- package/dist/modules/pricing/repository/index.d.ts.map +1 -1
- package/dist/modules/pricing/repository/index.js +19 -0
- package/dist/modules/pricing/service.d.ts +18 -0
- package/dist/modules/pricing/service.d.ts.map +1 -1
- package/dist/modules/pricing/service.js +43 -5
- package/dist/modules/settings/module.d.ts +6 -0
- package/dist/modules/settings/module.d.ts.map +1 -0
- package/dist/modules/settings/module.js +11 -0
- package/dist/modules/settings/repository/index.d.ts +16 -0
- package/dist/modules/settings/repository/index.d.ts.map +1 -0
- package/dist/modules/settings/repository/index.js +41 -0
- package/dist/modules/settings/schema.d.ts +121 -0
- package/dist/modules/settings/schema.d.ts.map +1 -0
- package/dist/modules/settings/schema.js +24 -0
- package/dist/modules/settings/service.d.ts +26 -0
- package/dist/modules/settings/service.d.ts.map +1 -0
- package/dist/modules/settings/service.js +51 -0
- package/dist/modules/shipping/calculator.d.ts +5 -0
- package/dist/modules/shipping/calculator.d.ts.map +1 -1
- package/dist/modules/shipping/calculator.js +10 -0
- package/dist/modules/shipping/module.d.ts +5 -1
- package/dist/modules/shipping/module.d.ts.map +1 -1
- package/dist/modules/shipping/module.js +4 -1
- package/dist/modules/shipping/repository/index.d.ts +27 -2
- package/dist/modules/shipping/repository/index.d.ts.map +1 -1
- package/dist/modules/shipping/repository/index.js +105 -1
- package/dist/modules/shipping/schema.d.ts +419 -0
- package/dist/modules/shipping/schema.d.ts.map +1 -0
- package/dist/modules/shipping/schema.js +56 -0
- package/dist/modules/shipping/service.d.ts +54 -0
- package/dist/modules/shipping/service.d.ts.map +1 -1
- package/dist/modules/shipping/service.js +161 -2
- package/dist/modules/tax/adapter.d.ts +13 -0
- package/dist/modules/tax/adapter.d.ts.map +1 -1
- package/dist/modules/tax/module.d.ts +5 -1
- package/dist/modules/tax/module.d.ts.map +1 -1
- package/dist/modules/tax/module.js +4 -1
- package/dist/modules/tax/repository/index.d.ts +38 -2
- package/dist/modules/tax/repository/index.d.ts.map +1 -1
- package/dist/modules/tax/repository/index.js +138 -1
- package/dist/modules/tax/schema.d.ts +355 -0
- package/dist/modules/tax/schema.d.ts.map +1 -0
- package/dist/modules/tax/schema.js +55 -0
- package/dist/modules/tax/service.d.ts +60 -1
- package/dist/modules/tax/service.d.ts.map +1 -1
- package/dist/modules/tax/service.js +227 -2
- package/dist/runtime/kernel-modules.d.ts +15 -2
- package/dist/runtime/kernel-modules.d.ts.map +1 -1
- package/dist/runtime/kernel-modules.js +4 -0
- package/dist/runtime/kernel-types.d.ts +5 -1
- package/dist/runtime/kernel-types.d.ts.map +1 -1
- package/dist/runtime/kernel-types.js +2 -0
- package/dist/runtime/server.js +1 -1
- package/dist/test-utils/create-plugin-test-app.d.ts +3 -0
- package/dist/test-utils/create-plugin-test-app.d.ts.map +1 -1
- package/dist/test-utils/create-plugin-test-app.js +15 -3
- package/dist/test-utils/create-test-config.d.ts.map +1 -1
- package/dist/test-utils/create-test-config.js +2 -0
- package/dist/test-utils/order-test-helpers.d.ts +12 -0
- package/dist/test-utils/order-test-helpers.d.ts.map +1 -0
- package/dist/test-utils/order-test-helpers.js +18 -0
- package/dist/test-utils/rest-api-test-utils.d.ts.map +1 -1
- package/dist/test-utils/rest-api-test-utils.js +7 -0
- package/dist/test-utils/test-actors.js +1 -1
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -0
- package/package.json +3 -3
- package/src/auth/middleware.ts +26 -7
- package/src/auth/setup.ts +11 -4
- package/src/config/defaults.ts +7 -0
- package/src/config/types.ts +26 -1
- package/src/generated/plugin-capabilities.d.ts +1 -0
- package/src/generated/plugin-manifest.ts +1 -0
- package/src/generated/plugin-repositories.d.ts +1 -0
- package/src/hooks/checkout-completion.ts +22 -16
- package/src/hooks/checkout.ts +36 -11
- package/src/index.ts +1 -0
- package/src/interfaces/rest/index.ts +12 -0
- package/src/interfaces/rest/routes/admin/staff.ts +260 -0
- package/src/interfaces/rest/routes/analytics.ts +33 -0
- package/src/interfaces/rest/routes/carts.ts +52 -2
- package/src/interfaces/rest/routes/catalog.ts +34 -0
- package/src/interfaces/rest/routes/checkout.ts +114 -27
- package/src/interfaces/rest/routes/documents.ts +61 -0
- package/src/interfaces/rest/routes/inventory.ts +14 -0
- package/src/interfaces/rest/routes/orders.ts +148 -1
- package/src/interfaces/rest/routes/pricing.ts +44 -1
- package/src/interfaces/rest/routes/settings.ts +61 -0
- package/src/interfaces/rest/routes/shipping.ts +92 -0
- package/src/interfaces/rest/routes/tax.ts +90 -0
- package/src/interfaces/rest/schemas/admin-staff.ts +110 -0
- package/src/interfaces/rest/schemas/analytics.ts +39 -0
- package/src/interfaces/rest/schemas/carts.ts +40 -0
- package/src/interfaces/rest/schemas/catalog.ts +53 -0
- package/src/interfaces/rest/schemas/checkout.ts +4 -0
- package/src/interfaces/rest/schemas/documents.ts +72 -0
- package/src/interfaces/rest/schemas/orders.ts +258 -1
- package/src/interfaces/rest/schemas/pricing.ts +75 -0
- package/src/interfaces/rest/schemas/settings.ts +93 -0
- package/src/interfaces/rest/schemas/shipping.ts +164 -0
- package/src/interfaces/rest/schemas/tax.ts +148 -0
- package/src/kernel/database/schema.ts +12 -0
- package/src/kernel/database/scoped-db.ts +70 -1
- package/src/kernel/hooks/types.ts +1 -0
- package/src/kernel/plugin/manifest.ts +41 -2
- package/src/modules/analytics/drizzle-adapter.ts +19 -3
- package/src/modules/analytics/module.ts +10 -0
- package/src/modules/analytics/reports.ts +311 -0
- package/src/modules/analytics/service.ts +23 -0
- package/src/modules/cart/repository/index.ts +30 -1
- package/src/modules/cart/schema.ts +2 -0
- package/src/modules/cart/schemas.ts +1 -0
- package/src/modules/cart/service.ts +104 -2
- package/src/modules/catalog/entity-service.ts +196 -4
- package/src/modules/catalog/schema.ts +5 -0
- package/src/modules/catalog/schemas.ts +3 -0
- package/src/modules/catalog/service.ts +30 -0
- package/src/modules/customers/service.ts +6 -0
- package/src/modules/documents/module.ts +19 -0
- package/src/modules/documents/pdf.ts +101 -0
- package/src/modules/documents/render.ts +231 -0
- package/src/modules/documents/repository/index.ts +68 -0
- package/src/modules/documents/schema.ts +44 -0
- package/src/modules/documents/service.ts +176 -0
- package/src/modules/fulfillment/service.ts +139 -1
- package/src/modules/orders/repository/index.ts +127 -1
- package/src/modules/orders/schema.ts +58 -1
- package/src/modules/orders/service.ts +1041 -65
- package/src/modules/orders/stale-order-cleanup.ts +18 -4
- package/src/modules/pricing/repository/index.ts +34 -0
- package/src/modules/pricing/service.ts +76 -6
- package/src/modules/settings/module.ts +18 -0
- package/src/modules/settings/repository/index.ts +59 -0
- package/src/modules/settings/schema.ts +25 -0
- package/src/modules/settings/service.ts +82 -0
- package/src/modules/shipping/calculator.ts +27 -0
- package/src/modules/shipping/module.ts +6 -2
- package/src/modules/shipping/repository/index.ts +136 -2
- package/src/modules/shipping/schema.ts +58 -0
- package/src/modules/shipping/service.ts +260 -1
- package/src/modules/tax/adapter.ts +9 -0
- package/src/modules/tax/module.ts +6 -2
- package/src/modules/tax/repository/index.ts +169 -2
- package/src/modules/tax/schema.ts +57 -0
- package/src/modules/tax/service.ts +313 -2
- package/src/runtime/kernel-modules.ts +4 -0
- package/src/runtime/kernel-types.ts +6 -0
- package/src/runtime/server.ts +1 -1
- package/src/test-utils/create-plugin-test-app.ts +17 -4
- package/src/test-utils/create-test-config.ts +2 -0
- package/src/test-utils/order-test-helpers.ts +26 -0
- package/src/test-utils/rest-api-test-utils.ts +7 -0
- package/src/test-utils/test-actors.ts +1 -1
- package/src/testing.ts +1 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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;
|
|
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<
|
|
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":"
|
|
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
|
-
|
|
3
|
-
|
|
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":"
|
|
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
|
-
|
|
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
|
+
}
|