@endora-commerce/contracts 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 (327) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +34 -0
  3. package/dist/actor.d.ts +79 -0
  4. package/dist/actor.d.ts.map +1 -0
  5. package/dist/actor.js +41 -0
  6. package/dist/actor.js.map +1 -0
  7. package/dist/addresses.d.ts +134 -0
  8. package/dist/addresses.d.ts.map +1 -0
  9. package/dist/addresses.js +16 -0
  10. package/dist/addresses.js.map +1 -0
  11. package/dist/admin-actions.d.ts +367 -0
  12. package/dist/admin-actions.d.ts.map +1 -0
  13. package/dist/admin-actions.js +287 -0
  14. package/dist/admin-actions.js.map +1 -0
  15. package/dist/admin-contributions.d.ts +518 -0
  16. package/dist/admin-contributions.d.ts.map +1 -0
  17. package/dist/admin-contributions.js +495 -0
  18. package/dist/admin-contributions.js.map +1 -0
  19. package/dist/admin-i18n.d.ts +135 -0
  20. package/dist/admin-i18n.d.ts.map +1 -0
  21. package/dist/admin-i18n.js +72 -0
  22. package/dist/admin-i18n.js.map +1 -0
  23. package/dist/admin-notifications.d.ts +55 -0
  24. package/dist/admin-notifications.d.ts.map +1 -0
  25. package/dist/admin-notifications.js +16 -0
  26. package/dist/admin-notifications.js.map +1 -0
  27. package/dist/admin-roles.d.ts +125 -0
  28. package/dist/admin-roles.d.ts.map +1 -0
  29. package/dist/admin-roles.js +2 -0
  30. package/dist/admin-roles.js.map +1 -0
  31. package/dist/admin-users.d.ts +178 -0
  32. package/dist/admin-users.d.ts.map +1 -0
  33. package/dist/admin-users.js +14 -0
  34. package/dist/admin-users.js.map +1 -0
  35. package/dist/admin.d.ts +243 -0
  36. package/dist/admin.d.ts.map +1 -0
  37. package/dist/admin.js +246 -0
  38. package/dist/admin.js.map +1 -0
  39. package/dist/analytics.d.ts +123 -0
  40. package/dist/analytics.d.ts.map +1 -0
  41. package/dist/analytics.js +68 -0
  42. package/dist/analytics.js.map +1 -0
  43. package/dist/api-keys.d.ts +97 -0
  44. package/dist/api-keys.d.ts.map +1 -0
  45. package/dist/api-keys.js +64 -0
  46. package/dist/api-keys.js.map +1 -0
  47. package/dist/assets-library.d.ts +684 -0
  48. package/dist/assets-library.d.ts.map +1 -0
  49. package/dist/assets-library.js +181 -0
  50. package/dist/assets-library.js.map +1 -0
  51. package/dist/audit-logs.d.ts +141 -0
  52. package/dist/audit-logs.d.ts.map +1 -0
  53. package/dist/audit-logs.js +31 -0
  54. package/dist/audit-logs.js.map +1 -0
  55. package/dist/auth.d.ts +174 -0
  56. package/dist/auth.d.ts.map +1 -0
  57. package/dist/auth.js +27 -0
  58. package/dist/auth.js.map +1 -0
  59. package/dist/blog.d.ts +669 -0
  60. package/dist/blog.d.ts.map +1 -0
  61. package/dist/blog.js +360 -0
  62. package/dist/blog.js.map +1 -0
  63. package/dist/capabilities.d.ts +40 -0
  64. package/dist/capabilities.d.ts.map +1 -0
  65. package/dist/capabilities.js +38 -0
  66. package/dist/capabilities.js.map +1 -0
  67. package/dist/carts.d.ts +1367 -0
  68. package/dist/carts.d.ts.map +1 -0
  69. package/dist/carts.js +405 -0
  70. package/dist/carts.js.map +1 -0
  71. package/dist/catalog.d.ts +2855 -0
  72. package/dist/catalog.d.ts.map +1 -0
  73. package/dist/catalog.js +1543 -0
  74. package/dist/catalog.js.map +1 -0
  75. package/dist/cms.d.ts +872 -0
  76. package/dist/cms.d.ts.map +1 -0
  77. package/dist/cms.js +468 -0
  78. package/dist/cms.js.map +1 -0
  79. package/dist/common.d.ts +82 -0
  80. package/dist/common.d.ts.map +1 -0
  81. package/dist/common.js +72 -0
  82. package/dist/common.js.map +1 -0
  83. package/dist/comparisons.d.ts +487 -0
  84. package/dist/comparisons.d.ts.map +1 -0
  85. package/dist/comparisons.js +221 -0
  86. package/dist/comparisons.js.map +1 -0
  87. package/dist/credentials.d.ts +292 -0
  88. package/dist/credentials.d.ts.map +1 -0
  89. package/dist/credentials.js +142 -0
  90. package/dist/credentials.js.map +1 -0
  91. package/dist/credit-limits.d.ts +111 -0
  92. package/dist/credit-limits.d.ts.map +1 -0
  93. package/dist/credit-limits.js +35 -0
  94. package/dist/credit-limits.js.map +1 -0
  95. package/dist/currencies.d.ts +127 -0
  96. package/dist/currencies.d.ts.map +1 -0
  97. package/dist/currencies.js +20 -0
  98. package/dist/currencies.js.map +1 -0
  99. package/dist/custom-fields.d.ts +345 -0
  100. package/dist/custom-fields.d.ts.map +1 -0
  101. package/dist/custom-fields.js +185 -0
  102. package/dist/custom-fields.js.map +1 -0
  103. package/dist/customer-accounts.d.ts +690 -0
  104. package/dist/customer-accounts.d.ts.map +1 -0
  105. package/dist/customer-accounts.js +41 -0
  106. package/dist/customer-accounts.js.map +1 -0
  107. package/dist/customers.d.ts +305 -0
  108. package/dist/customers.d.ts.map +1 -0
  109. package/dist/customers.js +158 -0
  110. package/dist/customers.js.map +1 -0
  111. package/dist/dictionary.d.ts +580 -0
  112. package/dist/dictionary.d.ts.map +1 -0
  113. package/dist/dictionary.js +297 -0
  114. package/dist/dictionary.js.map +1 -0
  115. package/dist/email-address.d.ts +62 -0
  116. package/dist/email-address.d.ts.map +1 -0
  117. package/dist/email-address.js +64 -0
  118. package/dist/email-address.js.map +1 -0
  119. package/dist/email.d.ts +175 -0
  120. package/dist/email.d.ts.map +1 -0
  121. package/dist/email.js +45 -0
  122. package/dist/email.js.map +1 -0
  123. package/dist/envelopes.d.ts +15 -0
  124. package/dist/envelopes.d.ts.map +1 -0
  125. package/dist/envelopes.js +16 -0
  126. package/dist/envelopes.js.map +1 -0
  127. package/dist/environment-inputs.d.ts +306 -0
  128. package/dist/environment-inputs.d.ts.map +1 -0
  129. package/dist/environment-inputs.js +277 -0
  130. package/dist/environment-inputs.js.map +1 -0
  131. package/dist/erp-connector.d.ts +52 -0
  132. package/dist/erp-connector.d.ts.map +1 -0
  133. package/dist/erp-connector.js +34 -0
  134. package/dist/erp-connector.js.map +1 -0
  135. package/dist/errors.d.ts +455 -0
  136. package/dist/errors.d.ts.map +1 -0
  137. package/dist/errors.js +532 -0
  138. package/dist/errors.js.map +1 -0
  139. package/dist/google-analytics.d.ts +181 -0
  140. package/dist/google-analytics.d.ts.map +1 -0
  141. package/dist/google-analytics.js +176 -0
  142. package/dist/google-analytics.js.map +1 -0
  143. package/dist/google-tag-manager.d.ts +111 -0
  144. package/dist/google-tag-manager.d.ts.map +1 -0
  145. package/dist/google-tag-manager.js +129 -0
  146. package/dist/google-tag-manager.js.map +1 -0
  147. package/dist/i18n.d.ts +69 -0
  148. package/dist/i18n.d.ts.map +1 -0
  149. package/dist/i18n.js +59 -0
  150. package/dist/i18n.js.map +1 -0
  151. package/dist/import-export.d.ts +63 -0
  152. package/dist/import-export.d.ts.map +1 -0
  153. package/dist/import-export.js +37 -0
  154. package/dist/import-export.js.map +1 -0
  155. package/dist/index.d.ts +82 -0
  156. package/dist/index.d.ts.map +1 -0
  157. package/dist/index.js +126 -0
  158. package/dist/index.js.map +1 -0
  159. package/dist/inventory.d.ts +673 -0
  160. package/dist/inventory.d.ts.map +1 -0
  161. package/dist/inventory.js +412 -0
  162. package/dist/inventory.js.map +1 -0
  163. package/dist/invoice-ledger.d.ts +366 -0
  164. package/dist/invoice-ledger.d.ts.map +1 -0
  165. package/dist/invoice-ledger.js +114 -0
  166. package/dist/invoice-ledger.js.map +1 -0
  167. package/dist/invoices.d.ts +845 -0
  168. package/dist/invoices.d.ts.map +1 -0
  169. package/dist/invoices.js +314 -0
  170. package/dist/invoices.js.map +1 -0
  171. package/dist/kernel.d.ts +49 -0
  172. package/dist/kernel.d.ts.map +1 -0
  173. package/dist/kernel.js +19 -0
  174. package/dist/kernel.js.map +1 -0
  175. package/dist/languages.d.ts +122 -0
  176. package/dist/languages.d.ts.map +1 -0
  177. package/dist/languages.js +24 -0
  178. package/dist/languages.js.map +1 -0
  179. package/dist/linkedin-ads.d.ts +167 -0
  180. package/dist/linkedin-ads.d.ts.map +1 -0
  181. package/dist/linkedin-ads.js +156 -0
  182. package/dist/linkedin-ads.js.map +1 -0
  183. package/dist/megamenu.d.ts +556 -0
  184. package/dist/megamenu.d.ts.map +1 -0
  185. package/dist/megamenu.js +186 -0
  186. package/dist/megamenu.js.map +1 -0
  187. package/dist/meta-ads.d.ts +126 -0
  188. package/dist/meta-ads.d.ts.map +1 -0
  189. package/dist/meta-ads.js +112 -0
  190. package/dist/meta-ads.js.map +1 -0
  191. package/dist/mfa.d.ts +274 -0
  192. package/dist/mfa.d.ts.map +1 -0
  193. package/dist/mfa.js +187 -0
  194. package/dist/mfa.js.map +1 -0
  195. package/dist/modules.d.ts +1706 -0
  196. package/dist/modules.d.ts.map +1 -0
  197. package/dist/modules.js +1390 -0
  198. package/dist/modules.js.map +1 -0
  199. package/dist/newsletter.d.ts +611 -0
  200. package/dist/newsletter.d.ts.map +1 -0
  201. package/dist/newsletter.js +345 -0
  202. package/dist/newsletter.js.map +1 -0
  203. package/dist/orders.d.ts +1175 -0
  204. package/dist/orders.d.ts.map +1 -0
  205. package/dist/orders.js +630 -0
  206. package/dist/orders.js.map +1 -0
  207. package/dist/organizations.d.ts +938 -0
  208. package/dist/organizations.d.ts.map +1 -0
  209. package/dist/organizations.js +418 -0
  210. package/dist/organizations.js.map +1 -0
  211. package/dist/pagination.d.ts +21 -0
  212. package/dist/pagination.d.ts.map +1 -0
  213. package/dist/pagination.js +22 -0
  214. package/dist/pagination.js.map +1 -0
  215. package/dist/payment-methods.d.ts +472 -0
  216. package/dist/payment-methods.d.ts.map +1 -0
  217. package/dist/payment-methods.js +175 -0
  218. package/dist/payment-methods.js.map +1 -0
  219. package/dist/payment-return-url.d.ts +53 -0
  220. package/dist/payment-return-url.d.ts.map +1 -0
  221. package/dist/payment-return-url.js +35 -0
  222. package/dist/payment-return-url.js.map +1 -0
  223. package/dist/payments.d.ts +386 -0
  224. package/dist/payments.d.ts.map +1 -0
  225. package/dist/payments.js +84 -0
  226. package/dist/payments.js.map +1 -0
  227. package/dist/pim-connector.d.ts +60 -0
  228. package/dist/pim-connector.d.ts.map +1 -0
  229. package/dist/pim-connector.js +43 -0
  230. package/dist/pim-connector.js.map +1 -0
  231. package/dist/pim-field-path.d.ts +6 -0
  232. package/dist/pim-field-path.d.ts.map +1 -0
  233. package/dist/pim-field-path.js +101 -0
  234. package/dist/pim-field-path.js.map +1 -0
  235. package/dist/platform-language.d.ts +20 -0
  236. package/dist/platform-language.d.ts.map +1 -0
  237. package/dist/platform-language.js +22 -0
  238. package/dist/platform-language.js.map +1 -0
  239. package/dist/price-lists.d.ts +685 -0
  240. package/dist/price-lists.d.ts.map +1 -0
  241. package/dist/price-lists.js +330 -0
  242. package/dist/price-lists.js.map +1 -0
  243. package/dist/product-feeds.d.ts +2837 -0
  244. package/dist/product-feeds.d.ts.map +1 -0
  245. package/dist/product-feeds.js +1504 -0
  246. package/dist/product-feeds.js.map +1 -0
  247. package/dist/product-scope-overrides.d.ts +134 -0
  248. package/dist/product-scope-overrides.d.ts.map +1 -0
  249. package/dist/product-scope-overrides.js +82 -0
  250. package/dist/product-scope-overrides.js.map +1 -0
  251. package/dist/product-value-resolver.d.ts +88 -0
  252. package/dist/product-value-resolver.d.ts.map +1 -0
  253. package/dist/product-value-resolver.js +128 -0
  254. package/dist/product-value-resolver.js.map +1 -0
  255. package/dist/promotions.d.ts +678 -0
  256. package/dist/promotions.d.ts.map +1 -0
  257. package/dist/promotions.js +479 -0
  258. package/dist/promotions.js.map +1 -0
  259. package/dist/prompt-actions.d.ts +582 -0
  260. package/dist/prompt-actions.d.ts.map +1 -0
  261. package/dist/prompt-actions.js +221 -0
  262. package/dist/prompt-actions.js.map +1 -0
  263. package/dist/pwa.d.ts +293 -0
  264. package/dist/pwa.d.ts.map +1 -0
  265. package/dist/pwa.js +204 -0
  266. package/dist/pwa.js.map +1 -0
  267. package/dist/quick-order.d.ts +340 -0
  268. package/dist/quick-order.d.ts.map +1 -0
  269. package/dist/quick-order.js +177 -0
  270. package/dist/quick-order.js.map +1 -0
  271. package/dist/quote-requests.d.ts +538 -0
  272. package/dist/quote-requests.d.ts.map +1 -0
  273. package/dist/quote-requests.js +308 -0
  274. package/dist/quote-requests.js.map +1 -0
  275. package/dist/returns.d.ts +774 -0
  276. package/dist/returns.d.ts.map +1 -0
  277. package/dist/returns.js +389 -0
  278. package/dist/returns.js.map +1 -0
  279. package/dist/sales-channels.d.ts +392 -0
  280. package/dist/sales-channels.d.ts.map +1 -0
  281. package/dist/sales-channels.js +285 -0
  282. package/dist/sales-channels.js.map +1 -0
  283. package/dist/scope-notice.d.ts +60 -0
  284. package/dist/scope-notice.d.ts.map +1 -0
  285. package/dist/scope-notice.js +56 -0
  286. package/dist/scope-notice.js.map +1 -0
  287. package/dist/search.d.ts +321 -0
  288. package/dist/search.d.ts.map +1 -0
  289. package/dist/search.js +160 -0
  290. package/dist/search.js.map +1 -0
  291. package/dist/seo.d.ts +113 -0
  292. package/dist/seo.d.ts.map +1 -0
  293. package/dist/seo.js +63 -0
  294. package/dist/seo.js.map +1 -0
  295. package/dist/settings.d.ts +453 -0
  296. package/dist/settings.d.ts.map +1 -0
  297. package/dist/settings.js +337 -0
  298. package/dist/settings.js.map +1 -0
  299. package/dist/shipments.d.ts +140 -0
  300. package/dist/shipments.d.ts.map +1 -0
  301. package/dist/shipments.js +14 -0
  302. package/dist/shipments.js.map +1 -0
  303. package/dist/shipping-methods.d.ts +350 -0
  304. package/dist/shipping-methods.d.ts.map +1 -0
  305. package/dist/shipping-methods.js +99 -0
  306. package/dist/shipping-methods.js.map +1 -0
  307. package/dist/shopping-lists.d.ts +122 -0
  308. package/dist/shopping-lists.d.ts.map +1 -0
  309. package/dist/shopping-lists.js +92 -0
  310. package/dist/shopping-lists.js.map +1 -0
  311. package/dist/taxes.d.ts +106 -0
  312. package/dist/taxes.d.ts.map +1 -0
  313. package/dist/taxes.js +80 -0
  314. package/dist/taxes.js.map +1 -0
  315. package/dist/text-normalization.d.ts +199 -0
  316. package/dist/text-normalization.d.ts.map +1 -0
  317. package/dist/text-normalization.js +205 -0
  318. package/dist/text-normalization.js.map +1 -0
  319. package/dist/transactional-emails.d.ts +459 -0
  320. package/dist/transactional-emails.d.ts.map +1 -0
  321. package/dist/transactional-emails.js +212 -0
  322. package/dist/transactional-emails.js.map +1 -0
  323. package/dist/webhooks.d.ts +69 -0
  324. package/dist/webhooks.d.ts.map +1 -0
  325. package/dist/webhooks.js +53 -0
  326. package/dist/webhooks.js.map +1 -0
  327. package/package.json +46 -0
@@ -0,0 +1,92 @@
1
+ import { z } from 'zod';
2
+ import { isoDateTimeSchema, uuidSchema } from './common.js';
3
+ /**
4
+ * Shopping list contracts (T202 / FR-031..FR-033).
5
+ *
6
+ * A shopping list is a per-customer, named collection of (product, variant?, quantity)
7
+ * rows with optional notes. Two terminal actions:
8
+ * - convertToCart — adds the (selected or all) items to the customer's active cart;
9
+ * archived products are skipped and reported.
10
+ * - convertToRfq — adds the (selected or all) items to the customer's draft RFQ;
11
+ * same skip-and-report behaviour.
12
+ *
13
+ * Lists are private to the (customer, organization) pair today; sharing within an
14
+ * organization is intentionally out of scope for the MVP — the entity has no
15
+ * `isShared` column yet to keep the surface narrow.
16
+ */
17
+ export const shoppingListItemSchema = z.object({
18
+ id: uuidSchema,
19
+ shoppingListId: uuidSchema,
20
+ productId: uuidSchema,
21
+ variantId: uuidSchema.nullable(),
22
+ quantity: z.number().int().positive(),
23
+ note: z.string().max(2000).nullable(),
24
+ createdAt: isoDateTimeSchema,
25
+ updatedAt: isoDateTimeSchema,
26
+ });
27
+ export const shoppingListSchema = z.object({
28
+ id: uuidSchema,
29
+ organizationId: z.string(),
30
+ customerAccountId: uuidSchema,
31
+ name: z.string().min(1).max(160),
32
+ /** Whether this is the customer's default shopping list (exactly one per customer). */
33
+ isDefault: z.boolean(),
34
+ items: z.array(shoppingListItemSchema),
35
+ createdAt: isoDateTimeSchema,
36
+ updatedAt: isoDateTimeSchema,
37
+ });
38
+ /** Lightweight default-list view for the storefront header heart badge. */
39
+ export const defaultShoppingListSummarySchema = z.object({
40
+ id: uuidSchema,
41
+ name: z.string(),
42
+ itemCount: z.number().int().nonnegative(),
43
+ });
44
+ // --- Requests --------------------------------------------------------------
45
+ export const createShoppingListRequestSchema = z.object({
46
+ name: z.string().min(1).max(160),
47
+ });
48
+ export const renameShoppingListRequestSchema = z
49
+ .object({
50
+ name: z.string().min(1).max(160),
51
+ })
52
+ .strict();
53
+ export const addShoppingListItemRequestSchema = z.object({
54
+ productId: uuidSchema,
55
+ variantId: uuidSchema.optional(),
56
+ quantity: z.number().int().positive(),
57
+ note: z.string().max(2000).optional(),
58
+ });
59
+ export const updateShoppingListItemRequestSchema = z
60
+ .object({
61
+ quantity: z.number().int().positive().optional(),
62
+ note: z.string().max(2000).nullable().optional(),
63
+ })
64
+ .strict();
65
+ /**
66
+ * The convert request optionally narrows by item id; an empty (or missing)
67
+ * `itemIds` array means "convert every item on the list".
68
+ */
69
+ export const convertShoppingListRequestSchema = z
70
+ .object({
71
+ itemIds: z.array(uuidSchema).optional(),
72
+ })
73
+ .strict();
74
+ // --- Responses -------------------------------------------------------------
75
+ /** Per-item skip note returned when conversion encounters an archived product. */
76
+ export const conversionSkipSchema = z.object({
77
+ itemId: uuidSchema,
78
+ productId: uuidSchema,
79
+ reason: z.enum(['product_archived', 'variant_unavailable', 'product_not_found']),
80
+ });
81
+ export const convertToCartResponseSchema = z.object({
82
+ shoppingListId: uuidSchema,
83
+ added: z.number().int().nonnegative(),
84
+ skipped: z.array(conversionSkipSchema),
85
+ });
86
+ export const convertToRfqResponseSchema = z.object({
87
+ shoppingListId: uuidSchema,
88
+ rfqId: uuidSchema,
89
+ added: z.number().int().nonnegative(),
90
+ skipped: z.array(conversionSkipSchema),
91
+ });
92
+ //# sourceMappingURL=shopping-lists.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shopping-lists.js","sourceRoot":"","sources":["../src/shopping-lists.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ import { z } from 'zod';
2
+ export declare const taxSchema: z.ZodObject<{
3
+ id: z.ZodString;
4
+ code: z.ZodString;
5
+ name: z.ZodString;
6
+ rate: z.ZodNumber;
7
+ country: z.ZodNullable<z.ZodString>;
8
+ productType: z.ZodNullable<z.ZodEnum<{
9
+ simple: "simple";
10
+ configurable: "configurable";
11
+ grouped: "grouped";
12
+ bundle: "bundle";
13
+ virtual: "virtual";
14
+ }>>;
15
+ appliesToVatStatuses: z.ZodArray<z.ZodEnum<{
16
+ vat_payer: "vat_payer";
17
+ vat_exempt: "vat_exempt";
18
+ reverse_charge: "reverse_charge";
19
+ }>>;
20
+ isDefault: z.ZodBoolean;
21
+ priority: z.ZodNumber;
22
+ createdAt: z.ZodString;
23
+ updatedAt: z.ZodString;
24
+ }, z.core.$strip>;
25
+ export type Tax = z.infer<typeof taxSchema>;
26
+ export declare const upsertTaxRequestSchema: z.ZodObject<{
27
+ code: z.ZodString;
28
+ name: z.ZodString;
29
+ rate: z.ZodNumber;
30
+ country: z.ZodOptional<z.ZodNullable<z.ZodString>>;
31
+ productType: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
32
+ simple: "simple";
33
+ configurable: "configurable";
34
+ grouped: "grouped";
35
+ bundle: "bundle";
36
+ virtual: "virtual";
37
+ }>>>;
38
+ appliesToVatStatuses: z.ZodOptional<z.ZodArray<z.ZodEnum<{
39
+ vat_payer: "vat_payer";
40
+ vat_exempt: "vat_exempt";
41
+ reverse_charge: "reverse_charge";
42
+ }>>>;
43
+ isDefault: z.ZodOptional<z.ZodBoolean>;
44
+ priority: z.ZodOptional<z.ZodNumber>;
45
+ }, z.core.$strip>;
46
+ export declare const taxResolutionInputSchema: z.ZodObject<{
47
+ country: z.ZodString;
48
+ productType: z.ZodEnum<{
49
+ simple: "simple";
50
+ configurable: "configurable";
51
+ grouped: "grouped";
52
+ bundle: "bundle";
53
+ virtual: "virtual";
54
+ }>;
55
+ vatStatus: z.ZodEnum<{
56
+ vat_payer: "vat_payer";
57
+ vat_exempt: "vat_exempt";
58
+ reverse_charge: "reverse_charge";
59
+ }>;
60
+ }, z.core.$strip>;
61
+ export type TaxResolutionInput = z.infer<typeof taxResolutionInputSchema>;
62
+ /**
63
+ * The resolver's answer, as a union rather than a record with a `rate` that is
64
+ * sometimes meaningless (issue #124).
65
+ *
66
+ * A **configured** 0% rate is a legitimate answer — zero-rated supplies exist —
67
+ * so `rule` and `default` carry a `rate` that a caller may spend. "No rule
68
+ * matched and no default is configured" is not an answer at all, so the `none`
69
+ * arm carries **no `rate` field**: a caller has to narrow on `source` before it
70
+ * can read a number, and therefore has to decide, in the open, what its own
71
+ * surface does about it.
72
+ *
73
+ * `{ rate: 0, source: 'none' }` was the previous shape and it collapsed exactly
74
+ * that distinction — every consumer read `.rate`, got `0`, and quoted a zero-VAT
75
+ * figure nobody had configured onto documents that had already gone out.
76
+ *
77
+ * A third state, "the `taxes` module is absent", is deliberately **not** in this
78
+ * union: absence is not a value. The port gate throws `MODULE_DISABLED` before a
79
+ * resolution runs, so a caller never has to tell an absent owner from a silent
80
+ * one.
81
+ */
82
+ export declare const resolvedTaxSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
83
+ source: z.ZodLiteral<"rule">;
84
+ rate: z.ZodNumber;
85
+ taxId: z.ZodString;
86
+ }, z.core.$strip>, z.ZodObject<{
87
+ source: z.ZodLiteral<"default">;
88
+ rate: z.ZodNumber;
89
+ taxId: z.ZodString;
90
+ }, z.core.$strip>, z.ZodObject<{
91
+ source: z.ZodLiteral<"none">;
92
+ }, z.core.$strip>], "source">;
93
+ export type ResolvedTax = z.infer<typeof resolvedTaxSchema>;
94
+ /**
95
+ * Container name: `taxService`. Owner: `taxes`.
96
+ *
97
+ * `ResolvedTax`'s `none` arm is the reason this shape is a union rather than a
98
+ * number: "no rule matched" is a real answer with real consequences, and a
99
+ * caller that read a `rate` off the `none` arm would get `undefined` and quote
100
+ * zero. Issue #124 was exactly that, in `orders`' closure, and the union is
101
+ * what makes the compiler refuse it.
102
+ */
103
+ export interface TaxServicePort {
104
+ taxRateFor(input: TaxResolutionInput): Promise<ResolvedTax>;
105
+ }
106
+ //# sourceMappingURL=taxes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxes.d.ts","sourceRoot":"","sources":["../src/taxes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkBxB,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;iBAYpB,CAAC;AACH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AAE5C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;iBAYjC,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;iBAInC,CAAC;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,iBAAiB;;;;;;;;;;6BAY5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAU5D;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7D"}
package/dist/taxes.js ADDED
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import { isoDateTimeSchema, uuidSchema } from './common.js';
3
+ /**
4
+ * Tax contracts (T128, T131 / FR-051).
5
+ *
6
+ * Each rule narrows by zero or more of:
7
+ * - `country` (delivery country, ISO 3166-1 alpha-2)
8
+ * - `productType` (the catalog Product.type — simple/variant/grouped/virtual)
9
+ * - `appliesToVatStatuses` (Organization.vatStatus values that match)
10
+ *
11
+ * Resolution picks the most specific matching rule (most narrowed
12
+ * conditions). Ties fall back to the rule with `priority` highest;
13
+ * when no rule matches, the default rule (if any) is used.
14
+ */
15
+ const VAT_STATUS = z.enum(['vat_payer', 'vat_exempt', 'reverse_charge']);
16
+ export const taxSchema = z.object({
17
+ id: uuidSchema,
18
+ code: z.string().min(1).max(64),
19
+ name: z.string().min(1).max(160),
20
+ rate: z.number().finite().nonnegative(),
21
+ country: z.string().length(2).nullable(),
22
+ productType: z.enum(['simple', 'configurable', 'grouped', 'bundle', 'virtual']).nullable(),
23
+ appliesToVatStatuses: z.array(VAT_STATUS),
24
+ isDefault: z.boolean(),
25
+ priority: z.number().int(),
26
+ createdAt: isoDateTimeSchema,
27
+ updatedAt: isoDateTimeSchema,
28
+ });
29
+ export const upsertTaxRequestSchema = z.object({
30
+ code: z.string().min(1).max(64),
31
+ name: z.string().min(1).max(160),
32
+ rate: z.number().finite().nonnegative(),
33
+ country: z.string().length(2).nullable().optional(),
34
+ productType: z
35
+ .enum(['simple', 'configurable', 'grouped', 'bundle', 'virtual'])
36
+ .nullable()
37
+ .optional(),
38
+ appliesToVatStatuses: z.array(VAT_STATUS).optional(),
39
+ isDefault: z.boolean().optional(),
40
+ priority: z.number().int().optional(),
41
+ });
42
+ export const taxResolutionInputSchema = z.object({
43
+ country: z.string().length(2),
44
+ productType: z.enum(['simple', 'configurable', 'grouped', 'bundle', 'virtual']),
45
+ vatStatus: VAT_STATUS,
46
+ });
47
+ /**
48
+ * The resolver's answer, as a union rather than a record with a `rate` that is
49
+ * sometimes meaningless (issue #124).
50
+ *
51
+ * A **configured** 0% rate is a legitimate answer — zero-rated supplies exist —
52
+ * so `rule` and `default` carry a `rate` that a caller may spend. "No rule
53
+ * matched and no default is configured" is not an answer at all, so the `none`
54
+ * arm carries **no `rate` field**: a caller has to narrow on `source` before it
55
+ * can read a number, and therefore has to decide, in the open, what its own
56
+ * surface does about it.
57
+ *
58
+ * `{ rate: 0, source: 'none' }` was the previous shape and it collapsed exactly
59
+ * that distinction — every consumer read `.rate`, got `0`, and quoted a zero-VAT
60
+ * figure nobody had configured onto documents that had already gone out.
61
+ *
62
+ * A third state, "the `taxes` module is absent", is deliberately **not** in this
63
+ * union: absence is not a value. The port gate throws `MODULE_DISABLED` before a
64
+ * resolution runs, so a caller never has to tell an absent owner from a silent
65
+ * one.
66
+ */
67
+ export const resolvedTaxSchema = z.discriminatedUnion('source', [
68
+ z.object({
69
+ source: z.literal('rule'),
70
+ rate: z.number().finite().nonnegative(),
71
+ taxId: uuidSchema,
72
+ }),
73
+ z.object({
74
+ source: z.literal('default'),
75
+ rate: z.number().finite().nonnegative(),
76
+ taxId: uuidSchema,
77
+ }),
78
+ z.object({ source: z.literal('none') }),
79
+ ]);
80
+ //# sourceMappingURL=taxes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"taxes.js","sourceRoot":"","sources":["../src/taxes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * One diacritic fold — and, since issue #245, one slug generator — for every
3
+ * package that has to compare or key two spellings of the same word.
4
+ *
5
+ * `foldDiacritics` is the fold (issue #240). `slugify` is the generator built
6
+ * on it, and it replaced **eight** private copies; its own doc block carries
7
+ * the reasons that are specific to slugs. Everything below is about the fold.
8
+ *
9
+ * ## Why this file exists rather than a helper inside one consumer
10
+ *
11
+ * The fold was correct and unreachable. It lived inside
12
+ * `normalizeOrganizationName` in `organizations.ts` — published from
13
+ * `@endora-commerce/contracts`, so importable from `backend/`, `admin/` and `storefront/`
14
+ * alike, but named after **one caller**. Nobody looking for "how do I compare
15
+ * `Łatwy` with `latwy`" searches for an organisation helper, so within a year
16
+ * four private copies had grown in `admin/` (repaired by !745) and two more
17
+ * stand in `backend/` today. Every one of them was the same one-liner and every
18
+ * one of them shipped the same bug.
19
+ *
20
+ * ## The bug the one-liner has
21
+ *
22
+ * ```
23
+ * input.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase()
24
+ * ```
25
+ *
26
+ * reads as complete and is not. `ł` is U+0142: a single indivisible code point
27
+ * with **no canonical decomposition**, so NFD leaves it exactly where it was,
28
+ * the strip finds no combining mark to remove, and the `[^a-z0-9]+` pass a
29
+ * slugifier runs next deletes the letter outright — `Łatwy szablon` becomes
30
+ * `atwy-szablon`. The same holds for `Ø ø Đ đ Ð ð Þ þ ß Æ æ Œ œ`. The fix is one
31
+ * extra step, and it is the only reason this is longer than a line: map those
32
+ * letters explicitly.
33
+ *
34
+ * ## A function, not a port
35
+ *
36
+ * It is pure over its argument, so it cannot answer differently depending on
37
+ * which modules are switched on, and a gated port answering 503 `MODULE_DISABLED`
38
+ * to "fold these diacritics" would be a bug rather than a safety property.
39
+ * Node natives only (Principle IV) — a full Unicode-folding library is not worth
40
+ * a dependency for fifteen letters.
41
+ */
42
+ /**
43
+ * Latin letters NFD does not decompose, and their ASCII approximations.
44
+ *
45
+ * Every entry is a **single code point** whose Unicode decomposition is empty:
46
+ * a stroked, hooked or ligated letter rather than a base plus a combining mark.
47
+ * That is precisely the set NFD cannot help with, which is why it is hand-written.
48
+ *
49
+ * Exported so a test can enumerate it and a reader can see the whole set at
50
+ * once — not as an invitation to write a second fold around it. Reach for
51
+ * `foldDiacritics` instead; a copy of the map without the decomposition step
52
+ * is half a fold, and half a fold is what this file exists to end.
53
+ */
54
+ export declare const NON_DECOMPOSING_LATIN: Readonly<Record<string, string>>;
55
+ /**
56
+ * Fold a string so two spellings of the same word compare equal: diacritics
57
+ * removed, non-decomposing Latin letters mapped to ASCII, lowercased.
58
+ *
59
+ * In order:
60
+ *
61
+ * 1. **Decompose (NFD)** — separate every base character from its combining
62
+ * marks, so `ó` becomes `o` + U+0301.
63
+ * 2. **Strip the combining marks** via `\p{Diacritic}`.
64
+ * 3. **Map what NFD could not decompose**, per `NON_DECOMPOSING_LATIN`. This
65
+ * step runs **after** the decomposition on purpose: `Ǿ` (U+01FE) decomposes
66
+ * to `Ø` + U+0301, so a map applied first would not see the `Ø` that step 1
67
+ * is about to expose. Mapping first — as `admin/`'s copy did until issue
68
+ * #240 — folds `Ǿ` to `ø` instead of `o`.
69
+ * 4. **Lowercase.** Callers compare case-insensitively without exception, and
70
+ * the ASCII expansions above (`Th`, `AE`, `OE`) have to be lowered anyway.
71
+ *
72
+ * Whitespace is deliberately **not** touched: what counts as one space is the
73
+ * caller's policy, not the fold's. `normalizeOrganizationName` collapses runs
74
+ * and trims because it writes a search column; `admin`'s `normalize` only
75
+ * trims. Both compose it here rather than each folding its own way.
76
+ *
77
+ * Not a case-folding routine in the Unicode sense: it lowercases, it does not
78
+ * apply full case folding, and anything outside Latin (Cyrillic, CJK, Greek)
79
+ * passes through with its case lowered and nothing else changed.
80
+ */
81
+ export declare function foldDiacritics(input: string): string;
82
+ /**
83
+ * How one caller's slug grammar differs from another's. Every field is the
84
+ * caller's own policy; the *steps* are not negotiable and are not options.
85
+ */
86
+ export interface SlugifyOptions {
87
+ /**
88
+ * What a run of unusable characters collapses to. `-` everywhere except the
89
+ * newsletter tag code, whose stored grammar is `_`.
90
+ */
91
+ readonly separator?: string;
92
+ /**
93
+ * The caller's own column or contract limit. **Deliberately not defaulted to
94
+ * a repo-wide number**: eight call sites cap at 80, 150, 160, 180 or not at
95
+ * all, and each cap decides which *new* values collide under that caller's
96
+ * unique constraint. Normalising them would be a data decision dressed as a
97
+ * tidy-up.
98
+ */
99
+ readonly maxLength?: number;
100
+ /** What an input that folds away to nothing produces. Empty string by default. */
101
+ readonly fallback?: string;
102
+ /**
103
+ * Characters the caller's grammar allows **besides** `[a-z0-9]`, which
104
+ * therefore survive instead of collapsing to the separator.
105
+ *
106
+ * Empty for every slug in the kebab family, and that is the reason this is an
107
+ * option rather than a widening of the rule: `pim_ergonode`'s option values
108
+ * are stored under `^[a-z0-9_-]{1,200}$`, a grammar with **two** usable
109
+ * punctuation characters, so a generator that knows only one separator cannot
110
+ * express it. Without this, folding those values correctly would also have
111
+ * turned every already-correct `xl-red` into `xl_red` — a rename with no
112
+ * defect behind it, and one that orphans the option every product already
113
+ * points at (issue #260).
114
+ *
115
+ * Members are matched literally inside a negated character class, so a
116
+ * caller passes the characters themselves (`'-'`), not an escaped class.
117
+ */
118
+ readonly preserve?: string;
119
+ }
120
+ /**
121
+ * The repository's one slug generator (issue #245).
122
+ *
123
+ * There were **eight**: three in `backend/`, five in `admin/`, each a private
124
+ * four-line chain that read as obviously right. Two of them
125
+ * (`cms-template-layout.ts`, `pim_ergonode`'s `slugFromSourceCode`) had no fold
126
+ * step at all — `Żółw` produced `w` and `Świeże Ćwikła` produced `wie-e-wik-a`
127
+ * — and `check:diacritic-folds` was structurally blind to both, because it
128
+ * counts folds written *outside* the shared helper and a site that folds
129
+ * nothing writes none. A shared generator is what makes "does this slug fold?"
130
+ * a question with one answer.
131
+ *
132
+ * The steps, in order:
133
+ *
134
+ * 1. **Fold** via `foldDiacritics` — decompose, strip the combining marks, map
135
+ * the letters NFD leaves standing, lowercase.
136
+ * 2. **Collapse** every run of `[^a-z0-9]` — minus whatever `preserve` adds to
137
+ * the usable set — to the separator.
138
+ * 3. **Strip a leading separator**, before the slice — a leading separator can
139
+ * never survive, so it must not spend a character of the caller's budget.
140
+ * 4. **Slice** to `maxLength`, when the caller has one.
141
+ * 5. **Strip trailing separators**, after the slice — the cut can *create* one,
142
+ * which is the whole point (see below).
143
+ * 6. **Fall back** when nothing survived.
144
+ *
145
+ * ## NFD, not NFKD, and that is a decision
146
+ *
147
+ * Three of the eight normalised with `NFKD`, which additionally maps the
148
+ * compatibility characters: `fi` to `fi`, `²` to `2`, the full-width forms to
149
+ * their ASCII. Those mappings reach a slug **only** through characters that map
150
+ * *into* `[a-z0-9]`, and none of them is typed into a product name, a feed
151
+ * template name or a CMS page title. Where one does appear it now collapses to
152
+ * the separator rather than to a letter, so the slug stays legal and merely
153
+ * loses a character nobody could see in it. Against that, `ł` was being deleted
154
+ * out of every Polish name those three touched. The trade was taken for the
155
+ * admin pair in !753 and holds identically here.
156
+ *
157
+ * It has one consequence worth naming: two inputs `NFKD` kept apart can now
158
+ * fold together (`Kabel²` and `Kabel³` both become `kabel`). Every caller whose
159
+ * slug is unique-constrained already allocates against the live table —
160
+ * `CatalogAdminService.allocateUniqueSlug`, `pim_ergonode`'s
161
+ * `createWithFreeSlug` — so the second one is suffixed rather than rejected,
162
+ * exactly as two products sharing a plain name already are.
163
+ *
164
+ * ## The trailing strip runs after the slice, which is a change for six sites
165
+ *
166
+ * Six of the eight stripped **both** edge separators before cutting to length,
167
+ * so a cut landing on a separator left the slug ending in one:
168
+ * `...w-wersji-rozszerzona-`. Only `BlockEditor` and `PageEditor` had it right.
169
+ * This is the one change here that is not about diacritics; it shows only on an
170
+ * input long enough to be truncated, and it makes the result satisfy the kebab
171
+ * grammar (`^[a-z0-9]+(?:-[a-z0-9]+)*$`) that `pim_ergonode`'s own doc block
172
+ * already claimed for it.
173
+ *
174
+ * The **leading** strip stays where all eight had it — before the slice — for a
175
+ * reason that is not symmetry: a leading separator is never part of the answer,
176
+ * so letting it consume a character of `maxLength` would shorten every slug
177
+ * whose input begins with punctuation by one, in six sites at once, for no
178
+ * gain. Only the trailing edge can be *created* by the cut, so only the
179
+ * trailing strip has to run after it.
180
+ *
181
+ * Historical values are **not** migrated (owner's ruling, 2026-08-19,
182
+ * originally for the admin pair in !753 and extended to the rest): only two
183
+ * developer environments exist, so re-slugging live rows buys nobody anything,
184
+ * and every one of these values is computed fresh at create time rather than
185
+ * re-derived to look an existing row up. New values are correct from here on.
186
+ *
187
+ * **`pim_ergonode` is the exception the ruling names, and it is the reason
188
+ * `preserve` exists.** Its two derivations are re-derived on every import run
189
+ * *to find the row a previous run created*, so a changed derivation orphans the
190
+ * row rather than improving it. Those values are migrated (issue #260), and the
191
+ * migration is only affordable because the derivation change is confined to the
192
+ * fold — which is what `preserve` buys.
193
+ *
194
+ * `normalizeOrganizationName` is **not** a caller and must not become one: it
195
+ * writes the persisted `organizations.name_search` column, which is a folded
196
+ * name rather than a slug.
197
+ */
198
+ export declare function slugify(input: string, options?: SlugifyOptions): string;
199
+ //# sourceMappingURL=text-normalization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text-normalization.d.ts","sourceRoot":"","sources":["../src/text-normalization.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,qBAAqB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgBlE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAOD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAmB3E"}