@endora-commerce/mod-customers 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 +60 -0
- package/dist/admin/index.d.ts +26 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +68 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/CustomerDetail.d.ts +9 -0
- package/dist/admin/pages/CustomerDetail.d.ts.map +1 -0
- package/dist/admin/pages/CustomerDetail.js +72 -0
- package/dist/admin/pages/CustomerDetail.js.map +1 -0
- package/dist/admin/pages/CustomersList.d.ts +9 -0
- package/dist/admin/pages/CustomersList.d.ts.map +1 -0
- package/dist/admin/pages/CustomersList.js +115 -0
- package/dist/admin/pages/CustomersList.js.map +1 -0
- package/dist/admin/pages/OnlineCustomers.d.ts +9 -0
- package/dist/admin/pages/OnlineCustomers.d.ts.map +1 -0
- package/dist/admin/pages/OnlineCustomers.js +77 -0
- package/dist/admin/pages/OnlineCustomers.js.map +1 -0
- package/dist/admin/panels/HistoryPanels.d.ts +11 -0
- package/dist/admin/panels/HistoryPanels.d.ts.map +1 -0
- package/dist/admin/panels/HistoryPanels.js +64 -0
- package/dist/admin/panels/HistoryPanels.js.map +1 -0
- package/dist/admin/panels/ManagementPanels.d.ts +14 -0
- package/dist/admin/panels/ManagementPanels.d.ts.map +1 -0
- package/dist/admin/panels/ManagementPanels.js +93 -0
- package/dist/admin/panels/ManagementPanels.js.map +1 -0
- package/dist/backend/entities/customer-address.entity.d.ts +25 -0
- package/dist/backend/entities/customer-address.entity.d.ts.map +1 -0
- package/dist/backend/entities/customer-address.entity.js +95 -0
- package/dist/backend/entities/customer-address.entity.js.map +1 -0
- package/dist/backend/index.d.ts +91 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +201 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/plugin.d.ts +168 -0
- package/dist/backend/plugin.d.ts.map +1 -0
- package/dist/backend/plugin.js +83 -0
- package/dist/backend/plugin.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +70 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +313 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.register.d.ts +12 -0
- package/dist/backend/routes.register.d.ts.map +1 -0
- package/dist/backend/routes.register.js +40 -0
- package/dist/backend/routes.register.js.map +1 -0
- package/dist/backend/routes.self.d.ts +46 -0
- package/dist/backend/routes.self.d.ts.map +1 -0
- package/dist/backend/routes.self.js +145 -0
- package/dist/backend/routes.self.js.map +1 -0
- package/dist/backend/serializers.d.ts +13 -0
- package/dist/backend/serializers.d.ts.map +1 -0
- package/dist/backend/serializers.js +39 -0
- package/dist/backend/serializers.js.map +1 -0
- package/dist/backend/services/customer-address-read-port.d.ts +29 -0
- package/dist/backend/services/customer-address-read-port.d.ts.map +1 -0
- package/dist/backend/services/customer-address-read-port.js +53 -0
- package/dist/backend/services/customer-address-read-port.js.map +1 -0
- package/dist/backend/services/customer-address-service.d.ts +94 -0
- package/dist/backend/services/customer-address-service.d.ts.map +1 -0
- package/dist/backend/services/customer-address-service.js +176 -0
- package/dist/backend/services/customer-address-service.js.map +1 -0
- package/dist/backend/services/customer-admin-query-service.d.ts +57 -0
- package/dist/backend/services/customer-admin-query-service.d.ts.map +1 -0
- package/dist/backend/services/customer-admin-query-service.js +97 -0
- package/dist/backend/services/customer-admin-query-service.js.map +1 -0
- package/dist/backend/services/customer-authority-service.d.ts +46 -0
- package/dist/backend/services/customer-authority-service.d.ts.map +1 -0
- package/dist/backend/services/customer-authority-service.js +42 -0
- package/dist/backend/services/customer-authority-service.js.map +1 -0
- package/dist/backend/services/customer-defaults-service.d.ts +48 -0
- package/dist/backend/services/customer-defaults-service.d.ts.map +1 -0
- package/dist/backend/services/customer-defaults-service.js +71 -0
- package/dist/backend/services/customer-defaults-service.js.map +1 -0
- package/dist/backend/services/customer-deletion-service.d.ts +39 -0
- package/dist/backend/services/customer-deletion-service.d.ts.map +1 -0
- package/dist/backend/services/customer-deletion-service.js +94 -0
- package/dist/backend/services/customer-deletion-service.js.map +1 -0
- package/dist/backend/services/customer-moderation-service.d.ts +58 -0
- package/dist/backend/services/customer-moderation-service.d.ts.map +1 -0
- package/dist/backend/services/customer-moderation-service.js +70 -0
- package/dist/backend/services/customer-moderation-service.js.map +1 -0
- package/dist/backend/services/customer-org-assignment-service.d.ts +52 -0
- package/dist/backend/services/customer-org-assignment-service.d.ts.map +1 -0
- package/dist/backend/services/customer-org-assignment-service.js +106 -0
- package/dist/backend/services/customer-org-assignment-service.js.map +1 -0
- package/dist/backend/services/customer-presence-service.d.ts +44 -0
- package/dist/backend/services/customer-presence-service.d.ts.map +1 -0
- package/dist/backend/services/customer-presence-service.js +47 -0
- package/dist/backend/services/customer-presence-service.js.map +1 -0
- package/dist/backend/services/customer-registration-service.d.ts +56 -0
- package/dist/backend/services/customer-registration-service.d.ts.map +1 -0
- package/dist/backend/services/customer-registration-service.js +43 -0
- package/dist/backend/services/customer-registration-service.js.map +1 -0
- package/dist/backend/workers/anonymization-sweep-worker.d.ts +15 -0
- package/dist/backend/workers/anonymization-sweep-worker.d.ts.map +1 -0
- package/dist/backend/workers/anonymization-sweep-worker.js +20 -0
- package/dist/backend/workers/anonymization-sweep-worker.js.map +1 -0
- package/dist/manifest.d.ts +181 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +192 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260611T140404_customers_customer_addresses_init.d.ts +14 -0
- package/dist/migrations/20260611T140404_customers_customer_addresses_init.d.ts.map +1 -0
- package/dist/migrations/20260611T140404_customers_customer_addresses_init.js +40 -0
- package/dist/migrations/20260611T140404_customers_customer_addresses_init.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/customers.md +108 -0
- package/i18n/en.json +94 -0
- package/i18n/pl.json +94 -0
- package/package.json +101 -0
- package/tailwind.css +14 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AuthSessionReadPort, CustomerAccountReadPort, OnlineCustomer } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* CustomerPresenceService — the admin "online customers" view (feature 040,
|
|
4
|
+
* US7 / FR-034). A customer is online if a session of theirs was active within
|
|
5
|
+
* the configurable freshness window.
|
|
6
|
+
*/
|
|
7
|
+
export interface RecentActivityPort {
|
|
8
|
+
listRecentlyActiveCustomers(windowMinutes: number): Promise<string[]>;
|
|
9
|
+
}
|
|
10
|
+
export declare class CustomerPresenceService {
|
|
11
|
+
private readonly sessions;
|
|
12
|
+
/**
|
|
13
|
+
* Feature 075 — `auth`'s published read, where this service ran
|
|
14
|
+
* `em.find(Session, …)` over that module's table to work out when each
|
|
15
|
+
* account was last seen. The port answers the same question and carries no
|
|
16
|
+
* `Session` entity, so the "who is online" panel no longer needs an
|
|
17
|
+
* `EntityManager` at all.
|
|
18
|
+
*/
|
|
19
|
+
private readonly sessionReads;
|
|
20
|
+
private readonly resolveFreshnessMinutes;
|
|
21
|
+
/**
|
|
22
|
+
* Feature 075 — `customer_accounts`' published read, where this service ran
|
|
23
|
+
* `em.find(CustomerAccount, …)` over that module's table. The four fields
|
|
24
|
+
* it renders are all on the record.
|
|
25
|
+
*/
|
|
26
|
+
private readonly accounts;
|
|
27
|
+
constructor(sessions: RecentActivityPort,
|
|
28
|
+
/**
|
|
29
|
+
* Feature 075 — `auth`'s published read, where this service ran
|
|
30
|
+
* `em.find(Session, …)` over that module's table to work out when each
|
|
31
|
+
* account was last seen. The port answers the same question and carries no
|
|
32
|
+
* `Session` entity, so the "who is online" panel no longer needs an
|
|
33
|
+
* `EntityManager` at all.
|
|
34
|
+
*/
|
|
35
|
+
sessionReads: AuthSessionReadPort, resolveFreshnessMinutes: () => Promise<number>,
|
|
36
|
+
/**
|
|
37
|
+
* Feature 075 — `customer_accounts`' published read, where this service ran
|
|
38
|
+
* `em.find(CustomerAccount, …)` over that module's table. The four fields
|
|
39
|
+
* it renders are all on the record.
|
|
40
|
+
*/
|
|
41
|
+
accounts: CustomerAccountReadPort);
|
|
42
|
+
listOnline(): Promise<OnlineCustomer[]>;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=customer-presence-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customer-presence-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-presence-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACvB,cAAc,EACf,MAAM,4BAA4B,CAAC;AAEpC;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACvE;AAED,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,uBAAuB;IACxC;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAfR,QAAQ,EAAE,kBAAkB;IAC7C;;;;;;OAMG;IACc,YAAY,EAAE,mBAAmB,EACjC,uBAAuB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;IAC/D;;;;OAIG;IACc,QAAQ,EAAE,uBAAuB;IAG9C,UAAU,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;CAqB9C"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export class CustomerPresenceService {
|
|
2
|
+
sessions;
|
|
3
|
+
sessionReads;
|
|
4
|
+
resolveFreshnessMinutes;
|
|
5
|
+
accounts;
|
|
6
|
+
constructor(sessions,
|
|
7
|
+
/**
|
|
8
|
+
* Feature 075 — `auth`'s published read, where this service ran
|
|
9
|
+
* `em.find(Session, …)` over that module's table to work out when each
|
|
10
|
+
* account was last seen. The port answers the same question and carries no
|
|
11
|
+
* `Session` entity, so the "who is online" panel no longer needs an
|
|
12
|
+
* `EntityManager` at all.
|
|
13
|
+
*/
|
|
14
|
+
sessionReads, resolveFreshnessMinutes,
|
|
15
|
+
/**
|
|
16
|
+
* Feature 075 — `customer_accounts`' published read, where this service ran
|
|
17
|
+
* `em.find(CustomerAccount, …)` over that module's table. The four fields
|
|
18
|
+
* it renders are all on the record.
|
|
19
|
+
*/
|
|
20
|
+
accounts) {
|
|
21
|
+
this.sessions = sessions;
|
|
22
|
+
this.sessionReads = sessionReads;
|
|
23
|
+
this.resolveFreshnessMinutes = resolveFreshnessMinutes;
|
|
24
|
+
this.accounts = accounts;
|
|
25
|
+
}
|
|
26
|
+
async listOnline() {
|
|
27
|
+
const windowMinutes = await this.resolveFreshnessMinutes();
|
|
28
|
+
const ids = await this.sessions.listRecentlyActiveCustomers(windowMinutes);
|
|
29
|
+
if (ids.length === 0)
|
|
30
|
+
return [];
|
|
31
|
+
const customers = await this.accounts.findByIds(ids, { activeOnly: true });
|
|
32
|
+
// Latest activity per customer, for the displayed timestamp.
|
|
33
|
+
const cutoff = new Date(Date.now() - windowMinutes * 60 * 1000);
|
|
34
|
+
const lastSeen = new Map();
|
|
35
|
+
for (const row of await this.sessionReads.lastSeenByCustomerAccount(ids, cutoff)) {
|
|
36
|
+
lastSeen.set(row.customerAccountId, row.lastSeenAt);
|
|
37
|
+
}
|
|
38
|
+
return customers.map((c) => ({
|
|
39
|
+
id: c.id,
|
|
40
|
+
email: c.email,
|
|
41
|
+
firstName: c.firstName,
|
|
42
|
+
lastName: c.lastName,
|
|
43
|
+
lastSeenAt: (lastSeen.get(c.id) ?? new Date()).toISOString(),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=customer-presence-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customer-presence-service.js","sourceRoot":"","sources":["../../../src/backend/services/customer-presence-service.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,uBAAuB;IAEf;IAQA;IACA;IAMA;IAhBnB,YACmB,QAA4B;IAC7C;;;;;;OAMG;IACc,YAAiC,EACjC,uBAA8C;IAC/D;;;;OAIG;IACc,QAAiC;QAfjC,aAAQ,GAAR,QAAQ,CAAoB;QAQ5B,iBAAY,GAAZ,YAAY,CAAqB;QACjC,4BAAuB,GAAvB,uBAAuB,CAAuB;QAM9C,aAAQ,GAAR,QAAQ,CAAyB;IACjD,CAAC;IAEJ,KAAK,CAAC,UAAU;QACd,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC3D,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,2BAA2B,CAAC,aAAa,CAAC,CAAC;QAC3E,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAEhC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3E,6DAA6D;QAC7D,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,aAAa,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAChE,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgB,CAAC;QACzC,KAAK,MAAM,GAAG,IAAI,MAAM,IAAI,CAAC,YAAY,CAAC,yBAAyB,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC;YACjF,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,GAAG,CAAC,UAAU,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,UAAU,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE;SAC7D,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type AuthSessionPort, type CustomerAccountLifecycleWritePort, type CustomerAccountRecord } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* CustomerRegistrationService — standalone (org-less) sign-up (feature 040,
|
|
4
|
+
* US1 / FR-002/FR-004).
|
|
5
|
+
*
|
|
6
|
+
* Gated by the `customers.allow_registration_without_organization` setting:
|
|
7
|
+
* when disabled, registration is refused with a clear, actionable error so
|
|
8
|
+
* the storefront can steer the visitor toward Organization registration.
|
|
9
|
+
*
|
|
10
|
+
* On success the new account and the individual's single-member personal
|
|
11
|
+
* organization are written in one transaction by `customer_accounts` (D-178),
|
|
12
|
+
* and an auto-login session is minted (parity with the Organization
|
|
13
|
+
* registration flow, which also logs the first user straight in).
|
|
14
|
+
*
|
|
15
|
+
* **Feature 075, Phase C — this service writes no table.** It used to create
|
|
16
|
+
* `customer_accounts`' entity itself, hash the password with `auth`'s hasher,
|
|
17
|
+
* and hand its own `EntityManager` to `organizations`' provisioner. All three
|
|
18
|
+
* are owner-side now: the account and its audit row come from
|
|
19
|
+
* `customerAccountLifecycleWritePort`, the hashing goes with them (a caller
|
|
20
|
+
* that hashes is a caller that has to be told which algorithm the owner uses),
|
|
21
|
+
* and the organisation is provisioned inside that same write (D-178). What is left
|
|
22
|
+
* here is the policy — whether a registration outside a company organization is
|
|
23
|
+
* allowed at all — which is this module's setting.
|
|
24
|
+
*/
|
|
25
|
+
export interface CustomerRegistrationDeps {
|
|
26
|
+
/** `customer_accounts`' account lifecycle. Creates the row and audits it. */
|
|
27
|
+
accounts: CustomerAccountLifecycleWritePort;
|
|
28
|
+
/**
|
|
29
|
+
* `auth`'s published session port (D-98.2), not the `SessionService` class
|
|
30
|
+
* this used to import: the only method reached here is `createSession`, the
|
|
31
|
+
* port declares it, and `authSessionPort` is the name the contract
|
|
32
|
+
* publishes.
|
|
33
|
+
*/
|
|
34
|
+
sessionService: AuthSessionPort;
|
|
35
|
+
/** Reads `customers.allow_registration_without_organization` (the per-channel B2C gate). */
|
|
36
|
+
resolveAllowRegistrationWithoutOrganization: () => Promise<boolean>;
|
|
37
|
+
}
|
|
38
|
+
export interface RegisterStandaloneInput {
|
|
39
|
+
email: string;
|
|
40
|
+
password: string;
|
|
41
|
+
firstName: string;
|
|
42
|
+
lastName: string;
|
|
43
|
+
ip?: string;
|
|
44
|
+
userAgent?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface RegisterStandaloneResult {
|
|
47
|
+
customerAccount: CustomerAccountRecord;
|
|
48
|
+
sessionCookieValue: string;
|
|
49
|
+
sessionExpiresAt: Date;
|
|
50
|
+
}
|
|
51
|
+
export declare class CustomerRegistrationService {
|
|
52
|
+
private readonly deps;
|
|
53
|
+
constructor(deps: CustomerRegistrationDeps);
|
|
54
|
+
registerStandalone(input: RegisterStandaloneInput): Promise<RegisterStandaloneResult>;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=customer-registration-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customer-registration-service.d.ts","sourceRoot":"","sources":["../../../src/backend/services/customer-registration-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,iCAAiC,EACtC,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAC;AAGpC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,WAAW,wBAAwB;IACvC,6EAA6E;IAC7E,QAAQ,EAAE,iCAAiC,CAAC;IAC5C;;;;;OAKG;IACH,cAAc,EAAE,eAAe,CAAC;IAChC,4FAA4F;IAC5F,2CAA2C,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;CACrE;AAED,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,eAAe,EAAE,qBAAqB,CAAC;IACvC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,IAAI,CAAC;CACxB;AAED,qBAAa,2BAA2B;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,wBAAwB;IAErD,kBAAkB,CACtB,KAAK,EAAE,uBAAuB,GAC7B,OAAO,CAAC,wBAAwB,CAAC;CA0CrC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ERROR_CODES, } from '@endora-commerce/contracts';
|
|
2
|
+
import { HttpError } from '@endora-commerce/platform/http';
|
|
3
|
+
export class CustomerRegistrationService {
|
|
4
|
+
deps;
|
|
5
|
+
constructor(deps) {
|
|
6
|
+
this.deps = deps;
|
|
7
|
+
}
|
|
8
|
+
async registerStandalone(input) {
|
|
9
|
+
const allowed = await this.deps.resolveAllowRegistrationWithoutOrganization();
|
|
10
|
+
if (!allowed) {
|
|
11
|
+
throw new HttpError(403, ERROR_CODES.REGISTRATION_REQUIRES_ORGANIZATION, 'Registration without an Organization is currently disabled. Please register your Organization instead.');
|
|
12
|
+
}
|
|
13
|
+
// The duplicate-address refusal is inside the write, so a registration
|
|
14
|
+
// racing its own pre-check still answers 409 rather than a 500 off the
|
|
15
|
+
// unique index.
|
|
16
|
+
//
|
|
17
|
+
// D-178 — the personal organization feature 051 requires is provisioned
|
|
18
|
+
// inside that call, in the same transaction as the account, so the record
|
|
19
|
+
// that comes back already carries it. This service used to call
|
|
20
|
+
// `personalOrganizationPort.ensureForCustomerAccount` on the next line, and
|
|
21
|
+
// that second unit of work is exactly what left a committed account with no
|
|
22
|
+
// organization whenever it failed: the visitor had no session to retry from
|
|
23
|
+
// and nothing anywhere re-ran the provisioning.
|
|
24
|
+
const customerAccount = await this.deps.accounts.createStandalone({
|
|
25
|
+
email: input.email,
|
|
26
|
+
password: input.password,
|
|
27
|
+
firstName: input.firstName,
|
|
28
|
+
lastName: input.lastName,
|
|
29
|
+
});
|
|
30
|
+
const session = await this.deps.sessionService.createSession({
|
|
31
|
+
kind: 'customer',
|
|
32
|
+
customerAccountId: customerAccount.id,
|
|
33
|
+
...(input.ip !== undefined ? { ipAddress: input.ip } : {}),
|
|
34
|
+
...(input.userAgent !== undefined ? { userAgent: input.userAgent } : {}),
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
customerAccount,
|
|
38
|
+
sessionCookieValue: session.cookieValue,
|
|
39
|
+
sessionExpiresAt: session.expiresAt,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=customer-registration-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"customer-registration-service.js","sourceRoot":"","sources":["../../../src/backend/services/customer-registration-service.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,GAIZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAsD3D,MAAM,OAAO,2BAA2B;IACT;IAA7B,YAA6B,IAA8B;QAA9B,SAAI,GAAJ,IAAI,CAA0B;IAAG,CAAC;IAE/D,KAAK,CAAC,kBAAkB,CACtB,KAA8B;QAE9B,MAAM,OAAO,GACX,MAAM,IAAI,CAAC,IAAI,CAAC,2CAA2C,EAAE,CAAC;QAChE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,WAAW,CAAC,kCAAkC,EAC9C,wGAAwG,CACzG,CAAC;QACJ,CAAC;QAED,uEAAuE;QACvE,uEAAuE;QACvE,gBAAgB;QAChB,EAAE;QACF,wEAAwE;QACxE,0EAA0E;QAC1E,gEAAgE;QAChE,4EAA4E;QAC5E,4EAA4E;QAC5E,4EAA4E;QAC5E,gDAAgD;QAChD,MAAM,eAAe,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAChE,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC;YAC3D,IAAI,EAAE,UAAU;YAChB,iBAAiB,EAAE,eAAe,CAAC,EAAE;YACrC,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACzE,CAAC,CAAC;QAEH,OAAO;YACL,eAAe;YACf,kBAAkB,EAAE,OAAO,CAAC,WAAW;YACvC,gBAAgB,EAAE,OAAO,CAAC,SAAS;SACpC,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CustomerDeletionService } from '../services/customer-deletion-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* AnonymizationSweepWorker — repeatable job that permanently anonymizes
|
|
4
|
+
* soft-deleted customer accounts once their retention window has elapsed
|
|
5
|
+
* (feature 040, US7 / FR-040). Mirrors the carts abandonment-sweep pattern:
|
|
6
|
+
* a thin class exposing `sweep()` so it can be driven by a scheduler or a CLI
|
|
7
|
+
* entry point. Returns the number of accounts anonymized.
|
|
8
|
+
*/
|
|
9
|
+
export declare class AnonymizationSweepWorker {
|
|
10
|
+
private readonly deletion;
|
|
11
|
+
private readonly resolveRetentionDays;
|
|
12
|
+
constructor(deletion: CustomerDeletionService, resolveRetentionDays: () => Promise<number>);
|
|
13
|
+
sweep(now?: Date): Promise<number>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=anonymization-sweep-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anonymization-sweep-worker.d.ts","sourceRoot":"","sources":["../../../src/backend/workers/anonymization-sweep-worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC;AAExF;;;;;;GAMG;AACH,qBAAa,wBAAwB;IAEjC,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBADpB,QAAQ,EAAE,uBAAuB,EACjC,oBAAoB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC;IAGxD,KAAK,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC,MAAM,CAAC;CAIrD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnonymizationSweepWorker — repeatable job that permanently anonymizes
|
|
3
|
+
* soft-deleted customer accounts once their retention window has elapsed
|
|
4
|
+
* (feature 040, US7 / FR-040). Mirrors the carts abandonment-sweep pattern:
|
|
5
|
+
* a thin class exposing `sweep()` so it can be driven by a scheduler or a CLI
|
|
6
|
+
* entry point. Returns the number of accounts anonymized.
|
|
7
|
+
*/
|
|
8
|
+
export class AnonymizationSweepWorker {
|
|
9
|
+
deletion;
|
|
10
|
+
resolveRetentionDays;
|
|
11
|
+
constructor(deletion, resolveRetentionDays) {
|
|
12
|
+
this.deletion = deletion;
|
|
13
|
+
this.resolveRetentionDays = resolveRetentionDays;
|
|
14
|
+
}
|
|
15
|
+
async sweep(now = new Date()) {
|
|
16
|
+
const retentionDays = await this.resolveRetentionDays();
|
|
17
|
+
return this.deletion.sweep(retentionDays, now);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=anonymization-sweep-worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anonymization-sweep-worker.js","sourceRoot":"","sources":["../../../src/backend/workers/anonymization-sweep-worker.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,OAAO,wBAAwB;IAEhB;IACA;IAFnB,YACmB,QAAiC,EACjC,oBAA2C;QAD3C,aAAQ,GAAR,QAAQ,CAAyB;QACjC,yBAAoB,GAApB,oBAAoB,CAAuB;IAC3D,CAAC;IAEJ,KAAK,CAAC,KAAK,CAAC,MAAY,IAAI,IAAI,EAAE;QAChC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;QACxD,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;CACF"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Customers (Klienci) module — feature 040.
|
|
3
|
+
*
|
|
4
|
+
* Owns the customer-lifecycle business logic and the admin + storefront
|
|
5
|
+
* surfaces on top of the `customer_accounts` data module: standalone
|
|
6
|
+
* registration, self-service (addresses, defaults, history), and admin
|
|
7
|
+
* oversight (block/unblock, impersonation, detail view, customer groups,
|
|
8
|
+
* password reset, soft-delete + restore, online presence).
|
|
9
|
+
*
|
|
10
|
+
* Three platform-wide settings are registered here and seeded by the
|
|
11
|
+
* module-lifecycle ManifestReconciler on boot.
|
|
12
|
+
*/
|
|
13
|
+
export declare const CUSTOMERS_SETTING_CODES: {
|
|
14
|
+
/** Gate standalone (org-less) registration (FR-001/FR-004). */
|
|
15
|
+
readonly ALLOW_REGISTRATION_WITHOUT_ORGANIZATION: "customers.allow_registration_without_organization";
|
|
16
|
+
/** Restore window (days) before permanent anonymization (FR-041). */
|
|
17
|
+
readonly DELETION_RETENTION_DAYS: "customers.deletion_retention_days";
|
|
18
|
+
/** "Online" threshold (minutes) for the admin presence view. */
|
|
19
|
+
readonly PRESENCE_FRESHNESS_MINUTES: "customers.presence_freshness_minutes";
|
|
20
|
+
};
|
|
21
|
+
export declare const manifest: {
|
|
22
|
+
id: string;
|
|
23
|
+
name: string;
|
|
24
|
+
version: string;
|
|
25
|
+
dependencies: string[];
|
|
26
|
+
description?: string | undefined;
|
|
27
|
+
acknowledgedDependencies?: {
|
|
28
|
+
moduleId: string;
|
|
29
|
+
port: string;
|
|
30
|
+
reason: string;
|
|
31
|
+
}[] | undefined;
|
|
32
|
+
nonBindingDependencies?: {
|
|
33
|
+
moduleId: string;
|
|
34
|
+
name: string;
|
|
35
|
+
kind: "contributes-to" | "degrades-without" | "refuses-without";
|
|
36
|
+
reason: string;
|
|
37
|
+
whenAbsent?: string | undefined;
|
|
38
|
+
}[] | undefined;
|
|
39
|
+
activation?: {
|
|
40
|
+
settingCode: string;
|
|
41
|
+
default: boolean;
|
|
42
|
+
} | {
|
|
43
|
+
nonDeactivatable: true;
|
|
44
|
+
reason: string;
|
|
45
|
+
} | undefined;
|
|
46
|
+
settings?: {
|
|
47
|
+
moduleCode: string;
|
|
48
|
+
groups: {
|
|
49
|
+
code: string;
|
|
50
|
+
name: string;
|
|
51
|
+
salesChannelCodes?: string[] | undefined;
|
|
52
|
+
isSystemProtected?: boolean | undefined;
|
|
53
|
+
}[];
|
|
54
|
+
settings: {
|
|
55
|
+
code: string;
|
|
56
|
+
name: string;
|
|
57
|
+
valueType: "string" | "number" | "boolean" | "json" | "string_list" | "secret" | "credential_ref";
|
|
58
|
+
defaultValue: unknown;
|
|
59
|
+
description?: string | undefined;
|
|
60
|
+
groupCode?: string | undefined;
|
|
61
|
+
previousDefaultValues?: unknown[] | undefined;
|
|
62
|
+
salesChannelCodes?: string[] | undefined;
|
|
63
|
+
enumOptions?: string[] | undefined;
|
|
64
|
+
configurationType?: string | undefined;
|
|
65
|
+
hidden?: boolean | undefined;
|
|
66
|
+
}[];
|
|
67
|
+
} | undefined;
|
|
68
|
+
i18n?: {
|
|
69
|
+
bundlesDir: string;
|
|
70
|
+
} | undefined;
|
|
71
|
+
docs?: false | {
|
|
72
|
+
dir: string;
|
|
73
|
+
} | undefined;
|
|
74
|
+
demo?: false | {
|
|
75
|
+
summary: string;
|
|
76
|
+
seed: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoSeedResult>;
|
|
77
|
+
reset: (context: import("@endora-commerce/contracts").ModuleDemoContext<never>) => Promise<import("@endora-commerce/contracts").DemoResetResult>;
|
|
78
|
+
after?: readonly string[] | undefined;
|
|
79
|
+
package?: string | undefined;
|
|
80
|
+
} | undefined;
|
|
81
|
+
actions?: {
|
|
82
|
+
id: string;
|
|
83
|
+
labelKey: string;
|
|
84
|
+
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";
|
|
85
|
+
targetRoute: string;
|
|
86
|
+
keywords: string[];
|
|
87
|
+
weight: number;
|
|
88
|
+
descriptionKey?: string | undefined;
|
|
89
|
+
requiredPermission?: string | undefined;
|
|
90
|
+
}[] | undefined;
|
|
91
|
+
permissions?: {
|
|
92
|
+
code: string;
|
|
93
|
+
label: string;
|
|
94
|
+
module?: string | undefined;
|
|
95
|
+
description?: string | undefined;
|
|
96
|
+
requires?: string[] | undefined;
|
|
97
|
+
}[] | undefined;
|
|
98
|
+
transactionalEmails?: {
|
|
99
|
+
code: string;
|
|
100
|
+
name: string;
|
|
101
|
+
variables: {
|
|
102
|
+
key: string;
|
|
103
|
+
label: string;
|
|
104
|
+
sampleValue?: string | undefined;
|
|
105
|
+
description?: string | undefined;
|
|
106
|
+
}[];
|
|
107
|
+
description?: string | undefined;
|
|
108
|
+
group?: string | undefined;
|
|
109
|
+
}[] | undefined;
|
|
110
|
+
capabilities?: string[] | undefined;
|
|
111
|
+
exclusiveCapabilities?: {
|
|
112
|
+
key: string;
|
|
113
|
+
errorCode: string;
|
|
114
|
+
}[] | undefined;
|
|
115
|
+
errorCodes?: {
|
|
116
|
+
code: string;
|
|
117
|
+
tokens?: string[] | undefined;
|
|
118
|
+
}[] | undefined;
|
|
119
|
+
blocks?: {
|
|
120
|
+
name: string;
|
|
121
|
+
labelKey: string;
|
|
122
|
+
category: string;
|
|
123
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
124
|
+
fields: Record<string, {
|
|
125
|
+
type: "number" | "object" | "array" | "text" | "textarea" | "select" | "radio" | "external" | "uuid" | "richtext";
|
|
126
|
+
label?: string | undefined;
|
|
127
|
+
required?: boolean | undefined;
|
|
128
|
+
options?: {
|
|
129
|
+
label: string;
|
|
130
|
+
value: string | number;
|
|
131
|
+
}[] | undefined;
|
|
132
|
+
refKind?: string | undefined;
|
|
133
|
+
}>;
|
|
134
|
+
descriptionKey?: string | undefined;
|
|
135
|
+
defaultProps?: Record<string, unknown> | undefined;
|
|
136
|
+
responsiveFields?: string[] | undefined;
|
|
137
|
+
previewIcon?: string | undefined;
|
|
138
|
+
weight?: number | undefined;
|
|
139
|
+
}[] | undefined;
|
|
140
|
+
blockCategories?: {
|
|
141
|
+
key: string;
|
|
142
|
+
titleKey: string;
|
|
143
|
+
contexts: ("email" | "invoice" | "cms" | "newsletter")[];
|
|
144
|
+
weight?: number | undefined;
|
|
145
|
+
visible?: boolean | undefined;
|
|
146
|
+
}[] | undefined;
|
|
147
|
+
env?: {
|
|
148
|
+
name: string;
|
|
149
|
+
describes: {
|
|
150
|
+
en: string;
|
|
151
|
+
pl: string;
|
|
152
|
+
};
|
|
153
|
+
requirement: {
|
|
154
|
+
kind: "required";
|
|
155
|
+
} | {
|
|
156
|
+
kind: "requiredWhen";
|
|
157
|
+
input: string;
|
|
158
|
+
equals: string;
|
|
159
|
+
} | {
|
|
160
|
+
kind: "optional";
|
|
161
|
+
without: {
|
|
162
|
+
en: string;
|
|
163
|
+
pl: string;
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
secret: boolean;
|
|
167
|
+
generable: boolean;
|
|
168
|
+
owner: {
|
|
169
|
+
kind: "platform";
|
|
170
|
+
} | {
|
|
171
|
+
kind: "application";
|
|
172
|
+
application: "admin" | "backend" | "storefront";
|
|
173
|
+
} | {
|
|
174
|
+
kind: "module";
|
|
175
|
+
moduleId: string;
|
|
176
|
+
};
|
|
177
|
+
consumers: ("admin" | "backend" | "storefront")[];
|
|
178
|
+
addressOf: "admin" | "backend" | "storefront" | null;
|
|
179
|
+
}[] | undefined;
|
|
180
|
+
};
|
|
181
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,uBAAuB;IAClC,+DAA+D;;IAG/D,qEAAqE;;IAErE,gEAAgE;;CAExD,CAAC;AAoCX,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4InB,CAAC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { defineModuleManifest, defineModuleSettingsManifest, } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Customers (Klienci) module — feature 040.
|
|
4
|
+
*
|
|
5
|
+
* Owns the customer-lifecycle business logic and the admin + storefront
|
|
6
|
+
* surfaces on top of the `customer_accounts` data module: standalone
|
|
7
|
+
* registration, self-service (addresses, defaults, history), and admin
|
|
8
|
+
* oversight (block/unblock, impersonation, detail view, customer groups,
|
|
9
|
+
* password reset, soft-delete + restore, online presence).
|
|
10
|
+
*
|
|
11
|
+
* Three platform-wide settings are registered here and seeded by the
|
|
12
|
+
* module-lifecycle ManifestReconciler on boot.
|
|
13
|
+
*/
|
|
14
|
+
export const CUSTOMERS_SETTING_CODES = {
|
|
15
|
+
/** Gate standalone (org-less) registration (FR-001/FR-004). */
|
|
16
|
+
ALLOW_REGISTRATION_WITHOUT_ORGANIZATION: 'customers.allow_registration_without_organization',
|
|
17
|
+
/** Restore window (days) before permanent anonymization (FR-041). */
|
|
18
|
+
DELETION_RETENTION_DAYS: 'customers.deletion_retention_days',
|
|
19
|
+
/** "Online" threshold (minutes) for the admin presence view. */
|
|
20
|
+
PRESENCE_FRESHNESS_MINUTES: 'customers.presence_freshness_minutes',
|
|
21
|
+
};
|
|
22
|
+
const settings = defineModuleSettingsManifest({
|
|
23
|
+
moduleCode: 'customers',
|
|
24
|
+
groups: [{ code: 'customers', name: 'Customers' }],
|
|
25
|
+
settings: [
|
|
26
|
+
{
|
|
27
|
+
code: CUSTOMERS_SETTING_CODES.ALLOW_REGISTRATION_WITHOUT_ORGANIZATION,
|
|
28
|
+
name: 'Allow registration without organization',
|
|
29
|
+
description: 'When enabled, visitors may register a standalone Customer account that is not attached to any Organization. When disabled, registration requires Organization context.',
|
|
30
|
+
groupCode: 'customers',
|
|
31
|
+
valueType: 'boolean',
|
|
32
|
+
defaultValue: false,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
code: CUSTOMERS_SETTING_CODES.DELETION_RETENTION_DAYS,
|
|
36
|
+
name: 'Customer deletion retention window (days)',
|
|
37
|
+
description: 'Number of days a deleted Customer account can be restored before its personal data is permanently anonymized. Default 365 (1 year).',
|
|
38
|
+
groupCode: 'customers',
|
|
39
|
+
valueType: 'number',
|
|
40
|
+
defaultValue: 365,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
code: CUSTOMERS_SETTING_CODES.PRESENCE_FRESHNESS_MINUTES,
|
|
44
|
+
name: 'Online customers freshness (minutes)',
|
|
45
|
+
description: 'A Customer counts as "online" if their session was active within this many minutes. Default 10.',
|
|
46
|
+
groupCode: 'customers',
|
|
47
|
+
valueType: 'number',
|
|
48
|
+
defaultValue: 10,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
export const manifest = defineModuleManifest({
|
|
53
|
+
id: 'customers',
|
|
54
|
+
name: 'Customers',
|
|
55
|
+
description: 'Customer lifecycle business logic: registration, self-service, blocking, impersonation, groups, deletion, and presence.',
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
// Feature 072 (T140) — the edges the module actually resolves. `auth`
|
|
58
|
+
// (sessions), `email` (the set-password mail), `quote_requests` (the RFQ
|
|
59
|
+
// history surface) and `custom_fields` were all reached through options a
|
|
60
|
+
// root passed down, which is why none of them appeared here.
|
|
61
|
+
// Feature 073 (Constitution XVII) — the operator's activation control. This
|
|
62
|
+
// is the *management* surface over customer accounts: the admin CRM screens,
|
|
63
|
+
// moderation, self-service profile and deletion. The accounts themselves live
|
|
64
|
+
// in `customer_accounts`, which is non-deactivatable, so switching this off
|
|
65
|
+
// removes screens and self-service rather than the ability to log in.
|
|
66
|
+
activation: { settingCode: 'customers.enabled', default: true },
|
|
67
|
+
//
|
|
68
|
+
// Issue #216 adds `quick_order`: the customer-detail screen and the
|
|
69
|
+
// self-service profile render and edit a buyer's default payment and
|
|
70
|
+
// delivery method, and those live in `quick_order`'s preference store. This
|
|
71
|
+
// module used to build a second instance of that module's service instead of
|
|
72
|
+
// resolving the port published for it, so the edge existed and was declared
|
|
73
|
+
// nowhere. Binding rather than `degrades-without`, because that is what
|
|
74
|
+
// `DefaultPreferencePort`'s contract says its seam does when the owner is
|
|
75
|
+
// off, and because a half-written set of ordering defaults is worse than a
|
|
76
|
+
// refusal.
|
|
77
|
+
//
|
|
78
|
+
// Feature 075's Phase-C cut adds `addresses`: the self-service and admin
|
|
79
|
+
// address panels list the buyer's **organisation's** shared addresses beside
|
|
80
|
+
// their personal ones, and this module used to query that module's table
|
|
81
|
+
// itself. It reads `addressReadPort` now. Binding, and unremarkably so —
|
|
82
|
+
// `addresses` is non-deactivatable, so the edge has no absent state to
|
|
83
|
+
// declare a degrade for.
|
|
84
|
+
//
|
|
85
|
+
// Feature 075's cut adds three more, each replacing a second instance this
|
|
86
|
+
// module built from an import of the owner's directory or a query against its
|
|
87
|
+
// table: `admin_users` (the impersonation seam behind the "view as this
|
|
88
|
+
// customer" control), `carts` (the reporting read behind the customer-detail
|
|
89
|
+
// cart panel) and `orders` (the list behind both order-history panels).
|
|
90
|
+
//
|
|
91
|
+
// All three are binding, and unremarkably so: every one of them declares
|
|
92
|
+
// `nonDeactivatable`, so none has an absent state for a degrade to describe.
|
|
93
|
+
// The `orders` edge in particular used to be a `degrades-without` whose
|
|
94
|
+
// reason argued that declaring it "would make `orders` undeactivatable" — a
|
|
95
|
+
// sentence about a module that has never had an activation control, kept
|
|
96
|
+
// alive by a presence probe that could not fire. It is a declared dependency
|
|
97
|
+
// now, and the panels read the gated port directly.
|
|
98
|
+
dependencies: [
|
|
99
|
+
'addresses',
|
|
100
|
+
// `specs/117-instance-bring-up/` Phase 6 — `customerModerationActorResolver`
|
|
101
|
+
// is this module's own since FR-033, and it reads the moderating admin's
|
|
102
|
+
// role through `adminRolePort` beside the `adminUserReadPort` already
|
|
103
|
+
// declared below. The production root asked the same question in
|
|
104
|
+
// `knex.raw` over both tables; a port needs the edge and the SQL never
|
|
105
|
+
// declared one. `admin_roles` declares `activation.nonDeactivatable`, so
|
|
106
|
+
// the edge costs no operator an activation control.
|
|
107
|
+
'admin_roles',
|
|
108
|
+
'admin_users',
|
|
109
|
+
'auth',
|
|
110
|
+
'carts',
|
|
111
|
+
'custom_fields',
|
|
112
|
+
'customer_accounts',
|
|
113
|
+
'email',
|
|
114
|
+
'orders',
|
|
115
|
+
'organizations',
|
|
116
|
+
'quick_order',
|
|
117
|
+
'quote_requests',
|
|
118
|
+
'settings',
|
|
119
|
+
],
|
|
120
|
+
settings,
|
|
121
|
+
i18n: { bundlesDir: 'i18n' },
|
|
122
|
+
docs: { dir: 'docs' },
|
|
123
|
+
/**
|
|
124
|
+
* Two codes from the platform block — D-129's remaining sweep, Tier A
|
|
125
|
+
* (`specs/090-module-owned-error-codes/d129-sweep.md` §5.2, Appendix A;
|
|
126
|
+
* MR 3).
|
|
127
|
+
*
|
|
128
|
+
* Both were declared by `_i18n` until this merge request, not because
|
|
129
|
+
* anybody judged them the platform's but because the deleted prefix chain
|
|
130
|
+
* had no rule for them and its last line was `return 'core'`.
|
|
131
|
+
*
|
|
132
|
+
* **`CUSTOMER_ADDRESS_NOT_FOUND` is here and the four `CUSTOMER_*` record
|
|
133
|
+
* codes are not, and that split is a ruling rather than a reading.** D-186
|
|
134
|
+
* §1 (`specs/080-f4-real-scope/rulings.md`) settles the noun "customer":
|
|
135
|
+
* `customer_accounts` owns the **record**, this module owns
|
|
136
|
+
* **`CustomerAddress`**. So `CUSTOMER_NOT_FOUND`,
|
|
137
|
+
* `CUSTOMER_ALREADY_DELETED`, `CUSTOMER_NOT_DELETED` and
|
|
138
|
+
* `CUSTOMER_RESTORE_WINDOW_ELAPSED` go to `customer_accounts` in a later
|
|
139
|
+
* batch — two of them raised from *this* module, which is the consequence
|
|
140
|
+
* the ruling accepts in terms and the shape D-95.2 already ruled for
|
|
141
|
+
* `INVOICE_NOT_READY`: the owner of a noun is not required to be the module
|
|
142
|
+
* that throws about it. The prefix is the same in all five; the noun is not,
|
|
143
|
+
* and the noun is what decides.
|
|
144
|
+
*
|
|
145
|
+
* **`REGISTRATION_REQUIRES_ORGANIZATION` is T2, not T1.** Its noun is a
|
|
146
|
+
* registration and an Organization, and `organizations` owns the second — so
|
|
147
|
+
* T1 has two claimants and does not decide it. T2 does: the code names a
|
|
148
|
+
* *mechanism*, the storefront's standalone self-registration path, which
|
|
149
|
+
* this module implements in `customer-registration-service.ts` and is the
|
|
150
|
+
* only thing in the tree that raises. What it refuses is this module's own
|
|
151
|
+
* setting being off, not a judgement `organizations` makes.
|
|
152
|
+
*
|
|
153
|
+
* **No sentence moves with them.** Neither has a sentence in either language
|
|
154
|
+
* anywhere in the tree; both were already on `UNTRANSLATED_ERROR_CODES`
|
|
155
|
+
* under `_i18n` and move to this module's group there, so the bundle this
|
|
156
|
+
* module already ships gains no key. The storefront's registration page
|
|
157
|
+
* carries its own copy for the refusal
|
|
158
|
+
* (`storefront/app/(auth)/register-customer/page.tsx`), which is a surface
|
|
159
|
+
* string and not the envelope's sentence.
|
|
160
|
+
*
|
|
161
|
+
* **`tokens` is derived from the raise sites, not from the bundle**
|
|
162
|
+
* (runbook §5), and there are none: both raises are a bare
|
|
163
|
+
* `HttpError(status, code, message)` with no `details`.
|
|
164
|
+
*/
|
|
165
|
+
errorCodes: [
|
|
166
|
+
{ code: 'CUSTOMER_ADDRESS_NOT_FOUND' },
|
|
167
|
+
{ code: 'REGISTRATION_REQUIRES_ORGANIZATION' },
|
|
168
|
+
],
|
|
169
|
+
actions: [
|
|
170
|
+
{
|
|
171
|
+
id: 'open-customers',
|
|
172
|
+
labelKey: 'actions.openCustomers.label',
|
|
173
|
+
descriptionKey: 'actions.openCustomers.description',
|
|
174
|
+
icon: 'Users',
|
|
175
|
+
targetRoute: '/customers',
|
|
176
|
+
requiredPermission: 'customers:read',
|
|
177
|
+
keywords: ['customers', 'clients', 'klienci', 'klient'],
|
|
178
|
+
weight: 210,
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'online-customers',
|
|
182
|
+
labelKey: 'actions.onlineCustomers.label',
|
|
183
|
+
descriptionKey: 'actions.onlineCustomers.description',
|
|
184
|
+
icon: 'Users',
|
|
185
|
+
targetRoute: '/customers/online',
|
|
186
|
+
requiredPermission: 'customers:read',
|
|
187
|
+
keywords: ['online', 'presence', 'aktywni', 'online klienci'],
|
|
188
|
+
weight: 205,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,4BAA4B,GAC7B,MAAM,4BAA4B,CAAC;AAEpC;;;;;;;;;;;GAWG;AAEH,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,+DAA+D;IAC/D,uCAAuC,EACrC,mDAAmD;IACrD,qEAAqE;IACrE,uBAAuB,EAAE,mCAAmC;IAC5D,gEAAgE;IAChE,0BAA0B,EAAE,sCAAsC;CAC1D,CAAC;AAEX,MAAM,QAAQ,GAAG,4BAA4B,CAAC;IAC5C,UAAU,EAAE,WAAW;IACvB,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAClD,QAAQ,EAAE;QACR;YACE,IAAI,EAAE,uBAAuB,CAAC,uCAAuC;YACrE,IAAI,EAAE,yCAAyC;YAC/C,WAAW,EACT,wKAAwK;YAC1K,SAAS,EAAE,WAAW;YACtB,SAAS,EAAE,SAAS;YACpB,YAAY,EAAE,KAAK;SACpB;QACD;YACE,IAAI,EAAE,uBAAuB,CAAC,uBAAuB;YACrD,IAAI,EAAE,2CAA2C;YACjD,WAAW,EACT,qIAAqI;YACvI,SAAS,EAAE,WAAW;YACtB,SAAS,EAAE,QAAQ;YACnB,YAAY,EAAE,GAAG;SAClB;QACD;YACE,IAAI,EAAE,uBAAuB,CAAC,0BAA0B;YACxD,IAAI,EAAE,sCAAsC;YAC5C,WAAW,EACT,iGAAiG;YACnG,SAAS,EAAE,WAAW;YACtB,SAAS,EAAE,QAAQ;YACnB,YAAY,EAAE,EAAE;SACjB;KACF;CACF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,oBAAoB,CAAC;IAC3C,EAAE,EAAE,WAAW;IACf,IAAI,EAAE,WAAW;IACjB,WAAW,EACT,yHAAyH;IAC3H,OAAO,EAAE,OAAO;IAChB,sEAAsE;IACtE,yEAAyE;IACzE,0EAA0E;IAC1E,6DAA6D;IAC7D,4EAA4E;IAC5E,6EAA6E;IAC7E,8EAA8E;IAC9E,4EAA4E;IAC5E,sEAAsE;IACtE,UAAU,EAAE,EAAE,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,IAAI,EAAE;IAC/D,EAAE;IACF,oEAAoE;IACpE,qEAAqE;IACrE,4EAA4E;IAC5E,6EAA6E;IAC7E,4EAA4E;IAC5E,wEAAwE;IACxE,0EAA0E;IAC1E,2EAA2E;IAC3E,WAAW;IACX,EAAE;IACF,yEAAyE;IACzE,6EAA6E;IAC7E,yEAAyE;IACzE,yEAAyE;IACzE,uEAAuE;IACvE,yBAAyB;IACzB,EAAE;IACF,2EAA2E;IAC3E,8EAA8E;IAC9E,wEAAwE;IACxE,6EAA6E;IAC7E,wEAAwE;IACxE,EAAE;IACF,yEAAyE;IACzE,6EAA6E;IAC7E,wEAAwE;IACxE,4EAA4E;IAC5E,yEAAyE;IACzE,6EAA6E;IAC7E,oDAAoD;IACpD,YAAY,EAAE;QACZ,WAAW;QACX,6EAA6E;QAC7E,yEAAyE;QACzE,sEAAsE;QACtE,iEAAiE;QACjE,uEAAuE;QACvE,yEAAyE;QACzE,oDAAoD;QACpD,aAAa;QACb,aAAa;QACb,MAAM;QACN,OAAO;QACP,eAAe;QACf,mBAAmB;QACnB,OAAO;QACP,QAAQ;QACR,eAAe;QACf,aAAa;QACb,gBAAgB;QAChB,UAAU;KACX;IACD,QAAQ;IACR,IAAI,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE;IAC5B,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAyCG;IACH,UAAU,EAAE;QACV,EAAE,IAAI,EAAE,4BAA4B,EAAE;QACtC,EAAE,IAAI,EAAE,oCAAoC,EAAE;KAC/C;IACD,OAAO,EAAE;QACP;YACE,EAAE,EAAE,gBAAgB;YACpB,QAAQ,EAAE,6BAA6B;YACvC,cAAc,EAAE,mCAAmC;YACnD,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,YAAY;YACzB,kBAAkB,EAAE,gBAAgB;YACpC,QAAQ,EAAE,CAAC,WAAW,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC;YACvD,MAAM,EAAE,GAAG;SACZ;QACD;YACE,EAAE,EAAE,kBAAkB;YACtB,QAAQ,EAAE,+BAA+B;YACzC,cAAc,EAAE,qCAAqC;YACrD,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,mBAAmB;YAChC,kBAAkB,EAAE,gBAAgB;YACpC,QAAQ,EAAE,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,gBAAgB,CAAC;YAC7D,MAAM,EAAE,GAAG;SACZ;KACF;CACF,CAAC,CAAC"}
|