@open-mercato/core 0.6.8-develop.6992.1.00c90fecff → 0.6.8-develop.6993.1.3fc50f3409
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.
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/documents/factory.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { splitCustomFieldPayload, extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { E } from '#generated/entities.ids.generated'\nimport type { SalesOrder, SalesQuote } from '../../data/entities'\nimport { SalesDocumentTagAssignment } from '../../data/entities'\nimport {\n ORDER_PAYMENT_LEDGER_FIELDS,\n ORDER_PAYMENT_LEDGER_WARNING_CODE,\n orderCreateSchema,\n quoteCreateSchema,\n type OrderPaymentLedgerWarning,\n} from '../../data/validators'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultDeleteRequestSchema,\n} from '../openapi'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { documentUpdateSchema } from '../../commands/documents'\nimport { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { parseIdsParam } from '@open-mercato/shared/lib/crud/ids'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { recalculateOrderTotalsForDisplay } from '../../commands/returns'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\ntype DocumentKind = 'order' | 'quote'\n\ntype DocumentBinding = {\n kind: DocumentKind\n entity: typeof SalesOrder | typeof SalesQuote\n entityId: (typeof E.sales)[keyof typeof E.sales]\n numberField: 'orderNumber' | 'quoteNumber'\n createCommandId: string\n updateCommandId: string\n deleteCommandId: string\n manageFeature: string\n viewFeature: string\n}\n\ntype DocumentCreateResult = {\n orderId?: string\n quoteId?: string\n id?: string\n warnings?: OrderPaymentLedgerWarning[]\n}\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst normalizeJsonRecord = (value: unknown): Record<string, unknown> | null => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n if (typeof value !== 'string') return null\n const parsed = parseDecryptedFieldValue(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : null\n}\n\nconst resolveCustomerName = (snapshot: Record<string, unknown> | null, fallback?: string | null) => {\n if (!snapshot) return fallback ?? null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const contact = snapshot.contact as Record<string, unknown> | undefined\n const displayName = typeof customer?.displayName === 'string' ? customer.displayName : null\n if (displayName) return displayName\n const first = typeof contact?.firstName === 'string' ? contact.firstName : null\n const last = typeof contact?.lastName === 'string' ? contact.lastName : null\n const preferred = typeof contact?.preferredName === 'string' ? contact.preferredName : null\n const parts = [preferred ?? first, last].filter((part) => part && part.trim().length)\n if (parts.length) return parts.join(' ')\n return fallback ?? null\n}\n\nconst resolveCustomerEmail = (snapshot: Record<string, unknown> | null) => {\n if (!snapshot) return null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const primary = typeof customer?.primaryEmail === 'string' ? customer.primaryEmail : null\n return primary ?? null\n}\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n id: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z\n .string()\n .optional()\n .describe(\n 'Comma-separated sales channel uuids; matches documents on any of them. Capped at 200 ids, malformed entries are dropped. Ignored when channelId is supplied; combines with channelIdsEmpty.',\n ),\n channelIdsEmpty: z\n .string()\n .optional()\n .describe(\n 'Boolean token; matches documents with no sales channel. Ignored when channelId is supplied; combines with channelIds.',\n ),\n lineItemCountMin: z.coerce.number().min(0).optional(),\n lineItemCountMax: z.coerce.number().min(0).optional(),\n totalNetMin: z.coerce.number().optional(),\n totalNetMax: z.coerce.number().optional(),\n totalGrossMin: z.coerce.number().optional(),\n totalGrossMax: z.coerce.number().optional(),\n dateFrom: z.string().optional(),\n dateTo: z.string().optional(),\n tagIds: z.string().optional(),\n tagIdsEmpty: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n })\n .passthrough()\n\ntype ListQuery = z.infer<typeof listSchema>\n\nfunction buildFilters(query: ListQuery, numberColumn: string, kind: DocumentKind) {\n const filters: Record<string, unknown> = {}\n if (query.id) filters.id = { $eq: query.id }\n if (query.search && query.search.trim().length > 0) {\n const term = buildIlikeTerm(query.search.trim())\n filters[numberColumn] = { $ilike: term }\n }\n if (query.customerId) {\n filters.customer_entity_id = { $eq: query.customerId }\n }\n // Singular wins over plural, mirroring how `api/channels/route.ts` resolves `id` before `ids`.\n // An all-malformed `channelIds` narrows to no channel filter rather than an empty-set filter, so\n // a typo returns the unfiltered page instead of silently returning zero rows.\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdsParam(query.channelIds)\n const wantsUnassigned = parseBooleanToken(query.channelIdsEmpty) === true\n if (channelIds.length && wantsUnassigned) {\n // A channel multi-select with an \"(No channel)\" entry produces both at once, so they combine\n // rather than one silently dropping the other. `filters.$or` is a single key \u2014 a future filter\n // that also needs `$or` would clobber this one; nothing else in this factory writes it today.\n filters.$or = [{ channel_id: { $in: channelIds } }, { channel_id: { $exists: false } }]\n } else if (wantsUnassigned) {\n filters.channel_id = { $exists: false }\n } else if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n const lineRange: Record<string, number> = {}\n if (typeof query.lineItemCountMin === 'number') lineRange.$gte = query.lineItemCountMin\n if (typeof query.lineItemCountMax === 'number') lineRange.$lte = query.lineItemCountMax\n if (Object.keys(lineRange).length) {\n filters.line_item_count = lineRange\n }\n const netRange: Record<string, number> = {}\n if (typeof query.totalNetMin === 'number') netRange.$gte = query.totalNetMin\n if (typeof query.totalNetMax === 'number') netRange.$lte = query.totalNetMax\n if (Object.keys(netRange).length) {\n filters.grand_total_net_amount = netRange\n }\n const grossRange: Record<string, number> = {}\n if (typeof query.totalGrossMin === 'number') grossRange.$gte = query.totalGrossMin\n if (typeof query.totalGrossMax === 'number') grossRange.$lte = query.totalGrossMax\n if (Object.keys(grossRange).length) {\n filters.grand_total_gross_amount = grossRange\n }\n const dateRange: Record<string, Date> = {}\n if (query.dateFrom) {\n const from = new Date(query.dateFrom)\n if (!Number.isNaN(from.getTime())) dateRange.$gte = from\n }\n if (query.dateTo) {\n const to = new Date(query.dateTo)\n if (!Number.isNaN(to.getTime())) dateRange.$lte = to\n }\n if (Object.keys(dateRange).length) {\n filters.created_at = dateRange\n }\n const tagIdsRaw = typeof query.tagIds === 'string' ? query.tagIds : ''\n const tagIds = tagIdsRaw\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (parseBooleanToken(query.tagIdsEmpty) === true) {\n filters.id = { $eq: '00000000-0000-0000-0000-000000000000' }\n } else if (tagIds.length) {\n filters['tag_assignments.tag_id'] = { $in: tagIds }\n filters['tag_assignments.document_kind'] = { $eq: kind }\n }\n return filters\n}\n\nfunction buildSortMap(numberColumn: string) {\n return {\n id: 'id',\n number: numberColumn,\n placedAt: 'placed_at',\n lineItemCount: 'line_item_count',\n grandTotalNetAmount: 'grand_total_net_amount',\n grandTotalGrossAmount: 'grand_total_gross_amount',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n }\n}\n\nconst mapUpdateResponse = (entity: any) => {\n const customerSnapshot = normalizeJsonRecord(entity?.customerSnapshot)\n const metadata = normalizeJsonRecord(entity?.metadata)\n\n return {\n id: entity?.id ?? null,\n orderNumber: entity?.orderNumber ?? null,\n quoteNumber: entity?.quoteNumber ?? null,\n customerEntityId: entity?.customerEntityId ?? null,\n customerContactId: entity?.customerContactId ?? null,\n customerSnapshot,\n metadata,\n externalReference: entity?.externalReference ?? null,\n customerReference: entity?.customerReference ?? null,\n comment: entity?.comments ?? null,\n statusEntryId: (entity as any)?.statusEntryId ?? null,\n status: (entity as any)?.status ?? null,\n channelId: (entity as any)?.channelId ?? null,\n customerName: resolveCustomerName(customerSnapshot, entity?.customerEntityId ?? null),\n contactEmail:\n resolveCustomerEmail(customerSnapshot) ??\n (typeof metadata?.customerEmail === 'string' ? metadata.customerEmail : null),\n currencyCode: entity?.currencyCode ?? null,\n placedAt: entity?.placedAt ? entity.placedAt.toISOString() : null,\n expectedDeliveryAt: entity?.expectedDeliveryAt ? entity.expectedDeliveryAt.toISOString() : null,\n shippingAddressId: entity?.shippingAddressId ?? null,\n billingAddressId: entity?.billingAddressId ?? null,\n shippingAddressSnapshot: normalizeJsonRecord(entity?.shippingAddressSnapshot),\n billingAddressSnapshot: normalizeJsonRecord(entity?.billingAddressSnapshot),\n shippingMethodId: entity?.shippingMethodId ?? null,\n shippingMethodCode: entity?.shippingMethodCode ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(entity?.shippingMethodSnapshot),\n paymentMethodId: entity?.paymentMethodId ?? null,\n paymentMethodCode: entity?.paymentMethodCode ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(entity?.paymentMethodSnapshot),\n // Return the fresh version so the client can refresh its optimistic-lock\n // token after a successful inline save \u2014 otherwise a second save on the same\n // page sends the now-stale updatedAt and falsely 409s (#2055 QA).\n updatedAt: entity?.updatedAt\n ? (entity.updatedAt instanceof Date ? entity.updatedAt.toISOString() : entity.updatedAt)\n : null,\n }\n}\n\nconst attachTags = async (payload: any, ctx: any) => {\n const items = Array.isArray(payload?.items) ? (payload.items as Array<Record<string, any>>) : []\n if (!items.length) return\n const ids = items\n .map((item) => (item && typeof item.id === 'string' ? item.id : null))\n .filter((id): id is string => !!id)\n if (!ids.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as any) : null\n if (!em) return\n const where: Record<string, unknown> = {\n documentId: { $in: ids },\n documentKind: ctx?.bindingKind ?? null,\n }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n const assignments = await em.find(\n SalesDocumentTagAssignment,\n where,\n { populate: ['tag'] },\n )\n const grouped = new Map<string, Array<{ id: string; label: string; color: string | null }>>()\n assignments.forEach((assignment: any) => {\n const tag = assignment?.tag\n const documentId = assignment?.documentId\n if (!tag || typeof tag.id !== 'string' || typeof documentId !== 'string') return\n const entry = {\n id: tag.id,\n label: typeof tag.label === 'string' && tag.label.trim().length ? tag.label : tag.slug ?? tag.id,\n color: typeof tag.color === 'string' && tag.color.trim().length ? tag.color : null,\n }\n const list = grouped.get(documentId) ?? []\n list.push(entry)\n grouped.set(documentId, list)\n })\n items.forEach((item: Record<string, any>) => {\n const id = item && typeof item.id === 'string' ? item.id : null\n if (!id) return\n const list = grouped.get(id)\n if (list) item.tags = list\n })\n}\n\nasync function ensureNumberEditPermission(\n ctx: CrudCtx,\n translate: (key: string, fallback?: string) => string\n) {\n const rbac = ctx.container?.resolve?.('rbacService') as RbacService | null\n const auth = ctx.auth\n if (!rbac || !auth?.sub) return\n const ok = await rbac.userHasAllFeatures(auth.sub, ['sales.documents.number.edit'], {\n tenantId: auth.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null,\n })\n if (!ok) {\n throw new CrudHttpError(403, {\n error: translate('sales.documents.errors.number_edit_forbidden', 'You cannot edit document numbers.'),\n })\n }\n}\n\nexport function buildDocumentCrudOptions(binding: DocumentBinding) {\n const numberColumn = binding.numberField === 'orderNumber' ? 'order_number' : 'quote_number'\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n\n const routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [binding.viewFeature] },\n POST: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n PUT: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n DELETE: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n }\n\n const commonFields = [\n 'id',\n numberColumn,\n 'status',\n 'status_entry_id',\n 'customer_entity_id',\n 'customer_contact_id',\n 'billing_address_id',\n 'shipping_address_id',\n 'customer_snapshot',\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_id',\n 'shipping_method_code',\n 'shipping_method_snapshot',\n 'payment_method_id',\n 'payment_method_code',\n 'payment_method_snapshot',\n 'customer_reference',\n 'metadata',\n 'external_reference',\n 'currency_code',\n 'comments',\n 'channel_id',\n 'placed_at',\n 'line_item_count',\n 'subtotal_net_amount',\n 'subtotal_gross_amount',\n 'tax_total_amount',\n 'discount_total_amount',\n 'grand_total_net_amount',\n 'grand_total_gross_amount',\n 'totals_snapshot',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ]\n\n const orderOnlyFields = [\n 'expected_delivery_at',\n 'shipping_net_amount',\n 'shipping_gross_amount',\n 'surcharge_total_amount',\n 'paid_total_amount',\n 'refunded_total_amount',\n 'outstanding_amount',\n ]\n\n const quoteOnlyFields = ['valid_from', 'valid_until']\n\n const listFields = [\n ...commonFields,\n ...(binding.kind === 'order' ? orderOnlyFields : quoteOnlyFields),\n ]\n\n // Large JSONB snapshot/payload columns that only the detail page renders. The\n // grid never reads them, so selecting them for list pages fetches and decrypts\n // blobs over the wire for nothing (#2233). `customer_snapshot` is intentionally\n // kept because the grid derives the customer name/email column from it.\n const detailOnlyProjectionFields = new Set([\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_snapshot',\n 'payment_method_snapshot',\n 'totals_snapshot',\n 'metadata',\n ])\n\n const gridFields = listFields.filter((field) => !detailOnlyProjectionFields.has(field))\n\n // The detail page fetches a single document through this same list route with an\n // `?id=` filter (there is no separate detail endpoint), so it needs the full\n // projection. Grid listings (no `id`) use the trimmed projection.\n const resolveListFields = (query: ListQuery) =>\n query && typeof query.id === 'string' && query.id.length ? listFields : gridFields\n\n return {\n metadata: routeMetadata,\n orm: {\n entity: binding.entity as any,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: binding.entityId,\n },\n enrichers: binding.kind === 'order' ? { entityId: binding.entityId } : undefined,\n list: {\n schema: listSchema,\n entityId: binding.entityId,\n fields: (query: ListQuery) => resolveListFields(query),\n sortFieldMap: buildSortMap(numberColumn),\n buildFilters: async (query: any) => buildFilters(query, numberColumn, binding.kind),\n decorateCustomFields: { entityIds: [binding.entityId] },\n joins: [\n {\n alias: 'tag_assignments',\n table: 'sales_document_tag_assignments',\n from: { field: 'id' },\n to: { field: 'document_id' },\n type: 'left' as const,\n },\n ],\n transformItem: (item: any) => {\n const toNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n }\n const base = {\n id: item.id,\n [binding.numberField]: item[numberColumn] ?? null,\n status: item.status ?? null,\n statusEntryId: item.status_entry_id ?? null,\n customerEntityId: item.customer_entity_id ?? null,\n customerContactId: item.customer_contact_id ?? null,\n billingAddressId: item.billing_address_id ?? null,\n shippingAddressId: item.shipping_address_id ?? null,\n shippingMethodId: item.shipping_method_id ?? null,\n shippingMethodCode: item.shipping_method_code ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(item.shipping_method_snapshot),\n paymentMethodId: item.payment_method_id ?? null,\n paymentMethodCode: item.payment_method_code ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(item.payment_method_snapshot),\n currencyCode: item.currency_code ?? null,\n channelId: item.channel_id ?? null,\n externalReference: item.external_reference ?? null,\n customerReference: item.customer_reference ?? null,\n placedAt: item.placed_at ?? null,\n expectedDeliveryAt: item.expected_delivery_at ?? null,\n comment: item.comments ?? null,\n validFrom: item.valid_from ?? null,\n validUntil: item.valid_until ?? null,\n lineItemCount: toNumber(item.line_item_count),\n subtotalNetAmount: toNumber(item.subtotal_net_amount),\n subtotalGrossAmount: toNumber(item.subtotal_gross_amount),\n discountTotalAmount: toNumber(item.discount_total_amount),\n taxTotalAmount: toNumber(item.tax_total_amount),\n shippingNetAmount: toNumber(item.shipping_net_amount),\n shippingGrossAmount: toNumber(item.shipping_gross_amount),\n surchargeTotalAmount: toNumber(item.surcharge_total_amount),\n grandTotalNetAmount: toNumber(item.grand_total_net_amount),\n grandTotalGrossAmount: toNumber(item.grand_total_gross_amount),\n paidTotalAmount: toNumber(item.paid_total_amount),\n refundedTotalAmount: toNumber(item.refunded_total_amount),\n outstandingAmount: toNumber(item.outstanding_amount),\n customerSnapshot: normalizeJsonRecord(item.customer_snapshot),\n billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),\n shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),\n metadata: normalizeJsonRecord(item.metadata),\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n const cfEntries = extractAllCustomFieldEntries(item as Record<string, unknown>)\n const normalized = { ...base }\n Object.keys(normalized).forEach((key) => {\n if (key.startsWith('cf:')) delete (normalized as any)[key]\n })\n return Object.keys(cfEntries).length ? { ...normalized, ...cfEntries } : normalized\n },\n },\n actions: {\n create: {\n commandId: binding.createCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const parsed = parseScopedCommandInput(\n createSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result?: DocumentCreateResult | null }) => ({\n id: result?.orderId ?? result?.quoteId ?? result?.id ?? null,\n ...(binding.kind === 'order' && Array.isArray(result?.warnings)\n ? { warnings: result.warnings }\n : {}),\n }),\n status: 201,\n },\n update: {\n commandId: binding.updateCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const numberValue =\n binding.kind === 'order'\n ? (base as Record<string, unknown>).orderNumber\n : (base as Record<string, unknown>).quoteNumber\n if (typeof numberValue === 'string') {\n await ensureNumberEditPermission(ctx, translate)\n }\n const parsed = parseScopedCommandInput(\n documentUpdateSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result: any }) =>\n mapUpdateResponse((result as any)?.order ?? (result as any)?.quote ?? result),\n },\n delete: {\n commandId: binding.deleteCommandId,\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }: { parsed: any; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload: any, ctx: CrudCtx) => {\n await attachTags(payload, { ...ctx, bindingKind: binding.kind })\n if (binding.kind === 'order' && Array.isArray(payload?.items) && payload.items.length === 1) {\n const item = payload.items[0] as Record<string, unknown>\n const orderId = typeof item?.id === 'string' ? item.id : null\n const tenantId = typeof item?.tenantId === 'string' ? item.tenantId : ctx?.auth?.tenantId ?? null\n const organizationId =\n typeof item?.organizationId === 'string' ? item.organizationId : ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null\n if (orderId && tenantId && organizationId) {\n const requestEm = ctx?.container?.resolve?.('em') as import('@mikro-orm/postgresql').EntityManager | undefined\n // Display-only totals recalculation: run on a forked EntityManager so\n // the order/line/adjustment entities loaded here never enter the\n // request's Unit of Work. This guarantees a GET can never flush an\n // UPDATE (and thus never advance `updated_at`), which would otherwise\n // surface as a spurious optimistic-lock 409 in another tab.\n const em = requestEm?.fork()\n if (em) {\n const totals = await recalculateOrderTotalsForDisplay(\n em,\n ctx.container,\n orderId,\n { tenantId, organizationId },\n )\n if (totals) {\n Object.assign(item, {\n subtotalNetAmount: totals.subtotalNetAmount,\n subtotalGrossAmount: totals.subtotalGrossAmount,\n discountTotalAmount: totals.discountTotalAmount,\n taxTotalAmount: totals.taxTotalAmount,\n shippingNetAmount: totals.shippingNetAmount,\n shippingGrossAmount: totals.shippingGrossAmount,\n surchargeTotalAmount: totals.surchargeTotalAmount,\n grandTotalNetAmount: totals.grandTotalNetAmount,\n grandTotalGrossAmount: totals.grandTotalGrossAmount,\n paidTotalAmount: totals.paidTotalAmount,\n refundedTotalAmount: totals.refundedTotalAmount,\n outstandingAmount: totals.outstandingAmount,\n })\n }\n }\n }\n }\n },\n },\n }\n}\n\nexport function buildDocumentOpenApi(binding: DocumentBinding) {\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n const itemSchema = z.object({\n id: z.string().uuid(),\n [binding.numberField]: z.string().nullable(),\n status: z.string().nullable(),\n statusEntryId: z.string().uuid().nullable().optional(),\n customerEntityId: z.string().uuid().nullable(),\n customerContactId: z.string().uuid().nullable(),\n billingAddressId: z.string().uuid().nullable(),\n shippingAddressId: z.string().uuid().nullable(),\n customerReference: z.string().nullable().optional(),\n externalReference: z.string().nullable().optional(),\n comment: z.string().nullable().optional(),\n placedAt: z.string().nullable().optional(),\n expectedDeliveryAt: z.string().nullable().optional(),\n customerSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n billingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingMethodId: z.string().uuid().nullable().optional(),\n shippingMethodCode: z.string().nullable().optional(),\n shippingMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n paymentMethodId: z.string().uuid().nullable().optional(),\n paymentMethodCode: z.string().nullable().optional(),\n paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n currencyCode: z.string().nullable(),\n channelId: z.string().uuid().nullable(),\n organizationId: z.string().uuid().nullable(),\n tenantId: z.string().uuid().nullable(),\n validFrom: z.string().nullable().optional(),\n validUntil: z.string().nullable().optional(),\n lineItemCount: z.number().nullable().optional(),\n subtotalNetAmount: z.number().nullable().optional(),\n subtotalGrossAmount: z.number().nullable().optional(),\n discountTotalAmount: z.number().nullable().optional(),\n taxTotalAmount: z.number().nullable().optional(),\n shippingNetAmount: z.number().nullable().optional(),\n shippingGrossAmount: z.number().nullable().optional(),\n surchargeTotalAmount: z.number().nullable().optional(),\n grandTotalNetAmount: z.number().nullable().optional(),\n grandTotalGrossAmount: z.number().nullable().optional(),\n paidTotalAmount: z.number().nullable().optional(),\n refundedTotalAmount: z.number().nullable().optional(),\n outstandingAmount: z.number().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n customValues: z.record(z.string(), z.unknown()).optional(),\n })\n\n const listResponseSchema = createPagedListResponseSchema(itemSchema)\n\n return createSalesCrudOpenApi({\n resourceName: binding.kind === 'order' ? 'Order' : 'Quote',\n querySchema: listSchema,\n listResponseSchema,\n create: {\n schema: createSchema,\n responseSchema: binding.kind === 'order'\n ? z.object({\n id: z.string().uuid().nullable(),\n warnings: z.array(z.object({\n code: z.literal(ORDER_PAYMENT_LEDGER_WARNING_CODE),\n fields: z.array(z.enum(ORDER_PAYMENT_LEDGER_FIELDS)),\n })).optional(),\n })\n : z.object({ id: z.string().uuid().nullable() }),\n description: binding.kind === 'order'\n ? 'Creates a new sales order. paidTotalAmount, refundedTotalAmount, and outstandingAmount are deprecated compatibility inputs: supplied values are ignored and reported in warnings. Record payments through sales.payments.create or POST /api/sales/payments.'\n : 'Creates a new sales quote.',\n },\n del: {\n schema: defaultDeleteRequestSchema,\n responseSchema: z.object({ ok: z.boolean() }),\n description: `Deletes a sales ${binding.kind}.`,\n },\n })\n}\n\n// Compatibility wrapper\nexport function createDocumentCrudRoute(binding: DocumentBinding) {\n const crud = makeCrudRoute(buildDocumentCrudOptions(binding))\n const { GET, POST, PUT, DELETE } = crud\n return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { splitCustomFieldPayload, extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { E } from '#generated/entities.ids.generated'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { SalesOrder, SalesQuote } from '../../data/entities'\nimport { SalesChannel, SalesDocumentTagAssignment } from '../../data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport {\n ORDER_PAYMENT_LEDGER_FIELDS,\n ORDER_PAYMENT_LEDGER_WARNING_CODE,\n orderCreateSchema,\n quoteCreateSchema,\n type OrderPaymentLedgerWarning,\n} from '../../data/validators'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultDeleteRequestSchema,\n} from '../openapi'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { documentUpdateSchema } from '../../commands/documents'\nimport { buildIlikeTerm } from '@open-mercato/shared/lib/db/buildIlikeTerm'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { parseIdsParam } from '@open-mercato/shared/lib/crud/ids'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { recalculateOrderTotalsForDisplay } from '../../commands/returns'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\n\ntype DocumentKind = 'order' | 'quote'\n\ntype DocumentBinding = {\n kind: DocumentKind\n entity: typeof SalesOrder | typeof SalesQuote\n entityId: (typeof E.sales)[keyof typeof E.sales]\n numberField: 'orderNumber' | 'quoteNumber'\n createCommandId: string\n updateCommandId: string\n deleteCommandId: string\n manageFeature: string\n viewFeature: string\n}\n\ntype DocumentCreateResult = {\n orderId?: string\n quoteId?: string\n id?: string\n warnings?: OrderPaymentLedgerWarning[]\n}\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst normalizeJsonRecord = (value: unknown): Record<string, unknown> | null => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>\n }\n if (typeof value !== 'string') return null\n const parsed = parseDecryptedFieldValue(value)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : null\n}\n\nconst resolveCustomerName = (snapshot: Record<string, unknown> | null, fallback?: string | null) => {\n if (!snapshot) return fallback ?? null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const contact = snapshot.contact as Record<string, unknown> | undefined\n const displayName = typeof customer?.displayName === 'string' ? customer.displayName : null\n if (displayName) return displayName\n const first = typeof contact?.firstName === 'string' ? contact.firstName : null\n const last = typeof contact?.lastName === 'string' ? contact.lastName : null\n const preferred = typeof contact?.preferredName === 'string' ? contact.preferredName : null\n const parts = [preferred ?? first, last].filter((part) => part && part.trim().length)\n if (parts.length) return parts.join(' ')\n return fallback ?? null\n}\n\nconst resolveCustomerEmail = (snapshot: Record<string, unknown> | null) => {\n if (!snapshot) return null\n const customer = snapshot.customer as Record<string, unknown> | undefined\n const primary = typeof customer?.primaryEmail === 'string' ? customer.primaryEmail : null\n return primary ?? null\n}\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n id: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n channelIds: z\n .string()\n .optional()\n .describe(\n 'Comma-separated sales channel uuids; matches documents on any of them. Capped at 200 ids, malformed entries are dropped. Ignored when channelId is supplied; combines with channelIdsEmpty.',\n ),\n channelIdsEmpty: z\n .string()\n .optional()\n .describe(\n 'Boolean token; matches documents with no sales channel. Ignored when channelId is supplied; combines with channelIds.',\n ),\n lineItemCountMin: z.coerce.number().min(0).optional(),\n lineItemCountMax: z.coerce.number().min(0).optional(),\n totalNetMin: z.coerce.number().optional(),\n totalNetMax: z.coerce.number().optional(),\n totalGrossMin: z.coerce.number().optional(),\n totalGrossMax: z.coerce.number().optional(),\n dateFrom: z.string().optional(),\n dateTo: z.string().optional(),\n tagIds: z.string().optional(),\n tagIdsEmpty: z.string().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n withDeleted: z.coerce.boolean().optional(),\n })\n .passthrough()\n\ntype ListQuery = z.infer<typeof listSchema>\n\nfunction buildFilters(query: ListQuery, numberColumn: string, kind: DocumentKind) {\n const filters: Record<string, unknown> = {}\n if (query.id) filters.id = { $eq: query.id }\n if (query.search && query.search.trim().length > 0) {\n const term = buildIlikeTerm(query.search.trim())\n filters[numberColumn] = { $ilike: term }\n }\n if (query.customerId) {\n filters.customer_entity_id = { $eq: query.customerId }\n }\n // Singular wins over plural, mirroring how `api/channels/route.ts` resolves `id` before `ids`.\n // An all-malformed `channelIds` narrows to no channel filter rather than an empty-set filter, so\n // a typo returns the unfiltered page instead of silently returning zero rows.\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n } else {\n const channelIds = parseIdsParam(query.channelIds)\n const wantsUnassigned = parseBooleanToken(query.channelIdsEmpty) === true\n if (channelIds.length && wantsUnassigned) {\n // A channel multi-select with an \"(No channel)\" entry produces both at once, so they combine\n // rather than one silently dropping the other. `filters.$or` is a single key \u2014 a future filter\n // that also needs `$or` would clobber this one; nothing else in this factory writes it today.\n filters.$or = [{ channel_id: { $in: channelIds } }, { channel_id: { $exists: false } }]\n } else if (wantsUnassigned) {\n filters.channel_id = { $exists: false }\n } else if (channelIds.length) {\n filters.channel_id = { $in: channelIds }\n }\n }\n const lineRange: Record<string, number> = {}\n if (typeof query.lineItemCountMin === 'number') lineRange.$gte = query.lineItemCountMin\n if (typeof query.lineItemCountMax === 'number') lineRange.$lte = query.lineItemCountMax\n if (Object.keys(lineRange).length) {\n filters.line_item_count = lineRange\n }\n const netRange: Record<string, number> = {}\n if (typeof query.totalNetMin === 'number') netRange.$gte = query.totalNetMin\n if (typeof query.totalNetMax === 'number') netRange.$lte = query.totalNetMax\n if (Object.keys(netRange).length) {\n filters.grand_total_net_amount = netRange\n }\n const grossRange: Record<string, number> = {}\n if (typeof query.totalGrossMin === 'number') grossRange.$gte = query.totalGrossMin\n if (typeof query.totalGrossMax === 'number') grossRange.$lte = query.totalGrossMax\n if (Object.keys(grossRange).length) {\n filters.grand_total_gross_amount = grossRange\n }\n const dateRange: Record<string, Date> = {}\n if (query.dateFrom) {\n const from = new Date(query.dateFrom)\n if (!Number.isNaN(from.getTime())) dateRange.$gte = from\n }\n if (query.dateTo) {\n const to = new Date(query.dateTo)\n if (!Number.isNaN(to.getTime())) dateRange.$lte = to\n }\n if (Object.keys(dateRange).length) {\n filters.created_at = dateRange\n }\n const tagIdsRaw = typeof query.tagIds === 'string' ? query.tagIds : ''\n const tagIds = tagIdsRaw\n .split(',')\n .map((value) => value.trim())\n .filter((value) => value.length > 0)\n if (parseBooleanToken(query.tagIdsEmpty) === true) {\n filters.id = { $eq: '00000000-0000-0000-0000-000000000000' }\n } else if (tagIds.length) {\n filters['tag_assignments.tag_id'] = { $in: tagIds }\n filters['tag_assignments.document_kind'] = { $eq: kind }\n }\n return filters\n}\n\nfunction buildSortMap(numberColumn: string) {\n return {\n id: 'id',\n number: numberColumn,\n placedAt: 'placed_at',\n lineItemCount: 'line_item_count',\n grandTotalNetAmount: 'grand_total_net_amount',\n grandTotalGrossAmount: 'grand_total_gross_amount',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n }\n}\n\nconst mapUpdateResponse = (entity: any) => {\n const customerSnapshot = normalizeJsonRecord(entity?.customerSnapshot)\n const metadata = normalizeJsonRecord(entity?.metadata)\n\n return {\n id: entity?.id ?? null,\n orderNumber: entity?.orderNumber ?? null,\n quoteNumber: entity?.quoteNumber ?? null,\n customerEntityId: entity?.customerEntityId ?? null,\n customerContactId: entity?.customerContactId ?? null,\n customerSnapshot,\n metadata,\n externalReference: entity?.externalReference ?? null,\n customerReference: entity?.customerReference ?? null,\n comment: entity?.comments ?? null,\n statusEntryId: (entity as any)?.statusEntryId ?? null,\n status: (entity as any)?.status ?? null,\n channelId: (entity as any)?.channelId ?? null,\n customerName: resolveCustomerName(customerSnapshot, entity?.customerEntityId ?? null),\n contactEmail:\n resolveCustomerEmail(customerSnapshot) ??\n (typeof metadata?.customerEmail === 'string' ? metadata.customerEmail : null),\n currencyCode: entity?.currencyCode ?? null,\n placedAt: entity?.placedAt ? entity.placedAt.toISOString() : null,\n expectedDeliveryAt: entity?.expectedDeliveryAt ? entity.expectedDeliveryAt.toISOString() : null,\n shippingAddressId: entity?.shippingAddressId ?? null,\n billingAddressId: entity?.billingAddressId ?? null,\n shippingAddressSnapshot: normalizeJsonRecord(entity?.shippingAddressSnapshot),\n billingAddressSnapshot: normalizeJsonRecord(entity?.billingAddressSnapshot),\n shippingMethodId: entity?.shippingMethodId ?? null,\n shippingMethodCode: entity?.shippingMethodCode ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(entity?.shippingMethodSnapshot),\n paymentMethodId: entity?.paymentMethodId ?? null,\n paymentMethodCode: entity?.paymentMethodCode ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(entity?.paymentMethodSnapshot),\n // Return the fresh version so the client can refresh its optimistic-lock\n // token after a successful inline save \u2014 otherwise a second save on the same\n // page sends the now-stale updatedAt and falsely 409s (#2055 QA).\n updatedAt: entity?.updatedAt\n ? (entity.updatedAt instanceof Date ? entity.updatedAt.toISOString() : entity.updatedAt)\n : null,\n }\n}\n\nconst attachTags = async (payload: any, ctx: any) => {\n const items = Array.isArray(payload?.items) ? (payload.items as Array<Record<string, any>>) : []\n if (!items.length) return\n const ids = items\n .map((item) => (item && typeof item.id === 'string' ? item.id : null))\n .filter((id): id is string => !!id)\n if (!ids.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as any) : null\n if (!em) return\n const where: Record<string, unknown> = {\n documentId: { $in: ids },\n documentKind: ctx?.bindingKind ?? null,\n }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n const assignments = await em.find(\n SalesDocumentTagAssignment,\n where,\n { populate: ['tag'] },\n )\n const grouped = new Map<string, Array<{ id: string; label: string; color: string | null }>>()\n assignments.forEach((assignment: any) => {\n const tag = assignment?.tag\n const documentId = assignment?.documentId\n if (!tag || typeof tag.id !== 'string' || typeof documentId !== 'string') return\n const entry = {\n id: tag.id,\n label: typeof tag.label === 'string' && tag.label.trim().length ? tag.label : tag.slug ?? tag.id,\n color: typeof tag.color === 'string' && tag.color.trim().length ? tag.color : null,\n }\n const list = grouped.get(documentId) ?? []\n list.push(entry)\n grouped.set(documentId, list)\n })\n items.forEach((item: Record<string, any>) => {\n const id = item && typeof item.id === 'string' ? item.id : null\n if (!id) return\n const list = grouped.get(id)\n if (list) item.tags = list\n })\n}\n\n// Liveness note: this hook runs BEFORE the CRUD list cache stores the payload, so `channelName`\n// and `channelCode` are embedded in the cached entry. What keeps them fresh is that the hook also\n// runs on the cache-HIT path (shared/lib/crud/factory.ts:1683) and reassigns both fields\n// unconditionally for every item carrying a channel id. Anything that later skips this hook on a\n// hit \u2014 the way `skipEnrichersOnCacheHit` does for record-pure enrichers \u2014 would start serving the\n// stale names baked into the entry.\nexport const attachChannelNames = async (\n payload: { items?: Array<Record<string, unknown>> },\n ctx: CrudCtx,\n) => {\n const items = Array.isArray(payload?.items) ? payload.items : []\n if (!items.length) return\n const channelIds = Array.from(\n new Set(\n items\n .map((item) => (item && typeof item.channelId === 'string' ? item.channelId : null))\n .filter((value): value is string => !!value)\n )\n )\n if (!channelIds.length) return\n const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as EntityManager) : null\n if (!em) return\n\n const where: Record<string, unknown> = { id: { $in: channelIds } }\n if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId\n const orgIds =\n Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length\n ? ctx.organizationIds.filter((val: string | null) => !!val)\n : ctx?.selectedOrganizationId\n ? [ctx.selectedOrganizationId]\n : []\n if (orgIds.length) where.organizationId = { $in: orgIds }\n\n // Only `name` and `code` are read. Without this projection the query loads the whole channel row,\n // and `sales/encryption.ts` declares eight of its columns encrypted (contact email/phone, the\n // address block) \u2014 so every documents-list request would decrypt PII it never renders, on the\n // cache-hit path too.\n const channels = await findWithDecryption(\n em,\n SalesChannel,\n where,\n { fields: ['id', 'name', 'code'] },\n {\n tenantId: ctx?.auth?.tenantId ?? null,\n organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null,\n }\n )\n const byId = new Map(channels.map((channel) => [channel.id, channel]))\n items.forEach((item) => {\n if (!item || typeof item.channelId !== 'string') return\n const channel = byId.get(item.channelId)\n item.channelName = channel?.name ?? null\n item.channelCode = channel?.code ?? null\n })\n}\n\nasync function ensureNumberEditPermission(\n ctx: CrudCtx,\n translate: (key: string, fallback?: string) => string\n) {\n const rbac = ctx.container?.resolve?.('rbacService') as RbacService | null\n const auth = ctx.auth\n if (!rbac || !auth?.sub) return\n const ok = await rbac.userHasAllFeatures(auth.sub, ['sales.documents.number.edit'], {\n tenantId: auth.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null,\n })\n if (!ok) {\n throw new CrudHttpError(403, {\n error: translate('sales.documents.errors.number_edit_forbidden', 'You cannot edit document numbers.'),\n })\n }\n}\n\nexport function buildDocumentCrudOptions(binding: DocumentBinding) {\n const numberColumn = binding.numberField === 'orderNumber' ? 'order_number' : 'quote_number'\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n\n const routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [binding.viewFeature] },\n POST: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n PUT: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n DELETE: { requireAuth: true, requireFeatures: [binding.manageFeature] },\n }\n\n const commonFields = [\n 'id',\n numberColumn,\n 'status',\n 'status_entry_id',\n 'customer_entity_id',\n 'customer_contact_id',\n 'billing_address_id',\n 'shipping_address_id',\n 'customer_snapshot',\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_id',\n 'shipping_method_code',\n 'shipping_method_snapshot',\n 'payment_method_id',\n 'payment_method_code',\n 'payment_method_snapshot',\n 'customer_reference',\n 'metadata',\n 'external_reference',\n 'currency_code',\n 'comments',\n 'channel_id',\n 'placed_at',\n 'line_item_count',\n 'subtotal_net_amount',\n 'subtotal_gross_amount',\n 'tax_total_amount',\n 'discount_total_amount',\n 'grand_total_net_amount',\n 'grand_total_gross_amount',\n 'totals_snapshot',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ]\n\n const orderOnlyFields = [\n 'expected_delivery_at',\n 'shipping_net_amount',\n 'shipping_gross_amount',\n 'surcharge_total_amount',\n 'paid_total_amount',\n 'refunded_total_amount',\n 'outstanding_amount',\n ]\n\n const quoteOnlyFields = ['valid_from', 'valid_until']\n\n const listFields = [\n ...commonFields,\n ...(binding.kind === 'order' ? orderOnlyFields : quoteOnlyFields),\n ]\n\n // Large JSONB snapshot/payload columns that only the detail page renders. The\n // grid never reads them, so selecting them for list pages fetches and decrypts\n // blobs over the wire for nothing (#2233). `customer_snapshot` is intentionally\n // kept because the grid derives the customer name/email column from it.\n const detailOnlyProjectionFields = new Set([\n 'billing_address_snapshot',\n 'shipping_address_snapshot',\n 'shipping_method_snapshot',\n 'payment_method_snapshot',\n 'totals_snapshot',\n 'metadata',\n ])\n\n const gridFields = listFields.filter((field) => !detailOnlyProjectionFields.has(field))\n\n // The detail page fetches a single document through this same list route with an\n // `?id=` filter (there is no separate detail endpoint), so it needs the full\n // projection. Grid listings (no `id`) use the trimmed projection.\n const resolveListFields = (query: ListQuery) =>\n query && typeof query.id === 'string' && query.id.length ? listFields : gridFields\n\n return {\n metadata: routeMetadata,\n orm: {\n entity: binding.entity as any,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: binding.entityId,\n },\n enrichers: binding.kind === 'order' ? { entityId: binding.entityId } : undefined,\n list: {\n schema: listSchema,\n entityId: binding.entityId,\n fields: (query: ListQuery) => resolveListFields(query),\n sortFieldMap: buildSortMap(numberColumn),\n buildFilters: async (query: any) => buildFilters(query, numberColumn, binding.kind),\n decorateCustomFields: { entityIds: [binding.entityId] },\n joins: [\n {\n alias: 'tag_assignments',\n table: 'sales_document_tag_assignments',\n from: { field: 'id' },\n to: { field: 'document_id' },\n type: 'left' as const,\n },\n ],\n transformItem: (item: any) => {\n const toNumber = (value: unknown): number | null => {\n if (typeof value === 'number') return Number.isNaN(value) ? null : value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isNaN(parsed) ? null : parsed\n }\n return null\n }\n const base = {\n id: item.id,\n [binding.numberField]: item[numberColumn] ?? null,\n status: item.status ?? null,\n statusEntryId: item.status_entry_id ?? null,\n customerEntityId: item.customer_entity_id ?? null,\n customerContactId: item.customer_contact_id ?? null,\n billingAddressId: item.billing_address_id ?? null,\n shippingAddressId: item.shipping_address_id ?? null,\n shippingMethodId: item.shipping_method_id ?? null,\n shippingMethodCode: item.shipping_method_code ?? null,\n shippingMethodSnapshot: normalizeJsonRecord(item.shipping_method_snapshot),\n paymentMethodId: item.payment_method_id ?? null,\n paymentMethodCode: item.payment_method_code ?? null,\n paymentMethodSnapshot: normalizeJsonRecord(item.payment_method_snapshot),\n currencyCode: item.currency_code ?? null,\n channelId: item.channel_id ?? null,\n externalReference: item.external_reference ?? null,\n customerReference: item.customer_reference ?? null,\n placedAt: item.placed_at ?? null,\n expectedDeliveryAt: item.expected_delivery_at ?? null,\n comment: item.comments ?? null,\n validFrom: item.valid_from ?? null,\n validUntil: item.valid_until ?? null,\n lineItemCount: toNumber(item.line_item_count),\n subtotalNetAmount: toNumber(item.subtotal_net_amount),\n subtotalGrossAmount: toNumber(item.subtotal_gross_amount),\n discountTotalAmount: toNumber(item.discount_total_amount),\n taxTotalAmount: toNumber(item.tax_total_amount),\n shippingNetAmount: toNumber(item.shipping_net_amount),\n shippingGrossAmount: toNumber(item.shipping_gross_amount),\n surchargeTotalAmount: toNumber(item.surcharge_total_amount),\n grandTotalNetAmount: toNumber(item.grand_total_net_amount),\n grandTotalGrossAmount: toNumber(item.grand_total_gross_amount),\n paidTotalAmount: toNumber(item.paid_total_amount),\n refundedTotalAmount: toNumber(item.refunded_total_amount),\n outstandingAmount: toNumber(item.outstanding_amount),\n customerSnapshot: normalizeJsonRecord(item.customer_snapshot),\n billingAddressSnapshot: normalizeJsonRecord(item.billing_address_snapshot),\n shippingAddressSnapshot: normalizeJsonRecord(item.shipping_address_snapshot),\n metadata: normalizeJsonRecord(item.metadata),\n organizationId: item.organization_id ?? null,\n tenantId: item.tenant_id ?? null,\n createdAt: item.created_at,\n updatedAt: item.updated_at,\n }\n const cfEntries = extractAllCustomFieldEntries(item as Record<string, unknown>)\n const normalized = { ...base }\n Object.keys(normalized).forEach((key) => {\n if (key.startsWith('cf:')) delete (normalized as any)[key]\n })\n return Object.keys(cfEntries).length ? { ...normalized, ...cfEntries } : normalized\n },\n },\n actions: {\n create: {\n commandId: binding.createCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const parsed = parseScopedCommandInput(\n createSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result?: DocumentCreateResult | null }) => ({\n id: result?.orderId ?? result?.quoteId ?? result?.id ?? null,\n ...(binding.kind === 'order' && Array.isArray(result?.warnings)\n ? { warnings: result.warnings }\n : {}),\n }),\n status: 201,\n },\n update: {\n commandId: binding.updateCommandId,\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }: { raw: unknown; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const { base, custom } = splitCustomFieldPayload(raw ?? {})\n const numberValue =\n binding.kind === 'order'\n ? (base as Record<string, unknown>).orderNumber\n : (base as Record<string, unknown>).quoteNumber\n if (typeof numberValue === 'string') {\n await ensureNumberEditPermission(ctx, translate)\n }\n const parsed = parseScopedCommandInput(\n documentUpdateSchema,\n Object.keys(custom).length ? { ...base, customFields: custom } : base,\n ctx,\n translate,\n )\n return parsed\n },\n response: ({ result }: { result: any }) =>\n mapUpdateResponse((result as any)?.order ?? (result as any)?.quote ?? result),\n },\n delete: {\n commandId: binding.deleteCommandId,\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }: { parsed: any; ctx: CrudCtx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload: any, ctx: CrudCtx) => {\n await attachTags(payload, { ...ctx, bindingKind: binding.kind })\n await attachChannelNames(payload, ctx)\n if (binding.kind === 'order' && Array.isArray(payload?.items) && payload.items.length === 1) {\n const item = payload.items[0] as Record<string, unknown>\n const orderId = typeof item?.id === 'string' ? item.id : null\n const tenantId = typeof item?.tenantId === 'string' ? item.tenantId : ctx?.auth?.tenantId ?? null\n const organizationId =\n typeof item?.organizationId === 'string' ? item.organizationId : ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null\n if (orderId && tenantId && organizationId) {\n const requestEm = ctx?.container?.resolve?.('em') as import('@mikro-orm/postgresql').EntityManager | undefined\n // Display-only totals recalculation: run on a forked EntityManager so\n // the order/line/adjustment entities loaded here never enter the\n // request's Unit of Work. This guarantees a GET can never flush an\n // UPDATE (and thus never advance `updated_at`), which would otherwise\n // surface as a spurious optimistic-lock 409 in another tab.\n const em = requestEm?.fork()\n if (em) {\n const totals = await recalculateOrderTotalsForDisplay(\n em,\n ctx.container,\n orderId,\n { tenantId, organizationId },\n )\n if (totals) {\n Object.assign(item, {\n subtotalNetAmount: totals.subtotalNetAmount,\n subtotalGrossAmount: totals.subtotalGrossAmount,\n discountTotalAmount: totals.discountTotalAmount,\n taxTotalAmount: totals.taxTotalAmount,\n shippingNetAmount: totals.shippingNetAmount,\n shippingGrossAmount: totals.shippingGrossAmount,\n surchargeTotalAmount: totals.surchargeTotalAmount,\n grandTotalNetAmount: totals.grandTotalNetAmount,\n grandTotalGrossAmount: totals.grandTotalGrossAmount,\n paidTotalAmount: totals.paidTotalAmount,\n refundedTotalAmount: totals.refundedTotalAmount,\n outstandingAmount: totals.outstandingAmount,\n })\n }\n }\n }\n }\n },\n },\n }\n}\n\nexport function buildDocumentOpenApi(binding: DocumentBinding) {\n const createSchema = binding.kind === 'order' ? orderCreateSchema : quoteCreateSchema\n const itemSchema = z.object({\n id: z.string().uuid(),\n [binding.numberField]: z.string().nullable(),\n status: z.string().nullable(),\n statusEntryId: z.string().uuid().nullable().optional(),\n customerEntityId: z.string().uuid().nullable(),\n customerContactId: z.string().uuid().nullable(),\n billingAddressId: z.string().uuid().nullable(),\n shippingAddressId: z.string().uuid().nullable(),\n customerReference: z.string().nullable().optional(),\n externalReference: z.string().nullable().optional(),\n comment: z.string().nullable().optional(),\n placedAt: z.string().nullable().optional(),\n expectedDeliveryAt: z.string().nullable().optional(),\n customerSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n billingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingAddressSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n shippingMethodId: z.string().uuid().nullable().optional(),\n shippingMethodCode: z.string().nullable().optional(),\n shippingMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n paymentMethodId: z.string().uuid().nullable().optional(),\n paymentMethodCode: z.string().nullable().optional(),\n paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),\n currencyCode: z.string().nullable(),\n channelId: z.string().uuid().nullable(),\n channelName: z.string().nullable().optional(),\n channelCode: z.string().nullable().optional(),\n organizationId: z.string().uuid().nullable(),\n tenantId: z.string().uuid().nullable(),\n validFrom: z.string().nullable().optional(),\n validUntil: z.string().nullable().optional(),\n lineItemCount: z.number().nullable().optional(),\n subtotalNetAmount: z.number().nullable().optional(),\n subtotalGrossAmount: z.number().nullable().optional(),\n discountTotalAmount: z.number().nullable().optional(),\n taxTotalAmount: z.number().nullable().optional(),\n shippingNetAmount: z.number().nullable().optional(),\n shippingGrossAmount: z.number().nullable().optional(),\n surchargeTotalAmount: z.number().nullable().optional(),\n grandTotalNetAmount: z.number().nullable().optional(),\n grandTotalGrossAmount: z.number().nullable().optional(),\n paidTotalAmount: z.number().nullable().optional(),\n refundedTotalAmount: z.number().nullable().optional(),\n outstandingAmount: z.number().nullable().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n customFields: z.record(z.string(), z.unknown()).optional(),\n customValues: z.record(z.string(), z.unknown()).optional(),\n })\n\n const listResponseSchema = createPagedListResponseSchema(itemSchema)\n\n return createSalesCrudOpenApi({\n resourceName: binding.kind === 'order' ? 'Order' : 'Quote',\n querySchema: listSchema,\n listResponseSchema,\n create: {\n schema: createSchema,\n responseSchema: binding.kind === 'order'\n ? z.object({\n id: z.string().uuid().nullable(),\n warnings: z.array(z.object({\n code: z.literal(ORDER_PAYMENT_LEDGER_WARNING_CODE),\n fields: z.array(z.enum(ORDER_PAYMENT_LEDGER_FIELDS)),\n })).optional(),\n })\n : z.object({ id: z.string().uuid().nullable() }),\n description: binding.kind === 'order'\n ? 'Creates a new sales order. paidTotalAmount, refundedTotalAmount, and outstandingAmount are deprecated compatibility inputs: supplied values are ignored and reported in warnings. Record payments through sales.payments.create or POST /api/sales/payments.'\n : 'Creates a new sales quote.',\n },\n del: {\n schema: defaultDeleteRequestSchema,\n responseSchema: z.object({ ok: z.boolean() }),\n description: `Deletes a sales ${binding.kind}.`,\n },\n })\n}\n\n// Compatibility wrapper\nexport function createDocumentCrudRoute(binding: DocumentBinding) {\n const crud = makeCrudRoute(buildDocumentCrudOptions(binding))\n const { GET, POST, PUT, DELETE } = crud\n return { GET, POST, PUT, DELETE, openApi: buildDocumentOpenApi(binding), metadata: crud.metadata }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.6993.1.3fc50f3409",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -254,16 +254,16 @@
|
|
|
254
254
|
"zod": "^4.4.3"
|
|
255
255
|
},
|
|
256
256
|
"peerDependencies": {
|
|
257
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
258
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
259
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
257
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.6993.1.3fc50f3409",
|
|
258
|
+
"@open-mercato/shared": "0.6.8-develop.6993.1.3fc50f3409",
|
|
259
|
+
"@open-mercato/ui": "0.6.8-develop.6993.1.3fc50f3409",
|
|
260
260
|
"react": "^19.0.0",
|
|
261
261
|
"react-dom": "^19.0.0"
|
|
262
262
|
},
|
|
263
263
|
"devDependencies": {
|
|
264
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
265
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
266
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
264
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.6993.1.3fc50f3409",
|
|
265
|
+
"@open-mercato/shared": "0.6.8-develop.6993.1.3fc50f3409",
|
|
266
|
+
"@open-mercato/ui": "0.6.8-develop.6993.1.3fc50f3409",
|
|
267
267
|
"@testing-library/dom": "^10.4.1",
|
|
268
268
|
"@testing-library/jest-dom": "^7.0.0",
|
|
269
269
|
"@testing-library/react": "^16.3.1",
|
|
@@ -4,8 +4,10 @@ import { splitCustomFieldPayload, extractAllCustomFieldEntries } from '@open-mer
|
|
|
4
4
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
5
5
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
6
6
|
import { E } from '#generated/entities.ids.generated'
|
|
7
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
7
8
|
import type { SalesOrder, SalesQuote } from '../../data/entities'
|
|
8
|
-
import { SalesDocumentTagAssignment } from '../../data/entities'
|
|
9
|
+
import { SalesChannel, SalesDocumentTagAssignment } from '../../data/entities'
|
|
10
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
9
11
|
import {
|
|
10
12
|
ORDER_PAYMENT_LEDGER_FIELDS,
|
|
11
13
|
ORDER_PAYMENT_LEDGER_WARNING_CODE,
|
|
@@ -298,6 +300,62 @@ const attachTags = async (payload: any, ctx: any) => {
|
|
|
298
300
|
})
|
|
299
301
|
}
|
|
300
302
|
|
|
303
|
+
// Liveness note: this hook runs BEFORE the CRUD list cache stores the payload, so `channelName`
|
|
304
|
+
// and `channelCode` are embedded in the cached entry. What keeps them fresh is that the hook also
|
|
305
|
+
// runs on the cache-HIT path (shared/lib/crud/factory.ts:1683) and reassigns both fields
|
|
306
|
+
// unconditionally for every item carrying a channel id. Anything that later skips this hook on a
|
|
307
|
+
// hit — the way `skipEnrichersOnCacheHit` does for record-pure enrichers — would start serving the
|
|
308
|
+
// stale names baked into the entry.
|
|
309
|
+
export const attachChannelNames = async (
|
|
310
|
+
payload: { items?: Array<Record<string, unknown>> },
|
|
311
|
+
ctx: CrudCtx,
|
|
312
|
+
) => {
|
|
313
|
+
const items = Array.isArray(payload?.items) ? payload.items : []
|
|
314
|
+
if (!items.length) return
|
|
315
|
+
const channelIds = Array.from(
|
|
316
|
+
new Set(
|
|
317
|
+
items
|
|
318
|
+
.map((item) => (item && typeof item.channelId === 'string' ? item.channelId : null))
|
|
319
|
+
.filter((value): value is string => !!value)
|
|
320
|
+
)
|
|
321
|
+
)
|
|
322
|
+
if (!channelIds.length) return
|
|
323
|
+
const em = ctx?.container?.resolve ? (ctx.container.resolve('em') as EntityManager) : null
|
|
324
|
+
if (!em) return
|
|
325
|
+
|
|
326
|
+
const where: Record<string, unknown> = { id: { $in: channelIds } }
|
|
327
|
+
if (ctx?.auth?.tenantId) where.tenantId = ctx.auth.tenantId
|
|
328
|
+
const orgIds =
|
|
329
|
+
Array.isArray(ctx?.organizationIds) && ctx.organizationIds.length
|
|
330
|
+
? ctx.organizationIds.filter((val: string | null) => !!val)
|
|
331
|
+
: ctx?.selectedOrganizationId
|
|
332
|
+
? [ctx.selectedOrganizationId]
|
|
333
|
+
: []
|
|
334
|
+
if (orgIds.length) where.organizationId = { $in: orgIds }
|
|
335
|
+
|
|
336
|
+
// Only `name` and `code` are read. Without this projection the query loads the whole channel row,
|
|
337
|
+
// and `sales/encryption.ts` declares eight of its columns encrypted (contact email/phone, the
|
|
338
|
+
// address block) — so every documents-list request would decrypt PII it never renders, on the
|
|
339
|
+
// cache-hit path too.
|
|
340
|
+
const channels = await findWithDecryption(
|
|
341
|
+
em,
|
|
342
|
+
SalesChannel,
|
|
343
|
+
where,
|
|
344
|
+
{ fields: ['id', 'name', 'code'] },
|
|
345
|
+
{
|
|
346
|
+
tenantId: ctx?.auth?.tenantId ?? null,
|
|
347
|
+
organizationId: ctx?.selectedOrganizationId ?? ctx?.auth?.orgId ?? null,
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
const byId = new Map(channels.map((channel) => [channel.id, channel]))
|
|
351
|
+
items.forEach((item) => {
|
|
352
|
+
if (!item || typeof item.channelId !== 'string') return
|
|
353
|
+
const channel = byId.get(item.channelId)
|
|
354
|
+
item.channelName = channel?.name ?? null
|
|
355
|
+
item.channelCode = channel?.code ?? null
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
301
359
|
async function ensureNumberEditPermission(
|
|
302
360
|
ctx: CrudCtx,
|
|
303
361
|
translate: (key: string, fallback?: string) => string
|
|
@@ -557,6 +615,7 @@ export function buildDocumentCrudOptions(binding: DocumentBinding) {
|
|
|
557
615
|
hooks: {
|
|
558
616
|
afterList: async (payload: any, ctx: CrudCtx) => {
|
|
559
617
|
await attachTags(payload, { ...ctx, bindingKind: binding.kind })
|
|
618
|
+
await attachChannelNames(payload, ctx)
|
|
560
619
|
if (binding.kind === 'order' && Array.isArray(payload?.items) && payload.items.length === 1) {
|
|
561
620
|
const item = payload.items[0] as Record<string, unknown>
|
|
562
621
|
const orderId = typeof item?.id === 'string' ? item.id : null
|
|
@@ -629,6 +688,8 @@ export function buildDocumentOpenApi(binding: DocumentBinding) {
|
|
|
629
688
|
paymentMethodSnapshot: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
630
689
|
currencyCode: z.string().nullable(),
|
|
631
690
|
channelId: z.string().uuid().nullable(),
|
|
691
|
+
channelName: z.string().nullable().optional(),
|
|
692
|
+
channelCode: z.string().nullable().optional(),
|
|
632
693
|
organizationId: z.string().uuid().nullable(),
|
|
633
694
|
tenantId: z.string().uuid().nullable(),
|
|
634
695
|
validFrom: z.string().nullable().optional(),
|