@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,14 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 040 (Customers) US2 — the personal address book.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the org-level `addresses` table but keyed by `customer_account_id`,
|
|
6
|
+
* so standalone (org-less) customers can keep their own billing/delivery
|
|
7
|
+
* addresses. "At most one default per (customer, kind)" is enforced by a
|
|
8
|
+
* partial unique index, exactly like `addresses`.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Migration20260611T140404CustomersCustomerAddressesInit extends Migration {
|
|
11
|
+
up(): Promise<void>;
|
|
12
|
+
down(): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=20260611T140404_customers_customer_addresses_init.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140404_customers_customer_addresses_init.d.ts","sourceRoot":"","sources":["../../src/migrations/20260611T140404_customers_customer_addresses_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;GAOG;AACH,qBAAa,sDAAuD,SAAQ,SAAS;IACpE,EAAE,IAAI,OAAO,CAAC,IAAI,CAAC;IAgCnB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAGrC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
/**
|
|
3
|
+
* Feature 040 (Customers) US2 — the personal address book.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the org-level `addresses` table but keyed by `customer_account_id`,
|
|
6
|
+
* so standalone (org-less) customers can keep their own billing/delivery
|
|
7
|
+
* addresses. "At most one default per (customer, kind)" is enforced by a
|
|
8
|
+
* partial unique index, exactly like `addresses`.
|
|
9
|
+
*/
|
|
10
|
+
export class Migration20260611T140404CustomersCustomerAddressesInit extends Migration {
|
|
11
|
+
async up() {
|
|
12
|
+
this.addSql(`
|
|
13
|
+
create table "customer_addresses" (
|
|
14
|
+
"id" uuid not null,
|
|
15
|
+
"customer_account_id" uuid not null,
|
|
16
|
+
"kind" varchar(16) not null,
|
|
17
|
+
"recipient_name" varchar(160) not null,
|
|
18
|
+
"street" varchar(255) not null,
|
|
19
|
+
"city" varchar(120) not null,
|
|
20
|
+
"postal_code" varchar(20) not null,
|
|
21
|
+
"country" varchar(2) not null,
|
|
22
|
+
"phone" varchar(32) null,
|
|
23
|
+
"is_default" boolean not null default false,
|
|
24
|
+
"created_at" timestamptz not null,
|
|
25
|
+
"updated_at" timestamptz not null,
|
|
26
|
+
"deleted_at" timestamptz null,
|
|
27
|
+
constraint "customer_addresses_pkey" primary key ("id"),
|
|
28
|
+
constraint "customer_addresses_customer_fk" foreign key ("customer_account_id")
|
|
29
|
+
references "customer_accounts" ("id") on delete cascade
|
|
30
|
+
);
|
|
31
|
+
`);
|
|
32
|
+
this.addSql('create index "customer_addresses_customer_account_id_index" on "customer_addresses" ("customer_account_id");');
|
|
33
|
+
this.addSql('create index "customer_addresses_kind_index" on "customer_addresses" ("kind");');
|
|
34
|
+
this.addSql(`create unique index "customer_addresses_one_default_per_kind" on "customer_addresses" ("customer_account_id", "kind") where "is_default" = true and "deleted_at" is null;`);
|
|
35
|
+
}
|
|
36
|
+
async down() {
|
|
37
|
+
this.addSql('drop table if exists "customer_addresses" cascade;');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=20260611T140404_customers_customer_addresses_init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"20260611T140404_customers_customer_addresses_init.js","sourceRoot":"","sources":["../../src/migrations/20260611T140404_customers_customer_addresses_init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,OAAO,sDAAuD,SAAQ,SAAS;IAC1E,KAAK,CAAC,EAAE;QACf,IAAI,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;;;KAmBX,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,CACT,8GAA8G,CAC/G,CAAC;QACF,IAAI,CAAC,MAAM,CACT,gFAAgF,CACjF,CAAC;QACF,IAAI,CAAC,MAAM,CACT,2KAA2K,CAC5K,CAAC;IACJ,CAAC;IAEQ,KAAK,CAAC,IAAI;QACjB,IAAI,CAAC,MAAM,CAAC,oDAAoD,CAAC,CAAC;IACpE,CAAC;CACF"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260611T140404CustomersCustomerAddressesInit } from './20260611T140404_customers_customer_addresses_init.js';
|
|
25
|
+
export declare const migrations: (typeof Migration20260611T140404CustomersCustomerAddressesInit)[];
|
|
26
|
+
export { Migration20260611T140404CustomersCustomerAddressesInit, };
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,sDAAsD,EAAE,MAAM,wDAAwD,CAAC;AAEhI,eAAO,MAAM,UAAU,mEAEtB,CAAC;AAEF,OAAO,EACL,sDAAsD,GACvD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `./migrations` subpath — every migration class this module owns, as one
|
|
3
|
+
* ordered `migrations` array.
|
|
4
|
+
*
|
|
5
|
+
* The array is what the platform reads when this module is **installed**:
|
|
6
|
+
* `src/packages/package-runtime.ts` takes `exported['migrations']` and refuses
|
|
7
|
+
* the package outright when it is absent (D-168).
|
|
8
|
+
*
|
|
9
|
+
* Listed in ascending timestamp, which is the order of this module's own
|
|
10
|
+
* migrations and of nothing else (feature 081): a manifest `dependencies` array
|
|
11
|
+
* is the only thing ordering this block against another module's.
|
|
12
|
+
*
|
|
13
|
+
* The **named** exports stay beside the array, and the asymmetry with
|
|
14
|
+
* `./backend` — which publishes an array and no named class (D-168) — is
|
|
15
|
+
* deliberate. `db/migrations-registry.generated.ts` imports each class by name
|
|
16
|
+
* from this specifier, and a migration class name is contract in a way an entity
|
|
17
|
+
* class name is not: `mikro_orm_migrations` persists it, so it is a string every
|
|
18
|
+
* already-migrated database holds.
|
|
19
|
+
*
|
|
20
|
+
* A class that is in neither the array nor the barrel is a migration that does
|
|
21
|
+
* not run: `migration:pending` reports nothing pending and the first symptom is
|
|
22
|
+
* a query against a table nobody created.
|
|
23
|
+
*/
|
|
24
|
+
import { Migration20260611T140404CustomersCustomerAddressesInit } from './20260611T140404_customers_customer_addresses_init.js';
|
|
25
|
+
export const migrations = [
|
|
26
|
+
Migration20260611T140404CustomersCustomerAddressesInit,
|
|
27
|
+
];
|
|
28
|
+
export { Migration20260611T140404CustomersCustomerAddressesInit, };
|
|
29
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,sDAAsD,EAAE,MAAM,wDAAwD,CAAC;AAEhI,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,sDAAsD;CACvD,CAAC;AAEF,OAAO,EACL,sDAAsD,GACvD,CAAC"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: customers
|
|
3
|
+
description: Customer lifecycle over `customer_accounts` — company and personal Organizations, sales-rep assignment and the admin customer surface
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# `customers`
|
|
7
|
+
|
|
8
|
+
Customer (Klienci) lifecycle business logic layered on top of the
|
|
9
|
+
`customer_accounts` data module. **Every customer belongs to an
|
|
10
|
+
Organization** — a company for B2B, a single-member *personal* organization for
|
|
11
|
+
an individual, and `customer_accounts.organization_id` is
|
|
12
|
+
`NOT NULL`. "Standalone" throughout this page means *outside a company
|
|
13
|
+
organization*, never *without one*: such a customer is their own tenant, and the
|
|
14
|
+
organization-scoped features a company offers (shared addresses, invitations,
|
|
15
|
+
credit limit, a sales-rep assignment) are simply absent for them rather than
|
|
16
|
+
switched off by a special case.
|
|
17
|
+
|
|
18
|
+
## Capabilities
|
|
19
|
+
|
|
20
|
+
**Storefront (self-service)**
|
|
21
|
+
|
|
22
|
+
- Standalone registration, gated by the
|
|
23
|
+
`customers.allow_registration_without_organization` setting. On success the
|
|
24
|
+
account and its personal organization are written in one transaction and the
|
|
25
|
+
new account is logged in automatically.
|
|
26
|
+
- A personal address book (billing/delivery), one default per kind, plus the
|
|
27
|
+
ability to select the Organization's shared addresses (org-bound customers).
|
|
28
|
+
- Default payment method, delivery method, and default addresses.
|
|
29
|
+
- Change password; read-only history of own orders and quote requests.
|
|
30
|
+
|
|
31
|
+
**Admin (oversight)**
|
|
32
|
+
|
|
33
|
+
- Customer list (search + status/organization/group filters) and a detail view
|
|
34
|
+
showing creation date, customer group, organization, blocked status, and last
|
|
35
|
+
login.
|
|
36
|
+
- Block / unblock and soft-delete / restore, with role-based authority: a
|
|
37
|
+
Platform Administrator may act on anyone; otherwise the authorized salesperson
|
|
38
|
+
is the one inherited from the customer's Organization. A personal organization
|
|
39
|
+
never carries a sales-rep assignment, so the unassigned-organization fallback
|
|
40
|
+
leaves a standalone customer open to any salesperson. An org-owner depletion
|
|
41
|
+
guard refuses blocking or deleting the last organization administrator.
|
|
42
|
+
- Impersonation; audited as `impersonation.start` / `impersonation.end`.
|
|
43
|
+
- Admin-triggered password reset (emails a set-password link).
|
|
44
|
+
- NIP/VAT validation (VIES / Biała lista port).
|
|
45
|
+
- Organization assign, and detach-from-organization, which moves the customer to
|
|
46
|
+
their own personal organization rather than leaving them without one.
|
|
47
|
+
Direct customer-group assignment (overrides the Organization's group when
|
|
48
|
+
resolving pricing and promotions).
|
|
49
|
+
- Read-only orders, quote-requests, and (current + abandoned) carts panels.
|
|
50
|
+
- An "online customers" view backed by recent session activity.
|
|
51
|
+
|
|
52
|
+
## Deletion & anonymization
|
|
53
|
+
|
|
54
|
+
Deletion is a soft-disable: login is denied, the account is hidden, and sessions
|
|
55
|
+
are revoked, while orders, quote requests, and audit history are retained.
|
|
56
|
+
Within a configurable retention window (`customers.deletion_retention_days`,
|
|
57
|
+
default 365) an authorized actor can restore the account. After the window the
|
|
58
|
+
anonymization sweep (`AnonymizationSweepWorker`) irreversibly scrubs personal
|
|
59
|
+
data, and restore is no longer possible.
|
|
60
|
+
|
|
61
|
+
## Public surface
|
|
62
|
+
|
|
63
|
+
Admin routes are gated by `customers:read` (reads), `customers:manage`
|
|
64
|
+
(mutations), and `customers:impersonate` (impersonation).
|
|
65
|
+
|
|
66
|
+
| Verb + Path | Audience | Purpose |
|
|
67
|
+
| --- | --- | --- |
|
|
68
|
+
| `POST /api/v1/customers/register` | anon | Standalone registration (setting-gated) |
|
|
69
|
+
| `GET /api/v1/me/customer` | customer | Own profile |
|
|
70
|
+
| `POST /api/v1/me/customer/change-password` | customer | Change password |
|
|
71
|
+
| `GET /api/v1/me/customer/addresses` | customer | Personal + org-shared addresses |
|
|
72
|
+
| `POST/PATCH/DELETE /api/v1/me/customer/addresses[/:id]` | customer | Manage personal addresses |
|
|
73
|
+
| `PUT /api/v1/me/customer/addresses/:id/default` | customer | Set a default address |
|
|
74
|
+
| `GET/PUT /api/v1/me/customer/defaults` | customer | Default payment/delivery method + addresses |
|
|
75
|
+
| `GET /api/v1/me/customer/orders` | customer | Own order history |
|
|
76
|
+
| `GET /api/v1/me/customer/quote-requests` | customer | Own RFQ history |
|
|
77
|
+
| `GET /api/v1/admin/customers` | admin | List (authority-scoped) |
|
|
78
|
+
| `GET /api/v1/admin/customers/:id` | admin | Detail |
|
|
79
|
+
| `POST /api/v1/admin/customers/:id/block` · `/unblock` | admin | Block / unblock |
|
|
80
|
+
| `DELETE /api/v1/admin/customers/:id` · `POST .../restore` | admin | Soft-delete / restore |
|
|
81
|
+
| `POST /api/v1/admin/customers/:id/impersonate` | admin | Start impersonation |
|
|
82
|
+
| `POST /api/v1/admin/customers/:id/password-reset` | admin | Email a set-password link |
|
|
83
|
+
| `POST/DELETE /api/v1/admin/customers/:id/organization` | admin | Assign / unassign organization |
|
|
84
|
+
| `PUT /api/v1/admin/customers/:id/customer-group` | admin | Set / clear customer group |
|
|
85
|
+
| `GET/POST/PATCH/DELETE /api/v1/admin/customers/:id/addresses[/:addressId]` | admin | Manage addresses |
|
|
86
|
+
| `POST /api/v1/admin/customers/:id/vat-validate` | admin | Validate a NIP/VAT number |
|
|
87
|
+
| `GET /api/v1/admin/customers/:id/orders` · `/quote-requests` · `/carts` | admin | Read-only history panels |
|
|
88
|
+
| `GET /api/v1/admin/customers/online` | admin | Currently-online customers |
|
|
89
|
+
|
|
90
|
+
## Settings
|
|
91
|
+
|
|
92
|
+
| Code | Type | Default | Purpose |
|
|
93
|
+
| --- | --- | --- | --- |
|
|
94
|
+
| `customers.allow_registration_without_organization` | boolean | `false` | Gate standalone registration |
|
|
95
|
+
| `customers.deletion_retention_days` | number | `365` | Restore window before permanent anonymization |
|
|
96
|
+
| `customers.presence_freshness_minutes` | number | `10` | "Online" threshold |
|
|
97
|
+
|
|
98
|
+
## Schema
|
|
99
|
+
|
|
100
|
+
- `060_customer_accounts_lifecycle` — adds `customer_group_id`, block state
|
|
101
|
+
(`blocked_at`, `block_reason`, `block_source`, `blocked_by_*`), and
|
|
102
|
+
deletion/anonymization state (`deletion_requested_by_admin_user_id`,
|
|
103
|
+
`anonymized_at`) to `customer_accounts`.
|
|
104
|
+
- `061_customer_addresses_init` — the `customer_addresses` table (one default
|
|
105
|
+
per `(customer, kind)`).
|
|
106
|
+
|
|
107
|
+
Default payment/delivery preferences reuse the `quick_order_default_preferences`
|
|
108
|
+
table; no new table is introduced for them.
|
package/i18n/en.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nav.customers.label": "Customers",
|
|
3
|
+
"nav.customersOnline.label": "Online customers",
|
|
4
|
+
|
|
5
|
+
"actions.openCustomers.label": "Customers",
|
|
6
|
+
"actions.openCustomers.description": "Browse and manage customer accounts",
|
|
7
|
+
"actions.onlineCustomers.label": "Online customers",
|
|
8
|
+
"actions.onlineCustomers.description": "See which customers are currently online",
|
|
9
|
+
|
|
10
|
+
"status.active": "Active",
|
|
11
|
+
"status.blocked": "Blocked",
|
|
12
|
+
"status.deleted": "Deleted",
|
|
13
|
+
|
|
14
|
+
"list.title": "Customers",
|
|
15
|
+
"list.description": "Browse and manage customer accounts",
|
|
16
|
+
"list.error": "Could not load customers.",
|
|
17
|
+
"list.loading": "Loading…",
|
|
18
|
+
"list.empty": "No customers found.",
|
|
19
|
+
"list.none": "None",
|
|
20
|
+
"list.field.status": "Status",
|
|
21
|
+
"list.field.search": "Search",
|
|
22
|
+
"list.field.organization": "Organization",
|
|
23
|
+
"list.filter.all": "All",
|
|
24
|
+
"list.filter.allOrgs": "All organizations",
|
|
25
|
+
"list.action.resetOrgMfa": "Reset 2FA for organization",
|
|
26
|
+
"list.action.resetOrgMfaDone": "2FA reset complete",
|
|
27
|
+
"list.column.customer": "Customer",
|
|
28
|
+
"list.column.status": "Status",
|
|
29
|
+
"list.column.organization": "Organization",
|
|
30
|
+
"list.column.group": "Customer group",
|
|
31
|
+
"list.column.created": "Created",
|
|
32
|
+
"list.action.open": "Open",
|
|
33
|
+
|
|
34
|
+
"detail.title": "Customer",
|
|
35
|
+
"detail.loading": "Loading…",
|
|
36
|
+
"detail.error": "Could not load the customer.",
|
|
37
|
+
"detail.actionError": "The action could not be completed.",
|
|
38
|
+
"detail.info.title": "Account information",
|
|
39
|
+
"detail.actions.title": "Actions",
|
|
40
|
+
"detail.field.email": "Email",
|
|
41
|
+
"detail.field.created": "Created",
|
|
42
|
+
"detail.field.group": "Customer group",
|
|
43
|
+
"detail.field.organization": "Organization",
|
|
44
|
+
"detail.field.status": "Status",
|
|
45
|
+
"detail.field.lastLogin": "Last login",
|
|
46
|
+
"detail.action.block": "Block",
|
|
47
|
+
"detail.action.unblock": "Unblock",
|
|
48
|
+
"detail.action.passwordReset": "Reset password",
|
|
49
|
+
"detail.action.resetMfa": "Reset 2FA",
|
|
50
|
+
"detail.action.impersonate": "Impersonate",
|
|
51
|
+
"detail.action.restore": "Restore",
|
|
52
|
+
"detail.action.delete": "Delete",
|
|
53
|
+
|
|
54
|
+
"online.title": "Online customers",
|
|
55
|
+
"online.description": "Customers active within the freshness window",
|
|
56
|
+
"online.error": "Could not load online customers.",
|
|
57
|
+
"online.loading": "Loading…",
|
|
58
|
+
"online.empty": "No customers are currently online.",
|
|
59
|
+
"online.column.lastSeen": "Last seen",
|
|
60
|
+
"online.refresh": "Refresh",
|
|
61
|
+
"online.autoRefresh": "Auto-refresh",
|
|
62
|
+
"online.intervalLabel": "Interval (seconds)",
|
|
63
|
+
"online.refreshedAt": "Updated {time}",
|
|
64
|
+
|
|
65
|
+
"detail.panel.loading": "Loading…",
|
|
66
|
+
"detail.panel.error": "Could not load this section.",
|
|
67
|
+
"detail.orders.title": "Orders",
|
|
68
|
+
"detail.orders.empty": "No orders.",
|
|
69
|
+
"detail.rfqs.title": "Quote requests",
|
|
70
|
+
"detail.rfqs.empty": "No quote requests.",
|
|
71
|
+
"detail.carts.title": "Carts",
|
|
72
|
+
"detail.carts.items": "items",
|
|
73
|
+
"detail.carts.noCurrent": "No active cart.",
|
|
74
|
+
"detail.carts.empty": "No carts.",
|
|
75
|
+
|
|
76
|
+
"detail.org.title": "Organization",
|
|
77
|
+
"detail.org.assignLabel": "Assign to organization",
|
|
78
|
+
"detail.org.assign": "Assign",
|
|
79
|
+
"detail.org.unassign": "Detach from organization",
|
|
80
|
+
"detail.org.unassignHint": "The customer keeps their account and moves to their own personal organization.",
|
|
81
|
+
"detail.group.title": "Customer group",
|
|
82
|
+
"detail.group.setLabel": "Set customer group",
|
|
83
|
+
"detail.group.set": "Set",
|
|
84
|
+
"detail.group.clear": "Clear group",
|
|
85
|
+
"detail.addresses.title": "Addresses",
|
|
86
|
+
"detail.addresses.empty": "No personal addresses.",
|
|
87
|
+
"detail.addresses.default": "default",
|
|
88
|
+
"detail.addresses.recipient": "Recipient",
|
|
89
|
+
"detail.addresses.street": "Street",
|
|
90
|
+
"detail.addresses.city": "City",
|
|
91
|
+
"detail.addresses.postal": "Postal code",
|
|
92
|
+
"detail.addresses.country": "Country",
|
|
93
|
+
"detail.addresses.add": "Add address"
|
|
94
|
+
}
|
package/i18n/pl.json
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nav.customers.label": "Klienci",
|
|
3
|
+
"nav.customersOnline.label": "Klienci online",
|
|
4
|
+
|
|
5
|
+
"actions.openCustomers.label": "Klienci",
|
|
6
|
+
"actions.openCustomers.description": "Przeglądaj i zarządzaj kontami klientów",
|
|
7
|
+
"actions.onlineCustomers.label": "Klienci online",
|
|
8
|
+
"actions.onlineCustomers.description": "Zobacz, którzy klienci są teraz online",
|
|
9
|
+
|
|
10
|
+
"status.active": "Aktywne",
|
|
11
|
+
"status.blocked": "Zablokowane",
|
|
12
|
+
"status.deleted": "Usunięte",
|
|
13
|
+
|
|
14
|
+
"list.title": "Klienci",
|
|
15
|
+
"list.description": "Przeglądaj i zarządzaj kontami klientów",
|
|
16
|
+
"list.error": "Nie udało się wczytać klientów.",
|
|
17
|
+
"list.loading": "Wczytywanie…",
|
|
18
|
+
"list.empty": "Nie znaleziono klientów.",
|
|
19
|
+
"list.none": "Brak",
|
|
20
|
+
"list.field.status": "Status",
|
|
21
|
+
"list.field.search": "Szukaj",
|
|
22
|
+
"list.field.organization": "Organizacja",
|
|
23
|
+
"list.filter.all": "Wszyscy",
|
|
24
|
+
"list.filter.allOrgs": "Wszystkie organizacje",
|
|
25
|
+
"list.action.resetOrgMfa": "Resetuj 2FA dla organizacji",
|
|
26
|
+
"list.action.resetOrgMfaDone": "Reset 2FA zakończony",
|
|
27
|
+
"list.column.customer": "Klient",
|
|
28
|
+
"list.column.status": "Status",
|
|
29
|
+
"list.column.organization": "Organizacja",
|
|
30
|
+
"list.column.group": "Grupa klientów",
|
|
31
|
+
"list.column.created": "Utworzono",
|
|
32
|
+
"list.action.open": "Otwórz",
|
|
33
|
+
|
|
34
|
+
"detail.title": "Klient",
|
|
35
|
+
"detail.loading": "Wczytywanie…",
|
|
36
|
+
"detail.error": "Nie udało się wczytać klienta.",
|
|
37
|
+
"detail.actionError": "Nie udało się wykonać akcji.",
|
|
38
|
+
"detail.info.title": "Informacje o koncie",
|
|
39
|
+
"detail.actions.title": "Akcje",
|
|
40
|
+
"detail.field.email": "E-mail",
|
|
41
|
+
"detail.field.created": "Utworzono",
|
|
42
|
+
"detail.field.group": "Grupa klientów",
|
|
43
|
+
"detail.field.organization": "Organizacja",
|
|
44
|
+
"detail.field.status": "Status",
|
|
45
|
+
"detail.field.lastLogin": "Ostatnie logowanie",
|
|
46
|
+
"detail.action.block": "Zablokuj",
|
|
47
|
+
"detail.action.unblock": "Odblokuj",
|
|
48
|
+
"detail.action.passwordReset": "Zresetuj hasło",
|
|
49
|
+
"detail.action.resetMfa": "Resetuj 2FA",
|
|
50
|
+
"detail.action.impersonate": "Zaloguj jako klient",
|
|
51
|
+
"detail.action.restore": "Przywróć",
|
|
52
|
+
"detail.action.delete": "Usuń",
|
|
53
|
+
|
|
54
|
+
"online.title": "Klienci online",
|
|
55
|
+
"online.description": "Klienci aktywni w oknie świeżości",
|
|
56
|
+
"online.error": "Nie udało się wczytać klientów online.",
|
|
57
|
+
"online.loading": "Wczytywanie…",
|
|
58
|
+
"online.empty": "Żaden klient nie jest teraz online.",
|
|
59
|
+
"online.column.lastSeen": "Ostatnio widziany",
|
|
60
|
+
"online.refresh": "Odśwież",
|
|
61
|
+
"online.autoRefresh": "Automatyczne odświeżanie",
|
|
62
|
+
"online.intervalLabel": "Interwał (sekundy)",
|
|
63
|
+
"online.refreshedAt": "Zaktualizowano {time}",
|
|
64
|
+
|
|
65
|
+
"detail.panel.loading": "Wczytywanie…",
|
|
66
|
+
"detail.panel.error": "Nie udało się wczytać tej sekcji.",
|
|
67
|
+
"detail.orders.title": "Zamówienia",
|
|
68
|
+
"detail.orders.empty": "Brak zamówień.",
|
|
69
|
+
"detail.rfqs.title": "Zapytania ofertowe",
|
|
70
|
+
"detail.rfqs.empty": "Brak zapytań ofertowych.",
|
|
71
|
+
"detail.carts.title": "Koszyki",
|
|
72
|
+
"detail.carts.items": "pozycji",
|
|
73
|
+
"detail.carts.noCurrent": "Brak aktywnego koszyka.",
|
|
74
|
+
"detail.carts.empty": "Brak koszyków.",
|
|
75
|
+
|
|
76
|
+
"detail.org.title": "Organizacja",
|
|
77
|
+
"detail.org.assignLabel": "Przypisz do organizacji",
|
|
78
|
+
"detail.org.assign": "Przypisz",
|
|
79
|
+
"detail.org.unassign": "Odłącz od organizacji",
|
|
80
|
+
"detail.org.unassignHint": "Klient zachowuje konto i przechodzi do własnej organizacji osobistej.",
|
|
81
|
+
"detail.group.title": "Grupa klientów",
|
|
82
|
+
"detail.group.setLabel": "Ustaw grupę klientów",
|
|
83
|
+
"detail.group.set": "Ustaw",
|
|
84
|
+
"detail.group.clear": "Wyczyść grupę",
|
|
85
|
+
"detail.addresses.title": "Adresy",
|
|
86
|
+
"detail.addresses.empty": "Brak adresów osobistych.",
|
|
87
|
+
"detail.addresses.default": "domyślny",
|
|
88
|
+
"detail.addresses.recipient": "Odbiorca",
|
|
89
|
+
"detail.addresses.street": "Ulica",
|
|
90
|
+
"detail.addresses.city": "Miasto",
|
|
91
|
+
"detail.addresses.postal": "Kod pocztowy",
|
|
92
|
+
"detail.addresses.country": "Kraj",
|
|
93
|
+
"detail.addresses.add": "Dodaj adres"
|
|
94
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@endora-commerce/mod-customers",
|
|
3
|
+
"version": "0.100.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"description": "Customer lifecycle business logic: registration, self-service, blocking, impersonation, groups, deletion, and presence.",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"endora": {
|
|
9
|
+
"type": "module",
|
|
10
|
+
"id": "customers"
|
|
11
|
+
},
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/endora-commerce/endora-commerce.git",
|
|
15
|
+
"directory": "packages/modules/customers"
|
|
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
|
+
"./admin": {
|
|
34
|
+
"types": "./dist/admin/index.d.ts",
|
|
35
|
+
"default": "./dist/admin/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./tailwind.css": "./tailwind.css",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"i18n",
|
|
43
|
+
"docs",
|
|
44
|
+
"tailwind.css"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=22.18.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@fastify/cookie": "^11",
|
|
51
|
+
"@mikro-orm/core": "^6",
|
|
52
|
+
"@mikro-orm/migrations": "^6",
|
|
53
|
+
"@mikro-orm/postgresql": "^6",
|
|
54
|
+
"fastify": "^5",
|
|
55
|
+
"lucide-react": "^1",
|
|
56
|
+
"react": "^19",
|
|
57
|
+
"react-router-dom": "^7",
|
|
58
|
+
"zod": "^4",
|
|
59
|
+
"@endora-commerce/admin-kit": "0.100.0",
|
|
60
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
61
|
+
"@endora-commerce/platform": "0.100.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@endora-commerce/admin-kit": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"lucide-react": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"react": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"react-router-dom": {
|
|
74
|
+
"optional": true
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"devDependencies": {
|
|
78
|
+
"@fastify/cookie": "^11.0.2",
|
|
79
|
+
"@mikro-orm/core": "^6.6.13",
|
|
80
|
+
"@mikro-orm/migrations": "^6.6.13",
|
|
81
|
+
"@mikro-orm/postgresql": "^6.6.13",
|
|
82
|
+
"@types/node": "^22.9.0",
|
|
83
|
+
"@types/react": "^19.2.14",
|
|
84
|
+
"fastify": "^5.12.5",
|
|
85
|
+
"lucide-react": "^1.11.0",
|
|
86
|
+
"react": "^19.2.5",
|
|
87
|
+
"react-router-dom": "^7.18.2",
|
|
88
|
+
"typescript": "^5.9.3",
|
|
89
|
+
"vitest": "^4.1.11",
|
|
90
|
+
"zod": "^4.2.0",
|
|
91
|
+
"@endora-commerce/platform": "0.100.0",
|
|
92
|
+
"@endora-commerce/contracts": "0.100.0",
|
|
93
|
+
"@endora-commerce/admin-kit": "0.100.0"
|
|
94
|
+
},
|
|
95
|
+
"scripts": {
|
|
96
|
+
"build": "tsc -p tsconfig.build.json && tsc -p tsconfig.ui.json",
|
|
97
|
+
"typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.ui.json --noEmit",
|
|
98
|
+
"lint": "eslint src",
|
|
99
|
+
"test": "vitest run"
|
|
100
|
+
}
|
|
101
|
+
}
|
package/tailwind.css
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/* @endora-commerce/mod-customers — 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";
|