@rovela-ai/sdk 0.21.0 → 0.22.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 (71) hide show
  1. package/dist/admin/api/products.d.ts.map +1 -1
  2. package/dist/admin/api/products.js +4 -0
  3. package/dist/admin/api/products.js.map +1 -1
  4. package/dist/admin/api/tax-zones.d.ts.map +1 -1
  5. package/dist/admin/api/tax-zones.js +7 -0
  6. package/dist/admin/api/tax-zones.js.map +1 -1
  7. package/dist/admin/components/OrderDetails.d.ts.map +1 -1
  8. package/dist/admin/components/OrderDetails.js +5 -1
  9. package/dist/admin/components/OrderDetails.js.map +1 -1
  10. package/dist/admin/components/ProductForm.d.ts.map +1 -1
  11. package/dist/admin/components/ProductForm.js +25 -1
  12. package/dist/admin/components/ProductForm.js.map +1 -1
  13. package/dist/admin/components/TaxSettings.d.ts.map +1 -1
  14. package/dist/admin/components/TaxSettings.js +14 -2
  15. package/dist/admin/components/TaxSettings.js.map +1 -1
  16. package/dist/admin/types.d.ts +15 -0
  17. package/dist/admin/types.d.ts.map +1 -1
  18. package/dist/checkout/api/manual.d.ts.map +1 -1
  19. package/dist/checkout/api/manual.js +7 -0
  20. package/dist/checkout/api/manual.js.map +1 -1
  21. package/dist/checkout/api/shipping.d.ts +0 -18
  22. package/dist/checkout/api/shipping.d.ts.map +1 -1
  23. package/dist/checkout/api/shipping.js +33 -3
  24. package/dist/checkout/api/shipping.js.map +1 -1
  25. package/dist/checkout/components/CheckoutFlow.d.ts.map +1 -1
  26. package/dist/checkout/components/CheckoutFlow.js +30 -13
  27. package/dist/checkout/components/CheckoutFlow.js.map +1 -1
  28. package/dist/checkout/components/OrderSummary.d.ts +1 -1
  29. package/dist/checkout/components/OrderSummary.d.ts.map +1 -1
  30. package/dist/checkout/components/OrderSummary.js +5 -2
  31. package/dist/checkout/components/OrderSummary.js.map +1 -1
  32. package/dist/checkout/server/create-checkout-session.d.ts.map +1 -1
  33. package/dist/checkout/server/create-checkout-session.js +38 -9
  34. package/dist/checkout/server/create-checkout-session.js.map +1 -1
  35. package/dist/checkout/server/handle-webhook.js +6 -0
  36. package/dist/checkout/server/handle-webhook.js.map +1 -1
  37. package/dist/checkout/server/order-service.d.ts.map +1 -1
  38. package/dist/checkout/server/order-service.js +21 -0
  39. package/dist/checkout/server/order-service.js.map +1 -1
  40. package/dist/checkout/server/pricing.d.ts +28 -1
  41. package/dist/checkout/server/pricing.d.ts.map +1 -1
  42. package/dist/checkout/server/pricing.js +84 -6
  43. package/dist/checkout/server/pricing.js.map +1 -1
  44. package/dist/checkout/types.d.ts +32 -0
  45. package/dist/checkout/types.d.ts.map +1 -1
  46. package/dist/core/db/queries.d.ts +106 -25
  47. package/dist/core/db/queries.d.ts.map +1 -1
  48. package/dist/core/db/queries.js +83 -2
  49. package/dist/core/db/queries.js.map +1 -1
  50. package/dist/core/db/schema.d.ts +65 -0
  51. package/dist/core/db/schema.d.ts.map +1 -1
  52. package/dist/core/db/schema.js +26 -0
  53. package/dist/core/db/schema.js.map +1 -1
  54. package/dist/emails/send/orders.d.ts +4 -0
  55. package/dist/emails/send/orders.d.ts.map +1 -1
  56. package/dist/emails/send/orders.js +1 -0
  57. package/dist/emails/send/orders.js.map +1 -1
  58. package/dist/emails/templates/order-confirmation.d.ts +9 -0
  59. package/dist/emails/templates/order-confirmation.d.ts.map +1 -1
  60. package/dist/emails/templates/order-confirmation.js +19 -8
  61. package/dist/emails/templates/order-confirmation.js.map +1 -1
  62. package/dist/emails/types.d.ts +5 -0
  63. package/dist/emails/types.d.ts.map +1 -1
  64. package/dist/products/api/products.d.ts.map +1 -1
  65. package/dist/products/api/products.js +18 -3
  66. package/dist/products/api/products.js.map +1 -1
  67. package/docs/changelog/epoch-09-catalog-tax.md +91 -0
  68. package/package.json +1 -1
  69. package/templates/store-template/.claude/skills/manage-products/SKILL.md +1 -1
  70. package/templates/store-template/app/account/orders/[id]/invoice/page.tsx +19 -4
  71. package/templates/store-template/package.json +1 -1
@@ -0,0 +1,91 @@
1
+ # E9 Catalog & tax — 0.22.0..0.22.0
2
+
3
+ ## What landed
4
+ Two catalog-commerce behaviors every PrestaShop-migrated store needs: the
5
+ public category filter is now TREE-AWARE (a parent-category link includes its
6
+ descendant categories' products — imported catalogs assign products to leaf
7
+ categories), and TAX CLASSES bring per-product VAT rates (French 5.5%/20%
8
+ food split): products carry a `tax_class`, tax zones carry per-class rates,
9
+ and every money surface (quote, Stripe session, orders, emails, invoice,
10
+ admin) computes and renders the per-rate VAT ventilation ("dont TVA par
11
+ taux") French invoices legally require.
12
+
13
+ ## SDK-internal (free)
14
+ - `findProducts`/`countProducts` accept `categoryId: string | string[]`
15
+ (IN-match). New `findCategoryDescendantIds(id)` (recursive CTE) and
16
+ `findTaxClassesByProductIds(ids)`.
17
+ - `resolveTax` accepts optional `lines` — per-line computation grouped by
18
+ rate, order discount allocated pro-rata (largest-remainder, sums exact),
19
+ shipping taxed at the zone's base rate. Returns `taxBreakdown` +
20
+ `taxClassRates`. Without `lines`, or on a zone with no `class_rates`, the
21
+ original lump formula runs — cent-identical to 0.21.x.
22
+ - `buildQuote` carries `taxBreakdown`/`taxClassRates` on the Quote; the
23
+ Stripe session divides each line by ITS OWN rate in tax-inclusive mode and
24
+ stamps `rovelaTaxBreakdown` metadata; both order writers persist
25
+ `orders.tax_breakdown`.
26
+ - Admin: TaxSettings zone editor gains per-class rate rows; ProductForm
27
+ gains a Tax class field (datalist fed from the zones' class names);
28
+ tax-zones API accepts `classRates`; products API accepts `taxClass`.
29
+
30
+ ## Template contract
31
+ - `app/account/orders/[id]/invoice/page.tsx` — the totals block renders one
32
+ `TVA {rate}%` row per `order.taxBreakdown` entry when the order carries a
33
+ multi-rate ventilation, else the single flat tax row (stock shape: gated
34
+ branch on `order.taxBreakdown?.length > 1`). Stores with a customized
35
+ invoice keep their layout and add the same branch.
36
+
37
+ ## Behavior contracts
38
+ - **Public `/api/products?category=` is tree-aware.** A parent category id or
39
+ slug now returns its own products PLUS every descendant category's. Stores
40
+ whose nav deliberately relied on parent links showing ONLY directly-assigned
41
+ products (rare; usually a bug they worked around) will see more products.
42
+ Admin product filters are UNCHANGED (exact match).
43
+ - **`OrderSummary` accepts `taxBreakdown`** and renders per-rate rows when
44
+ more than one rate is present; `CheckoutFlow` computes it from the estimate
45
+ response. Single-rate stores render byte-identically to 0.21.x.
46
+ - The pre-checkout shipping estimate (`/api/checkout/shipping`) accepts an
47
+ optional `productId` per item and returns `tax.breakdown`. Clients that
48
+ omit `productId` get the previous base-rate estimate (totals unchanged in
49
+ tax-inclusive stores).
50
+
51
+ ## Custom-code hazards
52
+ - `grep -rn "resolveTax(" app lib components` — custom callers keep working
53
+ (the `lines` param is optional; omitted = old math). If a custom checkout
54
+ displays tax recomputed from `taxRate` alone, it cannot reproduce a
55
+ multi-rate cart: read `tax.breakdown`/`quote.taxBreakdown` when present
56
+ (see CheckoutFlow's per-group recompute for the reference implementation).
57
+ - `grep -rn "taxRate" app components` — any custom "TVA (20%)" label stays
58
+ correct for single-rate stores; multi-rate stores should render the
59
+ breakdown rows instead.
60
+ - `grep -rn "category=" app components lib` — store-authored nav links to
61
+ PARENT categories now include leaf products (this is almost always the
62
+ desired fix, not a regression).
63
+
64
+ ## DB
65
+ - `products.tax_class varchar(50)` NULL — self-heals via
66
+ `ensureCatalogTranslationsSchema` (every catalog chokepoint).
67
+ - `tax_zones.class_rates jsonb NOT NULL DEFAULT '{}'` — self-heals via
68
+ `ensureVisibilityColumns`.
69
+ - `orders.tax_breakdown jsonb` NULL — self-heals via
70
+ `ensurePaymentMethodsSchema`.
71
+ - All three are additive with behavior-preserving defaults: no class rates
72
+ configured = 0.21.x math to the cent. Pre-applying is optional.
73
+
74
+ ## New surfaces
75
+ - None. (TaxSettings/ProductForm gains are inside existing admin pages; no
76
+ new routes to mount.)
77
+
78
+ ## Verify
79
+ 1. `/products?category=<parent slug>` lists products assigned to child
80
+ categories; the admin Products page filtered to the same parent does NOT.
81
+ 2. Settings → Tax: add a class rate (e.g. `tva-5-5` = 5.5) on the active
82
+ zone; assign the class to one product (Product form → Pricing → Tax
83
+ class).
84
+ 3. Cart with one classed + one unclassed product → checkout review shows two
85
+ `TVA x%` rows summing to the receipt tax; totals unchanged
86
+ (tax-inclusive).
87
+ 4. Place a test order → `orders.tax_breakdown` populated; invoice page and
88
+ order-confirmation email render the ventilation; admin order detail shows
89
+ per-rate rows.
90
+ 5. A store that configures NO class rates: quote totals and every tax line
91
+ render exactly as before the update.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rovela-ai/sdk",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Rovela SDK - Pre-built e-commerce components for AI-powered store generation",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@ Usage: `nextjs_call` with `port="3000"` and `toolName="execute_sql"`
27
27
 
28
28
  | Table | Key Columns |
29
29
  |-------|-------------|
30
- | `products` | id, name, slug, price, status (`draft`/`active`/`archived`), categoryId, hasVariants, images (JSONB string[]), videos (JSONB string[]), metafields (JSONB key/label/value/type), translations (JSONB per-locale name/description) |
30
+ | `products` | id, name, slug, price, status (`draft`/`active`/`archived`), categoryId, hasVariants, images (JSONB string[]), videos (JSONB string[]), metafields (JSONB key/label/value/type), translations (JSONB per-locale name/description), taxClass (0.22.0 — names a rate in tax_zones.class_rates; NULL = zone base rate) |
31
31
  | `product_variants` | id, productId (CASCADE), sku, name, price, inventory, attributes (JSONB), image, video |
32
32
  | `categories` | id, name, slug, parentId (self-ref), order, translations (JSONB) |
33
33
  | `orders` | id, customerId, email, status, total, currency (ISO snapshot — display orders in THIS currency), refundAmount, returnReason, shippingAddress (JSONB) |
@@ -42,6 +42,8 @@ interface InvoiceOrder {
42
42
  total: number
43
43
  subtotal: number
44
44
  tax: number
45
+ /** Per-rate VAT ventilation (0.22.0) — cents; null/absent = flat tax line. */
46
+ taxBreakdown?: Array<{ rate: number; baseCents: number; taxCents: number }> | null
45
47
  shipping: number
46
48
  discountAmount?: number | string
47
49
  discountCode?: string | null
@@ -245,10 +247,23 @@ function InvoiceContent({ orderId }: { orderId: string }) {
245
247
  <span>{t('common.shipping')}</span>
246
248
  <span>{order.shipping > 0 ? formatPrice(order.shipping, orderCurrency) : t('common.free')}</span>
247
249
  </div>
248
- <div className="flex justify-between text-muted-foreground">
249
- <span>{t('common.tax')}</span>
250
- <span>{formatPrice(order.tax, orderCurrency)}</span>
251
- </div>
250
+ {order.taxBreakdown && order.taxBreakdown.length > 1 ? (
251
+ // Per-rate VAT ventilation (0.22.0) — the legally required French
252
+ // invoice breakdown ("dont TVA par taux").
253
+ order.taxBreakdown.map((entry) => (
254
+ <div key={entry.rate} className="flex justify-between text-muted-foreground">
255
+ <span>
256
+ {t('common.tax')} {entry.rate}%
257
+ </span>
258
+ <span>{formatPrice(entry.taxCents / 100, orderCurrency)}</span>
259
+ </div>
260
+ ))
261
+ ) : (
262
+ <div className="flex justify-between text-muted-foreground">
263
+ <span>{t('common.tax')}</span>
264
+ <span>{formatPrice(order.tax, orderCurrency)}</span>
265
+ </div>
266
+ )}
252
267
  <div className="flex justify-between border-t pt-2 text-base font-semibold text-foreground">
253
268
  <span>{t('common.total')}</span>
254
269
  <span>{formatPrice(order.total, orderCurrency)}</span>
@@ -9,7 +9,7 @@
9
9
  "lint": "eslint ."
10
10
  },
11
11
  "dependencies": {
12
- "@rovela-ai/sdk": "0.21.0",
12
+ "@rovela-ai/sdk": "0.22.0",
13
13
  "@tailwindcss/forms": "0.5.11",
14
14
  "@tailwindcss/typography": "0.5.19",
15
15
  "autoprefixer": "10.4.24",