@endora-commerce/mod-taxes 0.100.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/admin/index.d.ts +25 -0
  4. package/dist/admin/index.d.ts.map +1 -0
  5. package/dist/admin/index.js +34 -0
  6. package/dist/admin/index.js.map +1 -0
  7. package/dist/admin/pages/TaxesPage.d.ts +4 -0
  8. package/dist/admin/pages/TaxesPage.d.ts.map +1 -0
  9. package/dist/admin/pages/TaxesPage.js +135 -0
  10. package/dist/admin/pages/TaxesPage.js.map +1 -0
  11. package/dist/backend/demo/reset.d.ts +13 -0
  12. package/dist/backend/demo/reset.d.ts.map +1 -0
  13. package/dist/backend/demo/reset.js +11 -0
  14. package/dist/backend/demo/reset.js.map +1 -0
  15. package/dist/backend/demo/rows.d.ts +33 -0
  16. package/dist/backend/demo/rows.d.ts.map +1 -0
  17. package/dist/backend/demo/rows.js +27 -0
  18. package/dist/backend/demo/rows.js.map +1 -0
  19. package/dist/backend/demo/seed.d.ts +16 -0
  20. package/dist/backend/demo/seed.d.ts.map +1 -0
  21. package/dist/backend/demo/seed.js +31 -0
  22. package/dist/backend/demo/seed.js.map +1 -0
  23. package/dist/backend/entities/tax.entity.d.ts +21 -0
  24. package/dist/backend/entities/tax.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/tax.entity.js +83 -0
  26. package/dist/backend/entities/tax.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +76 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +86 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/routes.d.ts +9 -0
  32. package/dist/backend/routes.d.ts.map +1 -0
  33. package/dist/backend/routes.js +63 -0
  34. package/dist/backend/routes.js.map +1 -0
  35. package/dist/backend/services/tax-country-reference.d.ts +12 -0
  36. package/dist/backend/services/tax-country-reference.d.ts.map +1 -0
  37. package/dist/backend/services/tax-country-reference.js +24 -0
  38. package/dist/backend/services/tax-country-reference.js.map +1 -0
  39. package/dist/backend/services/tax-service.d.ts +51 -0
  40. package/dist/backend/services/tax-service.d.ts.map +1 -0
  41. package/dist/backend/services/tax-service.js +169 -0
  42. package/dist/backend/services/tax-service.js.map +1 -0
  43. package/dist/manifest.d.ts +169 -0
  44. package/dist/manifest.d.ts.map +1 -0
  45. package/dist/manifest.js +113 -0
  46. package/dist/manifest.js.map +1 -0
  47. package/dist/migrations/20260426T081516_taxes_promotions_init.d.ts +14 -0
  48. package/dist/migrations/20260426T081516_taxes_promotions_init.d.ts.map +1 -0
  49. package/dist/migrations/20260426T081516_taxes_promotions_init.js +63 -0
  50. package/dist/migrations/20260426T081516_taxes_promotions_init.js.map +1 -0
  51. package/dist/migrations/20260912T094654_taxes_sales_channel_taxes.d.ts +27 -0
  52. package/dist/migrations/20260912T094654_taxes_sales_channel_taxes.d.ts.map +1 -0
  53. package/dist/migrations/20260912T094654_taxes_sales_channel_taxes.js +44 -0
  54. package/dist/migrations/20260912T094654_taxes_sales_channel_taxes.js.map +1 -0
  55. package/dist/migrations/index.d.ts +28 -0
  56. package/dist/migrations/index.d.ts.map +1 -0
  57. package/dist/migrations/index.js +31 -0
  58. package/dist/migrations/index.js.map +1 -0
  59. package/docs/taxes.md +75 -0
  60. package/i18n/en.json +3 -0
  61. package/i18n/pl.json +3 -0
  62. package/package.json +92 -0
  63. package/tailwind.css +14 -0
@@ -0,0 +1,76 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
3
+ import type { ModuleContext } from '@endora-commerce/platform/kernel';
4
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
5
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
6
+ import type { ChannelMemberEntityType } from '@endora-commerce/contracts';
7
+ import type { DictionaryValidator } from '@endora-commerce/contracts';
8
+ import { TaxService } from './services/tax-service.js';
9
+ import { Tax } from './entities/tax.entity.js';
10
+ /**
11
+ * `taxes` — three optional arguments, all of which change what a write means
12
+ * (feature 072, wave 2, T119).
13
+ *
14
+ * `TaxService` took its channel-membership binder, its dictionary validator and
15
+ * its audit writer as optional constructor arguments. Omit them and a tax rate
16
+ * is created that binds to no sales channel, validates no country or region
17
+ * code, and records nothing — a write that looks identical from the API and is
18
+ * wrong in three separate ways. Both roots passed all three, so nothing was
19
+ * live; this is the eleventh, twelfth and thirteenth removal of that shape in
20
+ * this transition, and the argument for removing it does not depend on any of
21
+ * them having fired.
22
+ *
23
+ * `taxService` is a **port**: `orders` and `carts` resolve it across a module
24
+ * boundary to price a line, so an operator switching taxes off should get the
25
+ * explicit 503 envelope rather than a service that quietly returns no rate —
26
+ * which, on a checkout path, is a discount.
27
+ */
28
+ export interface TaxesCradle {
29
+ /**
30
+ * Feature 120 (FR-015) — the platform's channel-bridge registry, resolved by
31
+ * **container name** rather than imported. The shape is the narrow one this
32
+ * module needs, which is what `ctx.cradle<C>()` asks an author to declare;
33
+ * the registry itself is the kernel's and is published on no barrel.
34
+ */
35
+ readonly salesChannelBridgeRegistry: {
36
+ register(bridge: {
37
+ entityType: ChannelMemberEntityType;
38
+ table: string;
39
+ entityIdColumn: string;
40
+ }): void;
41
+ };
42
+ readonly emFactory: () => EntityManager;
43
+ readonly auditLogService: AuditPort;
44
+ readonly requireAdmin: RequireAdminFactory;
45
+ readonly salesChannelMembershipPort: SalesChannelMembershipPort;
46
+ readonly dictionaryValidator: DictionaryValidator | undefined;
47
+ readonly taxService: TaxService;
48
+ }
49
+ export declare function registerModule(ctx: ModuleContext): void;
50
+ /**
51
+ * The module's persisted entity classes, on the `./backend` subpath, as one
52
+ * array and **no named class export** (D-168).
53
+ *
54
+ * This is the shape the platform reads when the package is *installed*: the
55
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
56
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
57
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
58
+ * the package pass of `check-entity-tenant-classification`. A missing array is
59
+ * answered with `[]` — zero entities registered, no error anywhere.
60
+ */
61
+ export declare const entities: (typeof Tax)[];
62
+ /**
63
+ * The sales-channel membership bridge this module owns (feature 120, FR-015).
64
+ *
65
+ * Exported as well as registered because the owning module is the only honest
66
+ * source for it: a caller that composes no platform — the bare-database test
67
+ * harness, which constructs the kernel's membership service directly — declares
68
+ * these from here rather than from a list of nine it keeps itself, which is
69
+ * exactly the total map this feature deleted.
70
+ */
71
+ export declare const salesChannelBridges: ReadonlyArray<{
72
+ readonly entityType: ChannelMemberEntityType;
73
+ readonly table: string;
74
+ readonly entityIdColumn: string;
75
+ }>;
76
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,KAAK,EACM,mBAAmB,EAEpC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAGvD,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAE/C;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,0BAA0B,EAAE;QACnC,QAAQ,CAAC,MAAM,EAAE;YACf,UAAU,EAAE,uBAAuB,CAAC;YACpC,KAAK,EAAE,MAAM,CAAC;YACd,cAAc,EAAE,MAAM,CAAC;SACxB,GAAG,IAAI,CAAC;KACV,CAAC;IACF,QAAQ,CAAC,SAAS,EAAE,MAAM,aAAa,CAAC;IACxC,QAAQ,CAAC,eAAe,EAAE,SAAS,CAAC;IACpC,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,QAAQ,CAAC,0BAA0B,EAAE,0BAA0B,CAAC;IAChE,QAAQ,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC9D,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAwEvD;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,QAAQ,gBAEpB,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,CAAC;IAC9C,QAAQ,CAAC,UAAU,EAAE,uBAAuB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,CAEA,CAAC"}
@@ -0,0 +1,86 @@
1
+ import { lazyPort } from '@endora-commerce/platform/kernel';
2
+ import { TaxService } from './services/tax-service.js';
3
+ import { registerTaxRoutes } from './routes.js';
4
+ import { registerTaxCountryReferences } from './services/tax-country-reference.js';
5
+ import { Tax } from './entities/tax.entity.js';
6
+ export function registerModule(ctx) {
7
+ /**
8
+ * Feature 120 (FR-015) — the sales-channel membership bridge this module owns.
9
+ *
10
+ * The platform used to hold a map **total** over
11
+ * `ChannelMemberEntityTypeSchema`, naming this module's bridge table from
12
+ * inside the kernel. That is D-52/D-53's prohibition expressed in a contract
13
+ * instead of an import, and it meant a membership call for 'tax' on an
14
+ * instance that never installed this module executed SQL against a relation
15
+ * that is not there (D-226). The module whose migration creates the table is
16
+ * the one that says where it is; a member nothing registers now refuses at
17
+ * the call instead.
18
+ *
19
+ * A **contribution** hook and deliberately **unprobed** (D-62/D-68): a
20
+ * registration is an inert statement about the schema rather than a
21
+ * capability, the rows outlive an operator switching this module off, and
22
+ * probing would make a runtime activation flip require a restart. It is
23
+ * declared first in this module so any later hook of its own that binds a
24
+ * membership finds the bridge already there.
25
+ */
26
+ ctx.onBoot(() => {
27
+ const { salesChannelBridgeRegistry } = ctx.cradle();
28
+ for (const bridge of salesChannelBridges)
29
+ salesChannelBridgeRegistry.register(bridge);
30
+ });
31
+ ctx.di.providePort('taxService', ctx
32
+ .asFunction(({ emFactory, auditLogService, }) => new TaxService(emFactory, lazyPort(ctx, 'salesChannelMembershipPort'), lazyPort(ctx, 'dictionaryValidator'), auditLogService))
33
+ .singleton());
34
+ ctx.routes(async (app) => {
35
+ const { requireAdmin } = ctx.cradle();
36
+ await registerTaxRoutes(app, {
37
+ // Lazily, even though the module owns this port: route *registration* runs
38
+ // inside `buildServer` whatever the module's effective state is, so
39
+ // destructuring the gate here would stop the next start instead of
40
+ // stopping the routes (D-40).
41
+ taxService: lazyPort(ctx, 'taxService'),
42
+ requireAdmin,
43
+ });
44
+ });
45
+ /**
46
+ * This module's rows carry a country code, so it answers "who still points at
47
+ * this country?" about its own tables (feature 077, D-87), where the owner used
48
+ * to count them with SQL naming this module's tables.
49
+ *
50
+ * A **contribution** hook: it pushes an inert descriptor into `countryReferenceRegistry`,
51
+ * an ungated registry, and carries no presence probe (D-62/D-68). Probing
52
+ * would be wrong in the dangerous direction — a switched-off module still owns
53
+ * the rows, so its country must still refuse the delete, which is the
54
+ * enumeration policy the registry states.
55
+ */
56
+ ctx.onBoot(() => {
57
+ registerTaxCountryReferences(lazyPort(ctx, 'countryReferenceRegistry'), ctx.cradle().emFactory);
58
+ });
59
+ }
60
+ /**
61
+ * The module's persisted entity classes, on the `./backend` subpath, as one
62
+ * array and **no named class export** (D-168).
63
+ *
64
+ * This is the shape the platform reads when the package is *installed*: the
65
+ * boot-time loader (`src/packages/package-runtime.ts`, `exported['entities']`)
66
+ * and the static declaration reader (`scripts/lib/package-declarations.ts`),
67
+ * which is the third source of `check:module-boundary`'s `table→owner` map and
68
+ * the package pass of `check-entity-tenant-classification`. A missing array is
69
+ * answered with `[]` — zero entities registered, no error anywhere.
70
+ */
71
+ export const entities = [
72
+ Tax,
73
+ ];
74
+ /**
75
+ * The sales-channel membership bridge this module owns (feature 120, FR-015).
76
+ *
77
+ * Exported as well as registered because the owning module is the only honest
78
+ * source for it: a caller that composes no platform — the bare-database test
79
+ * harness, which constructs the kernel's membership service directly — declares
80
+ * these from here rather than from a list of nine it keeps itself, which is
81
+ * exactly the total map this feature deleted.
82
+ */
83
+ export const salesChannelBridges = [
84
+ { entityType: 'tax', table: 'sales_channel_taxes', entityIdColumn: 'tax_id' },
85
+ ];
86
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/backend/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAQ5D,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,4BAA4B,EAAE,MAAM,qCAAqC,CAAC;AACnF,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AA2C/C,MAAM,UAAU,cAAc,CAAC,GAAkB;IAC/C;;;;;;;;;;;;;;;;;;OAkBG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,MAAM,EAAE,0BAA0B,EAAE,GAAG,GAAG,CAAC,MAAM,EAAe,CAAC;QACjE,KAAK,MAAM,MAAM,IAAI,mBAAmB;YAAE,0BAA0B,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxF,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,WAAW,CAChB,YAAY,EACZ,GAAG;SACA,UAAU,CACT,CAAC,EACC,SAAS,EACT,eAAe,GACH,EAAE,EAAE,CAChB,IAAI,UAAU,CACZ,SAAS,EACT,QAAQ,CAA6B,GAAG,EAAE,4BAA4B,CAAC,EACvE,QAAQ,CAAsB,GAAG,EAAE,qBAAqB,CAAC,EACzD,eAAe,CAChB,CACJ;SACA,SAAS,EAAE,CACf,CAAC;IAEF,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;QACvB,MAAM,EAAE,YAAY,EAAE,GAAG,GAAG,CAAC,MAAM,EAAe,CAAC;QACnD,MAAM,iBAAiB,CAAC,GAAG,EAAE;YAC3B,2EAA2E;YAC3E,oEAAoE;YACpE,mEAAmE;YACnE,8BAA8B;YAC9B,UAAU,EAAE,QAAQ,CAAa,GAAG,EAAE,YAAY,CAAC;YACnD,YAAY;SACb,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH;;;;;;;;;;OAUG;IACH,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,4BAA4B,CAC1B,QAAQ,CAAkC,GAAG,EAAE,0BAA0B,CAAC,EAC1E,GAAG,CAAC,MAAM,EAAe,CAAC,SAAS,CACpC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,GAAG;CACJ,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAI3B;IACH,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,QAAQ,EAAE;CAC9E,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { FastifyInstance } from 'fastify';
2
+ import type { TaxService } from './services/tax-service.js';
3
+ import type { RequireAdminFactory } from '@endora-commerce/platform/kernel';
4
+ export interface TaxRoutesDeps {
5
+ taxService: TaxService;
6
+ requireAdmin: RequireAdminFactory;
7
+ }
8
+ export declare function registerTaxRoutes(app: FastifyInstance, deps: TaxRoutesDeps): Promise<void>;
9
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAE5E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,YAAY,EAAE,mBAAmB,CAAC;CACnC;AAED,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,eAAe,EACpB,IAAI,EAAE,aAAa,GAClB,OAAO,CAAC,IAAI,CAAC,CAiEf"}
@@ -0,0 +1,63 @@
1
+ import { taxResolutionInputSchema, upsertTaxRequestSchema } from '@endora-commerce/contracts';
2
+ export async function registerTaxRoutes(app, deps) {
3
+ const { taxService, requireAdmin } = deps;
4
+ app.get('/api/v1/admin/taxes', { preHandler: requireAdmin('taxes:read') }, async () => {
5
+ const rows = await taxService.list();
6
+ return { data: rows.map(serialize) };
7
+ });
8
+ app.put('/api/v1/admin/taxes/:code', {
9
+ preHandler: requireAdmin('taxes:write'),
10
+ schema: { body: upsertTaxRequestSchema },
11
+ }, async (request) => {
12
+ const body = upsertTaxRequestSchema.parse(request.body);
13
+ const row = await taxService.upsertByCode({
14
+ code: request.params.code,
15
+ name: body.name,
16
+ rate: body.rate,
17
+ ...(body.country !== undefined ? { country: body.country } : {}),
18
+ ...(body.productType !== undefined ? { productType: body.productType } : {}),
19
+ ...(body.appliesToVatStatuses !== undefined
20
+ ? { appliesToVatStatuses: body.appliesToVatStatuses }
21
+ : {}),
22
+ ...(body.isDefault !== undefined ? { isDefault: body.isDefault } : {}),
23
+ ...(body.priority !== undefined ? { priority: body.priority } : {}),
24
+ });
25
+ return { data: serialize(row) };
26
+ });
27
+ app.delete('/api/v1/admin/taxes/:id', { preHandler: requireAdmin('taxes:write') }, async (request, reply) => {
28
+ await taxService.remove(request.params.id);
29
+ return reply.status(204).send();
30
+ });
31
+ /**
32
+ * `taxes:read`, with the list, and not `taxes:write`: the preview reads the
33
+ * configuration one resolution further on — "what rate would this order line
34
+ * be charged" — and writes nothing. All four routes were `catalog:write`
35
+ * until 2026-08-28, so seeing the tax table at all required the authority to
36
+ * rewrite it; the pair is what separates the two.
37
+ */
38
+ app.get('/api/v1/admin/taxes/preview', { preHandler: requireAdmin('taxes:read') }, async (request) => {
39
+ const input = taxResolutionInputSchema.parse({
40
+ country: request.query.country,
41
+ productType: request.query.productType,
42
+ vatStatus: request.query.vatStatus,
43
+ });
44
+ const result = await taxService.taxRateFor(input);
45
+ return { data: result };
46
+ });
47
+ }
48
+ function serialize(row) {
49
+ return {
50
+ id: row.id,
51
+ code: row.code,
52
+ name: row.name,
53
+ rate: Number(row.rate),
54
+ country: row.country ?? null,
55
+ productType: row.productType ?? null,
56
+ appliesToVatStatuses: row.appliesToVatStatuses,
57
+ isDefault: row.isDefault,
58
+ priority: row.priority,
59
+ createdAt: row.createdAt.toISOString(),
60
+ updatedAt: row.updatedAt.toISOString(),
61
+ };
62
+ }
63
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/backend/routes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AAU9F,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,GAAoB,EACpB,IAAmB;IAEnB,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAE1C,GAAG,CAAC,GAAG,CACL,qBAAqB,EACrB,EAAE,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,EAC1C,KAAK,IAAI,EAAE;QACT,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QACrC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;IACvC,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,GAAG,CACL,2BAA2B,EAC3B;QACE,UAAU,EAAE,YAAY,CAAC,aAAa,CAAC;QACvC,MAAM,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE;KACzC,EACD,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,IAAI,GAAG,sBAAsB,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC;YACxC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI;YACzB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,GAAG,CAAC,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5E,GAAG,CAAC,IAAI,CAAC,oBAAoB,KAAK,SAAS;gBACzC,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE;gBACrD,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpE,CAAC,CAAC;QACH,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;IAClC,CAAC,CACF,CAAC;IAEF,GAAG,CAAC,MAAM,CACR,yBAAyB,EACzB,EAAE,UAAU,EAAE,YAAY,CAAC,aAAa,CAAC,EAAE,EAC3C,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE;QACvB,MAAM,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3C,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAClC,CAAC,CACF,CAAC;IAEF;;;;;;OAMG;IACH,GAAG,CAAC,GAAG,CACL,6BAA6B,EAC7B,EAAE,UAAU,EAAE,YAAY,CAAC,YAAY,CAAC,EAAE,EAC1C,KAAK,EAAE,OAAO,EAAE,EAAE;QAChB,MAAM,KAAK,GAAG,wBAAwB,CAAC,KAAK,CAAC;YAC3C,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,OAAO;YAC9B,WAAW,EAAE,OAAO,CAAC,KAAK,CAAC,WAAW;YACtC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,SAAS;SACnC,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;QAClD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,SAAS,CAAC,GAAQ;IACzB,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QACtB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,IAAI;QAC5B,WAAW,EAAE,GAAG,CAAC,WAAW,IAAI,IAAI;QACpC,oBAAoB,EAAE,GAAG,CAAC,oBAAoB;QAC9C,SAAS,EAAE,GAAG,CAAC,SAAS;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;QACtC,SAAS,EAAE,GAAG,CAAC,SAAS,CAAC,WAAW,EAAE;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { DictionaryReferenceRegistryPort } from '@endora-commerce/contracts';
2
+ import type { EntityManager } from '@mikro-orm/postgresql';
3
+ /**
4
+ * Who in `taxes` still points at a country (feature 077, D-87).
5
+ *
6
+ * `dictionaries` used to count this table itself and join it a second time
7
+ * for the orphan report, naming it in strings no import-level boundary check
8
+ * can see. A tax rule is scoped by country, so a country the platform drops
9
+ * leaves the rule unapplicable.
10
+ */
11
+ export declare function registerTaxCountryReferences(registry: DictionaryReferenceRegistryPort, emFactory: () => EntityManager): void;
12
+ //# sourceMappingURL=tax-country-reference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-country-reference.d.ts","sourceRoot":"","sources":["../../../src/backend/services/tax-country-reference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,4BAA4B,CAAC;AAClF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,+BAA+B,EACzC,SAAS,EAAE,MAAM,aAAa,GAC7B,IAAI,CAoBN"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Who in `taxes` still points at a country (feature 077, D-87).
3
+ *
4
+ * `dictionaries` used to count this table itself and join it a second time
5
+ * for the orphan report, naming it in strings no import-level boundary check
6
+ * can see. A tax rule is scoped by country, so a country the platform drops
7
+ * leaves the rule unapplicable.
8
+ */
9
+ export function registerTaxCountryReferences(registry, emFactory) {
10
+ registry.register({
11
+ ownerModuleId: 'taxes',
12
+ consumer: 'taxes',
13
+ tableName: 'taxes',
14
+ columnName: 'country',
15
+ blocking: true,
16
+ countReferences: async (code) => {
17
+ const rows = (await emFactory().execute(`select count(*)::int as n from "taxes" where "country" = ?`, [code]));
18
+ return rows[0]?.n ?? 0;
19
+ },
20
+ usedCodes: async () => (await emFactory().execute(`select "country" as code, count(*)::int as count
21
+ from "taxes" where "country" is not null group by "country"`)),
22
+ });
23
+ }
24
+ //# sourceMappingURL=tax-country-reference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-country-reference.js","sourceRoot":"","sources":["../../../src/backend/services/tax-country-reference.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,MAAM,UAAU,4BAA4B,CAC1C,QAAyC,EACzC,SAA8B;IAE9B,QAAQ,CAAC,QAAQ,CAAC;QAChB,aAAa,EAAE,OAAO;QACtB,QAAQ,EAAE,OAAO;QACjB,SAAS,EAAE,OAAO;QAClB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACrC,4DAA4D,EAC5D,CAAC,IAAI,CAAC,CACP,CAAyB,CAAC;YAC3B,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACzB,CAAC;QACD,SAAS,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,MAAM,SAAS,EAAE,CAAC,OAAO,CACxB;uEAC+D,CAChE,CAA2C;KAC/C,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,51 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql';
2
+ import { type DictionaryValidator, type ResolvedTax, type TaxResolutionInput } from '@endora-commerce/contracts';
3
+ import type { AuditPort } from '@endora-commerce/platform/kernel';
4
+ import type { SalesChannelMembershipPort } from '@endora-commerce/platform/kernel';
5
+ import { Tax } from '../entities/tax.entity.js';
6
+ /**
7
+ * TaxService (T131 / FR-051).
8
+ *
9
+ * `taxRateFor({ country, productType, vatStatus })` returns the applicable
10
+ * rate. Resolution rules:
11
+ * - A rule matches when each of its narrowing fields is either null
12
+ * (unconstrained) or equal to the corresponding input field.
13
+ * - Among matching rules, the one with the most narrowed fields wins.
14
+ * - Ties are broken by `priority` desc, then `createdAt` asc.
15
+ * - When no rule matches, the row with `isDefault=true` wins.
16
+ * - When no default exists either, the resolver returns `{ source: 'none' }` —
17
+ * an answer that carries no rate, because there is none to carry.
18
+ *
19
+ * The last line is the whole point of the union (issue #124). A configured 0%
20
+ * rate comes back as `{ source: 'rule' | 'default', rate: 0 }` and prices an
21
+ * order; "nothing is configured" comes back without a `rate` at all, so no
22
+ * consumer can spend it as zero by accident. Absence of the module itself is
23
+ * neither: the port gate refuses the resolution before it starts.
24
+ */
25
+ export declare class TaxService {
26
+ #private;
27
+ private readonly emFactory;
28
+ /** Feature 005 / T027b — auto-bind newly-created Taxes to the system default. */
29
+ private readonly salesChannelMembership?;
30
+ private readonly dictionaryValidator?;
31
+ private readonly auditLog?;
32
+ constructor(emFactory: () => EntityManager,
33
+ /** Feature 005 / T027b — auto-bind newly-created Taxes to the system default. */
34
+ salesChannelMembership?: SalesChannelMembershipPort | undefined, dictionaryValidator?: DictionaryValidator | undefined, auditLog?: AuditPort | undefined);
35
+ list(): Promise<Tax[]>;
36
+ getById(id: string): Promise<Tax>;
37
+ upsertByCode(input: {
38
+ code: string;
39
+ name: string;
40
+ rate: number;
41
+ country?: string | null;
42
+ productType?: 'simple' | 'configurable' | 'grouped' | 'bundle' | 'virtual' | null;
43
+ appliesToVatStatuses?: Array<'vat_payer' | 'vat_exempt' | 'reverse_charge'>;
44
+ isDefault?: boolean;
45
+ priority?: number;
46
+ }): Promise<Tax>;
47
+ remove(id: string): Promise<void>;
48
+ taxRateFor(input: TaxResolutionInput): Promise<ResolvedTax>;
49
+ private validateCountry;
50
+ }
51
+ //# sourceMappingURL=tax-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/tax-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACxB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAClE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,GAAG,EAAE,MAAM,2BAA2B,CAAC;AAEhD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,UAAU;;IAEnB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IACxC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;gBAJT,SAAS,EAAE,MAAM,aAAa;IAC/C,iFAAiF;IAChE,sBAAsB,CAAC,EAAE,0BAA0B,YAAA,EACnD,mBAAmB,CAAC,EAAE,mBAAmB,YAAA,EACzC,QAAQ,CAAC,EAAE,SAAS,YAAA;IASjC,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAItB,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAMjC,YAAY,CAAC,KAAK,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;QAClF,oBAAoB,CAAC,EAAE,KAAK,CAAC,WAAW,GAAG,YAAY,GAAG,gBAAgB,CAAC,CAAC;QAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,GAAG,CAAC;IAoDV,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQjC,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC;YAkCnD,eAAe;CAqB9B"}
@@ -0,0 +1,169 @@
1
+ import { DictionaryReferenceError, ERROR_CODES, dispatchValidatorMode, } from '@endora-commerce/contracts';
2
+ import { HttpError } from '@endora-commerce/platform/http';
3
+ import { recordAuditFromContext } from '@endora-commerce/platform/commands';
4
+ import { Tax } from '../entities/tax.entity.js';
5
+ /**
6
+ * TaxService (T131 / FR-051).
7
+ *
8
+ * `taxRateFor({ country, productType, vatStatus })` returns the applicable
9
+ * rate. Resolution rules:
10
+ * - A rule matches when each of its narrowing fields is either null
11
+ * (unconstrained) or equal to the corresponding input field.
12
+ * - Among matching rules, the one with the most narrowed fields wins.
13
+ * - Ties are broken by `priority` desc, then `createdAt` asc.
14
+ * - When no rule matches, the row with `isDefault=true` wins.
15
+ * - When no default exists either, the resolver returns `{ source: 'none' }` —
16
+ * an answer that carries no rate, because there is none to carry.
17
+ *
18
+ * The last line is the whole point of the union (issue #124). A configured 0%
19
+ * rate comes back as `{ source: 'rule' | 'default', rate: 0 }` and prices an
20
+ * order; "nothing is configured" comes back without a `rate` at all, so no
21
+ * consumer can spend it as zero by accident. Absence of the module itself is
22
+ * neither: the port gate refuses the resolution before it starts.
23
+ */
24
+ export class TaxService {
25
+ emFactory;
26
+ salesChannelMembership;
27
+ dictionaryValidator;
28
+ auditLog;
29
+ constructor(emFactory,
30
+ /** Feature 005 / T027b — auto-bind newly-created Taxes to the system default. */
31
+ salesChannelMembership, dictionaryValidator, auditLog) {
32
+ this.emFactory = emFactory;
33
+ this.salesChannelMembership = salesChannelMembership;
34
+ this.dictionaryValidator = dictionaryValidator;
35
+ this.auditLog = auditLog;
36
+ }
37
+ #audit(em, action, objectId, stateBefore, stateAfter) {
38
+ if (this.auditLog) {
39
+ recordAuditFromContext(this.auditLog, em, { action, objectType: 'tax', objectId, stateBefore, stateAfter });
40
+ }
41
+ }
42
+ async list() {
43
+ return this.emFactory().find(Tax, {}, { orderBy: { priority: 'desc', code: 'asc' } });
44
+ }
45
+ async getById(id) {
46
+ const row = await this.emFactory().findOne(Tax, { id });
47
+ if (!row)
48
+ throw new HttpError(404, ERROR_CODES.NOT_FOUND, `Tax ${id} not found.`);
49
+ return row;
50
+ }
51
+ async upsertByCode(input) {
52
+ const em = this.emFactory();
53
+ if (input.isDefault) {
54
+ await em.getConnection().execute(`update taxes set is_default = false where is_default = true and code <> ?`, [input.code]);
55
+ }
56
+ const existing = await em.findOne(Tax, { code: input.code });
57
+ if (input.country != null) {
58
+ await this.validateCountry(input.country, existing
59
+ ? dispatchValidatorMode(existing.country, input.country)
60
+ : 'create-or-change');
61
+ }
62
+ if (existing) {
63
+ existing.name = input.name;
64
+ existing.rate = String(input.rate);
65
+ if (input.country !== undefined)
66
+ existing.country = input.country;
67
+ if (input.productType !== undefined)
68
+ existing.productType = input.productType;
69
+ if (input.appliesToVatStatuses !== undefined) {
70
+ existing.appliesToVatStatuses = input.appliesToVatStatuses;
71
+ }
72
+ if (input.isDefault !== undefined)
73
+ existing.isDefault = input.isDefault;
74
+ if (input.priority !== undefined)
75
+ existing.priority = input.priority;
76
+ this.#audit(em, 'tax.upsert', existing.id, null, { code: existing.code, rate: existing.rate });
77
+ await em.flush();
78
+ return existing;
79
+ }
80
+ const row = em.create(Tax, {
81
+ code: input.code,
82
+ name: input.name,
83
+ rate: String(input.rate),
84
+ ...(input.country !== undefined ? { country: input.country } : {}),
85
+ ...(input.productType !== undefined ? { productType: input.productType } : {}),
86
+ ...(input.appliesToVatStatuses !== undefined
87
+ ? { appliesToVatStatuses: input.appliesToVatStatuses }
88
+ : {}),
89
+ ...(input.isDefault !== undefined ? { isDefault: input.isDefault } : {}),
90
+ ...(input.priority !== undefined ? { priority: input.priority } : {}),
91
+ });
92
+ em.persist(row);
93
+ this.#audit(em, 'tax.upsert', row.id, null, { code: row.code, rate: row.rate });
94
+ await em.flush();
95
+ if (this.salesChannelMembership) {
96
+ await this.salesChannelMembership.bindToDefaultIfEmpty('tax', row.id);
97
+ }
98
+ return row;
99
+ }
100
+ async remove(id) {
101
+ const em = this.emFactory();
102
+ const row = await em.findOne(Tax, { id });
103
+ if (!row)
104
+ return;
105
+ this.#audit(em, 'tax.delete', row.id, { code: row.code }, null);
106
+ await em.removeAndFlush(row);
107
+ }
108
+ async taxRateFor(input) {
109
+ const em = this.emFactory();
110
+ const all = await em.find(Tax, {});
111
+ const matching = all
112
+ .filter((t) => !t.isDefault)
113
+ .filter((t) => {
114
+ if (t.country != null && t.country !== input.country)
115
+ return false;
116
+ if (t.productType != null && t.productType !== input.productType)
117
+ return false;
118
+ if (t.appliesToVatStatuses.length > 0 &&
119
+ !t.appliesToVatStatuses.includes(input.vatStatus)) {
120
+ return false;
121
+ }
122
+ return true;
123
+ });
124
+ if (matching.length > 0) {
125
+ matching.sort((a, b) => {
126
+ const specA = specificity(a);
127
+ const specB = specificity(b);
128
+ if (specA !== specB)
129
+ return specB - specA;
130
+ if (a.priority !== b.priority)
131
+ return b.priority - a.priority;
132
+ return a.createdAt.getTime() - b.createdAt.getTime();
133
+ });
134
+ const winner = matching[0];
135
+ return { rate: Number(winner.rate), taxId: winner.id, source: 'rule' };
136
+ }
137
+ const defaultRule = all.find((t) => t.isDefault);
138
+ if (defaultRule) {
139
+ return { rate: Number(defaultRule.rate), taxId: defaultRule.id, source: 'default' };
140
+ }
141
+ return { source: 'none' };
142
+ }
143
+ async validateCountry(country, mode) {
144
+ if (!this.dictionaryValidator)
145
+ return;
146
+ try {
147
+ await this.dictionaryValidator.validateCountryCode(country, mode);
148
+ }
149
+ catch (err) {
150
+ if (err instanceof DictionaryReferenceError) {
151
+ throw new HttpError(409, err.code, err.code === 'DICTIONARY_ENTRY_INACTIVE'
152
+ ? `Country code ${err.entryCode} is no longer available for tax rules.`
153
+ : `Country code ${err.entryCode} is not recognised.`, [{ path: 'country', issue: err.code }]);
154
+ }
155
+ throw err;
156
+ }
157
+ }
158
+ }
159
+ function specificity(t) {
160
+ let n = 0;
161
+ if (t.country != null)
162
+ n += 1;
163
+ if (t.productType != null)
164
+ n += 1;
165
+ if (t.appliesToVatStatuses.length > 0)
166
+ n += 1;
167
+ return n;
168
+ }
169
+ //# sourceMappingURL=tax-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tax-service.js","sourceRoot":"","sources":["../../../src/backend/services/tax-service.ts"],"names":[],"mappings":"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"}