@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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -2
  3. package/dist/admin/api/comparisons-client.d.ts +24 -0
  4. package/dist/admin/api/comparisons-client.d.ts.map +1 -0
  5. package/dist/admin/api/comparisons-client.js +15 -0
  6. package/dist/admin/api/comparisons-client.js.map +1 -0
  7. package/dist/admin/index.d.ts +37 -0
  8. package/dist/admin/index.d.ts.map +1 -0
  9. package/dist/admin/index.js +49 -0
  10. package/dist/admin/index.js.map +1 -0
  11. package/dist/admin/pages/ComparisonDetailPage.d.ts +19 -0
  12. package/dist/admin/pages/ComparisonDetailPage.d.ts.map +1 -0
  13. package/dist/admin/pages/ComparisonDetailPage.js +62 -0
  14. package/dist/admin/pages/ComparisonDetailPage.js.map +1 -0
  15. package/dist/admin/pages/ComparisonsListPage.d.ts +10 -0
  16. package/dist/admin/pages/ComparisonsListPage.d.ts.map +1 -0
  17. package/dist/admin/pages/ComparisonsListPage.js +71 -0
  18. package/dist/admin/pages/ComparisonsListPage.js.map +1 -0
  19. package/dist/backend/entities/comparison-product.entity.d.ts +22 -0
  20. package/dist/backend/entities/comparison-product.entity.d.ts.map +1 -0
  21. package/dist/backend/entities/comparison-product.entity.js +54 -0
  22. package/dist/backend/entities/comparison-product.entity.js.map +1 -0
  23. package/dist/backend/entities/comparison.entity.d.ts +61 -0
  24. package/dist/backend/entities/comparison.entity.d.ts.map +1 -0
  25. package/dist/backend/entities/comparison.entity.js +115 -0
  26. package/dist/backend/entities/comparison.entity.js.map +1 -0
  27. package/dist/backend/index.d.ts +61 -0
  28. package/dist/backend/index.d.ts.map +1 -0
  29. package/dist/backend/index.js +117 -0
  30. package/dist/backend/index.js.map +1 -0
  31. package/dist/backend/request-actor.d.ts +22 -0
  32. package/dist/backend/request-actor.d.ts.map +1 -0
  33. package/dist/backend/request-actor.js +24 -0
  34. package/dist/backend/request-actor.js.map +1 -0
  35. package/dist/backend/routes.admin.d.ts +22 -0
  36. package/dist/backend/routes.admin.d.ts.map +1 -0
  37. package/dist/backend/routes.admin.js +41 -0
  38. package/dist/backend/routes.admin.js.map +1 -0
  39. package/dist/backend/routes.public.d.ts +33 -0
  40. package/dist/backend/routes.public.d.ts.map +1 -0
  41. package/dist/backend/routes.public.js +215 -0
  42. package/dist/backend/routes.public.js.map +1 -0
  43. package/dist/backend/routes.share.d.ts +27 -0
  44. package/dist/backend/routes.share.d.ts.map +1 -0
  45. package/dist/backend/routes.share.js +49 -0
  46. package/dist/backend/routes.share.js.map +1 -0
  47. package/dist/backend/services/anonymous-token-cookie.d.ts +23 -0
  48. package/dist/backend/services/anonymous-token-cookie.d.ts.map +1 -0
  49. package/dist/backend/services/anonymous-token-cookie.js +33 -0
  50. package/dist/backend/services/anonymous-token-cookie.js.map +1 -0
  51. package/dist/backend/services/asset-byte-fetcher.d.ts +37 -0
  52. package/dist/backend/services/asset-byte-fetcher.d.ts.map +1 -0
  53. package/dist/backend/services/asset-byte-fetcher.js +70 -0
  54. package/dist/backend/services/asset-byte-fetcher.js.map +1 -0
  55. package/dist/backend/services/comparable-attribute-projection.d.ts +57 -0
  56. package/dist/backend/services/comparable-attribute-projection.d.ts.map +1 -0
  57. package/dist/backend/services/comparable-attribute-projection.js +136 -0
  58. package/dist/backend/services/comparable-attribute-projection.js.map +1 -0
  59. package/dist/backend/services/comparison-admin.service.d.ts +31 -0
  60. package/dist/backend/services/comparison-admin.service.d.ts.map +1 -0
  61. package/dist/backend/services/comparison-admin.service.js +184 -0
  62. package/dist/backend/services/comparison-admin.service.js.map +1 -0
  63. package/dist/backend/services/comparison-pdf-renderer.d.ts +57 -0
  64. package/dist/backend/services/comparison-pdf-renderer.d.ts.map +1 -0
  65. package/dist/backend/services/comparison-pdf-renderer.js +211 -0
  66. package/dist/backend/services/comparison-pdf-renderer.js.map +1 -0
  67. package/dist/backend/services/comparison-service.d.ts +339 -0
  68. package/dist/backend/services/comparison-service.d.ts.map +1 -0
  69. package/dist/backend/services/comparison-service.js +588 -0
  70. package/dist/backend/services/comparison-service.js.map +1 -0
  71. package/dist/backend/services/share-token-generator.d.ts +20 -0
  72. package/dist/backend/services/share-token-generator.d.ts.map +1 -0
  73. package/dist/backend/services/share-token-generator.js +23 -0
  74. package/dist/backend/services/share-token-generator.js.map +1 -0
  75. package/dist/manifest.d.ts +199 -0
  76. package/dist/manifest.d.ts.map +1 -0
  77. package/dist/manifest.js +179 -0
  78. package/dist/manifest.js.map +1 -0
  79. package/dist/migrations/20260501T185834_comparisons_init.d.ts +32 -0
  80. package/dist/migrations/20260501T185834_comparisons_init.d.ts.map +1 -0
  81. package/dist/migrations/20260501T185834_comparisons_init.js +102 -0
  82. package/dist/migrations/20260501T185834_comparisons_init.js.map +1 -0
  83. package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts +84 -0
  84. package/dist/migrations/20260830T163143_comparisons_organization_attribution.d.ts.map +1 -0
  85. package/dist/migrations/20260830T163143_comparisons_organization_attribution.js +147 -0
  86. package/dist/migrations/20260830T163143_comparisons_organization_attribution.js.map +1 -0
  87. package/dist/migrations/index.d.ts +28 -0
  88. package/dist/migrations/index.d.ts.map +1 -0
  89. package/dist/migrations/index.js +31 -0
  90. package/dist/migrations/index.js.map +1 -0
  91. package/docs/comparisons.md +278 -0
  92. package/i18n/en.json +51 -0
  93. package/i18n/pl.json +51 -0
  94. package/package.json +102 -3
  95. 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
- # Temporary Holding Version
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
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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"}