@open-mercato/core 0.4.5-develop-3ce83a8b24 → 0.4.5-develop-539cff4960
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/generated/entities/catalog_product/index.js +16 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_unit_conversion/index.js +27 -0
- package/dist/generated/entities/catalog_product_unit_conversion/index.js.map +7 -0
- package/dist/generated/entities/sales_credit_memo_line/index.js +7 -1
- package/dist/generated/entities/sales_credit_memo_line/index.js.map +2 -2
- package/dist/generated/entities/sales_invoice_line/index.js +7 -1
- package/dist/generated/entities/sales_invoice_line/index.js.map +2 -2
- package/dist/generated/entities/sales_order_line/index.js +6 -0
- package/dist/generated/entities/sales_order_line/index.js.map +2 -2
- package/dist/generated/entities/sales_quote_line/index.js +6 -0
- package/dist/generated/entities/sales_quote_line/index.js.map +2 -2
- package/dist/generated/entities.ids.generated.js +1 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/prices/route.js +123 -8
- package/dist/modules/catalog/api/prices/route.js.map +2 -2
- package/dist/modules/catalog/api/product-unit-conversions/route.js +194 -0
- package/dist/modules/catalog/api/product-unit-conversions/route.js.map +7 -0
- package/dist/modules/catalog/api/products/route.js +351 -201
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +1267 -497
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +733 -210
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/index.js +1 -0
- package/dist/modules/catalog/commands/index.js.map +2 -2
- package/dist/modules/catalog/commands/productUnitConversions.js +503 -0
- package/dist/modules/catalog/commands/productUnitConversions.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +355 -73
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +18 -4
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +591 -0
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +7 -0
- package/dist/modules/catalog/components/products/productForm.js +66 -5
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/productFormUtils.js +68 -0
- package/dist/modules/catalog/components/products/productFormUtils.js.map +7 -0
- package/dist/modules/catalog/data/entities.js +86 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +65 -3
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/events.js +3 -0
- package/dist/modules/catalog/events.js.map +2 -2
- package/dist/modules/catalog/lib/unitCodes.js +7 -0
- package/dist/modules/catalog/lib/unitCodes.js.map +7 -0
- package/dist/modules/catalog/lib/unitResolution.js +53 -0
- package/dist/modules/catalog/lib/unitResolution.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js +19 -0
- package/dist/modules/catalog/migrations/Migration20260218225422.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js +27 -0
- package/dist/modules/catalog/migrations/Migration20260219084500.js.map +7 -0
- package/dist/modules/catalog/search.js +69 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/catalog/seed/examples.js +91 -42
- package/dist/modules/catalog/seed/examples.js.map +2 -2
- package/dist/modules/dashboards/seed/analytics.js +3 -0
- package/dist/modules/dashboards/seed/analytics.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +98 -15
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +101 -14
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/public/[token]/route.js +87 -12
- package/dist/modules/sales/api/quotes/public/[token]/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +1424 -260
- package/dist/modules/sales/commands/documents.js.map +3 -3
- package/dist/modules/sales/commands/shared.js +6 -2
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ItemsSection.js +216 -86
- package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +913 -241
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js +15 -3
- package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
- package/dist/modules/sales/data/entities.js +59 -3
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +35 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/frontend/quote/[token]/page.js +15 -1
- package/dist/modules/sales/frontend/quote/[token]/page.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260218225423.js +31 -0
- package/dist/modules/sales/migrations/Migration20260218225423.js.map +7 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js +71 -0
- package/dist/modules/sales/migrations/Migration20260219084501.js.map +7 -0
- package/dist/modules/sales/search.js +28 -0
- package/dist/modules/sales/search.js.map +2 -2
- package/dist/modules/sales/seed/examples.js +14 -1
- package/dist/modules/sales/seed/examples.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +1 -1
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js +28 -15
- package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
- package/dist/modules/staff/translations.js +9 -0
- package/dist/modules/staff/translations.js.map +7 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js +97 -0
- package/dist/modules/translations/components/TranslationDrawerAction.js.map +7 -0
- package/dist/modules/translations/lib/extract-record-id.js +31 -2
- package/dist/modules/translations/lib/extract-record-id.js.map +2 -2
- package/dist/modules/translations/lib/resolve-field-list.js +3 -0
- package/dist/modules/translations/lib/resolve-field-list.js.map +2 -2
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js +105 -36
- package/dist/modules/translations/widgets/injection/translation-manager/widget.client.js.map +2 -2
- package/dist/modules/translations/widgets/injection-table.js +18 -29
- package/dist/modules/translations/widgets/injection-table.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +8 -0
- package/generated/entities/catalog_product_unit_conversion/index.ts +12 -0
- package/generated/entities/sales_credit_memo_line/index.ts +3 -0
- package/generated/entities/sales_invoice_line/index.ts +3 -0
- package/generated/entities/sales_order_line/index.ts +3 -0
- package/generated/entities/sales_quote_line/index.ts +3 -0
- package/generated/entities.ids.generated.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +2 -2
- package/src/modules/auth/i18n/de.json +1 -1
- package/src/modules/auth/i18n/en.json +1 -1
- package/src/modules/auth/i18n/es.json +1 -1
- package/src/modules/auth/i18n/pl.json +1 -1
- package/src/modules/catalog/api/prices/route.ts +213 -81
- package/src/modules/catalog/api/product-unit-conversions/route.ts +195 -0
- package/src/modules/catalog/api/products/route.ts +638 -402
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2085 -1072
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1288 -593
- package/src/modules/catalog/commands/index.ts +1 -0
- package/src/modules/catalog/commands/productUnitConversions.ts +626 -0
- package/src/modules/catalog/commands/products.ts +1151 -693
- package/src/modules/catalog/commands/shared.ts +19 -5
- package/src/modules/catalog/components/products/ProductUomSection.tsx +745 -0
- package/src/modules/catalog/components/products/productForm.ts +369 -256
- package/src/modules/catalog/components/products/productFormUtils.ts +82 -0
- package/src/modules/catalog/data/entities.ts +82 -1
- package/src/modules/catalog/data/validators.ts +118 -34
- package/src/modules/catalog/events.ts +3 -0
- package/src/modules/catalog/i18n/de.json +56 -0
- package/src/modules/catalog/i18n/en.json +56 -0
- package/src/modules/catalog/i18n/es.json +56 -0
- package/src/modules/catalog/i18n/pl.json +56 -0
- package/src/modules/catalog/lib/unitCodes.ts +1 -0
- package/src/modules/catalog/lib/unitResolution.ts +62 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +245 -0
- package/src/modules/catalog/migrations/Migration20260218225422.ts +21 -0
- package/src/modules/catalog/migrations/Migration20260219084500.ts +26 -0
- package/src/modules/catalog/search.ts +73 -1
- package/src/modules/catalog/seed/examples.ts +552 -479
- package/src/modules/dashboards/i18n/de.json +1 -1
- package/src/modules/dashboards/i18n/en.json +1 -1
- package/src/modules/dashboards/i18n/es.json +1 -1
- package/src/modules/dashboards/i18n/pl.json +1 -1
- package/src/modules/dashboards/seed/analytics.ts +3 -0
- package/src/modules/sales/api/order-lines/route.ts +158 -68
- package/src/modules/sales/api/quote-lines/route.ts +161 -67
- package/src/modules/sales/api/quotes/public/[token]/route.ts +122 -36
- package/src/modules/sales/commands/documents.ts +4250 -2424
- package/src/modules/sales/commands/shared.ts +7 -2
- package/src/modules/sales/components/documents/ItemsSection.tsx +580 -310
- package/src/modules/sales/components/documents/LineItemDialog.tsx +1988 -833
- package/src/modules/sales/components/documents/ShipmentsSection.tsx +17 -3
- package/src/modules/sales/components/documents/lineItemTypes.ts +6 -0
- package/src/modules/sales/data/entities.ts +53 -0
- package/src/modules/sales/data/validators.ts +36 -0
- package/src/modules/sales/frontend/quote/[token]/page.tsx +25 -1
- package/src/modules/sales/i18n/de.json +23 -3
- package/src/modules/sales/i18n/en.json +23 -3
- package/src/modules/sales/i18n/es.json +23 -3
- package/src/modules/sales/i18n/pl.json +23 -3
- package/src/modules/sales/lib/types.ts +30 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +172 -0
- package/src/modules/sales/migrations/Migration20260218225423.ts +37 -0
- package/src/modules/sales/migrations/Migration20260219084501.ts +73 -0
- package/src/modules/sales/search.ts +28 -0
- package/src/modules/sales/seed/examples.ts +20 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +1 -1
- package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +8 -0
- package/src/modules/staff/translations.ts +5 -0
- package/src/modules/translations/components/TranslationDrawerAction.tsx +107 -0
- package/src/modules/translations/lib/extract-record-id.ts +47 -3
- package/src/modules/translations/lib/resolve-field-list.ts +4 -0
- package/src/modules/translations/widgets/injection/translation-manager/widget.client.tsx +108 -36
- package/src/modules/translations/widgets/injection-table.ts +19 -33
- package/src/modules/workflows/i18n/de.json +4 -4
- package/src/modules/workflows/i18n/en.json +4 -4
- package/src/modules/workflows/i18n/es.json +4 -4
- package/src/modules/workflows/i18n/pl.json +4 -4
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildCustomFieldFiltersFromQuery,
|
|
6
|
+
extractAllCustomFieldEntries
|
|
7
|
+
} from "@open-mercato/shared/lib/crud/custom-fields";
|
|
5
8
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
6
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
CatalogProduct,
|
|
11
|
+
CatalogProductPrice,
|
|
12
|
+
CatalogProductUnitConversion,
|
|
13
|
+
CatalogProductVariant
|
|
14
|
+
} from "../../data/entities.js";
|
|
7
15
|
import { priceCreateSchema, priceUpdateSchema } from "../../data/validators.js";
|
|
8
16
|
import { parseScopedCommandInput, resolveCrudRecordId } from "../utils.js";
|
|
9
17
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
@@ -13,6 +21,11 @@ import {
|
|
|
13
21
|
createPagedListResponseSchema,
|
|
14
22
|
defaultOkResponseSchema
|
|
15
23
|
} from "../openapi.js";
|
|
24
|
+
import { toUnitLookupKey } from "../../lib/unitCodes.js";
|
|
25
|
+
import {
|
|
26
|
+
findWithDecryption,
|
|
27
|
+
findOneWithDecryption
|
|
28
|
+
} from "@open-mercato/shared/lib/encryption/find";
|
|
16
29
|
const rawBodySchema = z.object({}).passthrough();
|
|
17
30
|
const listSchema = z.object({
|
|
18
31
|
page: z.coerce.number().min(1).default(1),
|
|
@@ -28,6 +41,8 @@ const listSchema = z.object({
|
|
|
28
41
|
userGroupId: z.string().uuid().optional(),
|
|
29
42
|
customerId: z.string().uuid().optional(),
|
|
30
43
|
customerGroupId: z.string().uuid().optional(),
|
|
44
|
+
quantity: z.coerce.number().min(1).max(1e5).optional(),
|
|
45
|
+
quantityUnit: z.string().trim().max(50).optional(),
|
|
31
46
|
withDeleted: z.coerce.boolean().optional(),
|
|
32
47
|
sortField: z.string().optional(),
|
|
33
48
|
sortDir: z.enum(["asc", "desc"]).optional()
|
|
@@ -39,6 +54,68 @@ const routeMetadata = {
|
|
|
39
54
|
DELETE: { requireAuth: true, requireFeatures: ["catalog.pricing.manage"] }
|
|
40
55
|
};
|
|
41
56
|
const metadata = routeMetadata;
|
|
57
|
+
async function resolveNormalizedQuantityForFilter(params) {
|
|
58
|
+
const quantity = params.quantity;
|
|
59
|
+
const quantityUnitKey = toUnitLookupKey(params.quantityUnit);
|
|
60
|
+
if (!params.productId && !params.variantId || !quantityUnitKey)
|
|
61
|
+
return quantity;
|
|
62
|
+
if (!params.organizationId || !params.tenantId) return quantity;
|
|
63
|
+
let targetProductId = params.productId;
|
|
64
|
+
if (!targetProductId && params.variantId) {
|
|
65
|
+
const variant = await findOneWithDecryption(
|
|
66
|
+
params.em,
|
|
67
|
+
CatalogProductVariant,
|
|
68
|
+
{
|
|
69
|
+
id: params.variantId,
|
|
70
|
+
organizationId: params.organizationId,
|
|
71
|
+
tenantId: params.tenantId,
|
|
72
|
+
deletedAt: null
|
|
73
|
+
},
|
|
74
|
+
{ fields: ["id", "product"] }
|
|
75
|
+
);
|
|
76
|
+
if (variant) {
|
|
77
|
+
targetProductId = typeof variant.product === "string" ? variant.product : variant.product?.id ?? null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!targetProductId) return quantity;
|
|
81
|
+
const product = await findOneWithDecryption(
|
|
82
|
+
params.em,
|
|
83
|
+
CatalogProduct,
|
|
84
|
+
{
|
|
85
|
+
id: targetProductId,
|
|
86
|
+
organizationId: params.organizationId,
|
|
87
|
+
tenantId: params.tenantId,
|
|
88
|
+
deletedAt: null
|
|
89
|
+
},
|
|
90
|
+
{ fields: ["id", "defaultUnit", "uomRoundingScale"] }
|
|
91
|
+
);
|
|
92
|
+
if (!product) return quantity;
|
|
93
|
+
const baseUnitKey = toUnitLookupKey(product.defaultUnit);
|
|
94
|
+
if (!baseUnitKey || baseUnitKey === quantityUnitKey) return quantity;
|
|
95
|
+
const conversions = await findWithDecryption(
|
|
96
|
+
params.em,
|
|
97
|
+
CatalogProductUnitConversion,
|
|
98
|
+
{
|
|
99
|
+
product: product.id,
|
|
100
|
+
organizationId: params.organizationId,
|
|
101
|
+
tenantId: params.tenantId,
|
|
102
|
+
isActive: true,
|
|
103
|
+
deletedAt: null
|
|
104
|
+
},
|
|
105
|
+
{ fields: ["id", "unitCode", "toBaseFactor"] }
|
|
106
|
+
);
|
|
107
|
+
const conversion = conversions.find(
|
|
108
|
+
(entry) => toUnitLookupKey(entry.unitCode) === quantityUnitKey
|
|
109
|
+
);
|
|
110
|
+
if (!conversion) return quantity;
|
|
111
|
+
const factor = Number(conversion.toBaseFactor);
|
|
112
|
+
if (!Number.isFinite(factor) || factor <= 0) return quantity;
|
|
113
|
+
const rawNormalized = quantity * factor;
|
|
114
|
+
const scale = Number(product.uomRoundingScale ?? 4);
|
|
115
|
+
const pow = Math.pow(10, scale);
|
|
116
|
+
const normalized = Math.round(rawNormalized * pow) / pow;
|
|
117
|
+
return Number.isFinite(normalized) && normalized > 0 ? normalized : quantity;
|
|
118
|
+
}
|
|
42
119
|
async function buildPriceFilters(query) {
|
|
43
120
|
const filters = {};
|
|
44
121
|
if (query.productId) {
|
|
@@ -65,7 +142,8 @@ async function buildPriceFilters(query) {
|
|
|
65
142
|
if (query.userId) filters.user_id = { $eq: query.userId };
|
|
66
143
|
if (query.userGroupId) filters.user_group_id = { $eq: query.userGroupId };
|
|
67
144
|
if (query.customerId) filters.customer_id = { $eq: query.customerId };
|
|
68
|
-
if (query.customerGroupId)
|
|
145
|
+
if (query.customerGroupId)
|
|
146
|
+
filters.customer_group_id = { $eq: query.customerGroupId };
|
|
69
147
|
return filters;
|
|
70
148
|
}
|
|
71
149
|
const crud = makeCrudRoute({
|
|
@@ -77,6 +155,9 @@ const crud = makeCrudRoute({
|
|
|
77
155
|
tenantField: "tenantId",
|
|
78
156
|
softDeleteField: null
|
|
79
157
|
},
|
|
158
|
+
indexer: {
|
|
159
|
+
entityType: E.catalog.catalog_product_price
|
|
160
|
+
},
|
|
80
161
|
list: {
|
|
81
162
|
schema: listSchema,
|
|
82
163
|
entityId: E.catalog.catalog_product_price,
|
|
@@ -116,8 +197,9 @@ const crud = makeCrudRoute({
|
|
|
116
197
|
buildFilters: async (query, ctx) => {
|
|
117
198
|
const filters = await buildPriceFilters(query);
|
|
118
199
|
const tenantId = ctx.auth?.tenantId ?? null;
|
|
200
|
+
const organizationId = ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
201
|
+
const em = ctx.container.resolve("em");
|
|
119
202
|
try {
|
|
120
|
-
const em = ctx.container.resolve("em");
|
|
121
203
|
const cfFilters = await buildCustomFieldFiltersFromQuery({
|
|
122
204
|
entityIds: [E.catalog.catalog_product_price],
|
|
123
205
|
query,
|
|
@@ -125,7 +207,24 @@ const crud = makeCrudRoute({
|
|
|
125
207
|
tenantId
|
|
126
208
|
});
|
|
127
209
|
Object.assign(filters, cfFilters);
|
|
128
|
-
} catch {
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (process.env.NODE_ENV === "development") console.warn("[catalog:prices] custom field filter error", err);
|
|
212
|
+
}
|
|
213
|
+
if (typeof query.quantity === "number" && Number.isFinite(query.quantity) && query.quantity > 0) {
|
|
214
|
+
const normalizedQuantity = await resolveNormalizedQuantityForFilter({
|
|
215
|
+
em,
|
|
216
|
+
organizationId,
|
|
217
|
+
tenantId,
|
|
218
|
+
productId: query.productId,
|
|
219
|
+
variantId: query.variantId,
|
|
220
|
+
quantity: query.quantity,
|
|
221
|
+
quantityUnit: query.quantityUnit
|
|
222
|
+
});
|
|
223
|
+
filters.min_quantity = { $lte: normalizedQuantity };
|
|
224
|
+
filters.$or = [
|
|
225
|
+
{ max_quantity: null },
|
|
226
|
+
{ max_quantity: { $gte: normalizedQuantity } }
|
|
227
|
+
];
|
|
129
228
|
}
|
|
130
229
|
return filters;
|
|
131
230
|
},
|
|
@@ -145,7 +244,12 @@ const crud = makeCrudRoute({
|
|
|
145
244
|
schema: rawBodySchema,
|
|
146
245
|
mapInput: async ({ raw, ctx }) => {
|
|
147
246
|
const { translate } = await resolveTranslations();
|
|
148
|
-
return parseScopedCommandInput(
|
|
247
|
+
return parseScopedCommandInput(
|
|
248
|
+
priceCreateSchema,
|
|
249
|
+
raw ?? {},
|
|
250
|
+
ctx,
|
|
251
|
+
translate
|
|
252
|
+
);
|
|
149
253
|
},
|
|
150
254
|
response: ({ result }) => ({ id: result?.priceId ?? result?.id ?? null }),
|
|
151
255
|
status: 201
|
|
@@ -155,7 +259,12 @@ const crud = makeCrudRoute({
|
|
|
155
259
|
schema: rawBodySchema,
|
|
156
260
|
mapInput: async ({ raw, ctx }) => {
|
|
157
261
|
const { translate } = await resolveTranslations();
|
|
158
|
-
return parseScopedCommandInput(
|
|
262
|
+
return parseScopedCommandInput(
|
|
263
|
+
priceUpdateSchema,
|
|
264
|
+
raw ?? {},
|
|
265
|
+
ctx,
|
|
266
|
+
translate
|
|
267
|
+
);
|
|
159
268
|
},
|
|
160
269
|
response: () => ({ ok: true })
|
|
161
270
|
},
|
|
@@ -165,7 +274,13 @@ const crud = makeCrudRoute({
|
|
|
165
274
|
mapInput: async ({ parsed, ctx }) => {
|
|
166
275
|
const { translate } = await resolveTranslations();
|
|
167
276
|
const id = resolveCrudRecordId(parsed, ctx, translate);
|
|
168
|
-
if (!id)
|
|
277
|
+
if (!id)
|
|
278
|
+
throw new CrudHttpError(400, {
|
|
279
|
+
error: translate(
|
|
280
|
+
"catalog.errors.id_required",
|
|
281
|
+
"Price id is required."
|
|
282
|
+
)
|
|
283
|
+
});
|
|
169
284
|
return { id };
|
|
170
285
|
},
|
|
171
286
|
response: () => ({ ok: true })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/catalog/api/prices/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 { buildCustomFieldFiltersFromQuery, extractAllCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CatalogProductPrice } from '../../data/entities'\nimport { priceCreateSchema, priceUpdateSchema } from '../../data/validators'\nimport { parseScopedCommandInput, resolveCrudRecordId } from '../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport * as FP from '#generated/entities/catalog_product_price'\nimport {\n createCatalogCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from '../openapi'\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(100).default(50),\n productId: z.string().uuid().optional(),\n variantId: z.string().uuid().optional(),\n offerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n currencyCode: z.string().optional(),\n priceKindId: z.string().uuid().optional(),\n kind: z.string().optional(),\n userId: z.string().uuid().optional(),\n userGroupId: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n customerGroupId: z.string().uuid().optional(),\n withDeleted: z.coerce.boolean().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype PriceQuery = z.infer<typeof listSchema>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['catalog.products.view'] },\n POST: { requireAuth: true, requireFeatures: ['catalog.pricing.manage'] },\n PUT: { requireAuth: true, requireFeatures: ['catalog.pricing.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['catalog.pricing.manage'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function buildPriceFilters(\n query: PriceQuery\n): Promise<Record<string, unknown>> {\n const filters: Record<string, unknown> = {}\n if (query.productId) {\n filters.product_id = { $eq: query.productId }\n }\n if (query.variantId) {\n filters.variant_id = { $eq: query.variantId }\n }\n if (query.offerId) {\n filters.offer_id = { $eq: query.offerId }\n }\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId }\n }\n if (query.currencyCode) {\n filters.currency_code = { $eq: query.currencyCode.trim().toUpperCase() }\n }\n if (query.priceKindId) {\n filters.price_kind_id = { $eq: query.priceKindId }\n }\n if (query.kind) {\n filters.kind = { $eq: query.kind }\n }\n if (query.userId) filters.user_id = { $eq: query.userId }\n if (query.userGroupId) filters.user_group_id = { $eq: query.userGroupId }\n if (query.customerId) filters.customer_id = { $eq: query.customerId }\n if (query.customerGroupId) filters.customer_group_id = { $eq: query.customerGroupId }\n return filters\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: CatalogProductPrice,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: null,\n },\n list: {\n schema: listSchema,\n entityId: E.catalog.catalog_product_price,\n fields: [\n FP.id,\n 'product_id',\n 'variant_id',\n 'offer_id',\n FP.currency_code,\n 'price_kind_id',\n FP.kind,\n FP.min_quantity,\n FP.max_quantity,\n FP.unit_price_net,\n FP.unit_price_gross,\n FP.tax_rate,\n FP.tax_amount,\n FP.channel_id,\n FP.user_id,\n FP.user_group_id,\n FP.customer_id,\n FP.customer_group_id,\n FP.metadata,\n FP.starts_at,\n FP.ends_at,\n FP.created_at,\n FP.updated_at,\n ],\n sortFieldMap: {\n currencyCode: FP.currency_code,\n priceKindId: 'price_kind_id',\n kind: FP.kind,\n minQuantity: FP.min_quantity,\n createdAt: FP.created_at,\n updatedAt: FP.updated_at,\n },\n buildFilters: async (query, ctx) => {\n const filters = await buildPriceFilters(query)\n const tenantId = ctx.auth?.tenantId ?? null\n try {\n const em = ctx.container.resolve('em') as EntityManager\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityIds: [E.catalog.catalog_product_price],\n query,\n em,\n tenantId,\n })\n Object.assign(filters, cfFilters)\n } catch {\n // ignore\n }\n return filters\n },\n transformItem: (item: any) => {\n if (!item) return item\n const normalized = { ...item }\n const cfEntries = extractAllCustomFieldEntries(item)\n for (const key of Object.keys(normalized)) {\n if (key.startsWith('cf:')) delete normalized[key]\n }\n return { ...normalized, ...cfEntries }\n },\n },\n actions: {\n create: {\n commandId: 'catalog.prices.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(priceCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({ id: result?.priceId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: 'catalog.prices.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(priceUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'catalog.prices.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n if (!id) throw new CrudHttpError(400, { error: translate('catalog.errors.id_required', 'Price id is required.') })\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst priceListItemSchema = z.object({\n id: z.string().uuid(),\n product_id: z.string().uuid().nullable().optional(),\n variant_id: z.string().uuid().nullable().optional(),\n offer_id: z.string().uuid().nullable().optional(),\n currency_code: z.string().nullable().optional(),\n price_kind_id: z.string().uuid().nullable().optional(),\n kind: z.string().nullable().optional(),\n min_quantity: z.number().nullable().optional(),\n max_quantity: z.number().nullable().optional(),\n unit_price_net: z.number().nullable().optional(),\n unit_price_gross: z.number().nullable().optional(),\n tax_rate: z.number().nullable().optional(),\n tax_amount: z.number().nullable().optional(),\n channel_id: z.string().uuid().nullable().optional(),\n user_id: z.string().uuid().nullable().optional(),\n user_group_id: z.string().uuid().nullable().optional(),\n customer_id: z.string().uuid().nullable().optional(),\n customer_group_id: z.string().uuid().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n starts_at: z.string().nullable().optional(),\n ends_at: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n})\n\nexport const openApi = createCatalogCrudOpenApi({\n resourceName: 'Price',\n pluralName: 'Prices',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(priceListItemSchema),\n create: {\n schema: priceCreateSchema,\n description: 'Creates a new price entry for a product or variant.',\n },\n update: {\n schema: priceUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates an existing price by id.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a price by id.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B,
|
|
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 {\n buildCustomFieldFiltersFromQuery,\n extractAllCustomFieldEntries,\n} from \"@open-mercato/shared/lib/crud/custom-fields\";\nimport { resolveTranslations } from \"@open-mercato/shared/lib/i18n/server\";\nimport {\n CatalogProduct,\n CatalogProductPrice,\n CatalogProductUnitConversion,\n CatalogProductVariant,\n} from \"../../data/entities\";\nimport { priceCreateSchema, priceUpdateSchema } from \"../../data/validators\";\nimport { parseScopedCommandInput, resolveCrudRecordId } from \"../utils\";\nimport { E } from \"#generated/entities.ids.generated\";\nimport * as FP from \"#generated/entities/catalog_product_price\";\nimport {\n createCatalogCrudOpenApi,\n createPagedListResponseSchema,\n defaultOkResponseSchema,\n} from \"../openapi\";\nimport { toUnitLookupKey } from \"../../lib/unitCodes\";\nimport {\n findWithDecryption,\n findOneWithDecryption,\n} 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(100).default(50),\n productId: z.string().uuid().optional(),\n variantId: z.string().uuid().optional(),\n offerId: z.string().uuid().optional(),\n channelId: z.string().uuid().optional(),\n currencyCode: z.string().optional(),\n priceKindId: z.string().uuid().optional(),\n kind: z.string().optional(),\n userId: z.string().uuid().optional(),\n userGroupId: z.string().uuid().optional(),\n customerId: z.string().uuid().optional(),\n customerGroupId: z.string().uuid().optional(),\n quantity: z.coerce.number().min(1).max(100000).optional(),\n quantityUnit: z.string().trim().max(50).optional(),\n withDeleted: z.coerce.boolean().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum([\"asc\", \"desc\"]).optional(),\n })\n .passthrough();\n\ntype PriceQuery = z.infer<typeof listSchema>;\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [\"catalog.products.view\"] },\n POST: { requireAuth: true, requireFeatures: [\"catalog.pricing.manage\"] },\n PUT: { requireAuth: true, requireFeatures: [\"catalog.pricing.manage\"] },\n DELETE: { requireAuth: true, requireFeatures: [\"catalog.pricing.manage\"] },\n};\n\nexport const metadata = routeMetadata;\n\nasync function resolveNormalizedQuantityForFilter(params: {\n em: EntityManager;\n organizationId: string | null;\n tenantId: string | null;\n productId?: string;\n variantId?: string;\n quantity: number;\n quantityUnit?: string;\n}): Promise<number> {\n const quantity = params.quantity;\n const quantityUnitKey = toUnitLookupKey(params.quantityUnit);\n if ((!params.productId && !params.variantId) || !quantityUnitKey)\n return quantity;\n if (!params.organizationId || !params.tenantId) return quantity;\n let targetProductId = params.productId;\n if (!targetProductId && params.variantId) {\n const variant = await findOneWithDecryption(\n params.em,\n CatalogProductVariant,\n {\n id: params.variantId,\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n deletedAt: null,\n },\n { fields: [\"id\", \"product\"] },\n );\n if (variant) {\n targetProductId =\n typeof variant.product === \"string\"\n ? variant.product\n : (variant.product?.id ?? null);\n }\n }\n if (!targetProductId) return quantity;\n const product = await findOneWithDecryption(\n params.em,\n CatalogProduct,\n {\n id: targetProductId,\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n deletedAt: null,\n },\n { fields: [\"id\", \"defaultUnit\", \"uomRoundingScale\"] },\n );\n if (!product) return quantity;\n const baseUnitKey = toUnitLookupKey(product.defaultUnit);\n if (!baseUnitKey || baseUnitKey === quantityUnitKey) return quantity;\n const conversions = await findWithDecryption(\n params.em,\n CatalogProductUnitConversion,\n {\n product: product.id,\n organizationId: params.organizationId,\n tenantId: params.tenantId,\n isActive: true,\n deletedAt: null,\n },\n { fields: [\"id\", \"unitCode\", \"toBaseFactor\"] },\n );\n const conversion = conversions.find(\n (entry) => toUnitLookupKey(entry.unitCode) === quantityUnitKey,\n );\n if (!conversion) return quantity;\n const factor = Number(conversion.toBaseFactor);\n if (!Number.isFinite(factor) || factor <= 0) return quantity;\n const rawNormalized = quantity * factor;\n const scale = Number(product.uomRoundingScale ?? 4);\n const pow = Math.pow(10, scale);\n const normalized = Math.round(rawNormalized * pow) / pow;\n return Number.isFinite(normalized) && normalized > 0 ? normalized : quantity;\n}\n\nexport async function buildPriceFilters(\n query: PriceQuery,\n): Promise<Record<string, unknown>> {\n const filters: Record<string, unknown> = {};\n if (query.productId) {\n filters.product_id = { $eq: query.productId };\n }\n if (query.variantId) {\n filters.variant_id = { $eq: query.variantId };\n }\n if (query.offerId) {\n filters.offer_id = { $eq: query.offerId };\n }\n if (query.channelId) {\n filters.channel_id = { $eq: query.channelId };\n }\n if (query.currencyCode) {\n filters.currency_code = { $eq: query.currencyCode.trim().toUpperCase() };\n }\n if (query.priceKindId) {\n filters.price_kind_id = { $eq: query.priceKindId };\n }\n if (query.kind) {\n filters.kind = { $eq: query.kind };\n }\n if (query.userId) filters.user_id = { $eq: query.userId };\n if (query.userGroupId) filters.user_group_id = { $eq: query.userGroupId };\n if (query.customerId) filters.customer_id = { $eq: query.customerId };\n if (query.customerGroupId)\n filters.customer_group_id = { $eq: query.customerGroupId };\n return filters;\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: CatalogProductPrice,\n idField: \"id\",\n orgField: \"organizationId\",\n tenantField: \"tenantId\",\n softDeleteField: null,\n },\n indexer: {\n entityType: E.catalog.catalog_product_price,\n },\n list: {\n schema: listSchema,\n entityId: E.catalog.catalog_product_price,\n fields: [\n FP.id,\n \"product_id\",\n \"variant_id\",\n \"offer_id\",\n FP.currency_code,\n \"price_kind_id\",\n FP.kind,\n FP.min_quantity,\n FP.max_quantity,\n FP.unit_price_net,\n FP.unit_price_gross,\n FP.tax_rate,\n FP.tax_amount,\n FP.channel_id,\n FP.user_id,\n FP.user_group_id,\n FP.customer_id,\n FP.customer_group_id,\n FP.metadata,\n FP.starts_at,\n FP.ends_at,\n FP.created_at,\n FP.updated_at,\n ],\n sortFieldMap: {\n currencyCode: FP.currency_code,\n priceKindId: \"price_kind_id\",\n kind: FP.kind,\n minQuantity: FP.min_quantity,\n createdAt: FP.created_at,\n updatedAt: FP.updated_at,\n },\n buildFilters: async (query, ctx) => {\n const filters = await buildPriceFilters(query);\n const tenantId = ctx.auth?.tenantId ?? null;\n const organizationId =\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;\n const em = ctx.container.resolve(\"em\") as EntityManager;\n try {\n const cfFilters = await buildCustomFieldFiltersFromQuery({\n entityIds: [E.catalog.catalog_product_price],\n query,\n em,\n tenantId,\n });\n Object.assign(filters, cfFilters);\n } catch (err) {\n // Custom field filter parsing may fail for non-existent or misconfigured fields.\n if (process.env.NODE_ENV === 'development') console.warn('[catalog:prices] custom field filter error', err);\n }\n if (\n typeof query.quantity === \"number\" &&\n Number.isFinite(query.quantity) &&\n query.quantity > 0\n ) {\n const normalizedQuantity = await resolveNormalizedQuantityForFilter({\n em,\n organizationId,\n tenantId,\n productId: query.productId,\n variantId: query.variantId,\n quantity: query.quantity,\n quantityUnit: query.quantityUnit,\n });\n filters.min_quantity = { $lte: normalizedQuantity };\n filters.$or = [\n { max_quantity: null },\n { max_quantity: { $gte: normalizedQuantity } },\n ];\n }\n return filters;\n },\n transformItem: (item: Record<string, unknown> | null | undefined) => {\n if (!item) return item;\n const normalized = { ...item };\n const cfEntries = extractAllCustomFieldEntries(item);\n for (const key of Object.keys(normalized)) {\n if (key.startsWith(\"cf:\")) delete normalized[key];\n }\n return { ...normalized, ...cfEntries };\n },\n },\n actions: {\n create: {\n commandId: \"catalog.prices.create\",\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations();\n return parseScopedCommandInput(\n priceCreateSchema,\n raw ?? {},\n ctx,\n translate,\n );\n },\n response: ({ result }) => ({ id: result?.priceId ?? result?.id ?? null }),\n status: 201,\n },\n update: {\n commandId: \"catalog.prices.update\",\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations();\n return parseScopedCommandInput(\n priceUpdateSchema,\n raw ?? {},\n ctx,\n translate,\n );\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: \"catalog.prices.delete\",\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations();\n const id = resolveCrudRecordId(parsed, ctx, translate);\n if (!id)\n throw new CrudHttpError(400, {\n error: translate(\n \"catalog.errors.id_required\",\n \"Price id is required.\",\n ),\n });\n return { id };\n },\n response: () => ({ ok: true }),\n },\n },\n});\n\nexport const GET = crud.GET;\nexport const POST = crud.POST;\nexport const PUT = crud.PUT;\nexport const DELETE = crud.DELETE;\n\nconst priceListItemSchema = z.object({\n id: z.string().uuid(),\n product_id: z.string().uuid().nullable().optional(),\n variant_id: z.string().uuid().nullable().optional(),\n offer_id: z.string().uuid().nullable().optional(),\n currency_code: z.string().nullable().optional(),\n price_kind_id: z.string().uuid().nullable().optional(),\n kind: z.string().nullable().optional(),\n min_quantity: z.number().nullable().optional(),\n max_quantity: z.number().nullable().optional(),\n unit_price_net: z.number().nullable().optional(),\n unit_price_gross: z.number().nullable().optional(),\n tax_rate: z.number().nullable().optional(),\n tax_amount: z.number().nullable().optional(),\n channel_id: z.string().uuid().nullable().optional(),\n user_id: z.string().uuid().nullable().optional(),\n user_group_id: z.string().uuid().nullable().optional(),\n customer_id: z.string().uuid().nullable().optional(),\n customer_group_id: z.string().uuid().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n starts_at: z.string().nullable().optional(),\n ends_at: z.string().nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n});\n\nexport const openApi = createCatalogCrudOpenApi({\n resourceName: \"Price\",\n pluralName: \"Prices\",\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(priceListItemSchema),\n create: {\n schema: priceCreateSchema,\n description: \"Creates a new price entry for a product or variant.\",\n },\n update: {\n schema: priceUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: \"Updates an existing price by id.\",\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: \"Deletes a price by id.\",\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
+
import { CatalogProductUnitConversion } from "../../data/entities.js";
|
|
6
|
+
import {
|
|
7
|
+
productUnitConversionCreateSchema,
|
|
8
|
+
productUnitConversionUpdateSchema,
|
|
9
|
+
productUnitConversionDeleteSchema
|
|
10
|
+
} from "../../data/validators.js";
|
|
11
|
+
import { parseScopedCommandInput } from "../utils.js";
|
|
12
|
+
import {
|
|
13
|
+
createCatalogCrudOpenApi,
|
|
14
|
+
createPagedListResponseSchema,
|
|
15
|
+
defaultCreateResponseSchema,
|
|
16
|
+
defaultOkResponseSchema
|
|
17
|
+
} from "../openapi.js";
|
|
18
|
+
import { canonicalizeUnitCode } from "../../lib/unitCodes.js";
|
|
19
|
+
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
20
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
21
|
+
const listSchema = z.object({
|
|
22
|
+
page: z.coerce.number().min(1).default(1),
|
|
23
|
+
pageSize: z.coerce.number().min(1).max(100).default(50),
|
|
24
|
+
id: z.string().uuid().optional(),
|
|
25
|
+
productId: z.string().uuid().optional(),
|
|
26
|
+
unitCode: z.string().trim().max(50).optional(),
|
|
27
|
+
isActive: z.coerce.boolean().optional(),
|
|
28
|
+
sortField: z.string().optional(),
|
|
29
|
+
sortDir: z.enum(["asc", "desc"]).optional()
|
|
30
|
+
}).passthrough();
|
|
31
|
+
const routeMetadata = {
|
|
32
|
+
GET: { requireAuth: true, requireFeatures: ["catalog.products.view"] },
|
|
33
|
+
POST: { requireAuth: true, requireFeatures: ["catalog.products.manage"] },
|
|
34
|
+
PUT: { requireAuth: true, requireFeatures: ["catalog.products.manage"] },
|
|
35
|
+
DELETE: { requireAuth: true, requireFeatures: ["catalog.products.manage"] }
|
|
36
|
+
};
|
|
37
|
+
const metadata = routeMetadata;
|
|
38
|
+
const crud = makeCrudRoute({
|
|
39
|
+
metadata: routeMetadata,
|
|
40
|
+
orm: {
|
|
41
|
+
entity: CatalogProductUnitConversion,
|
|
42
|
+
idField: "id",
|
|
43
|
+
orgField: "organizationId",
|
|
44
|
+
tenantField: "tenantId",
|
|
45
|
+
softDeleteField: "deletedAt"
|
|
46
|
+
},
|
|
47
|
+
indexer: {
|
|
48
|
+
entityType: E.catalog.catalog_product_unit_conversion
|
|
49
|
+
},
|
|
50
|
+
list: {
|
|
51
|
+
schema: listSchema,
|
|
52
|
+
entityId: E.catalog.catalog_product_unit_conversion,
|
|
53
|
+
fields: [
|
|
54
|
+
"id",
|
|
55
|
+
"product_id",
|
|
56
|
+
"unit_code",
|
|
57
|
+
"to_base_factor",
|
|
58
|
+
"sort_order",
|
|
59
|
+
"is_active",
|
|
60
|
+
"metadata",
|
|
61
|
+
"created_at",
|
|
62
|
+
"updated_at"
|
|
63
|
+
],
|
|
64
|
+
sortFieldMap: {
|
|
65
|
+
createdAt: "created_at",
|
|
66
|
+
updatedAt: "updated_at",
|
|
67
|
+
sortOrder: "sort_order",
|
|
68
|
+
unitCode: "unit_code"
|
|
69
|
+
},
|
|
70
|
+
buildFilters: async (query) => {
|
|
71
|
+
const filters = {};
|
|
72
|
+
if (query.id) filters.id = { $eq: query.id };
|
|
73
|
+
if (query.productId) filters.product_id = { $eq: query.productId };
|
|
74
|
+
const unitCode = canonicalizeUnitCode(query.unitCode);
|
|
75
|
+
if (unitCode) {
|
|
76
|
+
filters.unit_code = { $eq: unitCode };
|
|
77
|
+
}
|
|
78
|
+
if (typeof query.isActive === "boolean") {
|
|
79
|
+
filters.is_active = query.isActive;
|
|
80
|
+
}
|
|
81
|
+
return filters;
|
|
82
|
+
},
|
|
83
|
+
transformItem: (item) => {
|
|
84
|
+
if (!item) return item;
|
|
85
|
+
const unitCode = canonicalizeUnitCode(
|
|
86
|
+
item["unit_code"] ?? item["unitCode"]
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
...item,
|
|
90
|
+
unit_code: unitCode,
|
|
91
|
+
unitCode
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
actions: {
|
|
96
|
+
create: {
|
|
97
|
+
commandId: "catalog.product-unit-conversions.create",
|
|
98
|
+
schema: rawBodySchema,
|
|
99
|
+
mapInput: async ({ raw, ctx }) => {
|
|
100
|
+
const { translate } = await resolveTranslations();
|
|
101
|
+
return parseScopedCommandInput(
|
|
102
|
+
productUnitConversionCreateSchema,
|
|
103
|
+
raw ?? {},
|
|
104
|
+
ctx,
|
|
105
|
+
translate
|
|
106
|
+
);
|
|
107
|
+
},
|
|
108
|
+
response: ({ result }) => ({ id: result?.conversionId ?? null }),
|
|
109
|
+
status: 201
|
|
110
|
+
},
|
|
111
|
+
update: {
|
|
112
|
+
commandId: "catalog.product-unit-conversions.update",
|
|
113
|
+
schema: rawBodySchema,
|
|
114
|
+
mapInput: async ({ raw, ctx }) => {
|
|
115
|
+
const { translate } = await resolveTranslations();
|
|
116
|
+
return parseScopedCommandInput(
|
|
117
|
+
productUnitConversionUpdateSchema,
|
|
118
|
+
raw ?? {},
|
|
119
|
+
ctx,
|
|
120
|
+
translate
|
|
121
|
+
);
|
|
122
|
+
},
|
|
123
|
+
response: () => ({ ok: true })
|
|
124
|
+
},
|
|
125
|
+
delete: {
|
|
126
|
+
commandId: "catalog.product-unit-conversions.delete",
|
|
127
|
+
schema: rawBodySchema,
|
|
128
|
+
mapInput: async ({ raw, ctx }) => {
|
|
129
|
+
const { translate } = await resolveTranslations();
|
|
130
|
+
const parsed = parseScopedCommandInput(
|
|
131
|
+
productUnitConversionDeleteSchema,
|
|
132
|
+
raw ?? {},
|
|
133
|
+
ctx,
|
|
134
|
+
translate
|
|
135
|
+
);
|
|
136
|
+
if (!parsed.id) {
|
|
137
|
+
throw new CrudHttpError(400, {
|
|
138
|
+
error: translate(
|
|
139
|
+
"catalog.errors.id_required",
|
|
140
|
+
"Record identifier is required."
|
|
141
|
+
)
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
return parsed;
|
|
145
|
+
},
|
|
146
|
+
response: () => ({ ok: true })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
const GET = crud.GET;
|
|
151
|
+
const POST = crud.POST;
|
|
152
|
+
const PUT = crud.PUT;
|
|
153
|
+
const DELETE = crud.DELETE;
|
|
154
|
+
const conversionListItemSchema = z.object({
|
|
155
|
+
id: z.string().uuid(),
|
|
156
|
+
product_id: z.string().uuid(),
|
|
157
|
+
unit_code: z.string(),
|
|
158
|
+
to_base_factor: z.number(),
|
|
159
|
+
sort_order: z.number().nullable().optional(),
|
|
160
|
+
is_active: z.boolean().nullable().optional(),
|
|
161
|
+
metadata: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
162
|
+
created_at: z.string().nullable().optional(),
|
|
163
|
+
updated_at: z.string().nullable().optional()
|
|
164
|
+
});
|
|
165
|
+
const openApi = createCatalogCrudOpenApi({
|
|
166
|
+
resourceName: "Product unit conversion",
|
|
167
|
+
pluralName: "Product unit conversions",
|
|
168
|
+
querySchema: listSchema,
|
|
169
|
+
listResponseSchema: createPagedListResponseSchema(conversionListItemSchema),
|
|
170
|
+
create: {
|
|
171
|
+
schema: productUnitConversionCreateSchema,
|
|
172
|
+
responseSchema: defaultCreateResponseSchema,
|
|
173
|
+
description: "Creates a product unit conversion."
|
|
174
|
+
},
|
|
175
|
+
update: {
|
|
176
|
+
schema: productUnitConversionUpdateSchema,
|
|
177
|
+
responseSchema: defaultOkResponseSchema,
|
|
178
|
+
description: "Updates an existing product unit conversion by id."
|
|
179
|
+
},
|
|
180
|
+
del: {
|
|
181
|
+
schema: productUnitConversionDeleteSchema,
|
|
182
|
+
responseSchema: defaultOkResponseSchema,
|
|
183
|
+
description: "Deletes a product unit conversion by id."
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
export {
|
|
187
|
+
DELETE,
|
|
188
|
+
GET,
|
|
189
|
+
POST,
|
|
190
|
+
PUT,
|
|
191
|
+
metadata,
|
|
192
|
+
openApi
|
|
193
|
+
};
|
|
194
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/catalog/api/product-unit-conversions/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from \"zod\";\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 { CatalogProductUnitConversion } from \"../../data/entities\";\nimport {\n productUnitConversionCreateSchema,\n productUnitConversionUpdateSchema,\n productUnitConversionDeleteSchema,\n} from \"../../data/validators\";\nimport { parseScopedCommandInput } from \"../utils\";\nimport {\n createCatalogCrudOpenApi,\n createPagedListResponseSchema,\n defaultCreateResponseSchema,\n defaultOkResponseSchema,\n} from \"../openapi\";\nimport { canonicalizeUnitCode } from \"../../lib/unitCodes\";\nimport { E } from \"#generated/entities.ids.generated\";\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(100).default(50),\n id: z.string().uuid().optional(),\n productId: z.string().uuid().optional(),\n unitCode: z.string().trim().max(50).optional(),\n isActive: z.coerce.boolean().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum([\"asc\", \"desc\"]).optional(),\n })\n .passthrough();\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: [\"catalog.products.view\"] },\n POST: { requireAuth: true, requireFeatures: [\"catalog.products.manage\"] },\n PUT: { requireAuth: true, requireFeatures: [\"catalog.products.manage\"] },\n DELETE: { requireAuth: true, requireFeatures: [\"catalog.products.manage\"] },\n};\n\nexport const metadata = routeMetadata;\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: CatalogProductUnitConversion,\n idField: \"id\",\n orgField: \"organizationId\",\n tenantField: \"tenantId\",\n softDeleteField: \"deletedAt\",\n },\n indexer: {\n entityType: E.catalog.catalog_product_unit_conversion,\n },\n list: {\n schema: listSchema,\n entityId: E.catalog.catalog_product_unit_conversion,\n fields: [\n \"id\",\n \"product_id\",\n \"unit_code\",\n \"to_base_factor\",\n \"sort_order\",\n \"is_active\",\n \"metadata\",\n \"created_at\",\n \"updated_at\",\n ],\n sortFieldMap: {\n createdAt: \"created_at\",\n updatedAt: \"updated_at\",\n sortOrder: \"sort_order\",\n unitCode: \"unit_code\",\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {};\n if (query.id) filters.id = { $eq: query.id };\n if (query.productId) filters.product_id = { $eq: query.productId };\n const unitCode = canonicalizeUnitCode(query.unitCode);\n if (unitCode) {\n filters.unit_code = { $eq: unitCode };\n }\n if (typeof query.isActive === \"boolean\") {\n filters.is_active = query.isActive;\n }\n return filters;\n },\n transformItem: (item: Record<string, unknown> | null | undefined) => {\n if (!item) return item;\n const unitCode = canonicalizeUnitCode(\n item[\"unit_code\"] ?? item[\"unitCode\"],\n );\n return {\n ...item,\n unit_code: unitCode,\n unitCode,\n };\n },\n },\n actions: {\n create: {\n commandId: \"catalog.product-unit-conversions.create\",\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations();\n return parseScopedCommandInput(\n productUnitConversionCreateSchema,\n raw ?? {},\n ctx,\n translate,\n );\n },\n response: ({ result }) => ({ id: result?.conversionId ?? null }),\n status: 201,\n },\n update: {\n commandId: \"catalog.product-unit-conversions.update\",\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations();\n return parseScopedCommandInput(\n productUnitConversionUpdateSchema,\n raw ?? {},\n ctx,\n translate,\n );\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: \"catalog.product-unit-conversions.delete\",\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations();\n const parsed = parseScopedCommandInput(\n productUnitConversionDeleteSchema,\n raw ?? {},\n ctx,\n translate,\n );\n if (!parsed.id) {\n throw new CrudHttpError(400, {\n error: translate(\n \"catalog.errors.id_required\",\n \"Record identifier is required.\",\n ),\n });\n }\n return parsed;\n },\n response: () => ({ ok: true }),\n },\n },\n});\n\nexport const GET = crud.GET;\nexport const POST = crud.POST;\nexport const PUT = crud.PUT;\nexport const DELETE = crud.DELETE;\n\nconst conversionListItemSchema = z.object({\n id: z.string().uuid(),\n product_id: z.string().uuid(),\n unit_code: z.string(),\n to_base_factor: z.number(),\n sort_order: z.number().nullable().optional(),\n is_active: z.boolean().nullable().optional(),\n metadata: z.record(z.string(), z.unknown()).nullable().optional(),\n created_at: z.string().nullable().optional(),\n updated_at: z.string().nullable().optional(),\n});\n\nexport const openApi = createCatalogCrudOpenApi({\n resourceName: \"Product unit conversion\",\n pluralName: \"Product unit conversions\",\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(conversionListItemSchema),\n create: {\n schema: productUnitConversionCreateSchema,\n responseSchema: defaultCreateResponseSchema,\n description: \"Creates a product unit conversion.\",\n },\n update: {\n schema: productUnitConversionUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: \"Updates an existing product unit conversion by id.\",\n },\n del: {\n schema: productUnitConversionDeleteSchema,\n responseSchema: defaultOkResponseSchema,\n description: \"Deletes a product unit conversion by id.\",\n },\n});\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|