@endora-commerce/mod-quick-order 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 +58 -0
- package/dist/admin/api/quick-order-client.d.ts +76 -0
- package/dist/admin/api/quick-order-client.d.ts.map +1 -0
- package/dist/admin/api/quick-order-client.js +18 -0
- package/dist/admin/api/quick-order-client.js.map +1 -0
- package/dist/admin/components/DefaultPreferencesPanel.d.ts +44 -0
- package/dist/admin/components/DefaultPreferencesPanel.d.ts.map +1 -0
- package/dist/admin/components/DefaultPreferencesPanel.js +107 -0
- package/dist/admin/components/DefaultPreferencesPanel.js.map +1 -0
- package/dist/admin/index.d.ts +48 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +120 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/QuickOrderOnBehalfPage.d.ts +16 -0
- package/dist/admin/pages/QuickOrderOnBehalfPage.d.ts.map +1 -0
- package/dist/admin/pages/QuickOrderOnBehalfPage.js +85 -0
- package/dist/admin/pages/QuickOrderOnBehalfPage.js.map +1 -0
- package/dist/admin/zones/CustomerDefaults.d.ts +27 -0
- package/dist/admin/zones/CustomerDefaults.d.ts.map +1 -0
- package/dist/admin/zones/CustomerDefaults.js +11 -0
- package/dist/admin/zones/CustomerDefaults.js.map +1 -0
- package/dist/admin/zones/OrderEntryQuickTab.d.ts +21 -0
- package/dist/admin/zones/OrderEntryQuickTab.d.ts.map +1 -0
- package/dist/admin/zones/OrderEntryQuickTab.js +26 -0
- package/dist/admin/zones/OrderEntryQuickTab.js.map +1 -0
- package/dist/admin/zones/OrganizationDefaults.d.ts +30 -0
- package/dist/admin/zones/OrganizationDefaults.d.ts.map +1 -0
- package/dist/admin/zones/OrganizationDefaults.js +11 -0
- package/dist/admin/zones/OrganizationDefaults.js.map +1 -0
- package/dist/backend/entities/quick-order-default-preference.entity.d.ts +22 -0
- package/dist/backend/entities/quick-order-default-preference.entity.d.ts.map +1 -0
- package/dist/backend/entities/quick-order-default-preference.entity.js +76 -0
- package/dist/backend/entities/quick-order-default-preference.entity.js.map +1 -0
- package/dist/backend/index.d.ts +88 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +235 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +35 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +64 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.d.ts +35 -0
- package/dist/backend/routes.d.ts.map +1 -0
- package/dist/backend/routes.js +77 -0
- package/dist/backend/routes.js.map +1 -0
- package/dist/backend/routes.one-click.d.ts +22 -0
- package/dist/backend/routes.one-click.d.ts.map +1 -0
- package/dist/backend/routes.one-click.js +35 -0
- package/dist/backend/routes.one-click.js.map +1 -0
- package/dist/backend/routes.preferences.admin.d.ts +23 -0
- package/dist/backend/routes.preferences.admin.d.ts.map +1 -0
- package/dist/backend/routes.preferences.admin.js +40 -0
- package/dist/backend/routes.preferences.admin.js.map +1 -0
- package/dist/backend/routes.preferences.d.ts +21 -0
- package/dist/backend/routes.preferences.d.ts.map +1 -0
- package/dist/backend/routes.preferences.js +42 -0
- package/dist/backend/routes.preferences.js.map +1 -0
- package/dist/backend/services/catalog-lookup.d.ts +39 -0
- package/dist/backend/services/catalog-lookup.d.ts.map +1 -0
- package/dist/backend/services/catalog-lookup.js +104 -0
- package/dist/backend/services/catalog-lookup.js.map +1 -0
- package/dist/backend/services/default-preference-authz.d.ts +31 -0
- package/dist/backend/services/default-preference-authz.d.ts.map +1 -0
- package/dist/backend/services/default-preference-authz.js +26 -0
- package/dist/backend/services/default-preference-authz.js.map +1 -0
- package/dist/backend/services/default-preference-resolver.d.ts +28 -0
- package/dist/backend/services/default-preference-resolver.d.ts.map +1 -0
- package/dist/backend/services/default-preference-resolver.js +27 -0
- package/dist/backend/services/default-preference-resolver.js.map +1 -0
- package/dist/backend/services/default-preference-service.d.ts +63 -0
- package/dist/backend/services/default-preference-service.d.ts.map +1 -0
- package/dist/backend/services/default-preference-service.js +176 -0
- package/dist/backend/services/default-preference-service.js.map +1 -0
- package/dist/backend/services/excel-importer.d.ts +11 -0
- package/dist/backend/services/excel-importer.d.ts.map +1 -0
- package/dist/backend/services/excel-importer.js +47 -0
- package/dist/backend/services/excel-importer.js.map +1 -0
- package/dist/backend/services/import-from-request.d.ts +9 -0
- package/dist/backend/services/import-from-request.d.ts.map +1 -0
- package/dist/backend/services/import-from-request.js +17 -0
- package/dist/backend/services/import-from-request.js.map +1 -0
- package/dist/backend/services/import-pipeline.d.ts +89 -0
- package/dist/backend/services/import-pipeline.d.ts.map +1 -0
- package/dist/backend/services/import-pipeline.js +188 -0
- package/dist/backend/services/import-pipeline.js.map +1 -0
- package/dist/backend/services/import-rows.d.ts +43 -0
- package/dist/backend/services/import-rows.d.ts.map +1 -0
- package/dist/backend/services/import-rows.js +102 -0
- package/dist/backend/services/import-rows.js.map +1 -0
- package/dist/backend/services/one-click-service.d.ts +50 -0
- package/dist/backend/services/one-click-service.d.ts.map +1 -0
- package/dist/backend/services/one-click-service.js +83 -0
- package/dist/backend/services/one-click-service.js.map +1 -0
- package/dist/backend/services/quick-order-build-service.d.ts +35 -0
- package/dist/backend/services/quick-order-build-service.d.ts.map +1 -0
- package/dist/backend/services/quick-order-build-service.js +61 -0
- package/dist/backend/services/quick-order-build-service.js.map +1 -0
- package/dist/backend/services/variant-resolver.d.ts +40 -0
- package/dist/backend/services/variant-resolver.d.ts.map +1 -0
- package/dist/backend/services/variant-resolver.js +47 -0
- package/dist/backend/services/variant-resolver.js.map +1 -0
- package/dist/manifest.d.ts +190 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +129 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140359_quick_order_default_preferences.d.ts +15 -0
- package/dist/migrations/20260611T140359_quick_order_default_preferences.d.ts.map +1 -0
- package/dist/migrations/20260611T140359_quick_order_default_preferences.js +53 -0
- package/dist/migrations/20260611T140359_quick_order_default_preferences.js.map +1 -0
- package/dist/migrations/index.d.ts +27 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +29 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/quick_order.md +98 -0
- package/i18n/en.json +47 -0
- package/i18n/pl.json +47 -0
- package/package.json +91 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ERROR_CODES, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
/**
|
|
4
|
+
* OneClickService (feature 039, US5). Computes whether the one-click button
|
|
5
|
+
* should show, and places a one-click order by reusing the platform's
|
|
6
|
+
* established single-active-cart flow: clear the cart, add the chosen product,
|
|
7
|
+
* then run `OrderService.placeOrder` with the buyer's resolved default methods
|
|
8
|
+
* and addresses. All order validation (org active, minimum value, credit limit,
|
|
9
|
+
* stock) and the payment-routing `nextAction` come from `placeOrder` (FR-029 /
|
|
10
|
+
* FR-030); no parallel ordering path (FR-031).
|
|
11
|
+
*
|
|
12
|
+
* Both collaborators are `@endora-commerce/contracts` ports since feature 075's Phase C —
|
|
13
|
+
* `cartWritePort` and `orderPlacementPort`. The lazy `() => OrderService | null`
|
|
14
|
+
* accessor is gone with them: the port's gate answers 503 `MODULE_DISABLED`
|
|
15
|
+
* when `orders` is not present, which is the same refusal the `null` branch
|
|
16
|
+
* spelled by hand, except that a `null` accessor was indistinguishable from a
|
|
17
|
+
* wiring mistake. `placeOrder` answers `PlacedOrderRecord`, which is
|
|
18
|
+
* `OrderRecord` **plus** `nextAction` — the payment routing this reply exists
|
|
19
|
+
* to carry.
|
|
20
|
+
*
|
|
21
|
+
* **The channel is a per-call argument, not a constructor default** (issue #99,
|
|
22
|
+
* D-48). It used to be `salesChannelId: string = 'default'` — a fifth
|
|
23
|
+
* constructor parameter `backend.ts` never passed, so every deployment read the
|
|
24
|
+
* setting against a channel *code*, tripped the settings seam guard, and
|
|
25
|
+
* reported `setting_disabled` indistinguishably from an operator having
|
|
26
|
+
* switched one-click buy off. The routes read the channel the canonical
|
|
27
|
+
* resolver put on the request and hand it down; `null` means there is none, and
|
|
28
|
+
* then the setting is read platform-wide (D-41 case c) and the order records no
|
|
29
|
+
* channel rather than an invented one.
|
|
30
|
+
*/
|
|
31
|
+
export class OneClickService {
|
|
32
|
+
preferenceService;
|
|
33
|
+
cartWrite;
|
|
34
|
+
orderPlacement;
|
|
35
|
+
resolveOneClickEnabled;
|
|
36
|
+
constructor(preferenceService, cartWrite, orderPlacement, resolveOneClickEnabled) {
|
|
37
|
+
this.preferenceService = preferenceService;
|
|
38
|
+
this.cartWrite = cartWrite;
|
|
39
|
+
this.orderPlacement = orderPlacement;
|
|
40
|
+
this.resolveOneClickEnabled = resolveOneClickEnabled;
|
|
41
|
+
}
|
|
42
|
+
async eligibility(customerAccountId, salesChannelId) {
|
|
43
|
+
const enabled = await this.resolveOneClickEnabled(salesChannelId);
|
|
44
|
+
if (!enabled)
|
|
45
|
+
return { enabled: false, reason: 'setting_disabled' };
|
|
46
|
+
const defaults = await this.preferenceService.resolveForCustomer(customerAccountId);
|
|
47
|
+
if (!defaults.paymentMethodId ||
|
|
48
|
+
!defaults.deliveryMethodId ||
|
|
49
|
+
!defaults.billingAddressId ||
|
|
50
|
+
!defaults.shippingAddressId) {
|
|
51
|
+
return { enabled: false, reason: 'missing_defaults' };
|
|
52
|
+
}
|
|
53
|
+
return { enabled: true, reason: null };
|
|
54
|
+
}
|
|
55
|
+
async place(ctx, input, salesChannelId) {
|
|
56
|
+
const eligibility = await this.eligibility(ctx.customerAccountId, salesChannelId);
|
|
57
|
+
if (!eligibility.enabled) {
|
|
58
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'One-click buy is not available.', {
|
|
59
|
+
code: 'one_click_unavailable',
|
|
60
|
+
reason: eligibility.reason,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const defaults = await this.preferenceService.resolveForCustomer(ctx.customerAccountId);
|
|
64
|
+
const customerCtx = { customerAccountId: ctx.customerAccountId, organizationId: ctx.organizationId };
|
|
65
|
+
// Clear-then-seed matches the single-active-cart model used by admin
|
|
66
|
+
// order-create and RFQ-convert.
|
|
67
|
+
await this.cartWrite.clearForCustomer(customerCtx);
|
|
68
|
+
await this.cartWrite.addItem({ customer: customerCtx }, {
|
|
69
|
+
productId: input.productId,
|
|
70
|
+
...(input.variantId ? { variantId: input.variantId } : {}),
|
|
71
|
+
quantity: input.quantity ?? 1,
|
|
72
|
+
});
|
|
73
|
+
return this.orderPlacement.placeOrder(customerCtx, {
|
|
74
|
+
deliveryAddressId: defaults.shippingAddressId,
|
|
75
|
+
billingAddressId: defaults.billingAddressId,
|
|
76
|
+
deliveryMethodId: defaults.deliveryMethodId,
|
|
77
|
+
paymentMethodId: defaults.paymentMethodId,
|
|
78
|
+
...(salesChannelId ? { salesChannelId } : {}),
|
|
79
|
+
...(input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=one-click-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"one-click-service.js","sourceRoot":"","sources":["../../../src/backend/services/one-click-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type CartWritePort, type QuickOrderBuildResponse, type QuickOrderTarget, type RfqCustomerPort } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Context for a quick-order build. `organizationId` may be null for a no-org
|
|
4
|
+
* customer (carts tolerate this; quote requests require an organization).
|
|
5
|
+
*/
|
|
6
|
+
export interface QuickOrderBuildContext {
|
|
7
|
+
customerAccountId: string;
|
|
8
|
+
organizationId: string | null;
|
|
9
|
+
isOrgAdmin?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface QuickOrderBuildLineInput {
|
|
12
|
+
productId: string;
|
|
13
|
+
variantId?: string | null;
|
|
14
|
+
quantity: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* QuickOrderBuildService (feature 039, US1). Turns a confirmed set of
|
|
18
|
+
* recognized lines into either a Cart (via {@link CartWritePort}) or a Quote
|
|
19
|
+
* Request (via {@link RfqCustomerPort}) — reusing the owning modules' surfaces
|
|
20
|
+
* rather than a parallel ordering path (FR-031). Pricing is owned by those
|
|
21
|
+
* modules (the buyer's / organization's current price list), never the import
|
|
22
|
+
* file (FR-007).
|
|
23
|
+
*/
|
|
24
|
+
export declare class QuickOrderBuildService {
|
|
25
|
+
private readonly cartWrite;
|
|
26
|
+
private readonly rfq;
|
|
27
|
+
constructor(cartWrite: CartWritePort, rfq: RfqCustomerPort);
|
|
28
|
+
build(ctx: QuickOrderBuildContext, input: {
|
|
29
|
+
target: QuickOrderTarget;
|
|
30
|
+
items: QuickOrderBuildLineInput[];
|
|
31
|
+
}): Promise<QuickOrderBuildResponse>;
|
|
32
|
+
private buildCart;
|
|
33
|
+
private buildQuoteRequest;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=quick-order-build-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quick-order-build-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/quick-order-build-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,uBAAuB,EAC5B,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACrB,MAAM,4BAA4B,CAAC;AAGpC;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,qBAAa,sBAAsB;IAE/B,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,SAAS,EAAE,aAAa,EACxB,GAAG,EAAE,eAAe;IAGjC,KAAK,CACT,GAAG,EAAE,sBAAsB,EAC3B,KAAK,EAAE;QAAE,MAAM,EAAE,gBAAgB,CAAC;QAAC,KAAK,EAAE,wBAAwB,EAAE,CAAA;KAAE,GACrE,OAAO,CAAC,uBAAuB,CAAC;YAWrB,SAAS;YAmBT,iBAAiB;CA+BhC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ERROR_CODES, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
/**
|
|
4
|
+
* QuickOrderBuildService (feature 039, US1). Turns a confirmed set of
|
|
5
|
+
* recognized lines into either a Cart (via {@link CartWritePort}) or a Quote
|
|
6
|
+
* Request (via {@link RfqCustomerPort}) — reusing the owning modules' surfaces
|
|
7
|
+
* rather than a parallel ordering path (FR-031). Pricing is owned by those
|
|
8
|
+
* modules (the buyer's / organization's current price list), never the import
|
|
9
|
+
* file (FR-007).
|
|
10
|
+
*/
|
|
11
|
+
export class QuickOrderBuildService {
|
|
12
|
+
cartWrite;
|
|
13
|
+
rfq;
|
|
14
|
+
constructor(cartWrite, rfq) {
|
|
15
|
+
this.cartWrite = cartWrite;
|
|
16
|
+
this.rfq = rfq;
|
|
17
|
+
}
|
|
18
|
+
async build(ctx, input) {
|
|
19
|
+
if (input.items.length === 0) {
|
|
20
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'No items to build.');
|
|
21
|
+
}
|
|
22
|
+
if (input.target === 'cart') {
|
|
23
|
+
return this.buildCart(ctx, input.items);
|
|
24
|
+
}
|
|
25
|
+
return this.buildQuoteRequest(ctx, input.items);
|
|
26
|
+
}
|
|
27
|
+
async buildCart(ctx, items) {
|
|
28
|
+
const customer = { customerAccountId: ctx.customerAccountId, organizationId: ctx.organizationId };
|
|
29
|
+
const cart = await this.cartWrite.getOrCreateForCustomer(customer);
|
|
30
|
+
for (const item of items) {
|
|
31
|
+
await this.cartWrite.addItem({ customer }, {
|
|
32
|
+
productId: item.productId,
|
|
33
|
+
...(item.variantId ? { variantId: item.variantId } : {}),
|
|
34
|
+
quantity: item.quantity,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return { target: 'cart', cartId: cart.id, checkoutUrl: `/checkout?cartId=${cart.id}` };
|
|
38
|
+
}
|
|
39
|
+
async buildQuoteRequest(ctx, items) {
|
|
40
|
+
if (!ctx.organizationId) {
|
|
41
|
+
throw new HttpError(422, ERROR_CODES.VALIDATION_FAILED, 'A quote request requires an organization.');
|
|
42
|
+
}
|
|
43
|
+
const rfq = await this.rfq.createForCustomer({
|
|
44
|
+
customerAccountId: ctx.customerAccountId,
|
|
45
|
+
organizationId: ctx.organizationId,
|
|
46
|
+
isOrgAdmin: ctx.isOrgAdmin ?? false,
|
|
47
|
+
}, {
|
|
48
|
+
items: items.map((item) => ({
|
|
49
|
+
productId: item.productId,
|
|
50
|
+
...(item.variantId ? { variantId: item.variantId } : {}),
|
|
51
|
+
quantity: item.quantity,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
target: 'quote_request',
|
|
56
|
+
quoteRequestId: rfq.id,
|
|
57
|
+
quoteRequestBusinessId: rfq.businessId,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=quick-order-build-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quick-order-build-service.js","sourceRoot":"","sources":["../../../src/backend/services/quick-order-build-service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,GAKZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAkB3D;;;;;;;GAOG;AACH,MAAM,OAAO,sBAAsB;IAEd;IACA;IAFnB,YACmB,SAAwB,EACxB,GAAoB;QADpB,cAAS,GAAT,SAAS,CAAe;QACxB,QAAG,GAAH,GAAG,CAAiB;IACpC,CAAC;IAEJ,KAAK,CAAC,KAAK,CACT,GAA2B,EAC3B,KAAsE;QAEtE,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,CAAC;QAChF,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAClD,CAAC;IAEO,KAAK,CAAC,SAAS,CACrB,GAA2B,EAC3B,KAAiC;QAEjC,MAAM,QAAQ,GAAG,EAAE,iBAAiB,EAAE,GAAG,CAAC,iBAAiB,EAAE,cAAc,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC;QAClG,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,sBAAsB,CAAC,QAAQ,CAAC,CAAC;QACnE,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,IAAI,CAAC,SAAS,CAAC,OAAO,CAC1B,EAAE,QAAQ,EAAE,EACZ;gBACE,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CACF,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,oBAAoB,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IACzF,CAAC;IAEO,KAAK,CAAC,iBAAiB,CAC7B,GAA2B,EAC3B,KAAiC;QAEjC,IAAI,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC;YACxB,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,iBAAiB,EAC7B,2CAA2C,CAC5C,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAC1C;YACE,iBAAiB,EAAE,GAAG,CAAC,iBAAiB;YACxC,cAAc,EAAE,GAAG,CAAC,cAAc;YAClC,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,KAAK;SACpC,EACD;YACE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1B,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACxD,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC,CAAC;SACJ,CACF,CAAC;QACF,OAAO;YACL,MAAM,EAAE,eAAe;YACvB,cAAc,EAAE,GAAG,CAAC,EAAE;YACtB,sBAAsB,EAAE,GAAG,CAAC,UAAU;SACvC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant resolution for quick-order imports (feature 039, FR-003).
|
|
3
|
+
*
|
|
4
|
+
* Given the variants of a parent product and the attribute values supplied
|
|
5
|
+
* on an import row (the columns beyond `sku` / `quantity`), decide which
|
|
6
|
+
* concrete variant the row refers to. Matching is exact-after-normalize
|
|
7
|
+
* against the parent's variant-axis attribute keys
|
|
8
|
+
* (`ProductVariant.variantAttributeValues`).
|
|
9
|
+
*
|
|
10
|
+
* Pure function — no DB access. The caller fetches the variants and feeds
|
|
11
|
+
* them in, which keeps the matching rule unit-testable.
|
|
12
|
+
*/
|
|
13
|
+
export interface VariantLike {
|
|
14
|
+
id: string;
|
|
15
|
+
sku: string;
|
|
16
|
+
variantAttributeValues: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export type VariantResolution = {
|
|
19
|
+
kind: 'no_variants';
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'resolved';
|
|
22
|
+
variantId: string;
|
|
23
|
+
variantSku: string;
|
|
24
|
+
} | {
|
|
25
|
+
kind: 'not_resolved';
|
|
26
|
+
} | {
|
|
27
|
+
kind: 'ambiguous';
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolve a variant from the row's attribute columns.
|
|
31
|
+
*
|
|
32
|
+
* - No variants on the product → `no_variants` (caller adds the base product;
|
|
33
|
+
* extra attribute columns are ignored, per the spec edge case).
|
|
34
|
+
* - Exactly one variant matches every supplied (key, value) → `resolved`.
|
|
35
|
+
* - Zero matches → `not_resolved`.
|
|
36
|
+
* - More than one match (including the case where no axis values were
|
|
37
|
+
* supplied for a multi-variant product) → `ambiguous`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveVariant(variants: VariantLike[], requestedAttributes: Record<string, string>): VariantResolution;
|
|
40
|
+
//# sourceMappingURL=variant-resolver.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant-resolver.d.ts","sourceRoot":"","sources":["../../../src/backend/services/variant-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjD;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,aAAa,CAAA;CAAE,GACvB;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,GACxB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC;AAS1B;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,WAAW,EAAE,EACvB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1C,iBAAiB,CAsBnB"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant resolution for quick-order imports (feature 039, FR-003).
|
|
3
|
+
*
|
|
4
|
+
* Given the variants of a parent product and the attribute values supplied
|
|
5
|
+
* on an import row (the columns beyond `sku` / `quantity`), decide which
|
|
6
|
+
* concrete variant the row refers to. Matching is exact-after-normalize
|
|
7
|
+
* against the parent's variant-axis attribute keys
|
|
8
|
+
* (`ProductVariant.variantAttributeValues`).
|
|
9
|
+
*
|
|
10
|
+
* Pure function — no DB access. The caller fetches the variants and feeds
|
|
11
|
+
* them in, which keeps the matching rule unit-testable.
|
|
12
|
+
*/
|
|
13
|
+
/** Lower-case + trim for case-insensitive axis comparison. */
|
|
14
|
+
function normalize(value) {
|
|
15
|
+
return String(value ?? '')
|
|
16
|
+
.trim()
|
|
17
|
+
.toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a variant from the row's attribute columns.
|
|
21
|
+
*
|
|
22
|
+
* - No variants on the product → `no_variants` (caller adds the base product;
|
|
23
|
+
* extra attribute columns are ignored, per the spec edge case).
|
|
24
|
+
* - Exactly one variant matches every supplied (key, value) → `resolved`.
|
|
25
|
+
* - Zero matches → `not_resolved`.
|
|
26
|
+
* - More than one match (including the case where no axis values were
|
|
27
|
+
* supplied for a multi-variant product) → `ambiguous`.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveVariant(variants, requestedAttributes) {
|
|
30
|
+
if (variants.length === 0)
|
|
31
|
+
return { kind: 'no_variants' };
|
|
32
|
+
const requested = Object.entries(requestedAttributes)
|
|
33
|
+
.map(([key, value]) => [key.trim().toLowerCase(), normalize(value)])
|
|
34
|
+
.filter(([, value]) => value.length > 0);
|
|
35
|
+
const matches = variants.filter((variant) => {
|
|
36
|
+
const axes = new Map(Object.entries(variant.variantAttributeValues).map(([key, value]) => [key.trim().toLowerCase(), normalize(value)]));
|
|
37
|
+
return requested.every(([key, value]) => axes.get(key) === value);
|
|
38
|
+
});
|
|
39
|
+
if (matches.length === 1) {
|
|
40
|
+
const [only] = matches;
|
|
41
|
+
return { kind: 'resolved', variantId: only.id, variantSku: only.sku };
|
|
42
|
+
}
|
|
43
|
+
if (matches.length === 0)
|
|
44
|
+
return { kind: 'not_resolved' };
|
|
45
|
+
return { kind: 'ambiguous' };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=variant-resolver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variant-resolver.js","sourceRoot":"","sources":["../../../src/backend/services/variant-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAcH,8DAA8D;AAC9D,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,IAAI,EAAE;SACN,WAAW,EAAE,CAAC;AACnB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,QAAuB,EACvB,mBAA2C;IAE3C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;IAE1D,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC;SAClD,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAU,CAAC;SAC5E,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAE3C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,CAClB,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC,GAAG,CAChD,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAU,CACxE,CACF,CAAC;QACF,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,IAAK,CAAC,EAAE,EAAE,UAAU,EAAE,IAAK,CAAC,GAAG,EAAE,CAAC;IAC1E,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC1D,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick Order module — manifest (Module Lifecycle, feature 018) extended for
|
|
3
|
+
* feature 039.
|
|
4
|
+
*
|
|
5
|
+
* Settings (feature 039):
|
|
6
|
+
* - quick_order.one_click_buy_enabled — gates the storefront one-click-buy
|
|
7
|
+
* button. Sales-channel-scoped (each channel may override the global
|
|
8
|
+
* value); default off.
|
|
9
|
+
* - quick_order.import_max_rows — upper bound on rows accepted by a single
|
|
10
|
+
* CSV / Excel import. Global.
|
|
11
|
+
*
|
|
12
|
+
* Setting codes follow the foundation regex `^[a-z][a-z0-9_][a-z0-9_.]*[a-z0-9]$`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const QUICK_ORDER_SETTING_CODES: {
|
|
15
|
+
readonly ONE_CLICK_BUY_ENABLED: "quick_order.one_click_buy_enabled";
|
|
16
|
+
readonly IMPORT_MAX_ROWS: "quick_order.import_max_rows";
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The manifest default, exported so the read that has to degrade degrades to
|
|
20
|
+
* *this* binding (feature 072, D-43). `backend.ts` kept its own `2000`; the two
|
|
21
|
+
* agreed, which is the drift waiting to happen.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_IMPORT_MAX_ROWS = 2000;
|
|
24
|
+
/**
|
|
25
|
+
* Same binding, for the one-click gate (issue #99). The read that has to
|
|
26
|
+
* degrade degrades to *this*, not to a `false` written a second time beside the
|
|
27
|
+
* manifest's `defaultValue`.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_ONE_CLICK_BUY_ENABLED = false;
|
|
30
|
+
export declare const manifest: {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
version: string;
|
|
34
|
+
dependencies: string[];
|
|
35
|
+
description?: string | undefined;
|
|
36
|
+
acknowledgedDependencies?: {
|
|
37
|
+
moduleId: string;
|
|
38
|
+
port: string;
|
|
39
|
+
reason: string;
|
|
40
|
+
}[] | undefined;
|
|
41
|
+
nonBindingDependencies?: {
|
|
42
|
+
moduleId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
45
|
+
reason: string;
|
|
46
|
+
whenAbsent?: string | undefined;
|
|
47
|
+
}[] | undefined;
|
|
48
|
+
activation?: {
|
|
49
|
+
settingCode: string;
|
|
50
|
+
default: boolean;
|
|
51
|
+
} | {
|
|
52
|
+
nonDeactivatable: true;
|
|
53
|
+
reason: string;
|
|
54
|
+
} | undefined;
|
|
55
|
+
settings?: {
|
|
56
|
+
moduleCode: string;
|
|
57
|
+
groups: {
|
|
58
|
+
code: string;
|
|
59
|
+
name: string;
|
|
60
|
+
salesChannelCodes?: string[] | undefined;
|
|
61
|
+
isSystemProtected?: boolean | undefined;
|
|
62
|
+
}[];
|
|
63
|
+
settings: {
|
|
64
|
+
code: string;
|
|
65
|
+
name: string;
|
|
66
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
67
|
+
defaultValue: unknown;
|
|
68
|
+
description?: string | undefined;
|
|
69
|
+
groupCode?: string | undefined;
|
|
70
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
71
|
+
salesChannelCodes?: string[] | undefined;
|
|
72
|
+
enumOptions?: string[] | undefined;
|
|
73
|
+
configurationType?: string | undefined;
|
|
74
|
+
hidden?: boolean | undefined;
|
|
75
|
+
}[];
|
|
76
|
+
} | undefined;
|
|
77
|
+
i18n?: {
|
|
78
|
+
bundlesDir: string;
|
|
79
|
+
} | undefined;
|
|
80
|
+
docs?: false | {
|
|
81
|
+
dir: string;
|
|
82
|
+
} | undefined;
|
|
83
|
+
demo?: false | {
|
|
84
|
+
summary: string;
|
|
85
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
86
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
87
|
+
after?: readonly string[] | undefined;
|
|
88
|
+
package?: string | undefined;
|
|
89
|
+
} | undefined;
|
|
90
|
+
actions?: {
|
|
91
|
+
id: string;
|
|
92
|
+
labelKey: string;
|
|
93
|
+
icon: "Plus" | "Sparkles" | "Settings" | "Search" | "Boxes" | "Layers" | "Menu" | "PlusCircle" | "PlusSquare" | "FilePlus" | "FolderPlus" | "Upload" | "FileUp" | "CloudUpload" | "Download" | "FileDown" | "FileText" | "BookOpen" | "Rss" | "Package" | "Tag" | "ShoppingCart" | "Receipt" | "CreditCard" | "Users" | "UserPlus" | "Inbox" | "ListChecks" | "ClipboardList" | "Image" | "Video" | "LayoutDashboard" | "PanelLeft" | "KeyRound" | "ShieldCheck" | "Edit" | "Archive" | "Box" | "Truck" | "CircleDollarSign" | "Activity" | "LineChart" | "Smartphone" | "Webhook" | "Scale" | "PlugZap" | "PercentDiamond" | "Newspaper" | "Languages" | "Eraser" | "Warehouse" | "TrendingDown" | "Bell" | "PackageOpen" | "Building2" | "Store" | "ClipboardCheck";
|
|
94
|
+
targetRoute: string;
|
|
95
|
+
keywords: string[];
|
|
96
|
+
weight: number;
|
|
97
|
+
descriptionKey?: string | undefined;
|
|
98
|
+
requiredPermission?: string | undefined;
|
|
99
|
+
}[] | undefined;
|
|
100
|
+
permissions?: {
|
|
101
|
+
code: string;
|
|
102
|
+
label: string;
|
|
103
|
+
module?: string | undefined;
|
|
104
|
+
description?: string | undefined;
|
|
105
|
+
requires?: string[] | undefined;
|
|
106
|
+
}[] | undefined;
|
|
107
|
+
transactionalEmails?: {
|
|
108
|
+
code: string;
|
|
109
|
+
name: string;
|
|
110
|
+
variables: {
|
|
111
|
+
key: string;
|
|
112
|
+
label: string;
|
|
113
|
+
sampleValue?: string | undefined;
|
|
114
|
+
description?: string | undefined;
|
|
115
|
+
}[];
|
|
116
|
+
description?: string | undefined;
|
|
117
|
+
group?: string | undefined;
|
|
118
|
+
}[] | undefined;
|
|
119
|
+
capabilities?: string[] | undefined;
|
|
120
|
+
exclusiveCapabilities?: {
|
|
121
|
+
key: string;
|
|
122
|
+
errorCode: string;
|
|
123
|
+
}[] | undefined;
|
|
124
|
+
errorCodes?: {
|
|
125
|
+
code: string;
|
|
126
|
+
tokens?: string[] | undefined;
|
|
127
|
+
}[] | undefined;
|
|
128
|
+
blocks?: {
|
|
129
|
+
name: string;
|
|
130
|
+
labelKey: string;
|
|
131
|
+
category: string;
|
|
132
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
133
|
+
fields: Record<string, {
|
|
134
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
135
|
+
label?: string | undefined;
|
|
136
|
+
required?: boolean | undefined;
|
|
137
|
+
options?: {
|
|
138
|
+
label: string;
|
|
139
|
+
value: string | number;
|
|
140
|
+
}[] | undefined;
|
|
141
|
+
refKind?: string | undefined;
|
|
142
|
+
}>;
|
|
143
|
+
descriptionKey?: string | undefined;
|
|
144
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
145
|
+
responsiveFields?: string[] | undefined;
|
|
146
|
+
previewIcon?: string | undefined;
|
|
147
|
+
weight?: number | undefined;
|
|
148
|
+
}[] | undefined;
|
|
149
|
+
blockCategories?: {
|
|
150
|
+
key: string;
|
|
151
|
+
titleKey: string;
|
|
152
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
153
|
+
weight?: number | undefined;
|
|
154
|
+
visible?: boolean | undefined;
|
|
155
|
+
}[] | undefined;
|
|
156
|
+
env?: {
|
|
157
|
+
name: string;
|
|
158
|
+
describes: {
|
|
159
|
+
en: string;
|
|
160
|
+
pl: string;
|
|
161
|
+
};
|
|
162
|
+
requirement: {
|
|
163
|
+
kind: "required";
|
|
164
|
+
} | {
|
|
165
|
+
kind: "requiredWhen";
|
|
166
|
+
input: string;
|
|
167
|
+
equals: string;
|
|
168
|
+
} | {
|
|
169
|
+
kind: "optional";
|
|
170
|
+
without: {
|
|
171
|
+
en: string;
|
|
172
|
+
pl: string;
|
|
173
|
+
};
|
|
174
|
+
};
|
|
175
|
+
secret: boolean;
|
|
176
|
+
generable: boolean;
|
|
177
|
+
owner: {
|
|
178
|
+
kind: "platform";
|
|
179
|
+
} | {
|
|
180
|
+
kind: "application";
|
|
181
|
+
application: "admin" | "backend" | "storefront";
|
|
182
|
+
} | {
|
|
183
|
+
kind: "module";
|
|
184
|
+
moduleId: string;
|
|
185
|
+
};
|
|
186
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
187
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
188
|
+
}[] | undefined;
|
|
189
|
+
};
|
|
190
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,yBAAyB;;;CAG5B,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C;;;;GAIG;AACH,eAAO,MAAM,6BAA6B,QAAQ,CAAC;AA2BnD,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8EnB,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { defineModuleManifest, defineModuleSettingsManifest } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Quick Order module — manifest (Module Lifecycle, feature 018) extended for
|
|
4
|
+
* feature 039.
|
|
5
|
+
*
|
|
6
|
+
* Settings (feature 039):
|
|
7
|
+
* - quick_order.one_click_buy_enabled — gates the storefront one-click-buy
|
|
8
|
+
* button. Sales-channel-scoped (each channel may override the global
|
|
9
|
+
* value); default off.
|
|
10
|
+
* - quick_order.import_max_rows — upper bound on rows accepted by a single
|
|
11
|
+
* CSV / Excel import. Global.
|
|
12
|
+
*
|
|
13
|
+
* Setting codes follow the foundation regex `^[a-z][a-z0-9_][a-z0-9_.]*[a-z0-9]$`.
|
|
14
|
+
*/
|
|
15
|
+
export const QUICK_ORDER_SETTING_CODES = {
|
|
16
|
+
ONE_CLICK_BUY_ENABLED: 'quick_order.one_click_buy_enabled',
|
|
17
|
+
IMPORT_MAX_ROWS: 'quick_order.import_max_rows',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The manifest default, exported so the read that has to degrade degrades to
|
|
21
|
+
* *this* binding (feature 072, D-43). `backend.ts` kept its own `2000`; the two
|
|
22
|
+
* agreed, which is the drift waiting to happen.
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_IMPORT_MAX_ROWS = 2000;
|
|
25
|
+
/**
|
|
26
|
+
* Same binding, for the one-click gate (issue #99). The read that has to
|
|
27
|
+
* degrade degrades to *this*, not to a `false` written a second time beside the
|
|
28
|
+
* manifest's `defaultValue`.
|
|
29
|
+
*/
|
|
30
|
+
export const DEFAULT_ONE_CLICK_BUY_ENABLED = false;
|
|
31
|
+
const settings = defineModuleSettingsManifest({
|
|
32
|
+
moduleCode: 'quick_order',
|
|
33
|
+
groups: [{ code: 'quick_order', name: 'Quick Order' }],
|
|
34
|
+
settings: [
|
|
35
|
+
{
|
|
36
|
+
code: QUICK_ORDER_SETTING_CODES.ONE_CLICK_BUY_ENABLED,
|
|
37
|
+
name: 'One-click buy enabled',
|
|
38
|
+
description: 'When enabled, buyers with all four default ordering preferences set see a "Buy in one click" button on product pages. The per-channel value overrides the global value.',
|
|
39
|
+
groupCode: 'quick_order',
|
|
40
|
+
valueType: 'boolean',
|
|
41
|
+
defaultValue: DEFAULT_ONE_CLICK_BUY_ENABLED,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
code: QUICK_ORDER_SETTING_CODES.IMPORT_MAX_ROWS,
|
|
45
|
+
name: 'Import row limit',
|
|
46
|
+
description: 'Maximum number of data rows accepted by a single CSV / Excel quick-order import. Rows beyond the limit are rejected.',
|
|
47
|
+
groupCode: 'quick_order',
|
|
48
|
+
valueType: 'number',
|
|
49
|
+
defaultValue: DEFAULT_IMPORT_MAX_ROWS,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
export const manifest = defineModuleManifest({
|
|
54
|
+
id: 'quick_order',
|
|
55
|
+
name: 'Quick Order',
|
|
56
|
+
description: 'Bulk Cart / Quote Request import (CSV + Excel, with variants), quick product search, reorder, default ordering preferences, and one-click buy.',
|
|
57
|
+
version: '2.0.0',
|
|
58
|
+
// Feature 072 (T139) — the six edges the module actually resolves, declared
|
|
59
|
+
// now that it composes itself rather than being mounted by `shopping_lists`.
|
|
60
|
+
// `carts`, `catalog`, `orders`, `organizations` and `quote_requests` were all
|
|
61
|
+
// reached before through options the host passed down, which is why none of
|
|
62
|
+
// them appeared here.
|
|
63
|
+
//
|
|
64
|
+
// Feature 075, Phase C adds `customer_accounts`: the two preference surfaces
|
|
65
|
+
// read `CustomerAccount` to work out whose defaults are being managed, out of
|
|
66
|
+
// a table deactivation leaves in place, so an operator switching accounts off
|
|
67
|
+
// still got an answer. It fails closed at `customerAccountReadPort` now, and
|
|
68
|
+
// it belongs in `dependencies` rather than `nonBindingDependencies` — a set
|
|
69
|
+
// of ordering defaults resolved without knowing whose they are is worse than
|
|
70
|
+
// no defaults at all.
|
|
71
|
+
dependencies: [
|
|
72
|
+
'addresses',
|
|
73
|
+
'carts',
|
|
74
|
+
'catalog',
|
|
75
|
+
'customer_accounts',
|
|
76
|
+
'delivery_methods',
|
|
77
|
+
'orders',
|
|
78
|
+
'organizations',
|
|
79
|
+
'payment_methods',
|
|
80
|
+
'quote_requests',
|
|
81
|
+
'settings',
|
|
82
|
+
],
|
|
83
|
+
// D-44 — real to the container, binding on no operator.
|
|
84
|
+
nonBindingDependencies: [
|
|
85
|
+
{
|
|
86
|
+
moduleId: 'customers',
|
|
87
|
+
name: 'customerAddressReadPort',
|
|
88
|
+
kind: 'degrades-without',
|
|
89
|
+
whenAbsent: 'a personal (buyer-owned) address stops being an eligible ordering default; the ' +
|
|
90
|
+
'organisation’s shared addresses still resolve, and one-click buy hides itself for ' +
|
|
91
|
+
'buyers whose default was personal',
|
|
92
|
+
reason: 'Issue #216 — the personal-address branch of the default-eligibility check reads ' +
|
|
93
|
+
'`customers`’ address book, and it is probed on every eligibility question. Failing ' +
|
|
94
|
+
'closed would answer 503 for the whole preference resolution, and with it one-click ' +
|
|
95
|
+
'buy for *every* buyer, because a CRM surface was switched off. The degrade is not ' +
|
|
96
|
+
'invented for the occasion: Principle XVII says an absent module behaves as if never ' +
|
|
97
|
+
'installed, and on a platform without `customers` there are no personal addresses — ' +
|
|
98
|
+
'only org-shared ones are eligible. Dropping an ineligible default to null is what ' +
|
|
99
|
+
'FR-020 already specifies. Reached through a presence probe ahead of the port, per ' +
|
|
100
|
+
'call, because a closed gate throws rather than answering null and an operator ' +
|
|
101
|
+
'switching the module back on must not need a restart.',
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
// Feature 073 (Constitution XVII) — its own activation control at last.
|
|
105
|
+
// Until T139 this module had no presence of its own: `shopping_lists` mounted
|
|
106
|
+
// its routes, so `shopping_lists.enabled` switched both off together and
|
|
107
|
+
// there was no way to run CSV import without shopping lists, or the reverse.
|
|
108
|
+
// Deactivatable, and not close to the line — bulk import, reorder and
|
|
109
|
+
// one-click buy are a convenience layer over ordering, and a deployment whose
|
|
110
|
+
// buyers order line by line is an ordinary one.
|
|
111
|
+
activation: { settingCode: 'quick_order.enabled', default: true },
|
|
112
|
+
settings,
|
|
113
|
+
i18n: { bundlesDir: 'i18n' },
|
|
114
|
+
docs: { dir: 'docs' },
|
|
115
|
+
actions: [
|
|
116
|
+
{
|
|
117
|
+
id: 'open-quick-order',
|
|
118
|
+
labelKey: 'actions.openQuickOrder.label',
|
|
119
|
+
descriptionKey: 'actions.openQuickOrder.description',
|
|
120
|
+
icon: 'FileUp',
|
|
121
|
+
targetRoute: '/orders/quick-order',
|
|
122
|
+
// The admin quick-order routes are gated on orders:write.
|
|
123
|
+
requiredPermission: 'orders:write',
|
|
124
|
+
keywords: ['quick order', 'import', 'csv', 'szybkie zamówienie', 'import csv'],
|
|
125
|
+
weight: 226,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Quick-order default ordering preferences (feature 039 — US2).
|
|
4
|
+
*
|
|
5
|
+
* One row per target (`scope` = organization | customer, `scope_id` = its id)
|
|
6
|
+
* holding four nullable default references. A customer row overrides the
|
|
7
|
+
* organization row per field; resolution is in the service. FKs are
|
|
8
|
+
* `ON DELETE SET NULL` so removing a method / address degrades the default to
|
|
9
|
+
* "unset" rather than blocking (FR-020).
|
|
10
|
+
*/
|
|
11
|
+
export declare class Migration20260611T140359QuickOrderDefaultPreferences extends Migration {
|
|
12
|
+
up(): Promise<void>;
|
|
13
|
+
down(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=20260611T140359_quick_order_default_preferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140359_quick_order_default_preferences.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140359_quick_order_default_preferences.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;;GAQG;AACH,qBAAa,oDAAqD,SAAQ,SAAS;IAClE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAwCnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
|