@endora-commerce/mod-carts 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.
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts +51 -0
- package/dist/admin/components/CartApprovalPolicyPanel.d.ts.map +1 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js +59 -0
- package/dist/admin/components/CartApprovalPolicyPanel.js.map +1 -0
- package/dist/admin/index.d.ts +38 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +73 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CartDetail.d.ts +3 -0
- package/dist/admin/pages/CartDetail.d.ts.map +1 -0
- package/dist/admin/pages/CartDetail.js +74 -0
- package/dist/admin/pages/CartDetail.js.map +1 -0
- package/dist/admin/pages/CartsList.d.ts +3 -0
- package/dist/admin/pages/CartsList.d.ts.map +1 -0
- package/dist/admin/pages/CartsList.js +90 -0
- package/dist/admin/pages/CartsList.js.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts +29 -0
- package/dist/admin/zones/CartApprovalPolicy.d.ts.map +1 -0
- package/dist/admin/zones/CartApprovalPolicy.js +11 -0
- package/dist/admin/zones/CartApprovalPolicy.js.map +1 -0
- package/dist/backend/cli/abandonment-sweep.d.ts +64 -0
- package/dist/backend/cli/abandonment-sweep.d.ts.map +1 -0
- package/dist/backend/cli/abandonment-sweep.js +17 -0
- package/dist/backend/cli/abandonment-sweep.js.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts +29 -0
- package/dist/backend/entities/cart-audit-entry.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-audit-entry.entity.js +109 -0
- package/dist/backend/entities/cart-audit-entry.entity.js.map +1 -0
- package/dist/backend/entities/cart-item.entity.d.ts +42 -0
- package/dist/backend/entities/cart-item.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart-item.entity.js +119 -0
- package/dist/backend/entities/cart-item.entity.js.map +1 -0
- package/dist/backend/entities/cart.entity.d.ts +96 -0
- package/dist/backend/entities/cart.entity.d.ts.map +1 -0
- package/dist/backend/entities/cart.entity.js +207 -0
- package/dist/backend/entities/cart.entity.js.map +1 -0
- package/dist/backend/index.d.ts +122 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +184 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +31 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +95 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.d.ts +101 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +620 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/routes.organization.d.ts +29 -0
- package/dist/backend/routes.organization.d.ts.map +1 -0
- package/dist/backend/routes.organization.js +150 -0
- package/dist/backend/routes.organization.js.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts +34 -0
- package/dist/backend/services/cart-abandonment-settings.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-settings.js +55 -0
- package/dist/backend/services/cart-abandonment-settings.js.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts +110 -0
- package/dist/backend/services/cart-abandonment-worker.d.ts.map +1 -0
- package/dist/backend/services/cart-abandonment-worker.js +185 -0
- package/dist/backend/services/cart-abandonment-worker.js.map +1 -0
- package/dist/backend/services/cart-admin-service.d.ts +58 -0
- package/dist/backend/services/cart-admin-service.d.ts.map +1 -0
- package/dist/backend/services/cart-admin-service.js +397 -0
- package/dist/backend/services/cart-admin-service.js.map +1 -0
- package/dist/backend/services/cart-approval-service.d.ts +147 -0
- package/dist/backend/services/cart-approval-service.d.ts.map +1 -0
- package/dist/backend/services/cart-approval-service.js +242 -0
- package/dist/backend/services/cart-approval-service.js.map +1 -0
- package/dist/backend/services/cart-audit-service.d.ts +75 -0
- package/dist/backend/services/cart-audit-service.d.ts.map +1 -0
- package/dist/backend/services/cart-audit-service.js +70 -0
- package/dist/backend/services/cart-audit-service.js.map +1 -0
- package/dist/backend/services/cart-conversion-service.d.ts +104 -0
- package/dist/backend/services/cart-conversion-service.d.ts.map +1 -0
- package/dist/backend/services/cart-conversion-service.js +175 -0
- package/dist/backend/services/cart-conversion-service.js.map +1 -0
- package/dist/backend/services/cart-coupon-service.d.ts +129 -0
- package/dist/backend/services/cart-coupon-service.d.ts.map +1 -0
- package/dist/backend/services/cart-coupon-service.js +244 -0
- package/dist/backend/services/cart-coupon-service.js.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts +43 -0
- package/dist/backend/services/cart-organization-visibility-service.d.ts.map +1 -0
- package/dist/backend/services/cart-organization-visibility-service.js +98 -0
- package/dist/backend/services/cart-organization-visibility-service.js.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts +38 -0
- package/dist/backend/services/cart-placement-apply-port.d.ts.map +1 -0
- package/dist/backend/services/cart-placement-apply-port.js +71 -0
- package/dist/backend/services/cart-placement-apply-port.js.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts +62 -0
- package/dist/backend/services/cart-pricing-recompute.d.ts.map +1 -0
- package/dist/backend/services/cart-pricing-recompute.js +131 -0
- package/dist/backend/services/cart-pricing-recompute.js.map +1 -0
- package/dist/backend/services/cart-query-service.d.ts +29 -0
- package/dist/backend/services/cart-query-service.d.ts.map +1 -0
- package/dist/backend/services/cart-query-service.js +40 -0
- package/dist/backend/services/cart-query-service.js.map +1 -0
- package/dist/backend/services/cart-read-port.d.ts +40 -0
- package/dist/backend/services/cart-read-port.d.ts.map +1 -0
- package/dist/backend/services/cart-read-port.js +144 -0
- package/dist/backend/services/cart-read-port.js.map +1 -0
- package/dist/backend/services/cart-recompute-cache.d.ts +49 -0
- package/dist/backend/services/cart-recompute-cache.d.ts.map +1 -0
- package/dist/backend/services/cart-recompute-cache.js +56 -0
- package/dist/backend/services/cart-recompute-cache.js.map +1 -0
- package/dist/backend/services/cart-service.d.ts +165 -0
- package/dist/backend/services/cart-service.d.ts.map +1 -0
- package/dist/backend/services/cart-service.js +556 -0
- package/dist/backend/services/cart-service.js.map +1 -0
- package/dist/backend/services/cart-state-machine.d.ts +51 -0
- package/dist/backend/services/cart-state-machine.d.ts.map +1 -0
- package/dist/backend/services/cart-state-machine.js +74 -0
- package/dist/backend/services/cart-state-machine.js.map +1 -0
- package/dist/backend/services/cart-upsell-service.d.ts +57 -0
- package/dist/backend/services/cart-upsell-service.d.ts.map +1 -0
- package/dist/backend/services/cart-upsell-service.js +100 -0
- package/dist/backend/services/cart-upsell-service.js.map +1 -0
- package/dist/backend/services/customer-group-resolver.d.ts +27 -0
- package/dist/backend/services/customer-group-resolver.d.ts.map +1 -0
- package/dist/backend/services/customer-group-resolver.js +32 -0
- package/dist/backend/services/customer-group-resolver.js.map +1 -0
- package/dist/manifest.d.ts +221 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +224 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts +43 -0
- package/dist/migrations/20260611T140352_carts_consolidation.d.ts.map +1 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js +237 -0
- package/dist/migrations/20260611T140352_carts_consolidation.js.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts +12 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.d.ts.map +1 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js +20 -0
- package/dist/migrations/20260611T140413_carts_cart_item_packaging.js.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts +50 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.d.ts.map +1 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js +62 -0
- package/dist/migrations/20260818T081253_carts_cart_completed_order_fk.js.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts +67 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.d.ts.map +1 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js +121 -0
- package/dist/migrations/20260830T112911_carts_organization_attribution_check.js.map +1 -0
- package/dist/migrations/index.d.ts +30 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +35 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/ports/index.d.ts +106 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +2 -0
- package/dist/ports/index.js.map +1 -0
- package/docs/carts.md +232 -0
- package/i18n/en.json +96 -0
- package/i18n/pl.json +96 -0
- package/package.json +107 -0
- package/tailwind.css +14 -0
package/docs/carts.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: carts
|
|
3
|
+
description: Customer shopping cart with anonymous→logged-in merge
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `carts`
|
|
7
|
+
|
|
8
|
+
Anonymous and customer-bound shopping carts. An anonymous cart is identified
|
|
9
|
+
by a long-lived cookie token; on login it merges into the Customer's cart
|
|
10
|
+
deterministically.
|
|
11
|
+
|
|
12
|
+
The carts consolidation of May 2026 extended the foundation cart
|
|
13
|
+
module with a full lifecycle (`active` / `abandoned` / `completed` /
|
|
14
|
+
`rejected`), an orthogonal approval sub-state, sales-channel scoping,
|
|
15
|
+
last-activity bookkeeping, coupon application against the Promotions module,
|
|
16
|
+
up-sell suggestions, three conversions (Cart ↔ Quote Request, Shopping List
|
|
17
|
+
→ Cart), Organization-Administrator visibility + approval gate, an
|
|
18
|
+
abandonment sweep, and a platform-admin observability surface.
|
|
19
|
+
|
|
20
|
+
## Lifecycle
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
┌──────────────────────────────────────────────────────┐
|
|
24
|
+
│ │
|
|
25
|
+
[ active / not_required ] ──(qty/add/remove/coupon)──┐ │
|
|
26
|
+
│ ▼ │
|
|
27
|
+
│ [ active / pending ] │
|
|
28
|
+
│ │ │
|
|
29
|
+
│ ├──(org-admin │
|
|
30
|
+
│ │ approve)──▶ │
|
|
31
|
+
│ │ [ active / │
|
|
32
|
+
│ │ approved ] │
|
|
33
|
+
│ │ │ │
|
|
34
|
+
│ ▼ ▼ │
|
|
35
|
+
│ (org-admin reject) [ rejected / rejected_by_org_admin ]
|
|
36
|
+
│
|
|
37
|
+
▼
|
|
38
|
+
[ abandoned ] ◀──(sweep: last_activity_at < threshold)── [ active / * ]
|
|
39
|
+
│ ▲
|
|
40
|
+
│ │
|
|
41
|
+
└────────────────(any buyer activity)──────────────────┘
|
|
42
|
+
|
|
43
|
+
[ active / approved ] ──(checkout success)──▶ [ completed / approved ]
|
|
44
|
+
[ active / * ] ──(Cart→QR conversion)─▶ [ completed / *; converted_to_quote_request_id set ]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Re-arm rule: any buyer-driven mutation (add/remove/quantity/coupon) on an
|
|
48
|
+
`approved` cart silently drops `approval_status` back to `pending`.
|
|
49
|
+
|
|
50
|
+
Self-approval exemption: a cart created by an Organization Administrator
|
|
51
|
+
is born with `approval_status='not_required'` regardless of the per-Org
|
|
52
|
+
policy flag.
|
|
53
|
+
|
|
54
|
+
## Public surface
|
|
55
|
+
|
|
56
|
+
### Storefront (buyer)
|
|
57
|
+
|
|
58
|
+
| Verb + Path | Purpose |
|
|
59
|
+
| --- | --- |
|
|
60
|
+
| `GET /api/v1/cart` | Active cart (lazy-create); the payload includes `status`, `approvalStatus`, `salesChannelId`, `grandTotal`, `discount`, `primaryCta`, `droppedLines`, `couponDroppedThisRead`, `lastActivityAt` |
|
|
61
|
+
| `POST /api/v1/cart/items` | Add line item (200-line cap enforced) |
|
|
62
|
+
| `PATCH /api/v1/cart/items/:itemId` | Update quantity (accepts `0` to delete) |
|
|
63
|
+
| `DELETE /api/v1/cart/items/:itemId` | Remove line |
|
|
64
|
+
| `POST /api/v1/cart/items/:itemId/save-to-shopping-list` | Push line to a Purchase List |
|
|
65
|
+
| `POST /api/v1/cart/touch` | Explicit page-open ping; bumps `last_activity_at` and reactivates an abandoned cart |
|
|
66
|
+
| `GET /api/v1/cart/upsells?limit=N` | Up-sell strip from Catalog `product_links` (kind = `up_sell`) |
|
|
67
|
+
| `POST /api/v1/cart/coupon` | Apply (or replace, or clear) the active coupon code; 422 `CART_COUPON_REJECTED` with reason on rejection |
|
|
68
|
+
| `DELETE /api/v1/cart/coupon` | Alias for `POST {code: null}` |
|
|
69
|
+
| `POST /api/v1/cart/convert-to-quote-request` | Cart → Quote Request; source cart becomes `completed` with `converted_to_quote_request_id` set |
|
|
70
|
+
| `POST /api/v1/cart/from-quote-request/:qrId` | Quote Request → Cart; lines re-priced from the customer's current price list |
|
|
71
|
+
| `POST /api/v1/cart/from-shopping-list/:listId` | Shopping List → Cart; lines re-priced |
|
|
72
|
+
|
|
73
|
+
### Storefront (Organization Administrator)
|
|
74
|
+
|
|
75
|
+
| Verb + Path | Purpose |
|
|
76
|
+
| --- | --- |
|
|
77
|
+
| `GET /api/v1/organization/carts` | List every cart in the caller's Organization |
|
|
78
|
+
| `GET /api/v1/organization/carts/:id` | Detail (member-owned cart) |
|
|
79
|
+
| `PATCH /api/v1/organization/policies/cart-approval` | Toggle `requires_cart_approval` for the Organization |
|
|
80
|
+
| `POST /api/v1/cart/submit-for-approval` | Buyer submits cart for Org-Admin approval (no-op when policy off; refused for Org-Admins themselves) |
|
|
81
|
+
| `POST /api/v1/organization/carts/:id/approve` | Approve a pending cart |
|
|
82
|
+
| `POST /api/v1/organization/carts/:id/reject` | Reject a pending cart with a required reason |
|
|
83
|
+
|
|
84
|
+
Role gate: `CustomerAccount.role === 'organization_admin'` for every
|
|
85
|
+
`organization/*` route. Foreign-organization carts return 404
|
|
86
|
+
(anti-enumeration symmetry).
|
|
87
|
+
|
|
88
|
+
### Admin
|
|
89
|
+
|
|
90
|
+
| Verb + Path | Capability | Purpose |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `GET /api/v1/admin/carts` | `carts:read` | Paginated platform-wide list (status / approvalStatus / org / customer / channel / date filters) |
|
|
93
|
+
| `GET /api/v1/admin/carts/:id` | `carts:read` | Read-only cart detail |
|
|
94
|
+
| `GET /api/v1/admin/carts/:id/audit` | `carts:read` | Cart-detail audit feed |
|
|
95
|
+
| `POST /api/v1/admin/carts/:id/reject` | `carts:reject` | Emergency terminal reject (requires reason) |
|
|
96
|
+
|
|
97
|
+
## Entities
|
|
98
|
+
|
|
99
|
+
- `Cart` (one active per `(customer_account_id, sales_channel_id)`; partial
|
|
100
|
+
unique index enforces this in PostgreSQL).
|
|
101
|
+
- `CartItem` (per-line with `unit_price` snapshot at add + cached
|
|
102
|
+
`recomputed_*` columns).
|
|
103
|
+
- `CartAuditEntry` (typed per-cart feed; indexed on `(cart_id, occurred_at
|
|
104
|
+
DESC)`). Every state transition also lands a row in the existing
|
|
105
|
+
`audit_log_entries` table via `AuditPort` for the platform-wide
|
|
106
|
+
audit timeline.
|
|
107
|
+
|
|
108
|
+
## Settings
|
|
109
|
+
|
|
110
|
+
| Key | Default | Description |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `carts.abandonment.inactivity_minutes` | `10080` (7 days) | Minutes of inactivity before an `active` cart is considered `abandoned`. `0` disables the sweep. |
|
|
113
|
+
| `carts.abandonment.notification_recipient` | `""` (empty) | Single e-mail address to notify on abandonment. Empty = no notification e-mail (the status flip still occurs). |
|
|
114
|
+
|
|
115
|
+
Seeded by the module-lifecycle ManifestReconciler on backend boot (see
|
|
116
|
+
`packages/modules/carts/src/manifest.ts`).
|
|
117
|
+
|
|
118
|
+
## Merge-on-login
|
|
119
|
+
|
|
120
|
+
`cart-service.ts#mergeAnonymousIntoCustomer()` is invoked by the
|
|
121
|
+
`organizations` module's `onLogin` hook. Conflicting line items combine
|
|
122
|
+
quantities; the anonymous cart cookie is invalidated atomically. The source
|
|
123
|
+
anonymous cart's `status` flips to `'completed'` (not `'abandoned'`) so the
|
|
124
|
+
sweep does not later fire a stray abandonment notification for it.
|
|
125
|
+
|
|
126
|
+
## Abandonment sweep
|
|
127
|
+
|
|
128
|
+
`cart-abandonment-worker.ts` exposes a plain async `sweep(now?)` mirroring
|
|
129
|
+
the `RfqExpiryWorker` pattern. The sweep:
|
|
130
|
+
|
|
131
|
+
1. Reads `carts.abandonment.inactivity_minutes` (≤ 0 disables).
|
|
132
|
+
2. Selects `active` carts whose `last_activity_at < now - threshold` AND
|
|
133
|
+
`abandonment_notified_at IS NULL` (idempotency).
|
|
134
|
+
3. Flips status to `abandoned`, stamps `abandonment_notified_at = now`.
|
|
135
|
+
4. Writes one `cart_audit_entries.abandonment_swept` row per affected cart.
|
|
136
|
+
5. Dispatches an e-mail to the configured recipient **only** for non-empty
|
|
137
|
+
carts (empty carts flip status but suppress the notification per the
|
|
138
|
+
spec edge case).
|
|
139
|
+
6. Notification dispatch failures do not roll back the status flip.
|
|
140
|
+
|
|
141
|
+
Reactivation: any buyer activity (touch, add/remove/qty/coupon) on an
|
|
142
|
+
`abandoned` cart returns it to `active` and clears
|
|
143
|
+
`abandonment_notified_at` so the next abandonment cycle can re-notify.
|
|
144
|
+
|
|
145
|
+
## Conversions
|
|
146
|
+
|
|
147
|
+
- **Cart → Quote Request**: `RfqService.createForCustomer` is reused; the
|
|
148
|
+
cart's buyer-snapshotted unit prices are carried over as
|
|
149
|
+
`desired_unit_price` so sales sees the cart's reference price. The source
|
|
150
|
+
cart's items are deleted and the cart flips to `'completed'` with
|
|
151
|
+
`converted_to_quote_request_id` set.
|
|
152
|
+
- **Quote Request → Cart**: iterates the QR's items through
|
|
153
|
+
`CartService.addItem`, which already routes through `PricingService` for
|
|
154
|
+
re-pricing. The QR's `desired_unit_price` is intentionally dropped — the
|
|
155
|
+
buyer's current contractual prices are the source of truth. Unavailable /
|
|
156
|
+
no-price / not-purchasable lines are skipped and returned in
|
|
157
|
+
`droppedLines[]` with typed reasons.
|
|
158
|
+
- **Shopping List → Cart**: delegated to the existing
|
|
159
|
+
`ShoppingListService.convertToCart`; the carts module
|
|
160
|
+
exposes a port that composition wires to it.
|
|
161
|
+
|
|
162
|
+
## Coupon application
|
|
163
|
+
|
|
164
|
+
The carts module never owns promotion logic — it plumbs the buyer's coupon
|
|
165
|
+
code through `PromotionService.applyToCart(snapshot)`. The
|
|
166
|
+
`CartCouponService.apply(cart, code)` method:
|
|
167
|
+
|
|
168
|
+
1. Looks up the named `Promotion` row.
|
|
169
|
+
2. Runs the local pre-checks (existence, validity window, org match, min
|
|
170
|
+
cart subtotal) and emits a typed `CouponDropReason` on the first failure.
|
|
171
|
+
3. If pre-checks pass, calls `applyToCart` and verifies the named promotion
|
|
172
|
+
is in the returned `appliedPromotions[]`.
|
|
173
|
+
4. On success: writes `cart.applied_promotion_code = code`, audits
|
|
174
|
+
`coupon_applied`, re-arms approval if needed.
|
|
175
|
+
5. On any failure: returns `{outcome: 'dropped', reason, shortfall?}` and
|
|
176
|
+
does not change the cart.
|
|
177
|
+
|
|
178
|
+
`reevaluateOnRead(cart, items)` is intended to be called from the GET-cart
|
|
179
|
+
read path so a previously-applied code that no longer fits (e.g. cart fell
|
|
180
|
+
below min spend after a line removal) is silently dropped and the response
|
|
181
|
+
surfaces `couponDroppedThisRead`. Wiring into the read path is a follow-up.
|
|
182
|
+
|
|
183
|
+
## Extension points
|
|
184
|
+
|
|
185
|
+
- **Re-pricing-on-read** — `CartPricingRecompute` + `CartRecomputeCache`
|
|
186
|
+
(30 s Redis TTL) exist and are tested but not yet wired into `GET
|
|
187
|
+
/api/v1/cart`; the snapshotted `unit_price` is returned today.
|
|
188
|
+
- **Email dispatch** — submit-for-approval / approve / reject / abandonment
|
|
189
|
+
e-mails are scaffolded in the service surfaces; dedicated templates are a
|
|
190
|
+
follow-up.
|
|
191
|
+
- **Abandonment scheduler** — the worker is constructed in production
|
|
192
|
+
composition but not yet wired to a cron / BullMQ schedule. The ops CLI
|
|
193
|
+
`pnpm --filter backend run cart:abandonment-sweep` runs one tick by hand.
|
|
194
|
+
It is a manifest-declared command the host runs, so it sweeps through the
|
|
195
|
+
composition's own `cartAbandonmentWorker` — see
|
|
196
|
+
`packages/modules/carts/src/backend/cli/abandonment-sweep.ts` for the body and
|
|
197
|
+
the `cliCommands` export in `packages/modules/carts/src/manifest.ts` for the
|
|
198
|
+
declaration.
|
|
199
|
+
|
|
200
|
+
## Audit retention
|
|
201
|
+
|
|
202
|
+
`cart_audit_entries` is the **per-cart action log** (line added/removed,
|
|
203
|
+
quantity changed, coupon applied/cleared/dropped, approval submitted /
|
|
204
|
+
approved / rejected, sweep marked the cart abandoned, conversion to /
|
|
205
|
+
from a Quote Request, conversion from a Shopping List). Every row also
|
|
206
|
+
lands in the platform-wide `audit_log_entries` table via
|
|
207
|
+
`CartAuditService.record`, so the carts table is the canonical
|
|
208
|
+
denormalised view used by the Org-Admin and platform-admin "Cart history"
|
|
209
|
+
panel.
|
|
210
|
+
|
|
211
|
+
Retention policy:
|
|
212
|
+
|
|
213
|
+
- **Never auto-purge `cart_audit_entries`.** Org admins and the platform
|
|
214
|
+
admin rely on a complete history to defend approval decisions and
|
|
215
|
+
reproduce buyer disputes. The table is append-only — services never
|
|
216
|
+
`UPDATE` or `DELETE` rows.
|
|
217
|
+
- **Cart deletion cascade.** `cart_audit_entries.cart_id` is `ON DELETE
|
|
218
|
+
CASCADE`. We do not currently delete carts in production; if a future
|
|
219
|
+
GDPR / right-to-erasure workflow ever does, the audit trail follows.
|
|
220
|
+
When that workflow lands, mirror the carts row plus its audit entries
|
|
221
|
+
into a long-term audit-only archive table *before* the cascade fires —
|
|
222
|
+
do not silently lose history.
|
|
223
|
+
- **Production data-purges must exclude `cart_audit_entries`.** Any
|
|
224
|
+
scheduled job that prunes carts, customer accounts, or organizations
|
|
225
|
+
for storage hygiene MUST either skip `cart_audit_entries` or archive
|
|
226
|
+
it first. Reviewers: add this table to the exclusion list in
|
|
227
|
+
`packages/modules/audit_logs/src/backend/retention-policy.ts` when that policy
|
|
228
|
+
is introduced.
|
|
229
|
+
- **The platform-wide `audit_log_entries` table follows the audit_logs
|
|
230
|
+
module's retention policy** (controlled outside this feature). The
|
|
231
|
+
carts-side mirror in `cart_audit_entries` is the source of truth for
|
|
232
|
+
the carts UI even when the platform-wide table has aged out.
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"errors.CART_EMPTY": "Your cart is empty.",
|
|
3
|
+
"errors.CART_LINE_CAP_EXCEEDED": "You have reached the maximum number of lines on this cart.",
|
|
4
|
+
"errors.CART_COUPON_REJECTED": "This coupon code could not be applied.",
|
|
5
|
+
"errors.CART_COUPON_REJECTED.invalid_code": "This coupon code is invalid.",
|
|
6
|
+
"errors.CART_COUPON_REJECTED.expired": "This coupon code has expired.",
|
|
7
|
+
"errors.CART_COUPON_REJECTED.below_min_spend": "Add more to your cart to unlock this promotion.",
|
|
8
|
+
"errors.CART_COUPON_REJECTED.wrong_channel": "This coupon does not apply in this sales channel.",
|
|
9
|
+
"errors.CART_COUPON_REJECTED.wrong_customer_group": "This coupon is not available for your customer group.",
|
|
10
|
+
"errors.CART_COUPON_REJECTED.wrong_organization": "This coupon is not available for your organization.",
|
|
11
|
+
"errors.CART_COUPON_REJECTED.coupon_format_invalid": "Coupon codes use only uppercase letters, digits, '-' and '_'.",
|
|
12
|
+
|
|
13
|
+
"lines.unavailable.out_of_stock": "Out of stock.",
|
|
14
|
+
"lines.unavailable.not_purchasable": "No longer available.",
|
|
15
|
+
"lines.unavailable.no_price_in_customer_list": "Pricing unavailable for your account.",
|
|
16
|
+
"lines.qty.label": "Quantity",
|
|
17
|
+
"lines.unit_price.label": "Unit price",
|
|
18
|
+
"lines.line_total.label": "Line total",
|
|
19
|
+
"lines.remove.label": "Remove",
|
|
20
|
+
"lines.save_to_list.label": "Save to shopping list",
|
|
21
|
+
|
|
22
|
+
"totals.subtotal": "Subtotal",
|
|
23
|
+
"totals.discount": "Discount",
|
|
24
|
+
"totals.grand_total": "Total",
|
|
25
|
+
"totals.empty": "Your cart is empty.",
|
|
26
|
+
|
|
27
|
+
"minicart.heading": "Cart",
|
|
28
|
+
"minicart.empty": "Your cart is empty.",
|
|
29
|
+
"minicart.view_full": "View cart",
|
|
30
|
+
"minicart.checkout": "Checkout",
|
|
31
|
+
"minicart.item_count_one": "{count} item",
|
|
32
|
+
"minicart.item_count_other": "{count} items",
|
|
33
|
+
|
|
34
|
+
"dropped.not_purchasable": "Removed because it is no longer available.",
|
|
35
|
+
"dropped.out_of_stock": "Removed because it is out of stock.",
|
|
36
|
+
"dropped.no_price_in_customer_list": "Removed because pricing is unavailable for your account.",
|
|
37
|
+
"dropped.removed_by_conversion": "Skipped during conversion.",
|
|
38
|
+
"dropped.banner.heading": "Some lines were skipped",
|
|
39
|
+
"dropped.banner.intro": "These products were not added because:",
|
|
40
|
+
|
|
41
|
+
"cta.checkout": "Proceed to checkout",
|
|
42
|
+
"cta.submit_for_approval": "Submit for approval",
|
|
43
|
+
"cta.awaiting_approval": "Awaiting approval",
|
|
44
|
+
"cta.blocked_by_organization": "Checkout unavailable",
|
|
45
|
+
"cta.convert_to_qr": "Convert to quote request",
|
|
46
|
+
"cta.convert_to_qr.hint": "Send this cart to sales as a quote request.",
|
|
47
|
+
"cta.convert_from_qr": "Convert to cart",
|
|
48
|
+
"cta.convert_from_qr.hint": "Start a new cart pre-filled from this quote request.",
|
|
49
|
+
"cta.add_list_to_cart": "Add list to cart",
|
|
50
|
+
"cta.add_list_to_cart.hint": "Pre-fill the cart with the items from this list.",
|
|
51
|
+
"cta.save_to_list": "Save to a shopping list",
|
|
52
|
+
"cta.apply_coupon": "Apply coupon",
|
|
53
|
+
"cta.clear_coupon": "Remove coupon",
|
|
54
|
+
|
|
55
|
+
"coupon.placeholder": "Enter coupon code",
|
|
56
|
+
"coupon.applied": "Coupon {code} is active.",
|
|
57
|
+
"coupon.dropped_on_read": "The coupon {code} is no longer valid for this cart and has been removed.",
|
|
58
|
+
|
|
59
|
+
"conversion.banner.qr_created": "We've sent your cart to sales as a new quote request.",
|
|
60
|
+
"conversion.banner.qr_converted": "We've started a new cart from quote request {reference}.",
|
|
61
|
+
"conversion.banner.list_added": "We've added the contents of \"{listName}\" to your cart.",
|
|
62
|
+
"conversion.banner.lines_dropped_during_conversion": "Some lines were skipped during conversion — see the panel below.",
|
|
63
|
+
|
|
64
|
+
"approval.banner.pending": "Your cart is awaiting approval by an organization administrator.",
|
|
65
|
+
"approval.banner.approved": "Your cart has been approved and is ready for checkout.",
|
|
66
|
+
"approval.banner.rejected": "Your cart was rejected by an organization administrator.",
|
|
67
|
+
"approval.banner.policy_on": "Your organization now requires cart approval before checkout.",
|
|
68
|
+
"approval.banner.policy_off": "Cart approval is no longer required by your organization.",
|
|
69
|
+
"approval.reject_reason.prompt": "Why are you rejecting this cart?",
|
|
70
|
+
"approval.reject_reason.placeholder": "Short explanation visible to the buyer",
|
|
71
|
+
"approval.reject_reason.required": "Please provide a rejection reason.",
|
|
72
|
+
|
|
73
|
+
"approval.email.submit.subject": "A cart was submitted for your approval",
|
|
74
|
+
"approval.email.submit.body": "{submitterDisplayName} submitted a cart for approval. Visit your dashboard to review or reject it.",
|
|
75
|
+
"approval.email.approve.subject": "Your cart was approved",
|
|
76
|
+
"approval.email.approve.body": "Your cart was approved by {approverDisplayName} and is ready for checkout.",
|
|
77
|
+
|
|
78
|
+
"abandonment.email.subject": "A cart was abandoned",
|
|
79
|
+
"abandonment.email.body": "A cart belonging to {ownerDisplayName} has been inactive for the configured threshold and has been marked Abandoned.",
|
|
80
|
+
|
|
81
|
+
"reject.email.subject": "Your cart was rejected",
|
|
82
|
+
"reject.email.body": "An administrator rejected your cart. Reason: {reason}",
|
|
83
|
+
|
|
84
|
+
"upsells.heading": "You might also need",
|
|
85
|
+
"upsells.empty": "No suggestions for this cart.",
|
|
86
|
+
"policy.title": "Cart approval",
|
|
87
|
+
"policy.description": "When this is on, ordinary members of this organization must submit their cart for an Organization Administrator's approval before they can check out.",
|
|
88
|
+
"policy.on": "Approval required before checkout",
|
|
89
|
+
"policy.off": "No approval required",
|
|
90
|
+
"policy.hint": "Turning the policy off returns every pending or approved cart in this organization to “not required”, and each change is recorded in the audit trail.",
|
|
91
|
+
"policy.saving": "Saving…",
|
|
92
|
+
"policy.turnedOn": "Cart approval is now required for this organization.",
|
|
93
|
+
"policy.turnedOff": "Cart approval is no longer required; pending carts were released.",
|
|
94
|
+
"policy.loadError": "Failed to load the cart-approval policy.",
|
|
95
|
+
"policy.saveError": "Failed to update the policy."
|
|
96
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"errors.CART_EMPTY": "Twój koszyk jest pusty.",
|
|
3
|
+
"errors.CART_LINE_CAP_EXCEEDED": "Osiągnięto maksymalną liczbę pozycji w koszyku.",
|
|
4
|
+
"errors.CART_COUPON_REJECTED": "Nie udało się zastosować kodu kuponu.",
|
|
5
|
+
"errors.CART_COUPON_REJECTED.invalid_code": "Nieprawidłowy kod kuponu.",
|
|
6
|
+
"errors.CART_COUPON_REJECTED.expired": "Kod kuponu wygasł.",
|
|
7
|
+
"errors.CART_COUPON_REJECTED.below_min_spend": "Dodaj więcej produktów, aby odblokować tę promocję.",
|
|
8
|
+
"errors.CART_COUPON_REJECTED.wrong_channel": "Ten kupon nie obowiązuje w tym kanale sprzedaży.",
|
|
9
|
+
"errors.CART_COUPON_REJECTED.wrong_customer_group": "Ten kupon nie jest dostępny dla Twojej grupy klientów.",
|
|
10
|
+
"errors.CART_COUPON_REJECTED.wrong_organization": "Ten kupon nie jest dostępny dla Twojej organizacji.",
|
|
11
|
+
"errors.CART_COUPON_REJECTED.coupon_format_invalid": "Kody kuponów mogą zawierać tylko duże litery, cyfry, '-' i '_'.",
|
|
12
|
+
|
|
13
|
+
"lines.unavailable.out_of_stock": "Brak na stanie.",
|
|
14
|
+
"lines.unavailable.not_purchasable": "Niedostępny.",
|
|
15
|
+
"lines.unavailable.no_price_in_customer_list": "Brak ceny dla Twojego konta.",
|
|
16
|
+
"lines.qty.label": "Ilość",
|
|
17
|
+
"lines.unit_price.label": "Cena jednostkowa",
|
|
18
|
+
"lines.line_total.label": "Wartość pozycji",
|
|
19
|
+
"lines.remove.label": "Usuń",
|
|
20
|
+
"lines.save_to_list.label": "Zapisz na liście zakupowej",
|
|
21
|
+
|
|
22
|
+
"totals.subtotal": "Suma częściowa",
|
|
23
|
+
"totals.discount": "Rabat",
|
|
24
|
+
"totals.grand_total": "Razem",
|
|
25
|
+
"totals.empty": "Twój koszyk jest pusty.",
|
|
26
|
+
|
|
27
|
+
"minicart.heading": "Koszyk",
|
|
28
|
+
"minicart.empty": "Twój koszyk jest pusty.",
|
|
29
|
+
"minicart.view_full": "Otwórz koszyk",
|
|
30
|
+
"minicart.checkout": "Do kasy",
|
|
31
|
+
"minicart.item_count_one": "{count} pozycja",
|
|
32
|
+
"minicart.item_count_other": "{count} pozycji",
|
|
33
|
+
|
|
34
|
+
"dropped.not_purchasable": "Pominięto — produkt jest niedostępny.",
|
|
35
|
+
"dropped.out_of_stock": "Pominięto — produkt jest niedostępny w magazynie.",
|
|
36
|
+
"dropped.no_price_in_customer_list": "Pominięto — brak ceny dla Twojego konta.",
|
|
37
|
+
"dropped.removed_by_conversion": "Pominięto podczas konwersji.",
|
|
38
|
+
"dropped.banner.heading": "Niektóre pozycje zostały pominięte",
|
|
39
|
+
"dropped.banner.intro": "Następujące produkty nie zostały dodane, ponieważ:",
|
|
40
|
+
|
|
41
|
+
"cta.checkout": "Przejdź do kasy",
|
|
42
|
+
"cta.submit_for_approval": "Zgłoś do akceptacji",
|
|
43
|
+
"cta.awaiting_approval": "Oczekuje na akceptację",
|
|
44
|
+
"cta.blocked_by_organization": "Kasa niedostępna",
|
|
45
|
+
"cta.convert_to_qr": "Zamień na zapytanie ofertowe",
|
|
46
|
+
"cta.convert_to_qr.hint": "Wyślij ten koszyk do działu sprzedaży jako zapytanie ofertowe.",
|
|
47
|
+
"cta.convert_from_qr": "Zamień na koszyk",
|
|
48
|
+
"cta.convert_from_qr.hint": "Utwórz nowy koszyk wypełniony pozycjami z tego zapytania ofertowego.",
|
|
49
|
+
"cta.add_list_to_cart": "Dodaj listę do koszyka",
|
|
50
|
+
"cta.add_list_to_cart.hint": "Wypełnij koszyk pozycjami z tej listy.",
|
|
51
|
+
"cta.save_to_list": "Zapisz na liście zakupowej",
|
|
52
|
+
"cta.apply_coupon": "Zastosuj kupon",
|
|
53
|
+
"cta.clear_coupon": "Usuń kupon",
|
|
54
|
+
|
|
55
|
+
"coupon.placeholder": "Wpisz kod kuponu",
|
|
56
|
+
"coupon.applied": "Kupon {code} jest aktywny.",
|
|
57
|
+
"coupon.dropped_on_read": "Kupon {code} nie obowiązuje już dla tego koszyka i został usunięty.",
|
|
58
|
+
|
|
59
|
+
"conversion.banner.qr_created": "Twój koszyk został wysłany do działu sprzedaży jako nowe zapytanie ofertowe.",
|
|
60
|
+
"conversion.banner.qr_converted": "Utworzono nowy koszyk na podstawie zapytania ofertowego {reference}.",
|
|
61
|
+
"conversion.banner.list_added": "Zawartość listy \"{listName}\" została dodana do koszyka.",
|
|
62
|
+
"conversion.banner.lines_dropped_during_conversion": "Niektóre pozycje zostały pominięte podczas konwersji — zobacz panel poniżej.",
|
|
63
|
+
|
|
64
|
+
"approval.banner.pending": "Twój koszyk oczekuje na akceptację administratora organizacji.",
|
|
65
|
+
"approval.banner.approved": "Twój koszyk został zaakceptowany i jest gotowy do realizacji.",
|
|
66
|
+
"approval.banner.rejected": "Twój koszyk został odrzucony przez administratora organizacji.",
|
|
67
|
+
"approval.banner.policy_on": "Twoja organizacja wymaga teraz akceptacji koszyka przed realizacją.",
|
|
68
|
+
"approval.banner.policy_off": "Twoja organizacja nie wymaga już akceptacji koszyka.",
|
|
69
|
+
"approval.reject_reason.prompt": "Dlaczego odrzucasz ten koszyk?",
|
|
70
|
+
"approval.reject_reason.placeholder": "Krótkie wyjaśnienie widoczne dla kupującego",
|
|
71
|
+
"approval.reject_reason.required": "Podaj powód odrzucenia.",
|
|
72
|
+
|
|
73
|
+
"approval.email.submit.subject": "Koszyk zgłoszony do Twojej akceptacji",
|
|
74
|
+
"approval.email.submit.body": "{submitterDisplayName} zgłosił(a) koszyk do akceptacji. Otwórz panel, aby go zaakceptować lub odrzucić.",
|
|
75
|
+
"approval.email.approve.subject": "Twój koszyk został zaakceptowany",
|
|
76
|
+
"approval.email.approve.body": "Twój koszyk został zaakceptowany przez {approverDisplayName} i jest gotowy do realizacji.",
|
|
77
|
+
|
|
78
|
+
"abandonment.email.subject": "Porzucony koszyk",
|
|
79
|
+
"abandonment.email.body": "Koszyk klienta {ownerDisplayName} został oznaczony jako Porzucony po przekroczeniu progu nieaktywności.",
|
|
80
|
+
|
|
81
|
+
"reject.email.subject": "Twój koszyk został odrzucony",
|
|
82
|
+
"reject.email.body": "Administrator odrzucił Twój koszyk. Powód: {reason}",
|
|
83
|
+
|
|
84
|
+
"upsells.heading": "Możesz też potrzebować",
|
|
85
|
+
"upsells.empty": "Brak sugestii dla tego koszyka.",
|
|
86
|
+
"policy.title": "Zatwierdzanie koszyka",
|
|
87
|
+
"policy.description": "Gdy opcja jest włączona, zwykli członkowie tej organizacji muszą przekazać koszyk do zatwierdzenia przez Administratora Organizacji, zanim będą mogli złożyć zamówienie.",
|
|
88
|
+
"policy.on": "Zatwierdzenie wymagane przed złożeniem zamówienia",
|
|
89
|
+
"policy.off": "Zatwierdzenie niewymagane",
|
|
90
|
+
"policy.hint": "Wyłączenie zasady przywraca każdy oczekujący lub zatwierdzony koszyk w tej organizacji do stanu „niewymagane”, a każda zmiana trafia do dziennika audytu.",
|
|
91
|
+
"policy.saving": "Zapisywanie…",
|
|
92
|
+
"policy.turnedOn": "Zatwierdzanie koszyka jest teraz wymagane w tej organizacji.",
|
|
93
|
+
"policy.turnedOff": "Zatwierdzanie koszyka nie jest już wymagane; oczekujące koszyki zostały zwolnione.",
|
|
94
|
+
"policy.loadError": "Nie udało się wczytać zasady zatwierdzania koszyka.",
|
|
95
|
+
"policy.saveError": "Nie udało się zaktualizować zasady."
|
|
96
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-carts",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Shopping cart aggregation, pricing, lifecycle, and approval gate.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "carts"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/carts"
|
|
16
|
+
},
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/manifest.d.ts",
|
|
23
|
+
"default": "./dist/manifest.js"
|
|
24
|
+
},
|
|
25
|
+
"./backend": {
|
|
26
|
+
"types": "./dist/backend/index.d.ts",
|
|
27
|
+
"default": "./dist/backend/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./migrations": {
|
|
30
|
+
"types": "./dist/migrations/index.d.ts",
|
|
31
|
+
"default": "./dist/migrations/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./ports": {
|
|
34
|
+
"types": "./dist/ports/index.d.ts",
|
|
35
|
+
"default": "./dist/ports/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./admin": {
|
|
38
|
+
"types": "./dist/admin/index.d.ts",
|
|
39
|
+
"default": "./dist/admin/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./tailwind.css": "./tailwind.css",
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"i18n",
|
|
47
|
+
"docs",
|
|
48
|
+
"tailwind.css"
|
|
49
|
+
],
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.18.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@fastify/cookie": "^11",
|
|
55
|
+
"@mikro-orm/core": "^6",
|
|
56
|
+
"@mikro-orm/migrations": "^6",
|
|
57
|
+
"@mikro-orm/postgresql": "^6",
|
|
58
|
+
"fastify": "^5",
|
|
59
|
+
"ioredis": "^5",
|
|
60
|
+
"lucide-react": "^1",
|
|
61
|
+
"react": "^19",
|
|
62
|
+
"react-router-dom": "^7",
|
|
63
|
+
"zod": "^4",
|
|
64
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
65
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
66
|
+
"@endora-commerce/platform": "0.100.0"
|
|
67
|
+
},
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"@endora-commerce/admin-kit": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"lucide-react": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"react": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"react-router-dom": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@fastify/cookie": "^11.0.2",
|
|
84
|
+
"@mikro-orm/core": "^6.6.13",
|
|
85
|
+
"@mikro-orm/migrations": "^6.6.13",
|
|
86
|
+
"@mikro-orm/postgresql": "^6.6.13",
|
|
87
|
+
"@types/node": "^22.9.0",
|
|
88
|
+
"@types/react": "^19.2.14",
|
|
89
|
+
"fastify": "^5.12.5",
|
|
90
|
+
"ioredis": "^5.10.1",
|
|
91
|
+
"lucide-react": "^1.11.0",
|
|
92
|
+
"react": "^19.2.5",
|
|
93
|
+
"react-router-dom": "^7.18.2",
|
|
94
|
+
"typescript": "^5.9.3",
|
|
95
|
+
"vitest": "^4.1.11",
|
|
96
|
+
"zod": "^4.2.0",
|
|
97
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
98
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
99
|
+
"@endora-commerce/platform": "0.100.0"
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
103
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
104
|
+
"lint": "eslint src",
|
|
105
|
+
"test": "vitest run"
|
|
106
|
+
}
|
|
107
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-carts — AUTO-GENERATED by `pnpm --filter backend run manifests:generate`.
|
|
2
|
+
*
|
|
3
|
+
* The `@source` directives this package asks its host to scan
|
|
4
|
+
* (`specs/110-instance-repository/contracts/admin-stylesheet-composition.md` R1).
|
|
5
|
+
* They resolve relative to **this file**, so they hold wherever the package is
|
|
6
|
+
* installed — a workspace link here, `node_modules` in a client's instance.
|
|
7
|
+
*
|
|
8
|
+
* The `dist` line is what a published tarball ships and is what an instance
|
|
9
|
+
* scans; the `src` line is inert there and is what keeps `pnpm --filter admin
|
|
10
|
+
* run dev` reading source in this repository. Do not edit: run
|
|
11
|
+
* `pnpm --filter backend run manifests:generate`.
|
|
12
|
+
*/
|
|
13
|
+
@source "./dist/admin";
|
|
14
|
+
@source "./src/admin";
|