@open-mercato/core 0.4.5-develop-f4858e0ef3 → 0.4.5-develop-4849712ccb
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/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/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
|
@@ -12,6 +12,13 @@ export const product_type = 'product_type'
|
|
|
12
12
|
export const status_entry_id = 'status_entry_id'
|
|
13
13
|
export const primary_currency_code = 'primary_currency_code'
|
|
14
14
|
export const default_unit = 'default_unit'
|
|
15
|
+
export const default_sales_unit = 'default_sales_unit'
|
|
16
|
+
export const default_sales_unit_quantity = 'default_sales_unit_quantity'
|
|
17
|
+
export const uom_rounding_scale = 'uom_rounding_scale'
|
|
18
|
+
export const uom_rounding_mode = 'uom_rounding_mode'
|
|
19
|
+
export const unit_price_enabled = 'unit_price_enabled'
|
|
20
|
+
export const unit_price_reference_unit = 'unit_price_reference_unit'
|
|
21
|
+
export const unit_price_base_quantity = 'unit_price_base_quantity'
|
|
15
22
|
export const default_media_id = 'default_media_id'
|
|
16
23
|
export const default_media_url = 'default_media_url'
|
|
17
24
|
export const weight_value = 'weight_value'
|
|
@@ -29,3 +36,4 @@ export const variants = 'variants'
|
|
|
29
36
|
export const offers = 'offers'
|
|
30
37
|
export const category_assignments = 'category_assignments'
|
|
31
38
|
export const tag_assignments = 'tag_assignments'
|
|
39
|
+
export const unit_conversions = 'unit_conversions'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const id = 'id'
|
|
2
|
+
export const product = 'product'
|
|
3
|
+
export const organization_id = 'organization_id'
|
|
4
|
+
export const tenant_id = 'tenant_id'
|
|
5
|
+
export const unit_code = 'unit_code'
|
|
6
|
+
export const to_base_factor = 'to_base_factor'
|
|
7
|
+
export const sort_order = 'sort_order'
|
|
8
|
+
export const is_active = 'is_active'
|
|
9
|
+
export const metadata = 'metadata'
|
|
10
|
+
export const created_at = 'created_at'
|
|
11
|
+
export const updated_at = 'updated_at'
|
|
12
|
+
export const deleted_at = 'deleted_at'
|
|
@@ -7,6 +7,9 @@ export const line_number = 'line_number'
|
|
|
7
7
|
export const description = 'description'
|
|
8
8
|
export const quantity = 'quantity'
|
|
9
9
|
export const quantity_unit = 'quantity_unit'
|
|
10
|
+
export const normalized_quantity = 'normalized_quantity'
|
|
11
|
+
export const normalized_unit = 'normalized_unit'
|
|
12
|
+
export const uom_snapshot = 'uom_snapshot'
|
|
10
13
|
export const currency_code = 'currency_code'
|
|
11
14
|
export const unit_price_net = 'unit_price_net'
|
|
12
15
|
export const unit_price_gross = 'unit_price_gross'
|
|
@@ -8,6 +8,9 @@ export const kind = 'kind'
|
|
|
8
8
|
export const description = 'description'
|
|
9
9
|
export const quantity = 'quantity'
|
|
10
10
|
export const quantity_unit = 'quantity_unit'
|
|
11
|
+
export const normalized_quantity = 'normalized_quantity'
|
|
12
|
+
export const normalized_unit = 'normalized_unit'
|
|
13
|
+
export const uom_snapshot = 'uom_snapshot'
|
|
11
14
|
export const currency_code = 'currency_code'
|
|
12
15
|
export const unit_price_net = 'unit_price_net'
|
|
13
16
|
export const unit_price_gross = 'unit_price_gross'
|
|
@@ -14,6 +14,9 @@ export const description = 'description'
|
|
|
14
14
|
export const comment = 'comment'
|
|
15
15
|
export const quantity = 'quantity'
|
|
16
16
|
export const quantity_unit = 'quantity_unit'
|
|
17
|
+
export const normalized_quantity = 'normalized_quantity'
|
|
18
|
+
export const normalized_unit = 'normalized_unit'
|
|
19
|
+
export const uom_snapshot = 'uom_snapshot'
|
|
17
20
|
export const reserved_quantity = 'reserved_quantity'
|
|
18
21
|
export const fulfilled_quantity = 'fulfilled_quantity'
|
|
19
22
|
export const invoiced_quantity = 'invoiced_quantity'
|
|
@@ -14,6 +14,9 @@ export const description = 'description'
|
|
|
14
14
|
export const comment = 'comment'
|
|
15
15
|
export const quantity = 'quantity'
|
|
16
16
|
export const quantity_unit = 'quantity_unit'
|
|
17
|
+
export const normalized_quantity = 'normalized_quantity'
|
|
18
|
+
export const normalized_unit = 'normalized_unit'
|
|
19
|
+
export const uom_snapshot = 'uom_snapshot'
|
|
17
20
|
export const currency_code = 'currency_code'
|
|
18
21
|
export const unit_price_net = 'unit_price_net'
|
|
19
22
|
export const unit_price_gross = 'unit_price_gross'
|
|
@@ -100,6 +100,7 @@ export const E = {
|
|
|
100
100
|
"catalog": {
|
|
101
101
|
"catalog_option_schema_template": "catalog:catalog_option_schema_template",
|
|
102
102
|
"catalog_product": "catalog:catalog_product",
|
|
103
|
+
"catalog_product_unit_conversion": "catalog:catalog_product_unit_conversion",
|
|
103
104
|
"catalog_product_category": "catalog:catalog_product_category",
|
|
104
105
|
"catalog_product_category_assignment": "catalog:catalog_product_category_assignment",
|
|
105
106
|
"catalog_product_tag": "catalog:catalog_product_tag",
|
|
@@ -15,6 +15,7 @@ import * as catalog_product_category_assignment from './entities/catalog_product
|
|
|
15
15
|
import * as catalog_product_price from './entities/catalog_product_price/index'
|
|
16
16
|
import * as catalog_product_tag from './entities/catalog_product_tag/index'
|
|
17
17
|
import * as catalog_product_tag_assignment from './entities/catalog_product_tag_assignment/index'
|
|
18
|
+
import * as catalog_product_unit_conversion from './entities/catalog_product_unit_conversion/index'
|
|
18
19
|
import * as catalog_product_variant from './entities/catalog_product_variant/index'
|
|
19
20
|
import * as catalog_product_variant_relation from './entities/catalog_product_variant_relation/index'
|
|
20
21
|
import * as currency from './entities/currency/index'
|
|
@@ -148,6 +149,7 @@ export const entityFieldsRegistry: Record<string, Record<string, string>> = {
|
|
|
148
149
|
catalog_product_price,
|
|
149
150
|
catalog_product_tag,
|
|
150
151
|
catalog_product_tag_assignment,
|
|
152
|
+
catalog_product_unit_conversion,
|
|
151
153
|
catalog_product_variant,
|
|
152
154
|
catalog_product_variant_relation,
|
|
153
155
|
currency,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.4.5-develop-
|
|
3
|
+
"version": "0.4.5-develop-4849712ccb",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
}
|
|
208
208
|
},
|
|
209
209
|
"dependencies": {
|
|
210
|
-
"@open-mercato/shared": "0.4.5-develop-
|
|
210
|
+
"@open-mercato/shared": "0.4.5-develop-4849712ccb",
|
|
211
211
|
"@types/semver": "^7.5.8",
|
|
212
212
|
"@xyflow/react": "^12.6.0",
|
|
213
213
|
"ai": "^6.0.0",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"auth.reset.description": "Gib deine E-Mail-Adresse ein, um einen Zurücksetzungslink zu erhalten",
|
|
79
79
|
"auth.reset.error": "Etwas ist schiefgelaufen",
|
|
80
80
|
"auth.reset.errors.failed": "Passwort konnte nicht zurückgesetzt werden",
|
|
81
|
-
"auth.reset.sent": "Falls ein Konto mit dieser E-Mail-Adresse existiert, haben wir einen Zurücksetzungslink gesendet. Überprüfe deinen Posteingang.",
|
|
82
81
|
"auth.reset.form.loading": "...",
|
|
83
82
|
"auth.reset.form.password": "Neues Passwort",
|
|
84
83
|
"auth.reset.form.submit": "Passwort aktualisieren",
|
|
84
|
+
"auth.reset.sent": "Falls ein Konto mit dieser E-Mail-Adresse existiert, haben wir einen Zurücksetzungslink gesendet. Überprüfe deinen Posteingang.",
|
|
85
85
|
"auth.reset.subtitle": "Wähle ein sicheres Passwort für dein Konto.",
|
|
86
86
|
"auth.reset.title": "Neues Passwort festlegen",
|
|
87
87
|
"auth.resetPassword": "Passwort zurücksetzen",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"auth.reset.description": "Enter your email to receive reset link",
|
|
79
79
|
"auth.reset.error": "Something went wrong",
|
|
80
80
|
"auth.reset.errors.failed": "Unable to reset password",
|
|
81
|
-
"auth.reset.sent": "If an account with that email exists, we sent a reset link. Please check your inbox.",
|
|
82
81
|
"auth.reset.form.loading": "...",
|
|
83
82
|
"auth.reset.form.password": "New password",
|
|
84
83
|
"auth.reset.form.submit": "Update password",
|
|
84
|
+
"auth.reset.sent": "If an account with that email exists, we sent a reset link. Please check your inbox.",
|
|
85
85
|
"auth.reset.subtitle": "Choose a strong password for your account.",
|
|
86
86
|
"auth.reset.title": "Set a new password",
|
|
87
87
|
"auth.resetPassword": "Reset password",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"auth.reset.description": "Ingresa tu correo para recibir un enlace de restablecimiento",
|
|
79
79
|
"auth.reset.error": "Algo salió mal",
|
|
80
80
|
"auth.reset.errors.failed": "No se pudo restablecer la contraseña",
|
|
81
|
-
"auth.reset.sent": "Si existe una cuenta con ese correo, enviamos un enlace de restablecimiento. Revisa tu bandeja de entrada.",
|
|
82
81
|
"auth.reset.form.loading": "...",
|
|
83
82
|
"auth.reset.form.password": "Nueva contraseña",
|
|
84
83
|
"auth.reset.form.submit": "Actualizar contraseña",
|
|
84
|
+
"auth.reset.sent": "Si existe una cuenta con ese correo, enviamos un enlace de restablecimiento. Revisa tu bandeja de entrada.",
|
|
85
85
|
"auth.reset.subtitle": "Elige una contraseña segura para tu cuenta.",
|
|
86
86
|
"auth.reset.title": "Establecer una nueva contraseña",
|
|
87
87
|
"auth.resetPassword": "Restablecer contraseña",
|
|
@@ -78,10 +78,10 @@
|
|
|
78
78
|
"auth.reset.description": "Podaj swój adres e-mail, aby otrzymać link do resetowania",
|
|
79
79
|
"auth.reset.error": "Coś poszło nie tak",
|
|
80
80
|
"auth.reset.errors.failed": "Nie udało się zresetować hasła",
|
|
81
|
-
"auth.reset.sent": "Jeśli konto z tym adresem e-mail istnieje, wysłaliśmy link do resetowania. Sprawdź swoją skrzynkę odbiorczą.",
|
|
82
81
|
"auth.reset.form.loading": "...",
|
|
83
82
|
"auth.reset.form.password": "Nowe hasło",
|
|
84
83
|
"auth.reset.form.submit": "Zaktualizuj hasło",
|
|
84
|
+
"auth.reset.sent": "Jeśli konto z tym adresem e-mail istnieje, wysłaliśmy link do resetowania. Sprawdź swoją skrzynkę odbiorczą.",
|
|
85
85
|
"auth.reset.subtitle": "Wybierz silne hasło dla swojego konta.",
|
|
86
86
|
"auth.reset.title": "Ustaw nowe hasło",
|
|
87
87
|
"auth.resetPassword": "Resetuj hasło",
|
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import type { EntityManager } from
|
|
3
|
-
import { makeCrudRoute } from
|
|
4
|
-
import { CrudHttpError } from
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { EntityManager } from "@mikro-orm/postgresql";
|
|
3
|
+
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
4
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
5
|
+
import {
|
|
6
|
+
buildCustomFieldFiltersFromQuery,
|
|
7
|
+
extractAllCustomFieldEntries,
|
|
8
|
+
} from "@open-mercato/shared/lib/crud/custom-fields";
|
|
9
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
10
|
+
import {
|
|
11
|
+
CatalogProduct,
|
|
12
|
+
CatalogProductPrice,
|
|
13
|
+
CatalogProductUnitConversion,
|
|
14
|
+
CatalogProductVariant,
|
|
15
|
+
} from "../../data/entities";
|
|
16
|
+
import { priceCreateSchema, priceUpdateSchema } from "../../data/validators";
|
|
17
|
+
import { parseScopedCommandInput, resolveCrudRecordId } from "../utils";
|
|
18
|
+
import { E } from "#generated/entities.ids.generated";
|
|
19
|
+
import * as FP from "#generated/entities/catalog_product_price";
|
|
12
20
|
import {
|
|
13
21
|
createCatalogCrudOpenApi,
|
|
14
22
|
createPagedListResponseSchema,
|
|
15
23
|
defaultOkResponseSchema,
|
|
16
|
-
} from
|
|
24
|
+
} from "../openapi";
|
|
25
|
+
import { toUnitLookupKey } from "../../lib/unitCodes";
|
|
26
|
+
import {
|
|
27
|
+
findWithDecryption,
|
|
28
|
+
findOneWithDecryption,
|
|
29
|
+
} from "@open-mercato/shared/lib/encryption/find";
|
|
17
30
|
|
|
18
|
-
const rawBodySchema = z.object({}).passthrough()
|
|
31
|
+
const rawBodySchema = z.object({}).passthrough();
|
|
19
32
|
|
|
20
33
|
const listSchema = z
|
|
21
34
|
.object({
|
|
@@ -32,74 +45,154 @@ const listSchema = z
|
|
|
32
45
|
userGroupId: z.string().uuid().optional(),
|
|
33
46
|
customerId: z.string().uuid().optional(),
|
|
34
47
|
customerGroupId: z.string().uuid().optional(),
|
|
48
|
+
quantity: z.coerce.number().min(1).max(100000).optional(),
|
|
49
|
+
quantityUnit: z.string().trim().max(50).optional(),
|
|
35
50
|
withDeleted: z.coerce.boolean().optional(),
|
|
36
51
|
sortField: z.string().optional(),
|
|
37
|
-
sortDir: z.enum([
|
|
52
|
+
sortDir: z.enum(["asc", "desc"]).optional(),
|
|
38
53
|
})
|
|
39
|
-
.passthrough()
|
|
54
|
+
.passthrough();
|
|
40
55
|
|
|
41
|
-
type PriceQuery = z.infer<typeof listSchema
|
|
56
|
+
type PriceQuery = z.infer<typeof listSchema>;
|
|
42
57
|
|
|
43
58
|
const routeMetadata = {
|
|
44
|
-
GET: { requireAuth: true, requireFeatures: [
|
|
45
|
-
POST: { requireAuth: true, requireFeatures: [
|
|
46
|
-
PUT: { requireAuth: true, requireFeatures: [
|
|
47
|
-
DELETE: { requireAuth: true, requireFeatures: [
|
|
48
|
-
}
|
|
59
|
+
GET: { requireAuth: true, requireFeatures: ["catalog.products.view"] },
|
|
60
|
+
POST: { requireAuth: true, requireFeatures: ["catalog.pricing.manage"] },
|
|
61
|
+
PUT: { requireAuth: true, requireFeatures: ["catalog.pricing.manage"] },
|
|
62
|
+
DELETE: { requireAuth: true, requireFeatures: ["catalog.pricing.manage"] },
|
|
63
|
+
};
|
|
49
64
|
|
|
50
|
-
export const metadata = routeMetadata
|
|
65
|
+
export const metadata = routeMetadata;
|
|
66
|
+
|
|
67
|
+
async function resolveNormalizedQuantityForFilter(params: {
|
|
68
|
+
em: EntityManager;
|
|
69
|
+
organizationId: string | null;
|
|
70
|
+
tenantId: string | null;
|
|
71
|
+
productId?: string;
|
|
72
|
+
variantId?: string;
|
|
73
|
+
quantity: number;
|
|
74
|
+
quantityUnit?: string;
|
|
75
|
+
}): Promise<number> {
|
|
76
|
+
const quantity = params.quantity;
|
|
77
|
+
const quantityUnitKey = toUnitLookupKey(params.quantityUnit);
|
|
78
|
+
if ((!params.productId && !params.variantId) || !quantityUnitKey)
|
|
79
|
+
return quantity;
|
|
80
|
+
if (!params.organizationId || !params.tenantId) return quantity;
|
|
81
|
+
let targetProductId = params.productId;
|
|
82
|
+
if (!targetProductId && params.variantId) {
|
|
83
|
+
const variant = await findOneWithDecryption(
|
|
84
|
+
params.em,
|
|
85
|
+
CatalogProductVariant,
|
|
86
|
+
{
|
|
87
|
+
id: params.variantId,
|
|
88
|
+
organizationId: params.organizationId,
|
|
89
|
+
tenantId: params.tenantId,
|
|
90
|
+
deletedAt: null,
|
|
91
|
+
},
|
|
92
|
+
{ fields: ["id", "product"] },
|
|
93
|
+
);
|
|
94
|
+
if (variant) {
|
|
95
|
+
targetProductId =
|
|
96
|
+
typeof variant.product === "string"
|
|
97
|
+
? variant.product
|
|
98
|
+
: (variant.product?.id ?? null);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (!targetProductId) return quantity;
|
|
102
|
+
const product = await findOneWithDecryption(
|
|
103
|
+
params.em,
|
|
104
|
+
CatalogProduct,
|
|
105
|
+
{
|
|
106
|
+
id: targetProductId,
|
|
107
|
+
organizationId: params.organizationId,
|
|
108
|
+
tenantId: params.tenantId,
|
|
109
|
+
deletedAt: null,
|
|
110
|
+
},
|
|
111
|
+
{ fields: ["id", "defaultUnit", "uomRoundingScale"] },
|
|
112
|
+
);
|
|
113
|
+
if (!product) return quantity;
|
|
114
|
+
const baseUnitKey = toUnitLookupKey(product.defaultUnit);
|
|
115
|
+
if (!baseUnitKey || baseUnitKey === quantityUnitKey) return quantity;
|
|
116
|
+
const conversions = await findWithDecryption(
|
|
117
|
+
params.em,
|
|
118
|
+
CatalogProductUnitConversion,
|
|
119
|
+
{
|
|
120
|
+
product: product.id,
|
|
121
|
+
organizationId: params.organizationId,
|
|
122
|
+
tenantId: params.tenantId,
|
|
123
|
+
isActive: true,
|
|
124
|
+
deletedAt: null,
|
|
125
|
+
},
|
|
126
|
+
{ fields: ["id", "unitCode", "toBaseFactor"] },
|
|
127
|
+
);
|
|
128
|
+
const conversion = conversions.find(
|
|
129
|
+
(entry) => toUnitLookupKey(entry.unitCode) === quantityUnitKey,
|
|
130
|
+
);
|
|
131
|
+
if (!conversion) return quantity;
|
|
132
|
+
const factor = Number(conversion.toBaseFactor);
|
|
133
|
+
if (!Number.isFinite(factor) || factor <= 0) return quantity;
|
|
134
|
+
const rawNormalized = quantity * factor;
|
|
135
|
+
const scale = Number(product.uomRoundingScale ?? 4);
|
|
136
|
+
const pow = Math.pow(10, scale);
|
|
137
|
+
const normalized = Math.round(rawNormalized * pow) / pow;
|
|
138
|
+
return Number.isFinite(normalized) && normalized > 0 ? normalized : quantity;
|
|
139
|
+
}
|
|
51
140
|
|
|
52
141
|
export async function buildPriceFilters(
|
|
53
|
-
query: PriceQuery
|
|
142
|
+
query: PriceQuery,
|
|
54
143
|
): Promise<Record<string, unknown>> {
|
|
55
|
-
const filters: Record<string, unknown> = {}
|
|
144
|
+
const filters: Record<string, unknown> = {};
|
|
56
145
|
if (query.productId) {
|
|
57
|
-
filters.product_id = { $eq: query.productId }
|
|
146
|
+
filters.product_id = { $eq: query.productId };
|
|
58
147
|
}
|
|
59
148
|
if (query.variantId) {
|
|
60
|
-
filters.variant_id = { $eq: query.variantId }
|
|
149
|
+
filters.variant_id = { $eq: query.variantId };
|
|
61
150
|
}
|
|
62
151
|
if (query.offerId) {
|
|
63
|
-
filters.offer_id = { $eq: query.offerId }
|
|
152
|
+
filters.offer_id = { $eq: query.offerId };
|
|
64
153
|
}
|
|
65
154
|
if (query.channelId) {
|
|
66
|
-
filters.channel_id = { $eq: query.channelId }
|
|
155
|
+
filters.channel_id = { $eq: query.channelId };
|
|
67
156
|
}
|
|
68
157
|
if (query.currencyCode) {
|
|
69
|
-
filters.currency_code = { $eq: query.currencyCode.trim().toUpperCase() }
|
|
158
|
+
filters.currency_code = { $eq: query.currencyCode.trim().toUpperCase() };
|
|
70
159
|
}
|
|
71
160
|
if (query.priceKindId) {
|
|
72
|
-
filters.price_kind_id = { $eq: query.priceKindId }
|
|
161
|
+
filters.price_kind_id = { $eq: query.priceKindId };
|
|
73
162
|
}
|
|
74
163
|
if (query.kind) {
|
|
75
|
-
filters.kind = { $eq: query.kind }
|
|
164
|
+
filters.kind = { $eq: query.kind };
|
|
76
165
|
}
|
|
77
|
-
if (query.userId) filters.user_id = { $eq: query.userId }
|
|
78
|
-
if (query.userGroupId) filters.user_group_id = { $eq: query.userGroupId }
|
|
79
|
-
if (query.customerId) filters.customer_id = { $eq: query.customerId }
|
|
80
|
-
if (query.customerGroupId)
|
|
81
|
-
|
|
166
|
+
if (query.userId) filters.user_id = { $eq: query.userId };
|
|
167
|
+
if (query.userGroupId) filters.user_group_id = { $eq: query.userGroupId };
|
|
168
|
+
if (query.customerId) filters.customer_id = { $eq: query.customerId };
|
|
169
|
+
if (query.customerGroupId)
|
|
170
|
+
filters.customer_group_id = { $eq: query.customerGroupId };
|
|
171
|
+
return filters;
|
|
82
172
|
}
|
|
83
173
|
|
|
84
174
|
const crud = makeCrudRoute({
|
|
85
175
|
metadata: routeMetadata,
|
|
86
176
|
orm: {
|
|
87
177
|
entity: CatalogProductPrice,
|
|
88
|
-
idField:
|
|
89
|
-
orgField:
|
|
90
|
-
tenantField:
|
|
178
|
+
idField: "id",
|
|
179
|
+
orgField: "organizationId",
|
|
180
|
+
tenantField: "tenantId",
|
|
91
181
|
softDeleteField: null,
|
|
92
182
|
},
|
|
183
|
+
indexer: {
|
|
184
|
+
entityType: E.catalog.catalog_product_price,
|
|
185
|
+
},
|
|
93
186
|
list: {
|
|
94
187
|
schema: listSchema,
|
|
95
188
|
entityId: E.catalog.catalog_product_price,
|
|
96
189
|
fields: [
|
|
97
190
|
FP.id,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
191
|
+
"product_id",
|
|
192
|
+
"variant_id",
|
|
193
|
+
"offer_id",
|
|
101
194
|
FP.currency_code,
|
|
102
|
-
|
|
195
|
+
"price_kind_id",
|
|
103
196
|
FP.kind,
|
|
104
197
|
FP.min_quantity,
|
|
105
198
|
FP.max_quantity,
|
|
@@ -120,77 +213,116 @@ const crud = makeCrudRoute({
|
|
|
120
213
|
],
|
|
121
214
|
sortFieldMap: {
|
|
122
215
|
currencyCode: FP.currency_code,
|
|
123
|
-
priceKindId:
|
|
216
|
+
priceKindId: "price_kind_id",
|
|
124
217
|
kind: FP.kind,
|
|
125
218
|
minQuantity: FP.min_quantity,
|
|
126
219
|
createdAt: FP.created_at,
|
|
127
220
|
updatedAt: FP.updated_at,
|
|
128
221
|
},
|
|
129
222
|
buildFilters: async (query, ctx) => {
|
|
130
|
-
const filters = await buildPriceFilters(query)
|
|
131
|
-
const tenantId = ctx.auth?.tenantId ?? null
|
|
223
|
+
const filters = await buildPriceFilters(query);
|
|
224
|
+
const tenantId = ctx.auth?.tenantId ?? null;
|
|
225
|
+
const organizationId =
|
|
226
|
+
ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null;
|
|
227
|
+
const em = ctx.container.resolve("em") as EntityManager;
|
|
132
228
|
try {
|
|
133
|
-
const em = ctx.container.resolve('em') as EntityManager
|
|
134
229
|
const cfFilters = await buildCustomFieldFiltersFromQuery({
|
|
135
230
|
entityIds: [E.catalog.catalog_product_price],
|
|
136
231
|
query,
|
|
137
232
|
em,
|
|
138
233
|
tenantId,
|
|
139
|
-
})
|
|
140
|
-
Object.assign(filters, cfFilters)
|
|
141
|
-
} catch {
|
|
142
|
-
//
|
|
234
|
+
});
|
|
235
|
+
Object.assign(filters, cfFilters);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
// Custom field filter parsing may fail for non-existent or misconfigured fields.
|
|
238
|
+
if (process.env.NODE_ENV === 'development') console.warn('[catalog:prices] custom field filter error', err);
|
|
239
|
+
}
|
|
240
|
+
if (
|
|
241
|
+
typeof query.quantity === "number" &&
|
|
242
|
+
Number.isFinite(query.quantity) &&
|
|
243
|
+
query.quantity > 0
|
|
244
|
+
) {
|
|
245
|
+
const normalizedQuantity = await resolveNormalizedQuantityForFilter({
|
|
246
|
+
em,
|
|
247
|
+
organizationId,
|
|
248
|
+
tenantId,
|
|
249
|
+
productId: query.productId,
|
|
250
|
+
variantId: query.variantId,
|
|
251
|
+
quantity: query.quantity,
|
|
252
|
+
quantityUnit: query.quantityUnit,
|
|
253
|
+
});
|
|
254
|
+
filters.min_quantity = { $lte: normalizedQuantity };
|
|
255
|
+
filters.$or = [
|
|
256
|
+
{ max_quantity: null },
|
|
257
|
+
{ max_quantity: { $gte: normalizedQuantity } },
|
|
258
|
+
];
|
|
143
259
|
}
|
|
144
|
-
return filters
|
|
260
|
+
return filters;
|
|
145
261
|
},
|
|
146
|
-
transformItem: (item:
|
|
147
|
-
if (!item) return item
|
|
148
|
-
const normalized = { ...item }
|
|
149
|
-
const cfEntries = extractAllCustomFieldEntries(item)
|
|
262
|
+
transformItem: (item: Record<string, unknown> | null | undefined) => {
|
|
263
|
+
if (!item) return item;
|
|
264
|
+
const normalized = { ...item };
|
|
265
|
+
const cfEntries = extractAllCustomFieldEntries(item);
|
|
150
266
|
for (const key of Object.keys(normalized)) {
|
|
151
|
-
if (key.startsWith(
|
|
267
|
+
if (key.startsWith("cf:")) delete normalized[key];
|
|
152
268
|
}
|
|
153
|
-
return { ...normalized, ...cfEntries }
|
|
269
|
+
return { ...normalized, ...cfEntries };
|
|
154
270
|
},
|
|
155
271
|
},
|
|
156
272
|
actions: {
|
|
157
273
|
create: {
|
|
158
|
-
commandId:
|
|
274
|
+
commandId: "catalog.prices.create",
|
|
159
275
|
schema: rawBodySchema,
|
|
160
276
|
mapInput: async ({ raw, ctx }) => {
|
|
161
|
-
const { translate } = await resolveTranslations()
|
|
162
|
-
return parseScopedCommandInput(
|
|
277
|
+
const { translate } = await resolveTranslations();
|
|
278
|
+
return parseScopedCommandInput(
|
|
279
|
+
priceCreateSchema,
|
|
280
|
+
raw ?? {},
|
|
281
|
+
ctx,
|
|
282
|
+
translate,
|
|
283
|
+
);
|
|
163
284
|
},
|
|
164
285
|
response: ({ result }) => ({ id: result?.priceId ?? result?.id ?? null }),
|
|
165
286
|
status: 201,
|
|
166
287
|
},
|
|
167
288
|
update: {
|
|
168
|
-
commandId:
|
|
289
|
+
commandId: "catalog.prices.update",
|
|
169
290
|
schema: rawBodySchema,
|
|
170
291
|
mapInput: async ({ raw, ctx }) => {
|
|
171
|
-
const { translate } = await resolveTranslations()
|
|
172
|
-
return parseScopedCommandInput(
|
|
292
|
+
const { translate } = await resolveTranslations();
|
|
293
|
+
return parseScopedCommandInput(
|
|
294
|
+
priceUpdateSchema,
|
|
295
|
+
raw ?? {},
|
|
296
|
+
ctx,
|
|
297
|
+
translate,
|
|
298
|
+
);
|
|
173
299
|
},
|
|
174
300
|
response: () => ({ ok: true }),
|
|
175
301
|
},
|
|
176
302
|
delete: {
|
|
177
|
-
commandId:
|
|
303
|
+
commandId: "catalog.prices.delete",
|
|
178
304
|
schema: rawBodySchema,
|
|
179
305
|
mapInput: async ({ parsed, ctx }) => {
|
|
180
|
-
const { translate } = await resolveTranslations()
|
|
181
|
-
const id = resolveCrudRecordId(parsed, ctx, translate)
|
|
182
|
-
if (!id)
|
|
183
|
-
|
|
306
|
+
const { translate } = await resolveTranslations();
|
|
307
|
+
const id = resolveCrudRecordId(parsed, ctx, translate);
|
|
308
|
+
if (!id)
|
|
309
|
+
throw new CrudHttpError(400, {
|
|
310
|
+
error: translate(
|
|
311
|
+
"catalog.errors.id_required",
|
|
312
|
+
"Price id is required.",
|
|
313
|
+
),
|
|
314
|
+
});
|
|
315
|
+
return { id };
|
|
184
316
|
},
|
|
185
317
|
response: () => ({ ok: true }),
|
|
186
318
|
},
|
|
187
319
|
},
|
|
188
|
-
})
|
|
320
|
+
});
|
|
189
321
|
|
|
190
|
-
export const GET = crud.GET
|
|
191
|
-
export const POST = crud.POST
|
|
192
|
-
export const PUT = crud.PUT
|
|
193
|
-
export const DELETE = crud.DELETE
|
|
322
|
+
export const GET = crud.GET;
|
|
323
|
+
export const POST = crud.POST;
|
|
324
|
+
export const PUT = crud.PUT;
|
|
325
|
+
export const DELETE = crud.DELETE;
|
|
194
326
|
|
|
195
327
|
const priceListItemSchema = z.object({
|
|
196
328
|
id: z.string().uuid(),
|
|
@@ -216,25 +348,25 @@ const priceListItemSchema = z.object({
|
|
|
216
348
|
ends_at: z.string().nullable().optional(),
|
|
217
349
|
created_at: z.string().nullable().optional(),
|
|
218
350
|
updated_at: z.string().nullable().optional(),
|
|
219
|
-
})
|
|
351
|
+
});
|
|
220
352
|
|
|
221
353
|
export const openApi = createCatalogCrudOpenApi({
|
|
222
|
-
resourceName:
|
|
223
|
-
pluralName:
|
|
354
|
+
resourceName: "Price",
|
|
355
|
+
pluralName: "Prices",
|
|
224
356
|
querySchema: listSchema,
|
|
225
357
|
listResponseSchema: createPagedListResponseSchema(priceListItemSchema),
|
|
226
358
|
create: {
|
|
227
359
|
schema: priceCreateSchema,
|
|
228
|
-
description:
|
|
360
|
+
description: "Creates a new price entry for a product or variant.",
|
|
229
361
|
},
|
|
230
362
|
update: {
|
|
231
363
|
schema: priceUpdateSchema,
|
|
232
364
|
responseSchema: defaultOkResponseSchema,
|
|
233
|
-
description:
|
|
365
|
+
description: "Updates an existing price by id.",
|
|
234
366
|
},
|
|
235
367
|
del: {
|
|
236
368
|
schema: z.object({ id: z.string().uuid() }),
|
|
237
369
|
responseSchema: defaultOkResponseSchema,
|
|
238
|
-
description:
|
|
370
|
+
description: "Deletes a price by id.",
|
|
239
371
|
},
|
|
240
|
-
})
|
|
372
|
+
});
|