@open-mercato/core 0.6.8-develop.6992.1.00c90fecff → 0.6.8-develop.6998.1.d2bc46c56c
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/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/sync_run/index.js +2 -0
- package/dist/generated/entities/sync_run/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +1 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +1 -1
- package/dist/modules/auth/lib/backendChrome.js +3 -3
- package/dist/modules/auth/lib/backendChrome.js.map +1 -1
- package/dist/modules/auth/services/sidebarPreferencesService.js +7 -2
- package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
- package/dist/modules/data_sync/api/options.js +1 -0
- package/dist/modules/data_sync/api/options.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +15 -1
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +23 -1
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/route.js +1 -0
- package/dist/modules/data_sync/api/runs/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +60 -17
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js +34 -2
- package/dist/modules/data_sync/backend/data-sync/runs/[id]/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +21 -15
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/data_sync/components/RunParameterFields.js +135 -0
- package/dist/modules/data_sync/components/RunParameterFields.js.map +7 -0
- package/dist/modules/data_sync/data/entities.js +3 -0
- package/dist/modules/data_sync/data/entities.js.map +2 -2
- package/dist/modules/data_sync/data/validators.js +4 -1
- package/dist/modules/data_sync/data/validators.js.map +2 -2
- package/dist/modules/data_sync/lib/run-parameters.js +108 -0
- package/dist/modules/data_sync/lib/run-parameters.js.map +7 -0
- package/dist/modules/data_sync/lib/start-run.js +1 -0
- package/dist/modules/data_sync/lib/start-run.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-engine.js +4 -2
- package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
- package/dist/modules/data_sync/lib/sync-run-service.js +1 -0
- package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
- package/dist/modules/data_sync/migrations/Migration20260810120000.js +13 -0
- package/dist/modules/data_sync/migrations/Migration20260810120000.js.map +7 -0
- package/dist/modules/data_sync/workers/sync-scheduled.js +23 -2
- package/dist/modules/data_sync/workers/sync-scheduled.js.map +2 -2
- package/dist/modules/query_index/di.js +125 -48
- package/dist/modules/query_index/di.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +39 -1
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +26 -5
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/generated/entities/sync_run/index.ts +1 -0
- package/generated/entity-fields-registry.ts +1 -0
- package/package.json +7 -7
- package/src/modules/auth/api/sidebar/preferences/route.ts +2 -2
- package/src/modules/auth/lib/backendChrome.tsx +2 -2
- package/src/modules/auth/services/sidebarPreferencesService.ts +22 -3
- package/src/modules/data_sync/AGENTS.md +83 -8
- package/src/modules/data_sync/api/options.ts +1 -0
- package/src/modules/data_sync/api/run.ts +17 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +32 -1
- package/src/modules/data_sync/api/runs/[id]/route.ts +1 -0
- package/src/modules/data_sync/backend/data-sync/page.tsx +73 -17
- package/src/modules/data_sync/backend/data-sync/runs/[id]/page.tsx +62 -2
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +28 -15
- package/src/modules/data_sync/components/RunParameterFields.tsx +229 -0
- package/src/modules/data_sync/data/entities.ts +4 -1
- package/src/modules/data_sync/data/validators.ts +3 -0
- package/src/modules/data_sync/i18n/de.json +10 -0
- package/src/modules/data_sync/i18n/en.json +10 -0
- package/src/modules/data_sync/i18n/es.json +10 -0
- package/src/modules/data_sync/i18n/ko.json +10 -0
- package/src/modules/data_sync/i18n/pl.json +10 -0
- package/src/modules/data_sync/lib/adapter.ts +88 -0
- package/src/modules/data_sync/lib/run-parameters.ts +196 -0
- package/src/modules/data_sync/lib/start-run.ts +2 -0
- package/src/modules/data_sync/lib/sync-engine.ts +5 -1
- package/src/modules/data_sync/lib/sync-run-service.ts +2 -0
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +17 -1
- package/src/modules/data_sync/migrations/Migration20260810120000.ts +13 -0
- package/src/modules/data_sync/workers/sync-scheduled.ts +32 -1
- package/src/modules/query_index/di.ts +153 -56
- package/src/modules/sales/api/documents/factory.ts +62 -1
- package/src/modules/sales/commands/documents.ts +40 -4
- package/src/modules/sales/i18n/de.json +1 -0
- package/src/modules/sales/i18n/en.json +1 -0
- package/src/modules/sales/i18n/es.json +1 -0
- package/src/modules/sales/i18n/ko.json +1 -0
- package/src/modules/sales/i18n/pl.json +1 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/data_sync/workers/sync-scheduled.ts"],
|
|
4
|
-
"sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { SyncSchedule } from '../data/entities'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { resolveAdapterForIntegration, resolveStartCursor } from '../lib/start-cursor'\n\ntype ScheduledSyncPayload = {\n scheduleId: string\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'data-sync-scheduled',\n id: 'data-sync:scheduled',\n concurrency: 3,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<ScheduledSyncPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const syncRunService = ctx.resolve<SyncRunService>('dataSyncRunService')\n const progressService = ctx.resolve<ProgressService>('progressService')\n const integrationStateService = ctx.resolve<IntegrationStateService>('integrationStateService')\n\n const schedule = await findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id: job.payload.scheduleId,\n organizationId: job.payload.scope.organizationId,\n tenantId: job.payload.scope.tenantId,\n deletedAt: null,\n },\n undefined,\n job.payload.scope,\n )\n\n if (!schedule || !schedule.isEnabled) {\n return\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(schedule.integrationId, job.payload.scope)\n if (!integrationEnabled) {\n return\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n schedule.integrationId,\n schedule.entityType,\n schedule.direction,\n job.payload.scope,\n )\n if (overlap) {\n return\n }\n\n const cursor = schedule.fullSync\n ? null\n : await resolveStartCursor({\n syncRunService,\n adapter
|
|
5
|
-
"mappings": "AAEA,SAAS,6BAA6B;AAItC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,8BAA8B,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import type { JobContext, QueuedJob, WorkerMeta } from '@open-mercato/queue'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { IntegrationStateService } from '../../integrations/lib/state-service'\nimport type { ProgressService } from '../../progress/lib/progressService'\nimport type { SyncRunService } from '../lib/sync-run-service'\nimport { SyncSchedule } from '../data/entities'\nimport { startDataSyncRun } from '../lib/start-run'\nimport { resolveAdapterForIntegration, resolveStartCursor } from '../lib/start-cursor'\nimport { normalizeRunParameters } from '../lib/run-parameters'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('data_sync').child({ component: 'sync-scheduled' })\n\ntype ScheduledSyncPayload = {\n scheduleId: string\n scope: {\n organizationId: string\n tenantId: string\n }\n}\n\nexport const metadata: WorkerMeta = {\n queue: 'data-sync-scheduled',\n id: 'data-sync:scheduled',\n concurrency: 3,\n}\n\ntype HandlerContext = JobContext & {\n resolve: <T = unknown>(name: string) => T\n}\n\nexport default async function handle(job: QueuedJob<ScheduledSyncPayload>, ctx: HandlerContext): Promise<void> {\n const em = ctx.resolve<EntityManager>('em')\n const syncRunService = ctx.resolve<SyncRunService>('dataSyncRunService')\n const progressService = ctx.resolve<ProgressService>('progressService')\n const integrationStateService = ctx.resolve<IntegrationStateService>('integrationStateService')\n\n const schedule = await findOneWithDecryption(\n em,\n SyncSchedule,\n {\n id: job.payload.scheduleId,\n organizationId: job.payload.scope.organizationId,\n tenantId: job.payload.scope.tenantId,\n deletedAt: null,\n },\n undefined,\n job.payload.scope,\n )\n\n if (!schedule || !schedule.isEnabled) {\n return\n }\n\n const integrationEnabled = await integrationStateService.isEnabled(schedule.integrationId, job.payload.scope)\n if (!integrationEnabled) {\n return\n }\n\n const overlap = await syncRunService.findRunningOverlap(\n schedule.integrationId,\n schedule.entityType,\n schedule.direction,\n job.payload.scope,\n )\n if (overlap) {\n return\n }\n\n const adapter = resolveAdapterForIntegration(schedule.integrationId)\n\n const cursor = schedule.fullSync\n ? null\n : await resolveStartCursor({\n syncRunService,\n adapter,\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n scope: job.payload.scope,\n })\n\n // A schedule carries no parameter form, but the adapter's declared defaults\n // still apply: normalizing an empty input materializes exactly those, so a\n // scheduled run reaches the adapter with the same set a manual run would\n // rather than an empty object. A declaration whose own default is invalid\n // stops the run instead of handing over a half-applied set.\n const normalizedParameters = normalizeRunParameters(\n adapter?.runParameters,\n schedule.direction,\n null,\n schedule.entityType,\n )\n if (!normalizedParameters.ok) {\n logger.error('Scheduled run skipped: adapter declares invalid run parameter defaults', {\n scheduleId: schedule.id,\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n keys: normalizedParameters.errors.map((error) => error.key),\n })\n return\n }\n const parameters = Object.keys(normalizedParameters.values).length > 0\n ? normalizedParameters.values\n : null\n\n schedule.lastRunAt = new Date()\n await em.flush()\n\n await startDataSyncRun({\n syncRunService,\n progressService,\n scope: job.payload.scope,\n input: {\n integrationId: schedule.integrationId,\n entityType: schedule.entityType,\n direction: schedule.direction,\n cursor,\n triggeredBy: 'scheduler',\n parameters,\n },\n })\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,6BAA6B;AAItC,SAAS,oBAAoB;AAC7B,SAAS,wBAAwB;AACjC,SAAS,8BAA8B,0BAA0B;AACjE,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW,EAAE,MAAM,EAAE,WAAW,iBAAiB,CAAC;AAUvE,MAAM,WAAuB;AAAA,EAClC,OAAO;AAAA,EACP,IAAI;AAAA,EACJ,aAAa;AACf;AAMA,eAAO,OAA8B,KAAsC,KAAoC;AAC7G,QAAM,KAAK,IAAI,QAAuB,IAAI;AAC1C,QAAM,iBAAiB,IAAI,QAAwB,oBAAoB;AACvE,QAAM,kBAAkB,IAAI,QAAyB,iBAAiB;AACtE,QAAM,0BAA0B,IAAI,QAAiC,yBAAyB;AAE9F,QAAM,WAAW,MAAM;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,MACE,IAAI,IAAI,QAAQ;AAAA,MAChB,gBAAgB,IAAI,QAAQ,MAAM;AAAA,MAClC,UAAU,IAAI,QAAQ,MAAM;AAAA,MAC5B,WAAW;AAAA,IACb;AAAA,IACA;AAAA,IACA,IAAI,QAAQ;AAAA,EACd;AAEA,MAAI,CAAC,YAAY,CAAC,SAAS,WAAW;AACpC;AAAA,EACF;AAEA,QAAM,qBAAqB,MAAM,wBAAwB,UAAU,SAAS,eAAe,IAAI,QAAQ,KAAK;AAC5G,MAAI,CAAC,oBAAoB;AACvB;AAAA,EACF;AAEA,QAAM,UAAU,MAAM,eAAe;AAAA,IACnC,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,IAAI,QAAQ;AAAA,EACd;AACA,MAAI,SAAS;AACX;AAAA,EACF;AAEA,QAAM,UAAU,6BAA6B,SAAS,aAAa;AAEnE,QAAM,SAAS,SAAS,WACpB,OACA,MAAM,mBAAmB;AAAA,IACvB;AAAA,IACA;AAAA,IACA,eAAe,SAAS;AAAA,IACxB,YAAY,SAAS;AAAA,IACrB,WAAW,SAAS;AAAA,IACpB,OAAO,IAAI,QAAQ;AAAA,EACrB,CAAC;AAOL,QAAM,uBAAuB;AAAA,IAC3B,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA,EACX;AACA,MAAI,CAAC,qBAAqB,IAAI;AAC5B,WAAO,MAAM,0EAA0E;AAAA,MACrF,YAAY,SAAS;AAAA,MACrB,eAAe,SAAS;AAAA,MACxB,YAAY,SAAS;AAAA,MACrB,MAAM,qBAAqB,OAAO,IAAI,CAAC,UAAU,MAAM,GAAG;AAAA,IAC5D,CAAC;AACD;AAAA,EACF;AACA,QAAM,aAAa,OAAO,KAAK,qBAAqB,MAAM,EAAE,SAAS,IACjE,qBAAqB,SACrB;AAEJ,WAAS,YAAY,oBAAI,KAAK;AAC9B,QAAM,GAAG,MAAM;AAEf,QAAM,iBAAiB;AAAA,IACrB;AAAA,IACA;AAAA,IACA,OAAO,IAAI,QAAQ;AAAA,IACnB,OAAO;AAAA,MACL,eAAe,SAAS;AAAA,MACxB,YAAY,SAAS;AAAA,MACrB,WAAW,SAAS;AAAA,MACpB;AAAA,MACA,aAAa;AAAA,MACb;AAAA,IACF;AAAA,EACF,CAAC;AACH;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,11 +1,85 @@
|
|
|
1
|
-
import { BasicQueryEngine, resolveEntityTableName } from "@open-mercato/shared/lib/query/engine";
|
|
2
|
-
import { HybridQueryEngine } from "./lib/engine.js";
|
|
3
1
|
import { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from "@open-mercato/shared/lib/crud/types";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
|
|
3
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
4
|
+
import { BasicQueryEngine } from "@open-mercato/shared/lib/query/engine";
|
|
5
|
+
import { HybridQueryEngine } from "./lib/engine.js";
|
|
6
|
+
import {
|
|
7
|
+
loadQueryIndexRowScope,
|
|
8
|
+
QueryIndexScopeError,
|
|
9
|
+
resolveQueryIndexRecordScope,
|
|
10
|
+
resolveQueryIndexSourceMetadata
|
|
11
|
+
} from "./lib/subscriber-scope.js";
|
|
12
|
+
const logger = createLogger("query_index").child({ component: "crud-bridge" });
|
|
13
|
+
function hasOwn(value, key) {
|
|
14
|
+
return !!value && typeof value === "object" && Object.prototype.hasOwnProperty.call(value, key);
|
|
15
|
+
}
|
|
16
|
+
function readScopeValue(source, keys) {
|
|
17
|
+
const sourceRecord = source && typeof source === "object" ? source : {};
|
|
18
|
+
const key = keys.find((candidate) => hasOwn(source, candidate));
|
|
19
|
+
if (!key) return { value: void 0, present: false };
|
|
20
|
+
const value = sourceRecord[key];
|
|
21
|
+
if (value === null) return { value: null, present: true };
|
|
22
|
+
if (typeof value === "string" && value.trim().length > 0) return { value, present: true };
|
|
23
|
+
return { value: void 0, present: false };
|
|
24
|
+
}
|
|
25
|
+
async function resolveBridgeRecordScope(em, entityType, recordId, payload, action, options) {
|
|
26
|
+
let organization = readScopeValue(payload, ["organizationId", "orgId"]);
|
|
27
|
+
let tenant = readScopeValue(payload, ["tenantId"]);
|
|
28
|
+
if (organization.present && tenant.present && options?.validateCompletePayload !== true) {
|
|
29
|
+
return {
|
|
30
|
+
organizationId: organization.value ?? null,
|
|
31
|
+
tenantId: tenant.value ?? null,
|
|
32
|
+
sourceValidated: false
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const source = resolveQueryIndexSourceMetadata(em, entityType);
|
|
36
|
+
const sourceScope = await loadQueryIndexRowScope(em, source, recordId);
|
|
37
|
+
if (sourceScope.kind === "global") {
|
|
38
|
+
if (!organization.present) organization = { value: null, present: true };
|
|
39
|
+
if (!tenant.present) tenant = { value: null, present: true };
|
|
40
|
+
} else if (sourceScope.kind === "missing" && action === "upsert") {
|
|
41
|
+
throw new QueryIndexScopeError(
|
|
42
|
+
"Query index upsert event source row scope could not be resolved"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const resolvedScope = resolveQueryIndexRecordScope({
|
|
46
|
+
payloadOrganizationId: organization.value,
|
|
47
|
+
payloadTenantId: tenant.value,
|
|
48
|
+
hasPayloadOrganizationId: organization.present,
|
|
49
|
+
hasPayloadTenantId: tenant.present,
|
|
50
|
+
sourceScope
|
|
51
|
+
});
|
|
52
|
+
return { ...resolvedScope, sourceValidated: true };
|
|
53
|
+
}
|
|
54
|
+
async function recordBridgeError(input) {
|
|
55
|
+
const handler = `event:query_index.crud_bridge.${input.handler}`;
|
|
56
|
+
if (!input.em) {
|
|
57
|
+
logger.error("CRUD bridge event failed before the entity manager was resolved", {
|
|
58
|
+
handler,
|
|
59
|
+
entityType: input.entityType,
|
|
60
|
+
recordId: input.recordId,
|
|
61
|
+
err: input.error
|
|
62
|
+
});
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
await recordIndexerError(
|
|
66
|
+
{ em: input.em },
|
|
67
|
+
{
|
|
68
|
+
source: "query_index",
|
|
69
|
+
handler,
|
|
70
|
+
error: input.error,
|
|
71
|
+
entityType: input.entityType,
|
|
72
|
+
recordId: input.recordId,
|
|
73
|
+
payload: input.payload
|
|
74
|
+
}
|
|
75
|
+
).catch((loggingError) => {
|
|
76
|
+
logger.error("Failed to record CRUD bridge error", {
|
|
77
|
+
handler,
|
|
78
|
+
entityType: input.entityType,
|
|
79
|
+
recordId: input.recordId,
|
|
80
|
+
err: loggingError
|
|
81
|
+
});
|
|
82
|
+
});
|
|
9
83
|
}
|
|
10
84
|
function register(container) {
|
|
11
85
|
try {
|
|
@@ -61,23 +135,21 @@ function register(container) {
|
|
|
61
135
|
return;
|
|
62
136
|
}
|
|
63
137
|
const makeUpsertHandler = (entityType) => async (payload, ctx) => {
|
|
138
|
+
let em = null;
|
|
139
|
+
let id = "";
|
|
64
140
|
try {
|
|
65
141
|
if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
let tenantId = payload?.tenantId || ctx?.tenantId || null;
|
|
69
|
-
const id = String(payload?.id || payload?.recordId || "");
|
|
142
|
+
em = ctx.resolve("em");
|
|
143
|
+
id = String(payload?.id || payload?.recordId || "");
|
|
70
144
|
if (!id) return;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
145
|
+
const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(
|
|
146
|
+
em,
|
|
147
|
+
entityType,
|
|
148
|
+
id,
|
|
149
|
+
payload,
|
|
150
|
+
"upsert"
|
|
151
|
+
);
|
|
152
|
+
let hasCustomFields = null;
|
|
81
153
|
try {
|
|
82
154
|
const db = em.getKysely();
|
|
83
155
|
let cfQuery = db.selectFrom("custom_field_defs").select(["id"]).where("entity_id", "=", entityType).where("is_active", "=", true);
|
|
@@ -97,42 +169,47 @@ function register(container) {
|
|
|
97
169
|
} else {
|
|
98
170
|
cfQuery = cfQuery.where("tenant_id", "is", null);
|
|
99
171
|
}
|
|
100
|
-
|
|
101
|
-
if (!hasCf) return;
|
|
172
|
+
hasCustomFields = !!await cfQuery.executeTakeFirst();
|
|
102
173
|
} catch {
|
|
103
174
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
175
|
+
if (hasCustomFields === false) {
|
|
176
|
+
if (!sourceValidated) {
|
|
177
|
+
await resolveBridgeRecordScope(
|
|
178
|
+
em,
|
|
179
|
+
entityType,
|
|
180
|
+
id,
|
|
181
|
+
payload,
|
|
182
|
+
"upsert",
|
|
183
|
+
{ validateCompletePayload: true }
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
return;
|
|
108
187
|
}
|
|
109
|
-
|
|
188
|
+
const bus2 = ctx.resolve("eventBus");
|
|
189
|
+
await bus2.emitEvent("query_index.upsert_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
await recordBridgeError({ em, handler: "upsert", error, entityType, recordId: id, payload });
|
|
110
192
|
}
|
|
111
193
|
};
|
|
112
194
|
const makeDeleteHandler = (entityType) => async (payload, ctx) => {
|
|
195
|
+
let em = null;
|
|
196
|
+
let id = "";
|
|
113
197
|
try {
|
|
114
198
|
if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let tenantId = payload?.tenantId || ctx?.tenantId || null;
|
|
118
|
-
const id = String(payload?.id || payload?.recordId || "");
|
|
199
|
+
em = ctx.resolve("em");
|
|
200
|
+
id = String(payload?.id || payload?.recordId || "");
|
|
119
201
|
if (!id) return;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const bus2 = ctx.resolve("eventBus");
|
|
132
|
-
await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
133
|
-
} catch {
|
|
134
|
-
}
|
|
135
|
-
} catch {
|
|
202
|
+
const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(
|
|
203
|
+
em,
|
|
204
|
+
entityType,
|
|
205
|
+
id,
|
|
206
|
+
payload,
|
|
207
|
+
"delete"
|
|
208
|
+
);
|
|
209
|
+
const bus2 = ctx.resolve("eventBus");
|
|
210
|
+
await bus2.emitEvent("query_index.delete_one", { entityType, recordId: id, organizationId: orgId, tenantId });
|
|
211
|
+
} catch (error) {
|
|
212
|
+
await recordBridgeError({ em, handler: "delete", error, entityType, recordId: id, payload });
|
|
136
213
|
}
|
|
137
214
|
};
|
|
138
215
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/query_index/di.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { BasicQueryEngine, resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport { markDeleted } from './lib/indexer'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\n\nfunction toEntityTypeFromEvent(event: string): string | null {\n // Expect '<module>.<entity>.<action>'\n const parts = event.split('.')\n if (parts.length !== 3) return null\n const [mod, ent] = parts\n return `${mod}:${ent}`\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n // Optional: only index when custom field definitions exist for this entity (org/global)\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n const hasCf = await cfQuery.executeTakeFirst()\n if (!hasCf) return\n } catch {}\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\nimport type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY } from '@open-mercato/shared/lib/crud/types'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { BasicQueryEngine } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport {\n loadQueryIndexRowScope,\n QueryIndexScopeError,\n resolveQueryIndexRecordScope,\n resolveQueryIndexSourceMetadata,\n} from './lib/subscriber-scope'\n\nconst logger = createLogger('query_index').child({ component: 'crud-bridge' })\n\nfunction hasOwn(value: unknown, key: string): boolean {\n return !!value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction readScopeValue(\n source: unknown,\n keys: string[],\n): { value: string | null | undefined; present: boolean } {\n const sourceRecord = source && typeof source === 'object' ? source as Record<string, unknown> : {}\n const key = keys.find((candidate) => hasOwn(source, candidate))\n if (!key) return { value: undefined, present: false }\n const value = sourceRecord[key]\n if (value === null) return { value: null, present: true }\n if (typeof value === 'string' && value.trim().length > 0) return { value, present: true }\n return { value: undefined, present: false }\n}\n\nasync function resolveBridgeRecordScope(\n em: EntityManager,\n entityType: string,\n recordId: string,\n payload: unknown,\n action: 'upsert' | 'delete',\n options?: { validateCompletePayload?: boolean },\n) {\n let organization = readScopeValue(payload, ['organizationId', 'orgId'])\n let tenant = readScopeValue(payload, ['tenantId'])\n if (organization.present && tenant.present && options?.validateCompletePayload !== true) {\n return {\n organizationId: organization.value ?? null,\n tenantId: tenant.value ?? null,\n sourceValidated: false,\n }\n }\n const source = resolveQueryIndexSourceMetadata(em, entityType)\n const sourceScope = await loadQueryIndexRowScope(em, source, recordId)\n\n if (sourceScope.kind === 'global') {\n if (!organization.present) organization = { value: null, present: true }\n if (!tenant.present) tenant = { value: null, present: true }\n } else if (sourceScope.kind === 'missing' && action === 'upsert') {\n throw new QueryIndexScopeError(\n 'Query index upsert event source row scope could not be resolved',\n )\n }\n\n const resolvedScope = resolveQueryIndexRecordScope({\n payloadOrganizationId: organization.value,\n payloadTenantId: tenant.value,\n hasPayloadOrganizationId: organization.present,\n hasPayloadTenantId: tenant.present,\n sourceScope,\n })\n return { ...resolvedScope, sourceValidated: true }\n}\n\nasync function recordBridgeError(input: {\n em: EntityManager | null\n handler: 'upsert' | 'delete'\n error: unknown\n entityType: string\n recordId: string\n payload: unknown\n}): Promise<void> {\n const handler = `event:query_index.crud_bridge.${input.handler}`\n if (!input.em) {\n logger.error('CRUD bridge event failed before the entity manager was resolved', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: input.error,\n })\n return\n }\n await recordIndexerError(\n { em: input.em },\n {\n source: 'query_index',\n handler,\n error: input.error,\n entityType: input.entityType,\n recordId: input.recordId,\n payload: input.payload,\n },\n ).catch((loggingError) => {\n logger.error('Failed to record CRUD bridge error', {\n handler,\n entityType: input.entityType,\n recordId: input.recordId,\n err: loggingError,\n })\n })\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n // DataEngine emits the canonical query_index.upsert_one itself. The\n // bridge only covers domain events from write paths that do not own an\n // indexer, otherwise failures and error logs are duplicated.\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId, sourceValidated } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n )\n // Optional: only index when custom field definitions exist for this entity (org/global)\n let hasCustomFields: boolean | null = null\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n hasCustomFields = !!await cfQuery.executeTakeFirst()\n } catch {}\n if (hasCustomFields === false) {\n if (!sourceValidated) {\n await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'upsert',\n { validateCompletePayload: true },\n )\n }\n return\n }\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'upsert', error, entityType, recordId: id, payload })\n }\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n let em: EntityManager | null = null\n let id = ''\n try {\n if (payload?.[CRUD_QUERY_INDEX_MANAGED_PAYLOAD_KEY] === true) return\n em = ctx.resolve('em') as EntityManager\n id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n const { organizationId: orgId, tenantId } = await resolveBridgeRecordScope(\n em,\n entityType,\n id,\n payload,\n 'delete',\n )\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch (error) {\n await recordBridgeError({ em, handler: 'delete', error, entityType, recordId: id, payload })\n }\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["bus"]
|
|
7
7
|
}
|
|
@@ -3,7 +3,8 @@ import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
|
3
3
|
import { splitCustomFieldPayload, extractAllCustomFieldEntries } from "@open-mercato/shared/lib/crud/custom-fields";
|
|
4
4
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
5
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
6
|
-
import { SalesDocumentTagAssignment } from "../../data/entities.js";
|
|
6
|
+
import { SalesChannel, SalesDocumentTagAssignment } from "../../data/entities.js";
|
|
7
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
7
8
|
import {
|
|
8
9
|
ORDER_PAYMENT_LEDGER_FIELDS,
|
|
9
10
|
ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
@@ -230,6 +231,39 @@ const attachTags = async (payload, ctx) => {
|
|
|
230
231
|
if (list) item.tags = list;
|
|
231
232
|
});
|
|
232
233
|
};
|
|
234
|
+
const attachChannelNames = async (payload, ctx) => {
|
|
235
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
236
|
+
if (!items.length) return;
|
|
237
|
+
const channelIds = Array.from(
|
|
238
|
+
new Set(
|
|
239
|
+
items.map((item) => item && typeof item.channelId === "string" ? item.channelId : null).filter((value) => !!value)
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
if (!channelIds.length) return;
|
|
243
|
+
const em = ctx?.container?.resolve ? ctx.container.resolve("em") : null;
|
|
244
|
+
if (!em) return;
|
|
245
|
+
const where = { id: { $in: channelIds } };
|
|
246
|
+
if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId;
|
|
247
|
+
const orgIds = Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length ? ctx.organizationIds.filter((val) => !!val) : ctx?.selectedOrganizationId ? [ctx.selectedOrganizationId] : [];
|
|
248
|
+
if (orgIds.length) where.organizationId = { $in: orgIds };
|
|
249
|
+
const channels = await findWithDecryption(
|
|
250
|
+
em,
|
|
251
|
+
SalesChannel,
|
|
252
|
+
where,
|
|
253
|
+
{ fields: ["id", "name", "code"] },
|
|
254
|
+
{
|
|
255
|
+
tenantId: ctx?.auth?.tenantId ?? null,
|
|
256
|
+
organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
const byId = new Map(channels.map((channel) => [channel.id, channel]));
|
|
260
|
+
items.forEach((item) => {
|
|
261
|
+
if (!item || typeof item.channelId !== "string") return;
|
|
262
|
+
const channel = byId.get(item.channelId);
|
|
263
|
+
item.channelName = channel?.name ?? null;
|
|
264
|
+
item.channelCode = channel?.code ?? null;
|
|
265
|
+
});
|
|
266
|
+
};
|
|
233
267
|
async function ensureNumberEditPermission(ctx, translate) {
|
|
234
268
|
const rbac = ctx.container?.resolve?.("rbacService");
|
|
235
269
|
const auth = ctx.auth;
|
|
@@ -462,6 +496,7 @@ function buildDocumentCrudOptions(binding) {
|
|
|
462
496
|
hooks: {
|
|
463
497
|
afterList: async (payload, ctx) => {
|
|
464
498
|
await attachTags(payload, { ...ctx, bindingKind: binding.kind });
|
|
499
|
+
await attachChannelNames(payload, ctx);
|
|
465
500
|
if (binding.kind === "order" && Array.isArray(payload?.items) && payload.items.length === 1) {
|
|
466
501
|
const item = payload.items[0];
|
|
467
502
|
const orderId = typeof item?.id === "string" ? item.id : null;
|
|
@@ -527,6 +562,8 @@ function buildDocumentOpenApi(binding) {
|
|
|
527
562
|
paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
528
563
|
currencyCode: z.string().nullable(),
|
|
529
564
|
channelId: z.string().uuid().nullable(),
|
|
565
|
+
channelName: z.string().nullable().optional(),
|
|
566
|
+
channelCode: z.string().nullable().optional(),
|
|
530
567
|
organizationId: z.string().uuid().nullable(),
|
|
531
568
|
tenantId: z.string().uuid().nullable(),
|
|
532
569
|
validFrom: z.string().nullable().optional(),
|
|
@@ -578,6 +615,7 @@ function createDocumentCrudRoute(binding) {
|
|
|
578
615
|
return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata };
|
|
579
616
|
}
|
|
580
617
|
export {
|
|
618
|
+
attachChannelNames,
|
|
581
619
|
buildDocumentCrudOptions,
|
|
582
620
|
buildDocumentOpenApi,
|
|
583
621
|
createDocumentCrudRoute
|