@open-mercato/core 0.6.4-develop.4015.1.efaafadf79 → 0.6.4-develop.4095.1.9c790dc021
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/dist/modules/query_index/lib/engine.js +32 -4
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/sales/api/shipments/route.js +135 -131
- package/dist/modules/sales/api/shipments/route.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/query_index/lib/engine.ts +37 -7
- package/src/modules/sales/api/shipments/route.ts +157 -150
|
@@ -44,6 +44,139 @@ const toNumber = (value) => {
|
|
|
44
44
|
}
|
|
45
45
|
return 0;
|
|
46
46
|
};
|
|
47
|
+
async function enrichShipmentListResponse(payload, ctx) {
|
|
48
|
+
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
49
|
+
if (!items.length) return;
|
|
50
|
+
const snapshotMap = /* @__PURE__ */ new Map();
|
|
51
|
+
items.forEach((item) => {
|
|
52
|
+
if (!item || typeof item !== "object") return;
|
|
53
|
+
const id = item.id;
|
|
54
|
+
if (typeof id !== "string") return;
|
|
55
|
+
const snapshot = readShipmentItemsSnapshot(
|
|
56
|
+
item.items_snapshot ?? item.itemsSnapshot ?? null
|
|
57
|
+
);
|
|
58
|
+
if (snapshot.length) {
|
|
59
|
+
snapshotMap.set(id, snapshot);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const shipmentIds = items.map((item) => {
|
|
63
|
+
if (!item || typeof item !== "object") return null;
|
|
64
|
+
const raw = item.id;
|
|
65
|
+
return typeof raw === "string" ? raw : null;
|
|
66
|
+
}).filter((value) => typeof value === "string");
|
|
67
|
+
if (!shipmentIds.length) return;
|
|
68
|
+
const em = ctx.container.resolve("em");
|
|
69
|
+
const statusIds = Array.from(
|
|
70
|
+
new Set(
|
|
71
|
+
items.map((item) => {
|
|
72
|
+
if (!item || typeof item !== "object") return null;
|
|
73
|
+
const raw = item.status_entry_id;
|
|
74
|
+
return typeof raw === "string" ? raw : null;
|
|
75
|
+
}).filter((value) => typeof value === "string" && value.length > 0)
|
|
76
|
+
)
|
|
77
|
+
);
|
|
78
|
+
const [shipmentItems, shippingMethods, statusEntries] = await Promise.all([
|
|
79
|
+
findWithDecryption(
|
|
80
|
+
em,
|
|
81
|
+
SalesShipmentItem,
|
|
82
|
+
{ shipment: { $in: shipmentIds } },
|
|
83
|
+
{ populate: ["orderLine"] },
|
|
84
|
+
{ tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null }
|
|
85
|
+
),
|
|
86
|
+
(async () => {
|
|
87
|
+
const ids = Array.from(
|
|
88
|
+
new Set(
|
|
89
|
+
items.map((item) => {
|
|
90
|
+
if (!item || typeof item !== "object") return null;
|
|
91
|
+
const raw = item.shipping_method_id;
|
|
92
|
+
return typeof raw === "string" ? raw : null;
|
|
93
|
+
}).filter((value) => typeof value === "string" && value.length > 0)
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
if (!ids.length) return [];
|
|
97
|
+
return em.find(SalesShippingMethod, { id: { $in: ids } });
|
|
98
|
+
})(),
|
|
99
|
+
statusIds.length ? em.find(DictionaryEntry, { id: { $in: statusIds } }) : []
|
|
100
|
+
]);
|
|
101
|
+
const orderLineIds = Array.from(
|
|
102
|
+
new Set(
|
|
103
|
+
shipmentItems.map(
|
|
104
|
+
(entry) => typeof entry.orderLine === "string" ? entry.orderLine : entry.orderLine?.id ?? entry.orderLineId ?? null
|
|
105
|
+
).filter((value) => typeof value === "string")
|
|
106
|
+
)
|
|
107
|
+
);
|
|
108
|
+
const orderLines = orderLineIds.length ? await em.find(SalesOrderLine, { id: { $in: orderLineIds } }) : [];
|
|
109
|
+
const lineMap = new Map(
|
|
110
|
+
orderLines.map((line) => [
|
|
111
|
+
line.id,
|
|
112
|
+
{
|
|
113
|
+
lineNumber: line.lineNumber ?? null,
|
|
114
|
+
name: line.name ?? (typeof line.catalogSnapshot === "object" && line.catalogSnapshot ? line.catalogSnapshot.name ?? null : null)
|
|
115
|
+
}
|
|
116
|
+
])
|
|
117
|
+
);
|
|
118
|
+
const grouped = shipmentItems.reduce((acc, entry) => {
|
|
119
|
+
const shipmentId = typeof entry.shipment === "string" ? entry.shipment : entry.shipment?.id ?? entry.shipment_id ?? null;
|
|
120
|
+
const lineId = typeof entry.orderLine === "string" ? entry.orderLine : entry.orderLine?.id ?? entry.order_line_id ?? null;
|
|
121
|
+
if (!shipmentId || !lineId) return acc;
|
|
122
|
+
const line = lineMap.get(lineId);
|
|
123
|
+
const list = acc.get(shipmentId) ?? [];
|
|
124
|
+
list.push({
|
|
125
|
+
id: entry.id,
|
|
126
|
+
orderLineId: lineId,
|
|
127
|
+
orderLineName: line?.name ?? null,
|
|
128
|
+
orderLineNumber: line?.lineNumber ?? null,
|
|
129
|
+
quantity: toNumber(entry.quantity),
|
|
130
|
+
metadata: entry.metadata ?? null
|
|
131
|
+
});
|
|
132
|
+
acc.set(shipmentId, list);
|
|
133
|
+
return acc;
|
|
134
|
+
}, /* @__PURE__ */ new Map());
|
|
135
|
+
const shippingMap = new Map(
|
|
136
|
+
shippingMethods.map((method) => [
|
|
137
|
+
method.id,
|
|
138
|
+
{
|
|
139
|
+
code: method.code ?? null,
|
|
140
|
+
name: method.name ?? method.code ?? null
|
|
141
|
+
}
|
|
142
|
+
])
|
|
143
|
+
);
|
|
144
|
+
const statusMap = /* @__PURE__ */ new Map();
|
|
145
|
+
statusEntries.forEach(
|
|
146
|
+
(entry) => statusMap.set(entry.id, {
|
|
147
|
+
value: entry.value ?? null,
|
|
148
|
+
label: entry.label ?? entry.value ?? null
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
items.forEach((item) => {
|
|
152
|
+
if (!item || typeof item !== "object") return;
|
|
153
|
+
const id = item.id;
|
|
154
|
+
if (typeof id !== "string") return;
|
|
155
|
+
const snapshot = snapshotMap.get(id);
|
|
156
|
+
if (snapshot?.length) {
|
|
157
|
+
;
|
|
158
|
+
item.items_snapshot = snapshot;
|
|
159
|
+
}
|
|
160
|
+
;
|
|
161
|
+
item.items = snapshot?.length ? snapshot : grouped.get(id) ?? [];
|
|
162
|
+
const shippingId = item.shipping_method_id;
|
|
163
|
+
if (typeof shippingId === "string" && shippingMap.has(shippingId)) {
|
|
164
|
+
const method = shippingMap.get(shippingId);
|
|
165
|
+
item.shipping_method_code = method?.code ?? null;
|
|
166
|
+
item.shipping_method_name = method?.name ?? null;
|
|
167
|
+
}
|
|
168
|
+
const statusId = item.status_entry_id;
|
|
169
|
+
if (typeof statusId === "string" && statusMap.has(statusId)) {
|
|
170
|
+
const status = statusMap.get(statusId);
|
|
171
|
+
if (!item.status) {
|
|
172
|
+
;
|
|
173
|
+
item.status = status?.value ?? null;
|
|
174
|
+
}
|
|
175
|
+
;
|
|
176
|
+
item.status_label = status?.label ?? status?.value ?? null;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
47
180
|
const crud = makeCrudRoute({
|
|
48
181
|
metadata: routeMetadata,
|
|
49
182
|
orm: {
|
|
@@ -144,137 +277,7 @@ const crud = makeCrudRoute({
|
|
|
144
277
|
}
|
|
145
278
|
},
|
|
146
279
|
hooks: {
|
|
147
|
-
afterList:
|
|
148
|
-
const items = Array.isArray(payload.items) ? payload.items : [];
|
|
149
|
-
if (!items.length) return;
|
|
150
|
-
const snapshotMap = /* @__PURE__ */ new Map();
|
|
151
|
-
items.forEach((item) => {
|
|
152
|
-
if (!item || typeof item !== "object") return;
|
|
153
|
-
const id = item.id;
|
|
154
|
-
if (typeof id !== "string") return;
|
|
155
|
-
const snapshot = readShipmentItemsSnapshot(
|
|
156
|
-
item.items_snapshot ?? item.itemsSnapshot ?? null
|
|
157
|
-
);
|
|
158
|
-
if (snapshot.length) {
|
|
159
|
-
snapshotMap.set(id, snapshot);
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
const shipmentIds = items.map((item) => item && typeof item === "object" ? item.id : null).filter((value) => typeof value === "string");
|
|
163
|
-
if (!shipmentIds.length) return;
|
|
164
|
-
const em = ctx.container.resolve("em");
|
|
165
|
-
const [shipmentItems, shippingMethods] = await Promise.all([
|
|
166
|
-
findWithDecryption(
|
|
167
|
-
em,
|
|
168
|
-
SalesShipmentItem,
|
|
169
|
-
{ shipment: { $in: shipmentIds } },
|
|
170
|
-
{ populate: ["orderLine"] },
|
|
171
|
-
{ tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null }
|
|
172
|
-
),
|
|
173
|
-
(async () => {
|
|
174
|
-
const ids = Array.from(
|
|
175
|
-
new Set(
|
|
176
|
-
items.map((item) => {
|
|
177
|
-
if (!item || typeof item !== "object") return null;
|
|
178
|
-
const raw = item.shipping_method_id;
|
|
179
|
-
return typeof raw === "string" ? raw : null;
|
|
180
|
-
}).filter((value) => typeof value === "string" && value.length > 0)
|
|
181
|
-
)
|
|
182
|
-
);
|
|
183
|
-
if (!ids.length) return [];
|
|
184
|
-
return em.find(SalesShippingMethod, { id: { $in: ids } });
|
|
185
|
-
})()
|
|
186
|
-
]);
|
|
187
|
-
const orderLineIds = Array.from(
|
|
188
|
-
new Set(
|
|
189
|
-
shipmentItems.map(
|
|
190
|
-
(entry) => typeof entry.orderLine === "string" ? entry.orderLine : entry.orderLine?.id ?? entry.orderLineId ?? null
|
|
191
|
-
).filter((value) => typeof value === "string")
|
|
192
|
-
)
|
|
193
|
-
);
|
|
194
|
-
const orderLines = orderLineIds.length ? await em.find(SalesOrderLine, { id: { $in: orderLineIds } }) : [];
|
|
195
|
-
const lineMap = new Map(
|
|
196
|
-
orderLines.map((line) => [
|
|
197
|
-
line.id,
|
|
198
|
-
{
|
|
199
|
-
lineNumber: line.lineNumber ?? null,
|
|
200
|
-
name: line.name ?? (typeof line.catalogSnapshot === "object" && line.catalogSnapshot ? line.catalogSnapshot.name ?? null : null)
|
|
201
|
-
}
|
|
202
|
-
])
|
|
203
|
-
);
|
|
204
|
-
const grouped = shipmentItems.reduce((acc, entry) => {
|
|
205
|
-
const shipmentId = typeof entry.shipment === "string" ? entry.shipment : entry.shipment?.id ?? entry.shipment_id ?? null;
|
|
206
|
-
const lineId = typeof entry.orderLine === "string" ? entry.orderLine : entry.orderLine?.id ?? entry.order_line_id ?? null;
|
|
207
|
-
if (!shipmentId || !lineId) return acc;
|
|
208
|
-
const line = lineMap.get(lineId);
|
|
209
|
-
const list = acc.get(shipmentId) ?? [];
|
|
210
|
-
list.push({
|
|
211
|
-
id: entry.id,
|
|
212
|
-
orderLineId: lineId,
|
|
213
|
-
orderLineName: line?.name ?? null,
|
|
214
|
-
orderLineNumber: line?.lineNumber ?? null,
|
|
215
|
-
quantity: toNumber(entry.quantity),
|
|
216
|
-
metadata: entry.metadata ?? null
|
|
217
|
-
});
|
|
218
|
-
acc.set(shipmentId, list);
|
|
219
|
-
return acc;
|
|
220
|
-
}, /* @__PURE__ */ new Map());
|
|
221
|
-
const shippingMap = new Map(
|
|
222
|
-
shippingMethods.map((method) => [
|
|
223
|
-
method.id,
|
|
224
|
-
{
|
|
225
|
-
code: method.code ?? null,
|
|
226
|
-
name: method.name ?? method.code ?? null
|
|
227
|
-
}
|
|
228
|
-
])
|
|
229
|
-
);
|
|
230
|
-
const statusIds = Array.from(
|
|
231
|
-
new Set(
|
|
232
|
-
items.map((item) => {
|
|
233
|
-
if (!item || typeof item !== "object") return null;
|
|
234
|
-
const raw = item.status_entry_id;
|
|
235
|
-
return typeof raw === "string" ? raw : null;
|
|
236
|
-
}).filter((value) => typeof value === "string" && value.length > 0)
|
|
237
|
-
)
|
|
238
|
-
);
|
|
239
|
-
const statusMap = /* @__PURE__ */ new Map();
|
|
240
|
-
if (statusIds.length) {
|
|
241
|
-
const entries = await em.find(DictionaryEntry, { id: { $in: statusIds } });
|
|
242
|
-
entries.forEach(
|
|
243
|
-
(entry) => statusMap.set(entry.id, {
|
|
244
|
-
value: entry.value ?? null,
|
|
245
|
-
label: entry.label ?? entry.value ?? null
|
|
246
|
-
})
|
|
247
|
-
);
|
|
248
|
-
}
|
|
249
|
-
items.forEach((item) => {
|
|
250
|
-
if (!item || typeof item !== "object") return;
|
|
251
|
-
const id = item.id;
|
|
252
|
-
if (typeof id !== "string") return;
|
|
253
|
-
const snapshot = snapshotMap.get(id);
|
|
254
|
-
if (snapshot?.length) {
|
|
255
|
-
;
|
|
256
|
-
item.items_snapshot = snapshot;
|
|
257
|
-
}
|
|
258
|
-
;
|
|
259
|
-
item.items = snapshot?.length ? snapshot : grouped.get(id) ?? [];
|
|
260
|
-
const shippingId = item.shipping_method_id;
|
|
261
|
-
if (typeof shippingId === "string" && shippingMap.has(shippingId)) {
|
|
262
|
-
const method = shippingMap.get(shippingId);
|
|
263
|
-
item.shipping_method_code = method?.code ?? null;
|
|
264
|
-
item.shipping_method_name = method?.name ?? null;
|
|
265
|
-
}
|
|
266
|
-
const statusId = item.status_entry_id;
|
|
267
|
-
if (typeof statusId === "string" && statusMap.has(statusId)) {
|
|
268
|
-
const status = statusMap.get(statusId);
|
|
269
|
-
if (!item.status) {
|
|
270
|
-
;
|
|
271
|
-
item.status = status?.value ?? null;
|
|
272
|
-
}
|
|
273
|
-
;
|
|
274
|
-
item.status_label = status?.label ?? status?.value ?? null;
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
280
|
+
afterList: (payload, ctx) => enrichShipmentListResponse(payload, ctx)
|
|
278
281
|
}
|
|
279
282
|
});
|
|
280
283
|
const { GET, POST, PUT, DELETE } = crud;
|
|
@@ -343,6 +346,7 @@ export {
|
|
|
343
346
|
GET,
|
|
344
347
|
POST,
|
|
345
348
|
PUT,
|
|
349
|
+
enrichShipmentListResponse,
|
|
346
350
|
metadata,
|
|
347
351
|
openApi
|
|
348
352
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/shipments/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { SalesOrderLine, SalesShipment, SalesShipmentItem, SalesShippingMethod } from '../../data/entities'\nimport { shipmentCreateSchema, shipmentUpdateSchema } from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { readShipmentItemsSnapshot } from '../../lib/shipments/snapshots'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/sales_shipment'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n orderId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.orders.view'] },\n POST: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n}\n\nconst deleteSchema = shipmentUpdateSchema.pick({\n id: true,\n orderId: true,\n organizationId: true,\n tenantId: true,\n})\n\nconst toNumber = (value: unknown): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesShipment,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.sales.sales_shipment,\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_shipment,\n fields: [\n F.id,\n 'order_id',\n F.shipment_number,\n F.shipping_method_id,\n F.status_entry_id,\n F.status,\n F.carrier_name,\n F.tracking_numbers,\n F.shipped_at,\n F.delivered_at,\n F.weight_value,\n F.weight_unit,\n F.declared_value_net,\n F.declared_value_gross,\n F.currency_code,\n F.notes,\n F.metadata,\n F.items_snapshot,\n F.created_at,\n F.updated_at,\n ],\n decorateCustomFields: { entityIds: [E.sales.sales_shipment] },\n sortFieldMap: {\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n shippedAt: F.shipped_at,\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.orderId) filters.order_id = { $eq: query.orderId }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.shipments.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base, custom } = splitCustomFieldPayload(scoped)\n return shipmentCreateSchema.parse({\n ...base,\n ...(Object.keys(custom).length ? { customFields: custom } : {}),\n })\n },\n response: ({ result }) => ({ id: result?.shipmentId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'sales.shipments.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base, custom } = splitCustomFieldPayload(scoped)\n return shipmentUpdateSchema.parse({\n ...base,\n ...(Object.keys(custom).length ? { customFields: custom } : {}),\n })\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'sales.shipments.delete',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const merged = {\n ...(raw?.body ?? {}),\n ...(raw?.query ?? {}),\n }\n const payload = deleteSchema.parse(withScopedPayload(merged, ctx, translate))\n if (!payload.id || !payload.orderId) {\n throw new CrudHttpError(400, {\n error: translate('sales.shipments.not_found', 'Shipment not found'),\n })\n }\n return payload\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const snapshotMap = new Map<string, ReturnType<typeof readShipmentItemsSnapshot>>()\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const id = (item as Record<string, unknown>).id\n if (typeof id !== 'string') return\n const snapshot = readShipmentItemsSnapshot(\n (item as any).items_snapshot ?? (item as any).itemsSnapshot ?? null\n )\n if (snapshot.length) {\n snapshotMap.set(id, snapshot)\n }\n })\n const shipmentIds = items\n .map((item: unknown) => (item && typeof item === 'object' ? (item as Record<string, unknown>).id : null))\n .filter((value: string | null): value is string => typeof value === 'string')\n if (!shipmentIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const [shipmentItems, shippingMethods] = await Promise.all([\n findWithDecryption(\n em,\n SalesShipmentItem,\n { shipment: { $in: shipmentIds } },\n { populate: ['orderLine'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n ),\n (async () => {\n const ids: string[] = Array.from(\n new Set(\n items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const raw = (item as Record<string, unknown>).shipping_method_id\n return typeof raw === 'string' ? raw : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n )\n )\n if (!ids.length) return []\n return em.find(SalesShippingMethod, { id: { $in: ids } })\n })(),\n ])\n const orderLineIds = Array.from(\n new Set(\n shipmentItems\n .map((entry) =>\n typeof entry.orderLine === 'string'\n ? entry.orderLine\n : entry.orderLine?.id ?? (entry as any).orderLineId ?? null\n )\n .filter((value): value is string => typeof value === 'string')\n )\n )\n const orderLines = orderLineIds.length\n ? await em.find(SalesOrderLine, { id: { $in: orderLineIds } })\n : []\n const lineMap = new Map(\n orderLines.map((line) => [\n line.id,\n {\n lineNumber: line.lineNumber ?? null,\n name:\n line.name ??\n (typeof line.catalogSnapshot === 'object' && line.catalogSnapshot\n ? ((line.catalogSnapshot as any).name as string | undefined) ?? null\n : null),\n },\n ])\n )\n const grouped = shipmentItems.reduce<Map<string, Array<Record<string, unknown>>>>((acc, entry) => {\n const shipmentId =\n typeof entry.shipment === 'string'\n ? entry.shipment\n : entry.shipment?.id ?? (entry as any).shipment_id ?? null\n const lineId =\n typeof entry.orderLine === 'string'\n ? entry.orderLine\n : entry.orderLine?.id ?? (entry as any).order_line_id ?? null\n if (!shipmentId || !lineId) return acc\n const line = lineMap.get(lineId)\n const list = acc.get(shipmentId) ?? []\n list.push({\n id: entry.id,\n orderLineId: lineId,\n orderLineName: line?.name ?? null,\n orderLineNumber: line?.lineNumber ?? null,\n quantity: toNumber(entry.quantity),\n metadata: entry.metadata ?? null,\n })\n acc.set(shipmentId, list)\n return acc\n }, new Map())\n const shippingMap = new Map(\n shippingMethods.map((method) => [\n method.id,\n {\n code: method.code ?? null,\n name: method.name ?? method.code ?? null,\n },\n ])\n )\n const statusIds: string[] = Array.from(\n new Set(\n items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const raw = (item as Record<string, unknown>).status_entry_id\n return typeof raw === 'string' ? raw : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n )\n )\n const statusMap = new Map<string, { value: string | null; label: string | null }>()\n if (statusIds.length) {\n const entries = await em.find(DictionaryEntry, { id: { $in: statusIds } })\n entries.forEach((entry) =>\n statusMap.set(entry.id, {\n value: entry.value ?? null,\n label: entry.label ?? entry.value ?? null,\n })\n )\n }\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const id = (item as Record<string, unknown>).id\n if (typeof id !== 'string') return\n const snapshot = snapshotMap.get(id)\n if (snapshot?.length) {\n ;(item as Record<string, unknown>).items_snapshot = snapshot\n }\n ;(item as Record<string, unknown>).items = snapshot?.length ? snapshot : grouped.get(id) ?? []\n const shippingId = (item as Record<string, unknown>).shipping_method_id\n if (typeof shippingId === 'string' && shippingMap.has(shippingId)) {\n const method = shippingMap.get(shippingId)\n ;(item as Record<string, unknown>).shipping_method_code = method?.code ?? null\n ;(item as Record<string, unknown>).shipping_method_name = method?.name ?? null\n }\n const statusId = (item as Record<string, unknown>).status_entry_id\n if (typeof statusId === 'string' && statusMap.has(statusId)) {\n const status = statusMap.get(statusId)\n if (!(item as Record<string, unknown>).status) {\n ;(item as Record<string, unknown>).status = status?.value ?? null\n }\n ;(item as Record<string, unknown>).status_label = status?.label ?? status?.value ?? null\n }\n })\n },\n },\n})\n\nconst { GET, POST, PUT, DELETE } = crud\n\nexport const metadata = routeMetadata\nexport { GET, POST, PUT, DELETE }\n\nconst shipmentItemSchema = z.object({\n id: z.string().uuid(),\n orderLineId: z.string().uuid(),\n orderLineName: z.string().nullable().optional(),\n orderLineNumber: z.number().nullable().optional(),\n quantity: z.number(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n})\n\nconst shipmentSchema = z\n .object({\n id: z.string().uuid(),\n order_id: z.string().uuid(),\n shipment_number: z.string().nullable().optional(),\n shipping_method_id: z.string().uuid().nullable().optional(),\n shipping_method_code: z.string().nullable().optional(),\n shipping_method_name: z.string().nullable().optional(),\n status_entry_id: z.string().uuid().nullable().optional(),\n status: z.string().nullable().optional(),\n status_label: z.string().nullable().optional(),\n carrier_name: z.string().nullable().optional(),\n tracking_numbers: z.array(z.string()).nullable().optional(),\n shipped_at: z.string().nullable().optional(),\n delivered_at: z.string().nullable().optional(),\n weight_value: z.number().nullable().optional(),\n weight_unit: z.string().nullable().optional(),\n declared_value_net: z.number().nullable().optional(),\n declared_value_gross: z.number().nullable().optional(),\n currency_code: z.string().nullable().optional(),\n notes: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n custom_values: z.record(z.string(), z.unknown()).nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n custom_fields: z.array(z.record(z.string(), z.unknown())).nullable().optional(),\n customFields: z.array(z.record(z.string(), z.unknown())).nullable().optional(),\n items_snapshot: z.array(shipmentItemSchema).nullable().optional(),\n itemsSnapshot: z.array(shipmentItemSchema).nullable().optional(),\n created_at: z.string(),\n updated_at: z.string(),\n items: z.array(shipmentItemSchema).optional(),\n })\n .passthrough()\n\nexport const openApi = createSalesCrudOpenApi({\n resourceName: 'Shipment',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(shipmentSchema),\n create: {\n schema: shipmentCreateSchema,\n responseSchema: z.object({ id: z.string().uuid().nullable() }),\n description: 'Creates a shipment for an order.',\n },\n update: {\n schema: shipmentUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a shipment.',\n },\n del: {\n schema: deleteSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a shipment.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport { SalesOrderLine, SalesShipment, SalesShipmentItem, SalesShippingMethod } from '../../data/entities'\nimport { shipmentCreateSchema, shipmentUpdateSchema } from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { readShipmentItemsSnapshot } from '../../lib/shipments/snapshots'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultOkResponseSchema,\n} from '../openapi'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/sales_shipment'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n orderId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.orders.view'] },\n POST: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['sales.shipments.manage'] },\n}\n\nconst deleteSchema = shipmentUpdateSchema.pick({\n id: true,\n orderId: true,\n organizationId: true,\n tenantId: true,\n})\n\nconst toNumber = (value: unknown): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nexport async function enrichShipmentListResponse(\n payload: { items?: unknown[] },\n ctx: CrudCtx,\n): Promise<void> {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const snapshotMap = new Map<string, ReturnType<typeof readShipmentItemsSnapshot>>()\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const id = (item as Record<string, unknown>).id\n if (typeof id !== 'string') return\n const snapshot = readShipmentItemsSnapshot(\n (item as any).items_snapshot ?? (item as any).itemsSnapshot ?? null\n )\n if (snapshot.length) {\n snapshotMap.set(id, snapshot)\n }\n })\n const shipmentIds = items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const raw = (item as Record<string, unknown>).id\n return typeof raw === 'string' ? raw : null\n })\n .filter((value: string | null): value is string => typeof value === 'string')\n if (!shipmentIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const statusIds: string[] = Array.from(\n new Set(\n items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const raw = (item as Record<string, unknown>).status_entry_id\n return typeof raw === 'string' ? raw : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n )\n )\n const [shipmentItems, shippingMethods, statusEntries] = await Promise.all([\n findWithDecryption(\n em,\n SalesShipmentItem,\n { shipment: { $in: shipmentIds } },\n { populate: ['orderLine'] },\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n ),\n (async () => {\n const ids: string[] = Array.from(\n new Set(\n items\n .map((item: unknown) => {\n if (!item || typeof item !== 'object') return null\n const raw = (item as Record<string, unknown>).shipping_method_id\n return typeof raw === 'string' ? raw : null\n })\n .filter((value: string | null): value is string => typeof value === 'string' && value.length > 0)\n )\n )\n if (!ids.length) return []\n return em.find(SalesShippingMethod, { id: { $in: ids } })\n })(),\n statusIds.length ? em.find(DictionaryEntry, { id: { $in: statusIds } }) : [],\n ])\n const orderLineIds = Array.from(\n new Set(\n shipmentItems\n .map((entry) =>\n typeof entry.orderLine === 'string'\n ? entry.orderLine\n : entry.orderLine?.id ?? (entry as any).orderLineId ?? null\n )\n .filter((value): value is string => typeof value === 'string')\n )\n )\n const orderLines = orderLineIds.length\n ? await em.find(SalesOrderLine, { id: { $in: orderLineIds } })\n : []\n const lineMap = new Map(\n orderLines.map((line) => [\n line.id,\n {\n lineNumber: line.lineNumber ?? null,\n name:\n line.name ??\n (typeof line.catalogSnapshot === 'object' && line.catalogSnapshot\n ? ((line.catalogSnapshot as any).name as string | undefined) ?? null\n : null),\n },\n ])\n )\n const grouped = shipmentItems.reduce<Map<string, Array<Record<string, unknown>>>>((acc, entry) => {\n const shipmentId =\n typeof entry.shipment === 'string'\n ? entry.shipment\n : entry.shipment?.id ?? (entry as any).shipment_id ?? null\n const lineId =\n typeof entry.orderLine === 'string'\n ? entry.orderLine\n : entry.orderLine?.id ?? (entry as any).order_line_id ?? null\n if (!shipmentId || !lineId) return acc\n const line = lineMap.get(lineId)\n const list = acc.get(shipmentId) ?? []\n list.push({\n id: entry.id,\n orderLineId: lineId,\n orderLineName: line?.name ?? null,\n orderLineNumber: line?.lineNumber ?? null,\n quantity: toNumber(entry.quantity),\n metadata: entry.metadata ?? null,\n })\n acc.set(shipmentId, list)\n return acc\n }, new Map())\n const shippingMap = new Map(\n shippingMethods.map((method) => [\n method.id,\n {\n code: method.code ?? null,\n name: method.name ?? method.code ?? null,\n },\n ])\n )\n const statusMap = new Map<string, { value: string | null; label: string | null }>()\n statusEntries.forEach((entry) =>\n statusMap.set(entry.id, {\n value: entry.value ?? null,\n label: entry.label ?? entry.value ?? null,\n })\n )\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const id = (item as Record<string, unknown>).id\n if (typeof id !== 'string') return\n const snapshot = snapshotMap.get(id)\n if (snapshot?.length) {\n ;(item as Record<string, unknown>).items_snapshot = snapshot\n }\n ;(item as Record<string, unknown>).items = snapshot?.length ? snapshot : grouped.get(id) ?? []\n const shippingId = (item as Record<string, unknown>).shipping_method_id\n if (typeof shippingId === 'string' && shippingMap.has(shippingId)) {\n const method = shippingMap.get(shippingId)\n ;(item as Record<string, unknown>).shipping_method_code = method?.code ?? null\n ;(item as Record<string, unknown>).shipping_method_name = method?.name ?? null\n }\n const statusId = (item as Record<string, unknown>).status_entry_id\n if (typeof statusId === 'string' && statusMap.has(statusId)) {\n const status = statusMap.get(statusId)\n if (!(item as Record<string, unknown>).status) {\n ;(item as Record<string, unknown>).status = status?.value ?? null\n }\n ;(item as Record<string, unknown>).status_label = status?.label ?? status?.value ?? null\n }\n })\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesShipment,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.sales.sales_shipment,\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_shipment,\n fields: [\n F.id,\n 'order_id',\n F.shipment_number,\n F.shipping_method_id,\n F.status_entry_id,\n F.status,\n F.carrier_name,\n F.tracking_numbers,\n F.shipped_at,\n F.delivered_at,\n F.weight_value,\n F.weight_unit,\n F.declared_value_net,\n F.declared_value_gross,\n F.currency_code,\n F.notes,\n F.metadata,\n F.items_snapshot,\n F.created_at,\n F.updated_at,\n ],\n decorateCustomFields: { entityIds: [E.sales.sales_shipment] },\n sortFieldMap: {\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n shippedAt: F.shipped_at,\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.orderId) filters.order_id = { $eq: query.orderId }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.shipments.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base, custom } = splitCustomFieldPayload(scoped)\n return shipmentCreateSchema.parse({\n ...base,\n ...(Object.keys(custom).length ? { customFields: custom } : {}),\n })\n },\n response: ({ result }) => ({ id: result?.shipmentId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'sales.shipments.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base, custom } = splitCustomFieldPayload(scoped)\n return shipmentUpdateSchema.parse({\n ...base,\n ...(Object.keys(custom).length ? { customFields: custom } : {}),\n })\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'sales.shipments.delete',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const merged = {\n ...(raw?.body ?? {}),\n ...(raw?.query ?? {}),\n }\n const payload = deleteSchema.parse(withScopedPayload(merged, ctx, translate))\n if (!payload.id || !payload.orderId) {\n throw new CrudHttpError(400, {\n error: translate('sales.shipments.not_found', 'Shipment not found'),\n })\n }\n return payload\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: (payload, ctx) => enrichShipmentListResponse(payload, ctx),\n },\n})\n\nconst { GET, POST, PUT, DELETE } = crud\n\nexport const metadata = routeMetadata\nexport { GET, POST, PUT, DELETE }\n\nconst shipmentItemSchema = z.object({\n id: z.string().uuid(),\n orderLineId: z.string().uuid(),\n orderLineName: z.string().nullable().optional(),\n orderLineNumber: z.number().nullable().optional(),\n quantity: z.number(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n})\n\nconst shipmentSchema = z\n .object({\n id: z.string().uuid(),\n order_id: z.string().uuid(),\n shipment_number: z.string().nullable().optional(),\n shipping_method_id: z.string().uuid().nullable().optional(),\n shipping_method_code: z.string().nullable().optional(),\n shipping_method_name: z.string().nullable().optional(),\n status_entry_id: z.string().uuid().nullable().optional(),\n status: z.string().nullable().optional(),\n status_label: z.string().nullable().optional(),\n carrier_name: z.string().nullable().optional(),\n tracking_numbers: z.array(z.string()).nullable().optional(),\n shipped_at: z.string().nullable().optional(),\n delivered_at: z.string().nullable().optional(),\n weight_value: z.number().nullable().optional(),\n weight_unit: z.string().nullable().optional(),\n declared_value_net: z.number().nullable().optional(),\n declared_value_gross: z.number().nullable().optional(),\n currency_code: z.string().nullable().optional(),\n notes: z.string().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n custom_values: z.record(z.string(), z.unknown()).nullable().optional(),\n customValues: z.record(z.string(), z.unknown()).nullable().optional(),\n custom_fields: z.array(z.record(z.string(), z.unknown())).nullable().optional(),\n customFields: z.array(z.record(z.string(), z.unknown())).nullable().optional(),\n items_snapshot: z.array(shipmentItemSchema).nullable().optional(),\n itemsSnapshot: z.array(shipmentItemSchema).nullable().optional(),\n created_at: z.string(),\n updated_at: z.string(),\n items: z.array(shipmentItemSchema).optional(),\n })\n .passthrough()\n\nexport const openApi = createSalesCrudOpenApi({\n resourceName: 'Shipment',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(shipmentSchema),\n create: {\n schema: shipmentCreateSchema,\n responseSchema: z.object({ id: z.string().uuid().nullable() }),\n description: 'Creates a shipment for an order.',\n },\n update: {\n schema: shipmentUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a shipment.',\n },\n del: {\n schema: deleteSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a shipment.',\n },\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.4-develop.
|
|
3
|
+
"version": "0.6.4-develop.4095.1.9c790dc021",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -243,16 +243,16 @@
|
|
|
243
243
|
"zod": "^4.4.3"
|
|
244
244
|
},
|
|
245
245
|
"peerDependencies": {
|
|
246
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
247
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
248
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
246
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4095.1.9c790dc021",
|
|
247
|
+
"@open-mercato/shared": "0.6.4-develop.4095.1.9c790dc021",
|
|
248
|
+
"@open-mercato/ui": "0.6.4-develop.4095.1.9c790dc021",
|
|
249
249
|
"react": "^19.0.0",
|
|
250
250
|
"react-dom": "^19.0.0"
|
|
251
251
|
},
|
|
252
252
|
"devDependencies": {
|
|
253
|
-
"@open-mercato/ai-assistant": "0.6.4-develop.
|
|
254
|
-
"@open-mercato/shared": "0.6.4-develop.
|
|
255
|
-
"@open-mercato/ui": "0.6.4-develop.
|
|
253
|
+
"@open-mercato/ai-assistant": "0.6.4-develop.4095.1.9c790dc021",
|
|
254
|
+
"@open-mercato/shared": "0.6.4-develop.4095.1.9c790dc021",
|
|
255
|
+
"@open-mercato/ui": "0.6.4-develop.4095.1.9c790dc021",
|
|
256
256
|
"@testing-library/dom": "^10.4.1",
|
|
257
257
|
"@testing-library/jest-dom": "^6.9.1",
|
|
258
258
|
"@testing-library/react": "^16.3.1",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QueryEngine, QueryOptions, QueryResult, FilterOp, Filter, QueryCustomFieldSource, PartialIndexWarning, QueryExtensionsConfig } from '@open-mercato/shared/lib/query/types'
|
|
1
|
+
import type { QueryEngine, QueryOptions, QueryResult, FilterOp, Filter, QueryCustomFieldSource, PartialIndexWarning, QueryExtensionsConfig, Sort } from '@open-mercato/shared/lib/query/types'
|
|
2
2
|
import { SortDir } from '@open-mercato/shared/lib/query/types'
|
|
3
3
|
import type { EntityId } from '@open-mercato/shared/modules/entities'
|
|
4
4
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { resolveSearchConfig, type SearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
22
22
|
import { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'
|
|
23
23
|
import { runBeforeQueryPipeline, runAfterQueryPipeline, type QueryExtensionContext } from '@open-mercato/shared/lib/query/query-extension-runner'
|
|
24
|
+
import { resolveEncryptedSortFields, sortRowsInMemory } from '@open-mercato/shared/lib/query/encrypted-sort'
|
|
24
25
|
|
|
25
26
|
function buildFilterableCustomFieldJoins(
|
|
26
27
|
sources: QueryCustomFieldSource[] | undefined,
|
|
@@ -90,6 +91,7 @@ type SearchRuntime = {
|
|
|
90
91
|
|
|
91
92
|
type EncryptionResolver = () => {
|
|
92
93
|
decryptEntityPayload?: (entityId: EntityId, payload: Record<string, unknown>, tenantId?: string | null, organizationId?: string | null) => Promise<Record<string, unknown>>
|
|
94
|
+
getEncryptedFieldNames?: (entityId: EntityId, tenantId?: string | null, organizationId?: string | null) => Promise<readonly string[]>
|
|
93
95
|
isEnabled?: () => boolean
|
|
94
96
|
} | null
|
|
95
97
|
|
|
@@ -504,6 +506,28 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
504
506
|
if (field === 'organization_id' && columns.has('id')) return 'id'
|
|
505
507
|
return null
|
|
506
508
|
}
|
|
509
|
+
const fallbackOrgId =
|
|
510
|
+
opts.organizationId
|
|
511
|
+
?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)
|
|
512
|
+
const encSvc = this.getEncryptionService()
|
|
513
|
+
const resolvedSorts: Sort[] = []
|
|
514
|
+
for (const sort of opts.sort || []) {
|
|
515
|
+
const field = String(sort.field)
|
|
516
|
+
if (field.startsWith('cf:')) {
|
|
517
|
+
resolvedSorts.push({ ...sort, field })
|
|
518
|
+
} else {
|
|
519
|
+
const baseField = resolveBaseColumn(field)
|
|
520
|
+
if (baseField) resolvedSorts.push({ ...sort, field: baseField })
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const encryptedSortFields = await resolveEncryptedSortFields(
|
|
524
|
+
encSvc,
|
|
525
|
+
entity,
|
|
526
|
+
resolvedSorts.filter((sort) => !sort.field.startsWith('cf:')).map((sort) => sort.field),
|
|
527
|
+
opts.tenantId ?? null,
|
|
528
|
+
fallbackOrgId,
|
|
529
|
+
)
|
|
530
|
+
const requiresPlaintextSort = encryptedSortFields.size > 0
|
|
507
531
|
|
|
508
532
|
// ────────────────────────────────────────────────────────────────
|
|
509
533
|
// Build a reusable "applyQueryShape" function that applies every
|
|
@@ -735,6 +759,9 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
735
759
|
|
|
736
760
|
// Selection (for data query)
|
|
737
761
|
const selectFieldSet = new Set<string>((opts.fields && opts.fields.length) ? opts.fields.map(String) : Array.from(columns.keys()))
|
|
762
|
+
if (requiresPlaintextSort) {
|
|
763
|
+
for (const field of encryptedSortFields) selectFieldSet.add(field)
|
|
764
|
+
}
|
|
738
765
|
if (opts.includeCustomFields === true) {
|
|
739
766
|
const entityIds = Array.from(new Set(indexSources.map((src) => String(src.entityId))))
|
|
740
767
|
try {
|
|
@@ -767,7 +794,8 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
767
794
|
|
|
768
795
|
const applySort = (q: AnyBuilder): AnyBuilder => {
|
|
769
796
|
let next = q
|
|
770
|
-
|
|
797
|
+
if (requiresPlaintextSort) return next
|
|
798
|
+
for (const s of resolvedSorts) {
|
|
771
799
|
const fieldName = String(s.field)
|
|
772
800
|
if (fieldName.startsWith('cf:')) {
|
|
773
801
|
const textExpr = this.buildCfTextExprSql(fieldName, indexSources)
|
|
@@ -845,7 +873,9 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
845
873
|
let dataBuilder = await applyQueryShape(dataRoot)
|
|
846
874
|
dataBuilder = applySelection(dataBuilder)
|
|
847
875
|
dataBuilder = applySort(dataBuilder)
|
|
848
|
-
|
|
876
|
+
if (!requiresPlaintextSort) {
|
|
877
|
+
dataBuilder = dataBuilder.limit(pageSize).offset((page - 1) * pageSize)
|
|
878
|
+
}
|
|
849
879
|
|
|
850
880
|
if (debugEnabled && sqlDebugEnabled) {
|
|
851
881
|
const compiled = dataBuilder.compile()
|
|
@@ -859,15 +889,11 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
859
889
|
if (debugEnabled) this.debug('query:complete', { entity, total, items: Array.isArray(itemsRaw) ? itemsRaw.length : 0 })
|
|
860
890
|
|
|
861
891
|
let items = itemsRaw as any[]
|
|
862
|
-
const encSvc = this.getEncryptionService()
|
|
863
892
|
const dekKeyCache = new Map<string | null, string | null>()
|
|
864
893
|
if (encSvc?.decryptEntityPayload) {
|
|
865
894
|
const decrypt = encSvc.decryptEntityPayload.bind(encSvc) as (
|
|
866
895
|
entityId: EntityId, payload: Record<string, unknown>, tenantId: string | null, organizationId: string | null,
|
|
867
896
|
) => Promise<Record<string, unknown>>
|
|
868
|
-
const fallbackOrgId =
|
|
869
|
-
opts.organizationId
|
|
870
|
-
?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null)
|
|
871
897
|
items = await Promise.all(
|
|
872
898
|
items.map(async (item) => {
|
|
873
899
|
try {
|
|
@@ -900,6 +926,10 @@ export class HybridQueryEngine implements QueryEngine {
|
|
|
900
926
|
}),
|
|
901
927
|
)
|
|
902
928
|
}
|
|
929
|
+
if (requiresPlaintextSort) {
|
|
930
|
+
items = sortRowsInMemory(items as Record<string, unknown>[], resolvedSorts)
|
|
931
|
+
.slice((page - 1) * pageSize, page * pageSize)
|
|
932
|
+
}
|
|
903
933
|
|
|
904
934
|
const typedItems = items as unknown as T[]
|
|
905
935
|
let result: QueryResult<T> = { items: typedItems, page, pageSize, total }
|