@endora-commerce/mod-comparisons 0.0.0-stage → 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 +61 -2
- package/dist/admin/api/comparisons-client.d.ts +24 -0
- package/dist/admin/api/comparisons-client.d.ts.map +1 -0
- package/dist/admin/api/comparisons-client.js +15 -0
- package/dist/admin/api/comparisons-client.js.map +1 -0
- package/dist/admin/index.d.ts +37 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +49 -0
- package/dist/admin/index.js.map +1 -0
- package/dist/admin/pages/ComparisonDetailPage.d.ts +19 -0
- package/dist/admin/pages/ComparisonDetailPage.d.ts.map +1 -0
- package/dist/admin/pages/ComparisonDetailPage.js +62 -0
- package/dist/admin/pages/ComparisonDetailPage.js.map +1 -0
- package/dist/admin/pages/ComparisonsListPage.d.ts +10 -0
- package/dist/admin/pages/ComparisonsListPage.d.ts.map +1 -0
- package/dist/admin/pages/ComparisonsListPage.js +71 -0
- package/dist/admin/pages/ComparisonsListPage.js.map +1 -0
- package/dist/backend/entities/comparison-product.entity.d.ts +22 -0
- package/dist/backend/entities/comparison-product.entity.d.ts.map +1 -0
- package/dist/backend/entities/comparison-product.entity.js +54 -0
- package/dist/backend/entities/comparison-product.entity.js.map +1 -0
- package/dist/backend/entities/comparison.entity.d.ts +61 -0
- package/dist/backend/entities/comparison.entity.d.ts.map +1 -0
- package/dist/backend/entities/comparison.entity.js +115 -0
- package/dist/backend/entities/comparison.entity.js.map +1 -0
- package/dist/backend/index.d.ts +61 -0
- package/dist/backend/index.d.ts.map +1 -0
- package/dist/backend/index.js +117 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/request-actor.d.ts +22 -0
- package/dist/backend/request-actor.d.ts.map +1 -0
- package/dist/backend/request-actor.js +24 -0
- package/dist/backend/request-actor.js.map +1 -0
- package/dist/backend/routes.admin.d.ts +22 -0
- package/dist/backend/routes.admin.d.ts.map +1 -0
- package/dist/backend/routes.admin.js +41 -0
- package/dist/backend/routes.admin.js.map +1 -0
- package/dist/backend/routes.public.d.ts +33 -0
- package/dist/backend/routes.public.d.ts.map +1 -0
- package/dist/backend/routes.public.js +215 -0
- package/dist/backend/routes.public.js.map +1 -0
- package/dist/backend/routes.share.d.ts +27 -0
- package/dist/backend/routes.share.d.ts.map +1 -0
- package/dist/backend/routes.share.js +49 -0
- package/dist/backend/routes.share.js.map +1 -0
- package/dist/backend/services/anonymous-token-cookie.d.ts +23 -0
- package/dist/backend/services/anonymous-token-cookie.d.ts.map +1 -0
- package/dist/backend/services/anonymous-token-cookie.js +33 -0
- package/dist/backend/services/anonymous-token-cookie.js.map +1 -0
- package/dist/backend/services/asset-byte-fetcher.d.ts +37 -0
- package/dist/backend/services/asset-byte-fetcher.d.ts.map +1 -0
- package/dist/backend/services/asset-byte-fetcher.js +70 -0
- package/dist/backend/services/asset-byte-fetcher.js.map +1 -0
- package/dist/backend/services/comparable-attribute-projection.d.ts +57 -0
- package/dist/backend/services/comparable-attribute-projection.d.ts.map +1 -0
- package/dist/backend/services/comparable-attribute-projection.js +136 -0
- package/dist/backend/services/comparable-attribute-projection.js.map +1 -0
- package/dist/backend/services/comparison-admin.service.d.ts +31 -0
- package/dist/backend/services/comparison-admin.service.d.ts.map +1 -0
- package/dist/backend/services/comparison-admin.service.js +184 -0
- package/dist/backend/services/comparison-admin.service.js.map +1 -0
- package/dist/backend/services/comparison-pdf-renderer.d.ts +57 -0
- package/dist/backend/services/comparison-pdf-renderer.d.ts.map +1 -0
- package/dist/backend/services/comparison-pdf-renderer.js +211 -0
- package/dist/backend/services/comparison-pdf-renderer.js.map +1 -0
- package/dist/backend/services/comparison-service.d.ts +339 -0
- package/dist/backend/services/comparison-service.d.ts.map +1 -0
- package/dist/backend/services/comparison-service.js +588 -0
- package/dist/backend/services/comparison-service.js.map +1 -0
- package/dist/backend/services/share-token-generator.d.ts +20 -0
- package/dist/backend/services/share-token-generator.d.ts.map +1 -0
- package/dist/backend/services/share-token-generator.js +23 -0
- package/dist/backend/services/share-token-generator.js.map +1 -0
- package/dist/manifest.d.ts +199 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +179 -0
- package/dist/manifest.js.map +1 -0
- package/dist/migrations/20260501T185834_comparisons_init.d.ts +32 -0
- package/dist/migrations/20260501T185834_comparisons_init.d.ts.map +1 -0
- package/dist/migrations/20260501T185834_comparisons_init.js +102 -0
- package/dist/migrations/20260501T185834_comparisons_init.js.map +1 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts +84 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts.map +1 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.js +147 -0
- package/dist/migrations/20260830T163143_comparisons_organization_attribution.js.map +1 -0
- package/dist/migrations/index.d.ts +28 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/migrations/index.js +31 -0
- package/dist/migrations/index.js.map +1 -0
- package/docs/comparisons.md +278 -0
- package/i18n/en.json +51 -0
- package/i18n/pl.json +51 -0
- package/package.json +102 -3
- package/tailwind.css +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Endora sp. z o.o. and the Endora Commerce contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,62 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- Generated by `pnpm --filter backend run manifests:generate`. Delete this line to take the file over; a README without it is never regenerated. -->
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# @endora-commerce/mod-comparisons
|
|
4
|
+
|
|
5
|
+
Customer-facing product comparison feature with shareable links and PDF export.
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
An **Endora Commerce module package**. Its module id is `comparisons` — the identity of record in the platform's registries, in its migrations and in its setting, permission and translation codes.
|
|
10
|
+
|
|
11
|
+
A module is not imported by application code. The platform discovers the extension packages an instance has installed, reads the manifest on the root subpath and composes the module from it, so installing this package and starting the instance is the whole integration.
|
|
12
|
+
|
|
13
|
+
## Entry points
|
|
14
|
+
|
|
15
|
+
| Import | Contents |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| `@endora-commerce/mod-comparisons` | the module manifest — its id, version, dependencies, settings and activation |
|
|
18
|
+
| `@endora-commerce/mod-comparisons/admin` | the Admin UI contribution: the screens, navigation and palette actions it adds |
|
|
19
|
+
| `@endora-commerce/mod-comparisons/backend` | the composition root the platform calls, with the entities, services, routes and workers it registers |
|
|
20
|
+
| `@endora-commerce/mod-comparisons/migrations` | the module’s own schema migrations, in the order the platform runs them |
|
|
21
|
+
| `@endora-commerce/mod-comparisons/tailwind.css` | the Tailwind source declaration for the admin code above |
|
|
22
|
+
|
|
23
|
+
## Depends on
|
|
24
|
+
|
|
25
|
+
Everything below is a **peer** dependency, so the application resolves exactly one copy of each — two copies of a host or of React are a runtime failure rather than a type error. An entry marked *optional* is needed only by the layers that use it.
|
|
26
|
+
|
|
27
|
+
**Endora packages**
|
|
28
|
+
|
|
29
|
+
- `@endora-commerce/admin-kit` — *optional*
|
|
30
|
+
- `@endora-commerce/contracts`
|
|
31
|
+
- `@endora-commerce/platform`
|
|
32
|
+
|
|
33
|
+
**Third-party**
|
|
34
|
+
|
|
35
|
+
- `@fastify/cookie` ^11
|
|
36
|
+
- `@mikro-orm/core` ^6
|
|
37
|
+
- `@mikro-orm/migrations` ^6
|
|
38
|
+
- `@mikro-orm/postgresql` ^6
|
|
39
|
+
- `@types/pdfmake` ^0
|
|
40
|
+
- `fastify` ^5
|
|
41
|
+
- `lucide-react` ^1 — *optional*
|
|
42
|
+
- `pdfmake` ^0
|
|
43
|
+
- `react` ^19 — *optional*
|
|
44
|
+
- `react-router-dom` ^7 — *optional*
|
|
45
|
+
- `zod` ^4
|
|
46
|
+
|
|
47
|
+
## What the tarball carries
|
|
48
|
+
|
|
49
|
+
- `dist/` — the compiled JavaScript and its type declarations
|
|
50
|
+
- `i18n/` — the translation bundles this package ships (`en`, `pl`), loaded by the platform
|
|
51
|
+
- `docs/` — the package’s own pages in the Endora Commerce documentation site
|
|
52
|
+
- `tailwind.css` — the Tailwind source declaration
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pnpm add @endora-commerce/mod-comparisons
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Licence
|
|
61
|
+
|
|
62
|
+
MIT — the text is in `LICENSE`, beside this file.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ComparisonAdminDetail, ComparisonAdminListItem, ComparisonAdminListQuery, ScopeNoticeCode } from '@endora-commerce/contracts';
|
|
2
|
+
/**
|
|
3
|
+
* Admin client helpers for the Comparisons overview (feature 007 / US5
|
|
4
|
+
* / T066). Maps query filters to the backend's GET surface and unwraps
|
|
5
|
+
* the standard `{ data, meta }` envelope.
|
|
6
|
+
*/
|
|
7
|
+
export interface ListResult {
|
|
8
|
+
data: ComparisonAdminListItem[];
|
|
9
|
+
meta: {
|
|
10
|
+
limit: number;
|
|
11
|
+
nextCursor: string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Present only when the server refused every row for want of an
|
|
14
|
+
* organization on the record (feature 087). Read it with `scopeNoticeOf`
|
|
15
|
+
* rather than by hand — the shape is the contract's, not this screen's.
|
|
16
|
+
*/
|
|
17
|
+
scopeNotice?: ScopeNoticeCode;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function listComparisons(filters: Partial<ComparisonAdminListQuery>): Promise<ListResult>;
|
|
21
|
+
export declare function getComparisonDetail(id: string): Promise<{
|
|
22
|
+
data: ComparisonAdminDetail;
|
|
23
|
+
}>;
|
|
24
|
+
//# sourceMappingURL=comparisons-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparisons-client.d.ts","sourceRoot":"","sources":["../../../src/admin/api/comparisons-client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC;;;;GAIG;AAEH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,uBAAuB,EAAE,CAAC;IAChC,IAAI,EAAE;QACJ,KAAK,EAAE,MAAM,CAAC;QACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B;;;;WAIG;QACH,WAAW,CAAC,EAAE,eAAe,CAAC;KAC/B,CAAC;CACH;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,CAAC,wBAAwB,CAAC,GACzC,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,GACT,OAAO,CAAC;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,CAAC,CAI1C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { apiClient } from '@endora-commerce/admin-kit/lib';
|
|
2
|
+
export async function listComparisons(filters) {
|
|
3
|
+
const params = new URLSearchParams();
|
|
4
|
+
for (const [k, v] of Object.entries(filters)) {
|
|
5
|
+
if (v === undefined || v === null || v === '')
|
|
6
|
+
continue;
|
|
7
|
+
params.set(k, String(v));
|
|
8
|
+
}
|
|
9
|
+
const qs = params.toString();
|
|
10
|
+
return apiClient.get(`/api/v1/admin/comparisons${qs ? `?${qs}` : ''}`);
|
|
11
|
+
}
|
|
12
|
+
export async function getComparisonDetail(id) {
|
|
13
|
+
return apiClient.get(`/api/v1/admin/comparisons/${encodeURIComponent(id)}`);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=comparisons-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparisons-client.js","sourceRoot":"","sources":["../../../src/admin/api/comparisons-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AA4B3D,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAA0C;IAE1C,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE;YAAE,SAAS;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,OAAO,SAAS,CAAC,GAAG,CAClB,4BAA4B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjD,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,EAAU;IAEV,OAAO,SAAS,CAAC,GAAG,CAClB,6BAA6B,kBAAkB,CAAC,EAAE,CAAC,EAAE,CACtD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `comparisons`' admin surface — two routes and one sidebar entry, declared by
|
|
3
|
+
* the module that owns them (feature 091, Phase 4, the plan's batch 6;
|
|
4
|
+
* `specs/091-module-owned-admin-surfaces/contracts/admin-contribution.md`).
|
|
5
|
+
*
|
|
6
|
+
* **The batch this module belongs to was unblocked by P2 rather than chosen by
|
|
7
|
+
* size.** `plan.md`'s sequence table records this module's drain as **1**, and
|
|
8
|
+
* that one key was `ComparisonsListPage.tsx`'s reach into
|
|
9
|
+
* `admin/src/components/sales-channel-picker` — a Group A entry in
|
|
10
|
+
* `backend/scripts/ledgers/admin-surface.ts`, retired when the picker became
|
|
11
|
+
* `@endora-commerce/admin-kit/components`'. The screen names the published
|
|
12
|
+
* component now, so this batch pays no reach of its own.
|
|
13
|
+
*
|
|
14
|
+
* **The detail route has no sidebar row and never will** — a nav entry is a
|
|
15
|
+
* landing surface. `registryCrumbs` matches anything beneath a contributed
|
|
16
|
+
* entry's `to`, so `/comparisons/:id` keeps a derived trail rather than falling
|
|
17
|
+
* onto the humanised-segment fallback; it is two crumbs where the hand-written
|
|
18
|
+
* `CRUMB_DICT` rule built three, which is the visible consequence this
|
|
19
|
+
* conversion records rather than hides.
|
|
20
|
+
*
|
|
21
|
+
* **Both routes take the read code, and that is the whole of what this surface
|
|
22
|
+
* enforces.** The admin side of this module is read-only —
|
|
23
|
+
* `routes.admin.ts` mounts one list and one detail handler, each behind
|
|
24
|
+
* `requireAdmin('comparisons:read')`, and there is no write route to gate.
|
|
25
|
+
*
|
|
26
|
+
* **This entry exports data and nothing else** (R2), like every `./admin`
|
|
27
|
+
* layer: `check:module-boundary`'s D-171 rule designates a subpath as contract
|
|
28
|
+
* surface when it emits no runtime binding, and `./admin` deliberately does not
|
|
29
|
+
* qualify, so a consumer reaching into another module's `./admin` stays a
|
|
30
|
+
* counted boundary reach.
|
|
31
|
+
*
|
|
32
|
+
* **Each component is a dynamic-import factory** (R6), so Vite has a split
|
|
33
|
+
* point whether or not anybody remembers to ask for one.
|
|
34
|
+
*/
|
|
35
|
+
import type { AdminContributions } from '@endora-commerce/admin-kit/contributions';
|
|
36
|
+
export declare const contributions: AdminContributions;
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AAcnF,eAAO,MAAM,aAAa,EAAE,kBAqC3B,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The module's landing route: the comparison overview. */
|
|
2
|
+
const ROUTE_PATH = '/comparisons';
|
|
3
|
+
/**
|
|
4
|
+
* The code both routes of this module's admin API enforce —
|
|
5
|
+
* `requireAdmin('comparisons:read')` on `GET /api/v1/admin/comparisons` and on
|
|
6
|
+
* `GET /api/v1/admin/comparisons/:id`. It is the same code the module's
|
|
7
|
+
* `open-comparisons` palette action declares, which
|
|
8
|
+
* `check:action-route-permissions` holds to the landing route.
|
|
9
|
+
*/
|
|
10
|
+
const PERMISSION = 'comparisons:read';
|
|
11
|
+
export const contributions = {
|
|
12
|
+
routes: [
|
|
13
|
+
{
|
|
14
|
+
path: ROUTE_PATH,
|
|
15
|
+
component: () => import('./pages/ComparisonsListPage.js'),
|
|
16
|
+
requiredPermission: PERMISSION,
|
|
17
|
+
index: true,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
path: `${ROUTE_PATH}/:id`,
|
|
21
|
+
component: () => import('./pages/ComparisonDetailPage.js'),
|
|
22
|
+
requiredPermission: PERMISSION,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
nav: [
|
|
26
|
+
{
|
|
27
|
+
to: ROUTE_PATH,
|
|
28
|
+
// Module-relative (R8), resolved in this module's own namespace out of
|
|
29
|
+
// `packages/modules/comparisons/i18n/`. It was `appShell.nav.comparisons`
|
|
30
|
+
// in the shared `_i18n` bundle, one of the four shared files a module
|
|
31
|
+
// author had to edit.
|
|
32
|
+
labelKey: 'nav.comparisons.label',
|
|
33
|
+
// The glyph `AppShell.tsx` rendered by hand. A nav entry names its icon,
|
|
34
|
+
// so `Scale` joins `KnownIconNameSchema` and the kit's `icon-map.ts` in
|
|
35
|
+
// this merge request rather than the sidebar quietly changing to a name
|
|
36
|
+
// that happened to be on the allowlist.
|
|
37
|
+
icon: 'Scale',
|
|
38
|
+
section: 'customers',
|
|
39
|
+
// The hand-written position in *Customers* times a hundred, which is
|
|
40
|
+
// batch four's convention: `/comparisons` was the sixth and last row of
|
|
41
|
+
// that section. Every other row there is still host-declared, so
|
|
42
|
+
// `composeNav` appends this one after all of them — which is the position
|
|
43
|
+
// it already had.
|
|
44
|
+
weight: 600,
|
|
45
|
+
requiredPermission: PERMISSION,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/admin/index.ts"],"names":[],"mappings":"AAoCA,2DAA2D;AAC3D,MAAM,UAAU,GAAG,cAAc,CAAC;AAElC;;;;;;GAMG;AACH,MAAM,UAAU,GAAG,kBAAkB,CAAC;AAEtC,MAAM,CAAC,MAAM,aAAa,GAAuB;IAC/C,MAAM,EAAE;QACN;YACE,IAAI,EAAE,UAAU;YAChB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gCAAgC,CAAC;YACzD,kBAAkB,EAAE,UAAU;YAC9B,KAAK,EAAE,IAAI;SACZ;QACD;YACE,IAAI,EAAE,GAAG,UAAU,MAAM;YACzB,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iCAAiC,CAAC;YAC1D,kBAAkB,EAAE,UAAU;SAC/B;KACF;IACD,GAAG,EAAE;QACH;YACE,EAAE,EAAE,UAAU;YACd,uEAAuE;YACvE,0EAA0E;YAC1E,sEAAsE;YACtE,sBAAsB;YACtB,QAAQ,EAAE,uBAAuB;YACjC,yEAAyE;YACzE,wEAAwE;YACxE,wEAAwE;YACxE,wCAAwC;YACxC,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,WAAW;YACpB,qEAAqE;YACrE,wEAAwE;YACxE,iEAAiE;YACjE,0EAA0E;YAC1E,kBAAkB;YAClB,MAAM,EAAE,GAAG;YACX,kBAAkB,EAAE,UAAU;SAC/B;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `<ComparisonDetailPage>` — feature 007 / US5 / T068.
|
|
4
|
+
*
|
|
5
|
+
* Read-only detail of a single Comparison. Renders the products and the
|
|
6
|
+
* comparable-attribute projection in the comparison's RECORDED sales-channel
|
|
7
|
+
* context (the creator's, not the admin's). No mutation controls.
|
|
8
|
+
*
|
|
9
|
+
* **The figures are the channel's, not the customer's.** Since the comparison
|
|
10
|
+
* became viewer-priced, the customer sees their own organisation's prices and
|
|
11
|
+
* an administrator — who has no buying organisation — sees the channel's. The
|
|
12
|
+
* screen says so rather than letting the reader assume the two agree, which is
|
|
13
|
+
* the same reason the storefront's shared view says whose prices it is showing.
|
|
14
|
+
* Every product is listed, including ones restricted to organizations other
|
|
15
|
+
* than the reader's: this is the audit view, and its question is what the
|
|
16
|
+
* customer put in the comparison.
|
|
17
|
+
*/
|
|
18
|
+
export default function ComparisonDetailPage(): ReactNode;
|
|
19
|
+
//# sourceMappingURL=ComparisonDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComparisonDetailPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/ComparisonDetailPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAsB5D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,OAAO,UAAU,oBAAoB,IAAI,SAAS,CA2IxD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Link, useParams } from 'react-router-dom';
|
|
4
|
+
import { ApiError } from '@endora-commerce/admin-kit/lib';
|
|
5
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
6
|
+
import { Alert, AlertDescription, Badge, Button, Card, CardContent, PageHeader, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@endora-commerce/admin-kit/ui';
|
|
7
|
+
import { getComparisonDetail } from '../api/comparisons-client.js';
|
|
8
|
+
/**
|
|
9
|
+
* `<ComparisonDetailPage>` — feature 007 / US5 / T068.
|
|
10
|
+
*
|
|
11
|
+
* Read-only detail of a single Comparison. Renders the products and the
|
|
12
|
+
* comparable-attribute projection in the comparison's RECORDED sales-channel
|
|
13
|
+
* context (the creator's, not the admin's). No mutation controls.
|
|
14
|
+
*
|
|
15
|
+
* **The figures are the channel's, not the customer's.** Since the comparison
|
|
16
|
+
* became viewer-priced, the customer sees their own organisation's prices and
|
|
17
|
+
* an administrator — who has no buying organisation — sees the channel's. The
|
|
18
|
+
* screen says so rather than letting the reader assume the two agree, which is
|
|
19
|
+
* the same reason the storefront's shared view says whose prices it is showing.
|
|
20
|
+
* Every product is listed, including ones restricted to organizations other
|
|
21
|
+
* than the reader's: this is the audit view, and its question is what the
|
|
22
|
+
* customer put in the comparison.
|
|
23
|
+
*/
|
|
24
|
+
export default function ComparisonDetailPage() {
|
|
25
|
+
const t = useTranslation('comparisons');
|
|
26
|
+
const { id } = useParams();
|
|
27
|
+
const [detail, setDetail] = useState(null);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!id)
|
|
31
|
+
return;
|
|
32
|
+
let cancelled = false;
|
|
33
|
+
void (async () => {
|
|
34
|
+
try {
|
|
35
|
+
const res = await getComparisonDetail(id);
|
|
36
|
+
if (!cancelled)
|
|
37
|
+
setDetail(res.data);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (!cancelled) {
|
|
41
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('error.load'));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
return () => {
|
|
46
|
+
cancelled = true;
|
|
47
|
+
};
|
|
48
|
+
}, [id]);
|
|
49
|
+
if (error) {
|
|
50
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('detail.page.title') }), _jsx(Alert, { variant: "destructive", children: _jsx(AlertDescription, { children: error }) }), _jsx("div", { className: "mt-4", children: _jsx(Button, { asChild: true, variant: "outline", children: _jsx(Link, { to: "/comparisons", children: t('detail.backToList') }) }) })] }));
|
|
51
|
+
}
|
|
52
|
+
if (!detail) {
|
|
53
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('detail.page.title') }), _jsx("p", { className: "text-sm text-muted-foreground", children: t('common.loading') })] }));
|
|
54
|
+
}
|
|
55
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('detail.page.title'), description: t('detail.page.description') }), _jsx(Alert, { className: "mb-4", children: _jsx(AlertDescription, { children: t('detail.pricesNote') }) }), _jsx(Card, { className: "mb-4", children: _jsxs(CardContent, { className: "pt-6 space-y-2", children: [_jsxs("div", { children: [_jsxs("strong", { children: [t('detail.owner'), ":"] }), ' ', detail.owner.kind === 'customer'
|
|
56
|
+
? detail.owner.email ?? t('owner.noEmail')
|
|
57
|
+
: t('owner.anonymousWithToken', { token: detail.owner.anonymousToken ?? '—' })] }), _jsxs("div", { children: [_jsxs("strong", { children: [t('detail.salesChannel'), ":"] }), ' ', _jsx("code", { className: "font-mono text-xs", children: detail.salesChannel.code })] }), _jsxs("div", { children: [_jsxs("strong", { children: [t('detail.displayMode'), ":"] }), ' ', _jsx(Badge, { variant: "default", children: t(`displayMode.${detail.displayMode}`) })] }), _jsxs("div", { children: [_jsxs("strong", { children: [t('detail.shareToken'), ":"] }), ' ', _jsx("code", { className: "font-mono text-xs", children: detail.shareToken })] }), _jsxs("div", { className: "text-muted-foreground", children: [t('detail.created'), " ", new Date(detail.createdAt).toLocaleString(), " \u00B7 ", t('detail.updated'), ' ', new Date(detail.updatedAt).toLocaleString()] })] }) }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('detail.attribute') }), detail.products.map((p) => (_jsxs(TableHead, { children: [_jsx("div", { children: localised(p.name) }), _jsx("div", { className: "text-xs text-muted-foreground", children: p.price ? `${p.price.amount} ${p.price.currency}` : '—' })] }, p.id)))] }) }), _jsx(TableBody, { children: detail.comparableAttributes.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: detail.products.length + 1, className: "text-muted-foreground", children: t('detail.noComparableAttributes') }) })) : (detail.comparableAttributes.map((row) => (_jsxs(TableRow, { children: [_jsxs(TableCell, { children: [_jsx("strong", { children: localised(row.label) }), ' ', _jsx(Badge, { variant: row.rowClass === 'common' ? 'success' : 'warning', className: "ml-2", children: row.rowClass })] }), row.values.map((v, idx) => (_jsx(TableCell, { children: v ?? '—' }, `${row.key}-${idx}`)))] }, row.key)))) })] }) }) }), _jsx("div", { className: "mt-4", children: _jsx(Button, { asChild: true, variant: "outline", children: _jsx(Link, { to: "/comparisons", children: t('detail.backToList') }) }) })] }));
|
|
58
|
+
}
|
|
59
|
+
function localised(value) {
|
|
60
|
+
return value['en-US'] ?? value['en'] ?? Object.values(value)[0] ?? '';
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=ComparisonDetailPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComparisonDetailPage.js","sourceRoot":"","sources":["../../../src/admin/pages/ComparisonDetailPage.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `<ComparisonsListPage>` — feature 007 / US5 / T067.
|
|
4
|
+
*
|
|
5
|
+
* Read-only listing of every Comparison the storefront has generated.
|
|
6
|
+
* Filters: sales channel id, owner type (customer / anonymous), time
|
|
7
|
+
* range. Cursor pagination. Click a row to open the detail page.
|
|
8
|
+
*/
|
|
9
|
+
export default function ComparisonsListPage(): ReactNode;
|
|
10
|
+
//# sourceMappingURL=ComparisonsListPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComparisonsListPage.d.ts","sourceRoot":"","sources":["../../../src/admin/pages/ComparisonsListPage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAoC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA2BzE;;;;;;GAMG;AACH,MAAM,CAAC,OAAO,UAAU,mBAAmB,IAAI,SAAS,CAoLvD"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
3
|
+
import { Link } from 'react-router-dom';
|
|
4
|
+
import { ArrowRight } from 'lucide-react';
|
|
5
|
+
import { scopeNoticeOf } from '@endora-commerce/contracts';
|
|
6
|
+
import { ApiError } from '@endora-commerce/admin-kit/lib';
|
|
7
|
+
import { useTranslation } from '@endora-commerce/admin-kit/i18n';
|
|
8
|
+
import { Alert, AlertDescription, Badge, Button, Card, CardContent, Input, Label, PageHeader, Select, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from '@endora-commerce/admin-kit/ui';
|
|
9
|
+
import { SalesChannelPicker, ScopeNotice } from '@endora-commerce/admin-kit/components';
|
|
10
|
+
import { listComparisons } from '../api/comparisons-client.js';
|
|
11
|
+
/**
|
|
12
|
+
* `<ComparisonsListPage>` — feature 007 / US5 / T067.
|
|
13
|
+
*
|
|
14
|
+
* Read-only listing of every Comparison the storefront has generated.
|
|
15
|
+
* Filters: sales channel id, owner type (customer / anonymous), time
|
|
16
|
+
* range. Cursor pagination. Click a row to open the detail page.
|
|
17
|
+
*/
|
|
18
|
+
export default function ComparisonsListPage() {
|
|
19
|
+
const t = useTranslation('comparisons');
|
|
20
|
+
const [rows, setRows] = useState([]);
|
|
21
|
+
const [loading, setLoading] = useState(false);
|
|
22
|
+
const [error, setError] = useState(null);
|
|
23
|
+
const [nextCursor, setNextCursor] = useState(null);
|
|
24
|
+
// Why the list is empty, when it is (feature 087). The server is the only
|
|
25
|
+
// party that can tell "none of these is yours" from "there are none", so the
|
|
26
|
+
// screen carries the answer rather than guessing at it.
|
|
27
|
+
const [scopeNotice, setScopeNotice] = useState(null);
|
|
28
|
+
const [salesChannelId, setSalesChannelId] = useState('');
|
|
29
|
+
const [ownerType, setOwnerType] = useState('');
|
|
30
|
+
const [createdAfter, setCreatedAfter] = useState('');
|
|
31
|
+
const [createdBefore, setCreatedBefore] = useState('');
|
|
32
|
+
const refresh = useCallback(async (cursor) => {
|
|
33
|
+
setLoading(true);
|
|
34
|
+
setError(null);
|
|
35
|
+
try {
|
|
36
|
+
const filters = {};
|
|
37
|
+
if (salesChannelId)
|
|
38
|
+
filters['salesChannelId'] = salesChannelId;
|
|
39
|
+
if (ownerType)
|
|
40
|
+
filters['ownerType'] = ownerType;
|
|
41
|
+
if (createdAfter)
|
|
42
|
+
filters['createdAfter'] = new Date(createdAfter).toISOString();
|
|
43
|
+
if (createdBefore)
|
|
44
|
+
filters['createdBefore'] = new Date(createdBefore).toISOString();
|
|
45
|
+
if (cursor)
|
|
46
|
+
filters['cursor'] = cursor;
|
|
47
|
+
const res = await listComparisons(filters);
|
|
48
|
+
if (cursor) {
|
|
49
|
+
setRows((prev) => [...prev, ...res.data]);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
setRows(res.data);
|
|
53
|
+
}
|
|
54
|
+
setNextCursor(res.meta.nextCursor);
|
|
55
|
+
setScopeNotice(scopeNoticeOf(res));
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
setError(err instanceof ApiError ? err.envelope.error.message : t('error.load'));
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
}
|
|
63
|
+
}, [salesChannelId, ownerType, createdAfter, createdBefore]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
void refresh();
|
|
66
|
+
}, [refresh]);
|
|
67
|
+
return (_jsxs(_Fragment, { children: [_jsx(PageHeader, { title: t('list.page.title'), description: t('list.page.description') }), error ? (_jsx(Alert, { variant: "destructive", className: "mb-4", children: _jsx(AlertDescription, { children: error }) })) : null, _jsx(Card, { className: "mb-4", children: _jsx(CardContent, { className: "pt-6", children: _jsxs("div", { className: "grid gap-4 md:grid-cols-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "sales-channel-id", children: t('list.filter.salesChannelId') }), _jsx(SalesChannelPicker, { id: "sales-channel-id", value: salesChannelId || null, onChange: (v) => setSalesChannelId(v ?? '') })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "owner-type", children: t('list.filter.owner') }), _jsxs(Select, { id: "owner-type", value: ownerType, onChange: (e) => setOwnerType(e.target.value), children: [_jsx("option", { value: "", children: t('owner.any') }), _jsx("option", { value: "customer", children: t('owner.customer') }), _jsx("option", { value: "anonymous", children: t('owner.anonymous') })] })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "created-after", children: t('list.filter.createdAfter') }), _jsx(Input, { id: "created-after", type: "datetime-local", value: createdAfter, onChange: (e) => setCreatedAfter(e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "created-before", children: t('list.filter.createdBefore') }), _jsx(Input, { id: "created-before", type: "datetime-local", value: createdBefore, onChange: (e) => setCreatedBefore(e.target.value) })] })] }) }) }), _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: loading && rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('common.loading') })) : rows.length === 0 ? (scopeNotice ? (_jsx(ScopeNotice, { notice: scopeNotice })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: t('list.empty') }))) : (_jsxs(_Fragment, { children: [_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t('list.column.owner') }), _jsx(TableHead, { children: t('list.column.salesChannel') }), _jsx(TableHead, { children: t('list.column.mode') }), _jsx(TableHead, { children: t('list.column.products') }), _jsx(TableHead, { children: t('list.column.created') }), _jsx(TableHead, { className: "text-right", children: t('list.column.open') })] }) }), _jsx(TableBody, { children: rows.map((c) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: c.owner.kind === 'customer' ? (_jsx("span", { children: c.owner.email ?? t('owner.noEmailOnFile') })) : (_jsx(Badge, { variant: "secondary", children: t('owner.anonymous') })) }), _jsx(TableCell, { children: _jsx("code", { className: "font-mono text-xs", children: c.salesChannel.code }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: "default", children: t(`displayMode.${c.displayMode}`) }) }), _jsx(TableCell, { children: c.productCount }), _jsx(TableCell, { className: "text-muted-foreground", children: new Date(c.createdAt).toLocaleString() }), _jsx(TableCell, { className: "text-right", children: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { to: `/comparisons/${c.id}`, children: _jsx(ArrowRight, { className: "size-4" }) }) }) })] }, c.id))) })] }), nextCursor ? (_jsx("div", { className: "mt-4 text-center", children: _jsx(Button, { variant: "outline", disabled: loading, onClick: () => {
|
|
68
|
+
void refresh(nextCursor);
|
|
69
|
+
}, children: loading ? t('common.loading') : t('list.loadMore') }) })) : null] })) }) })] }));
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=ComparisonsListPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ComparisonsListPage.js","sourceRoot":"","sources":["../../../src/admin/pages/ComparisonsListPage.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
/**
|
|
3
|
+
* ComparisonProduct — feature 007 / T012 / data-model.md §1.2.
|
|
4
|
+
*
|
|
5
|
+
* Ordered bridge between a Comparison and the Products it holds.
|
|
6
|
+
* Composite PK on (comparisonId, productId) — a given product appears at
|
|
7
|
+
* most once per comparison. `position` defines column order on the
|
|
8
|
+
* comparison page and PDF; gaps are tolerated when a product is removed
|
|
9
|
+
* mid-session.
|
|
10
|
+
*
|
|
11
|
+
* Both FKs cascade: removing a Product removes it from every Comparison
|
|
12
|
+
* that referenced it (no dangling rows, no orphaned columns); removing a
|
|
13
|
+
* Comparison removes its bridge rows.
|
|
14
|
+
*/
|
|
15
|
+
export declare class ComparisonProduct {
|
|
16
|
+
[OptionalProps]?: 'addedAt';
|
|
17
|
+
comparisonId: string;
|
|
18
|
+
productId: string;
|
|
19
|
+
position: number;
|
|
20
|
+
addedAt: Date;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=comparison-product.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison-product.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/comparison-product.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAwB,MAAM,iBAAiB,CAAC;AAGrF;;;;;;;;;;;;GAYG;AACH,qBAEa,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAC,EAAE,SAAS,CAAC;IAG5B,YAAY,EAAG,MAAM,CAAC;IAItB,SAAS,EAAG,MAAM,CAAC;IAGnB,QAAQ,EAAG,MAAM,CAAC;IAGlB,OAAO,EAAE,IAAI,CAAc;CAC5B"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
import { Entity, Index, OptionalProps, PrimaryKey, Property } from '@mikro-orm/core';
|
|
11
|
+
import { GlobalEntity } from '@endora-commerce/platform/tenancy';
|
|
12
|
+
/**
|
|
13
|
+
* ComparisonProduct — feature 007 / T012 / data-model.md §1.2.
|
|
14
|
+
*
|
|
15
|
+
* Ordered bridge between a Comparison and the Products it holds.
|
|
16
|
+
* Composite PK on (comparisonId, productId) — a given product appears at
|
|
17
|
+
* most once per comparison. `position` defines column order on the
|
|
18
|
+
* comparison page and PDF; gaps are tolerated when a product is removed
|
|
19
|
+
* mid-session.
|
|
20
|
+
*
|
|
21
|
+
* Both FKs cascade: removing a Product removes it from every Comparison
|
|
22
|
+
* that referenced it (no dangling rows, no orphaned columns); removing a
|
|
23
|
+
* Comparison removes its bridge rows.
|
|
24
|
+
*/
|
|
25
|
+
let ComparisonProduct = class ComparisonProduct {
|
|
26
|
+
[OptionalProps];
|
|
27
|
+
comparisonId;
|
|
28
|
+
productId;
|
|
29
|
+
position;
|
|
30
|
+
addedAt = new Date();
|
|
31
|
+
};
|
|
32
|
+
__decorate([
|
|
33
|
+
PrimaryKey({ type: 'uuid' }),
|
|
34
|
+
__metadata("design:type", String)
|
|
35
|
+
], ComparisonProduct.prototype, "comparisonId", void 0);
|
|
36
|
+
__decorate([
|
|
37
|
+
PrimaryKey({ type: 'uuid' }),
|
|
38
|
+
Index(),
|
|
39
|
+
__metadata("design:type", String)
|
|
40
|
+
], ComparisonProduct.prototype, "productId", void 0);
|
|
41
|
+
__decorate([
|
|
42
|
+
Property({ type: 'smallint' }),
|
|
43
|
+
__metadata("design:type", Number)
|
|
44
|
+
], ComparisonProduct.prototype, "position", void 0);
|
|
45
|
+
__decorate([
|
|
46
|
+
Property({ type: 'datetime', onCreate: () => new Date() }),
|
|
47
|
+
__metadata("design:type", Date)
|
|
48
|
+
], ComparisonProduct.prototype, "addedAt", void 0);
|
|
49
|
+
ComparisonProduct = __decorate([
|
|
50
|
+
GlobalEntity(),
|
|
51
|
+
Entity({ tableName: 'comparison_products' })
|
|
52
|
+
], ComparisonProduct);
|
|
53
|
+
export { ComparisonProduct };
|
|
54
|
+
//# sourceMappingURL=comparison-product.entity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison-product.entity.js","sourceRoot":"","sources":["../../../src/backend/entities/comparison-product.entity.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEjE;;;;;;;;;;;;GAYG;AAGI,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAC5B,CAAC,aAAa,CAAC,CAAa;IAG5B,YAAY,CAAU;IAItB,SAAS,CAAU;IAGnB,QAAQ,CAAU;IAGlB,OAAO,GAAS,IAAI,IAAI,EAAE,CAAC;CAC5B,CAAA;AAXC;IADC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;;uDACP;AAItB;IAFC,UAAU,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC5B,KAAK,EAAE;;oDACW;AAGnB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;;mDACb;AAGlB;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;8BAClD,IAAI;kDAAc;AAdhB,iBAAiB;IAF7B,YAAY,EAAE;IACd,MAAM,CAAC,EAAE,SAAS,EAAE,qBAAqB,EAAE,CAAC;GAChC,iBAAiB,CAe7B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { OptionalProps } from '@mikro-orm/core';
|
|
2
|
+
export type ComparisonDisplayMode = 'all' | 'common' | 'differences';
|
|
3
|
+
/**
|
|
4
|
+
* Comparison — feature 007 / T011 / data-model.md §1.1.
|
|
5
|
+
*
|
|
6
|
+
* A customer-owned set of product references with a chosen display mode,
|
|
7
|
+
* an owner (customer account or anonymous browser session), an associated
|
|
8
|
+
* sales channel, and a stable shareable URL token. Owner is *exclusively*
|
|
9
|
+
* one of the two — the DB CHECK constraint
|
|
10
|
+
* `comparisons_owner_xor_chk` enforces it; the service layer also
|
|
11
|
+
* validates before insert.
|
|
12
|
+
*
|
|
13
|
+
* `displayMode` persists across re-opens so the customer's chosen mode is
|
|
14
|
+
* remembered. The default `'all'` matches spec FR-008.
|
|
15
|
+
*
|
|
16
|
+
* State transitions: live → deleted (hard delete; `comparison_products`
|
|
17
|
+
* cascades). No soft-delete: spec FR-014 requires the share-token endpoint
|
|
18
|
+
* to resolve to a clear "no longer exists" state, which a missing row
|
|
19
|
+
* already satisfies.
|
|
20
|
+
*/
|
|
21
|
+
export declare class Comparison {
|
|
22
|
+
[OptionalProps]?: 'id' | 'createdAt' | 'updatedAt' | 'displayMode' | 'customerAccountId' | 'organizationId' | 'anonymousToken';
|
|
23
|
+
id: string;
|
|
24
|
+
shareToken: string;
|
|
25
|
+
/**
|
|
26
|
+
* Set when the owner is authenticated. Mutually exclusive with
|
|
27
|
+
* `anonymousToken` (DB CHECK + service-side guard).
|
|
28
|
+
*/
|
|
29
|
+
customerAccountId?: string | null;
|
|
30
|
+
/**
|
|
31
|
+
* The organisation that owns this comparison — feature 087 Group B, ruling
|
|
32
|
+
* D-187.
|
|
33
|
+
*
|
|
34
|
+
* Derived from {@link customerAccountId}'s account and stamped at the write,
|
|
35
|
+
* never resolved on read: `customerOrganizationColumn` asks the ORM's own
|
|
36
|
+
* metadata whether this property exists, so its presence is what makes
|
|
37
|
+
* `customerFilterCond`'s `allowed-set` arm **grant** on this table rather
|
|
38
|
+
* than refuse it whole. A sales representative assigned to the buyer's
|
|
39
|
+
* organisation sees this row because this column is here.
|
|
40
|
+
*
|
|
41
|
+
* Nullable, and the constraint is an implication rather than an equivalence:
|
|
42
|
+
* `comparisons_organization_attribution_chk` requires an organisation of a
|
|
43
|
+
* row that names an account, and says nothing about a row that names none.
|
|
44
|
+
* An **anonymous** comparison is a representable state (FR-011) — this table
|
|
45
|
+
* enforces the owner XOR, so half of it is anonymous by construction — and
|
|
46
|
+
* who such a row belongs to is R-6's open question, which a constraint must
|
|
47
|
+
* not answer.
|
|
48
|
+
*/
|
|
49
|
+
organizationId?: string | null;
|
|
50
|
+
/**
|
|
51
|
+
* Set when the owner is an anonymous browser session. Same encoding as
|
|
52
|
+
* `shareToken` (16 random bytes, base64url) but a separate value so a
|
|
53
|
+
* leaked anonymous-cookie token does not double as a public share link.
|
|
54
|
+
*/
|
|
55
|
+
anonymousToken?: string | null;
|
|
56
|
+
salesChannelId: string;
|
|
57
|
+
displayMode: ComparisonDisplayMode;
|
|
58
|
+
createdAt: Date;
|
|
59
|
+
updatedAt: Date;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=comparison.entity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comparison.entity.d.ts","sourceRoot":"","sources":["../../../src/backend/entities/comparison.entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,aAAa,EAAgC,MAAM,iBAAiB,CAAC;AAI7F,MAAM,MAAM,qBAAqB,GAAG,KAAK,GAAG,QAAQ,GAAG,aAAa,CAAC;AAErE;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAEa,UAAU;IACrB,CAAC,aAAa,CAAC,CAAC,EACZ,IAAI,GACJ,WAAW,GACX,WAAW,GACX,aAAa,GACb,mBAAmB,GACnB,gBAAgB,GAChB,gBAAgB,CAAC;IAGrB,EAAE,EAAE,MAAM,CAAgB;IAI1B,UAAU,EAAG,MAAM,CAAC;IAEpB;;;OAGG;IAEH,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAElC;;;;;;;;;;;;;;;;;;OAkBG;IAGH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAE/B;;;;OAIG;IAEH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAI/B,cAAc,EAAG,MAAM,CAAC;IAGxB,WAAW,EAAE,qBAAqB,CAAS;IAG3C,SAAS,EAAE,IAAI,CAAc;IAG7B,SAAS,EAAE,IAAI,CAAc;CAC9B"}
|