@open-mercato/core 0.6.8-develop.7077.1.94f1126c13 → 0.6.8-develop.7079.1.dbf29dd6a1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/feature_toggles/defaults.json +0 -8
- package/dist/modules/sales/commands/documents.js +79 -1
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js +33 -0
- package/dist/modules/sales/lib/salesChannelsToggleSeed.js.map +7 -0
- package/dist/modules/sales/setup.js +2 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/feature_toggles/defaults.json +0 -8
- package/src/modules/sales/commands/documents.ts +93 -0
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/ko.json +3 -0
- package/src/modules/sales/i18n/pl.json +3 -0
- package/src/modules/sales/lib/salesChannelsToggleSeed.ts +42 -0
- package/src/modules/sales/setup.ts +2 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FeatureToggle } from "@open-mercato/core/modules/feature_toggles/data/entities";
|
|
2
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { SALES_CHANNELS_TOGGLE_ID } from "./salesChannelsToggleId.js";
|
|
4
|
+
const SALES_CHANNELS_TOGGLE_DEFINITION = {
|
|
5
|
+
identifier: SALES_CHANNELS_TOGGLE_ID,
|
|
6
|
+
name: "Sales Channels",
|
|
7
|
+
description: "Show sales channel pickers, filters, and management UI. Disable for tenants that do not use sales channels.",
|
|
8
|
+
category: "sales",
|
|
9
|
+
type: "boolean",
|
|
10
|
+
defaultValue: true
|
|
11
|
+
};
|
|
12
|
+
async function seedSalesChannelsToggle(em) {
|
|
13
|
+
const existing = await findOneWithDecryption(em, FeatureToggle, {
|
|
14
|
+
identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier
|
|
15
|
+
});
|
|
16
|
+
if (existing) return;
|
|
17
|
+
em.persist(
|
|
18
|
+
em.create(FeatureToggle, {
|
|
19
|
+
identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier,
|
|
20
|
+
name: SALES_CHANNELS_TOGGLE_DEFINITION.name,
|
|
21
|
+
description: SALES_CHANNELS_TOGGLE_DEFINITION.description,
|
|
22
|
+
category: SALES_CHANNELS_TOGGLE_DEFINITION.category,
|
|
23
|
+
type: SALES_CHANNELS_TOGGLE_DEFINITION.type,
|
|
24
|
+
defaultValue: SALES_CHANNELS_TOGGLE_DEFINITION.defaultValue
|
|
25
|
+
})
|
|
26
|
+
);
|
|
27
|
+
await em.flush();
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
SALES_CHANNELS_TOGGLE_DEFINITION,
|
|
31
|
+
seedSalesChannelsToggle
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=salesChannelsToggleSeed.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/sales/lib/salesChannelsToggleSeed.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { SALES_CHANNELS_TOGGLE_ID } from './salesChannelsToggleId'\n\nexport const SALES_CHANNELS_TOGGLE_DEFINITION = {\n identifier: SALES_CHANNELS_TOGGLE_ID,\n name: 'Sales Channels',\n description: 'Show sales channel pickers, filters, and management UI. Disable for tenants that do not use sales channels.',\n category: 'sales',\n type: 'boolean' as const,\n defaultValue: true,\n} as const\n\n// The sales module owns this toggle definition, so tenant setup registers it the\n// same way portal, customers and wms register theirs. Shipping it only in the\n// core defaults file left it unregistered on every database initialized before\n// the toggle existed, and `useSalesChannelsEnabled` then 404s on every mount.\n//\n// The lookup deliberately does not filter on `deletedAt`. Deletion is soft\n// (`feature_toggles.global.delete`) while `feature_toggles_identifier_unique`\n// covers every row, so filtering would make this insert a duplicate and fail the\n// whole seed run on any installation where the toggle was deleted. Matching the\n// create command's own uniqueness check also leaves an operator's deletion alone\n// instead of resurrecting the definition on every tenant seed.\nexport async function seedSalesChannelsToggle(em: EntityManager): Promise<void> {\n const existing = await findOneWithDecryption(em, FeatureToggle, {\n identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier,\n })\n if (existing) return\n em.persist(\n em.create(FeatureToggle, {\n identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier,\n name: SALES_CHANNELS_TOGGLE_DEFINITION.name,\n description: SALES_CHANNELS_TOGGLE_DEFINITION.description,\n category: SALES_CHANNELS_TOGGLE_DEFINITION.category,\n type: SALES_CHANNELS_TOGGLE_DEFINITION.type,\n defaultValue: SALES_CHANNELS_TOGGLE_DEFINITION.defaultValue,\n }),\n )\n await em.flush()\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AACtC,SAAS,gCAAgC;AAElC,MAAM,mCAAmC;AAAA,EAC9C,YAAY;AAAA,EACZ,MAAM;AAAA,EACN,aAAa;AAAA,EACb,UAAU;AAAA,EACV,MAAM;AAAA,EACN,cAAc;AAChB;AAaA,eAAsB,wBAAwB,IAAkC;AAC9E,QAAM,WAAW,MAAM,sBAAsB,IAAI,eAAe;AAAA,IAC9D,YAAY,iCAAiC;AAAA,EAC/C,CAAC;AACD,MAAI,SAAU;AACd,KAAG;AAAA,IACD,GAAG,OAAO,eAAe;AAAA,MACvB,YAAY,iCAAiC;AAAA,MAC7C,MAAM,iCAAiC;AAAA,MACvC,aAAa,iCAAiC;AAAA,MAC9C,UAAU,iCAAiC;AAAA,MAC3C,MAAM,iCAAiC;AAAA,MACvC,cAAc,iCAAiC;AAAA,IACjD,CAAC;AAAA,EACH;AACA,QAAM,GAAG,MAAM;AACjB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SalesSettings, SalesDocumentSequence, SalesTaxRate } from "./data/entities.js";
|
|
2
2
|
import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from "./lib/documentNumberTokens.js";
|
|
3
3
|
import { seedSalesStatusDictionaries, seedSalesAdjustmentKinds } from "./lib/dictionaries.js";
|
|
4
|
+
import { seedSalesChannelsToggle } from "./lib/salesChannelsToggleSeed.js";
|
|
4
5
|
import { ensureExampleShippingMethods, ensureExamplePaymentMethods } from "./seed/examples-data.js";
|
|
5
6
|
import { seedSalesExamples } from "./seed/examples.js";
|
|
6
7
|
const DEFAULT_TAX_RATES = [
|
|
@@ -104,6 +105,7 @@ const setup = {
|
|
|
104
105
|
await seedSalesAdjustmentKinds(em, scope);
|
|
105
106
|
await ensureExampleShippingMethods(em, scope);
|
|
106
107
|
await ensureExamplePaymentMethods(em, scope);
|
|
108
|
+
await seedSalesChannelsToggle(em);
|
|
107
109
|
},
|
|
108
110
|
async seedExamples({ em, container, tenantId, organizationId }) {
|
|
109
111
|
const scope = { tenantId, organizationId };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/sales/setup.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { SalesSettings, SalesDocumentSequence, SalesTaxRate } from './data/entities'\nimport { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from './lib/documentNumberTokens'\nimport { seedSalesStatusDictionaries, seedSalesAdjustmentKinds } from './lib/dictionaries'\nimport { ensureExampleShippingMethods, ensureExamplePaymentMethods } from './seed/examples-data'\nimport { seedSalesExamples } from './seed/examples'\n\ntype SeedScope = { tenantId: string; organizationId: string }\n\nconst DEFAULT_TAX_RATES = [\n { code: 'vat-23', name: '23% VAT', rate: '23' },\n { code: 'vat-0', name: '0% VAT', rate: '0' },\n] as const\n\nasync function seedSalesTaxRates(em: EntityManager, scope: SeedScope): Promise<void> {\n await em.transactional(async (tem) => {\n const existing = await tem.find(SalesTaxRate, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n const existingCodes = new Set(existing.map((rate) => rate.code))\n const hasDefault = existing.some((rate) => rate.isDefault)\n const now = new Date()\n let isFirst = !hasDefault\n\n for (const seed of DEFAULT_TAX_RATES) {\n if (existingCodes.has(seed.code)) continue\n tem.persist(\n tem.create(SalesTaxRate, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n code: seed.code,\n name: seed.name,\n rate: seed.rate,\n priority: 0,\n isCompound: false,\n isDefault: isFirst,\n createdAt: now,\n updatedAt: now,\n })\n )\n isFirst = false\n }\n })\n}\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n admin: ['sales.*', 'sales.documents.number.edit'],\n employee: [\n 'sales.channels.view',\n 'sales.channels.manage',\n 'sales.settings.view',\n 'sales.settings.manage',\n 'sales.orders.view',\n 'sales.orders.manage',\n 'sales.orders.approve',\n 'sales.widgets.new-orders',\n 'sales.widgets.new-quotes',\n 'sales.quotes.view',\n 'sales.quotes.manage',\n 'sales.shipments.manage',\n 'sales.payments.manage',\n 'sales.returns.view',\n 'sales.returns.create',\n 'sales.returns.manage',\n 'sales.invoices.manage',\n 'sales.credit_memos.manage',\n ],\n },\n\n async onTenantCreated({ em, tenantId, organizationId }) {\n const exists = await em.findOne(SalesSettings, { tenantId, organizationId })\n if (!exists) {\n em.persist(\n em.create(SalesSettings, {\n tenantId,\n organizationId,\n orderNumberFormat: DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: DEFAULT_QUOTE_NUMBER_FORMAT,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n )\n }\n\n for (const kind of ['order', 'quote', 'return', 'invoice', 'credit_memo'] as const) {\n const seq = await em.findOne(SalesDocumentSequence, {\n tenantId,\n organizationId,\n documentKind: kind,\n })\n if (!seq) {\n em.persist(\n em.create(SalesDocumentSequence, {\n tenantId,\n organizationId,\n documentKind: kind,\n currentValue: 0,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n )\n }\n }\n\n await em.flush()\n },\n\n async seedDefaults({ em, tenantId, organizationId }) {\n const scope = { tenantId, organizationId }\n await seedSalesTaxRates(em, scope)\n await seedSalesStatusDictionaries(em, scope)\n await seedSalesAdjustmentKinds(em, scope)\n await ensureExampleShippingMethods(em, scope)\n await ensureExamplePaymentMethods(em, scope)\n },\n\n async seedExamples({ em, container, tenantId, organizationId }) {\n const scope = { tenantId, organizationId }\n await seedSalesExamples(em, container, scope)\n },\n}\n\nexport default setup\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport { SalesSettings, SalesDocumentSequence, SalesTaxRate } from './data/entities'\nimport { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from './lib/documentNumberTokens'\nimport { seedSalesStatusDictionaries, seedSalesAdjustmentKinds } from './lib/dictionaries'\nimport { seedSalesChannelsToggle } from './lib/salesChannelsToggleSeed'\nimport { ensureExampleShippingMethods, ensureExamplePaymentMethods } from './seed/examples-data'\nimport { seedSalesExamples } from './seed/examples'\n\ntype SeedScope = { tenantId: string; organizationId: string }\n\nconst DEFAULT_TAX_RATES = [\n { code: 'vat-23', name: '23% VAT', rate: '23' },\n { code: 'vat-0', name: '0% VAT', rate: '0' },\n] as const\n\nasync function seedSalesTaxRates(em: EntityManager, scope: SeedScope): Promise<void> {\n await em.transactional(async (tem) => {\n const existing = await tem.find(SalesTaxRate, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n const existingCodes = new Set(existing.map((rate) => rate.code))\n const hasDefault = existing.some((rate) => rate.isDefault)\n const now = new Date()\n let isFirst = !hasDefault\n\n for (const seed of DEFAULT_TAX_RATES) {\n if (existingCodes.has(seed.code)) continue\n tem.persist(\n tem.create(SalesTaxRate, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n code: seed.code,\n name: seed.name,\n rate: seed.rate,\n priority: 0,\n isCompound: false,\n isDefault: isFirst,\n createdAt: now,\n updatedAt: now,\n })\n )\n isFirst = false\n }\n })\n}\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n admin: ['sales.*', 'sales.documents.number.edit'],\n employee: [\n 'sales.channels.view',\n 'sales.channels.manage',\n 'sales.settings.view',\n 'sales.settings.manage',\n 'sales.orders.view',\n 'sales.orders.manage',\n 'sales.orders.approve',\n 'sales.widgets.new-orders',\n 'sales.widgets.new-quotes',\n 'sales.quotes.view',\n 'sales.quotes.manage',\n 'sales.shipments.manage',\n 'sales.payments.manage',\n 'sales.returns.view',\n 'sales.returns.create',\n 'sales.returns.manage',\n 'sales.invoices.manage',\n 'sales.credit_memos.manage',\n ],\n },\n\n async onTenantCreated({ em, tenantId, organizationId }) {\n const exists = await em.findOne(SalesSettings, { tenantId, organizationId })\n if (!exists) {\n em.persist(\n em.create(SalesSettings, {\n tenantId,\n organizationId,\n orderNumberFormat: DEFAULT_ORDER_NUMBER_FORMAT,\n quoteNumberFormat: DEFAULT_QUOTE_NUMBER_FORMAT,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n )\n }\n\n for (const kind of ['order', 'quote', 'return', 'invoice', 'credit_memo'] as const) {\n const seq = await em.findOne(SalesDocumentSequence, {\n tenantId,\n organizationId,\n documentKind: kind,\n })\n if (!seq) {\n em.persist(\n em.create(SalesDocumentSequence, {\n tenantId,\n organizationId,\n documentKind: kind,\n currentValue: 0,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n )\n }\n }\n\n await em.flush()\n },\n\n async seedDefaults({ em, tenantId, organizationId }) {\n const scope = { tenantId, organizationId }\n await seedSalesTaxRates(em, scope)\n await seedSalesStatusDictionaries(em, scope)\n await seedSalesAdjustmentKinds(em, scope)\n await ensureExampleShippingMethods(em, scope)\n await ensureExamplePaymentMethods(em, scope)\n await seedSalesChannelsToggle(em)\n },\n\n async seedExamples({ em, container, tenantId, organizationId }) {\n const scope = { tenantId, organizationId }\n await seedSalesExamples(em, container, scope)\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.8-develop.
|
|
3
|
+
"version": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -252,16 +252,16 @@
|
|
|
252
252
|
"zod": "^4.4.3"
|
|
253
253
|
},
|
|
254
254
|
"peerDependencies": {
|
|
255
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
256
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
257
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
255
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
256
|
+
"@open-mercato/shared": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
257
|
+
"@open-mercato/ui": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
258
258
|
"react": "^19.0.0",
|
|
259
259
|
"react-dom": "^19.0.0"
|
|
260
260
|
},
|
|
261
261
|
"devDependencies": {
|
|
262
|
-
"@open-mercato/ai-assistant": "0.6.8-develop.
|
|
263
|
-
"@open-mercato/shared": "0.6.8-develop.
|
|
264
|
-
"@open-mercato/ui": "0.6.8-develop.
|
|
262
|
+
"@open-mercato/ai-assistant": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
263
|
+
"@open-mercato/shared": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
264
|
+
"@open-mercato/ui": "0.6.8-develop.7079.1.dbf29dd6a1",
|
|
265
265
|
"@testing-library/dom": "^10.4.1",
|
|
266
266
|
"@testing-library/jest-dom": "^7.0.0",
|
|
267
267
|
"@testing-library/react": "^16.3.1",
|
|
@@ -16,14 +16,6 @@
|
|
|
16
16
|
"type": "string",
|
|
17
17
|
"defaultValue": "Free shipping on orders over $50"
|
|
18
18
|
},
|
|
19
|
-
{
|
|
20
|
-
"identifier": "sales_channels_enabled",
|
|
21
|
-
"name": "Sales Channels",
|
|
22
|
-
"description": "Show sales channel pickers, filters, and management UI. Disable for tenants that do not use sales channels.",
|
|
23
|
-
"category": "sales",
|
|
24
|
-
"type": "boolean",
|
|
25
|
-
"defaultValue": true
|
|
26
|
-
},
|
|
27
19
|
{
|
|
28
20
|
"identifier": "order_review_threshold",
|
|
29
21
|
"name": "Order Review Threshold",
|
|
@@ -584,6 +584,11 @@ const addressSnapshotSchema = z
|
|
|
584
584
|
.nullable()
|
|
585
585
|
.optional();
|
|
586
586
|
|
|
587
|
+
// Mirrors the create schema's `decimal({ min: 0 })` (data/validators.ts). `null`
|
|
588
|
+
// comes first in the union below because the coercion accepts it — `Number(null)`
|
|
589
|
+
// is 0 — and would turn a clear into a written zero.
|
|
590
|
+
const exchangeRateSchema = z.coerce.number().min(0);
|
|
591
|
+
|
|
587
592
|
export const documentUpdateSchema = z
|
|
588
593
|
.object({
|
|
589
594
|
id: z.string().uuid(),
|
|
@@ -602,6 +607,13 @@ export const documentUpdateSchema = z
|
|
|
602
607
|
orderNumber: z.string().trim().min(1).max(191).optional(),
|
|
603
608
|
quoteNumber: z.string().trim().min(1).max(191).optional(),
|
|
604
609
|
currencyCode: currencyCodeSchema.optional(),
|
|
610
|
+
// Order-only columns — SalesQuote declares none of them. Declared here
|
|
611
|
+
// because the create schema declares them and a caller reusing its create
|
|
612
|
+
// payload on an update otherwise lost them silently; `applyDocumentUpdate`
|
|
613
|
+
// rejects each with a 400 on a quote rather than dropping it.
|
|
614
|
+
exchangeRate: z.union([z.null(), exchangeRateSchema]).optional(),
|
|
615
|
+
paymentStatusEntryId: z.string().uuid().nullable().optional(),
|
|
616
|
+
fulfillmentStatusEntryId: z.string().uuid().nullable().optional(),
|
|
605
617
|
channelId: z.string().uuid().nullable().optional(),
|
|
606
618
|
statusEntryId: z.string().uuid().nullable().optional(),
|
|
607
619
|
placedAt: z.union([dateOnlySchema, z.null()]).optional(),
|
|
@@ -633,6 +645,9 @@ export const documentUpdateSchema = z
|
|
|
633
645
|
input.expectedDeliveryAt !== undefined ||
|
|
634
646
|
input.channelId !== undefined ||
|
|
635
647
|
input.statusEntryId !== undefined ||
|
|
648
|
+
input.exchangeRate !== undefined ||
|
|
649
|
+
input.paymentStatusEntryId !== undefined ||
|
|
650
|
+
input.fulfillmentStatusEntryId !== undefined ||
|
|
636
651
|
input.shippingAddressId !== undefined ||
|
|
637
652
|
input.billingAddressId !== undefined ||
|
|
638
653
|
input.customerEntityId !== undefined ||
|
|
@@ -1198,6 +1213,19 @@ async function applyDocumentUpdate({
|
|
|
1198
1213
|
if (typeof input.currencyCode === "string") {
|
|
1199
1214
|
entity.currencyCode = input.currencyCode;
|
|
1200
1215
|
}
|
|
1216
|
+
// Orders only — SalesQuote has no exchange_rate column, so the field is
|
|
1217
|
+
// rejected rather than ignored for the same reason as internalNotes above.
|
|
1218
|
+
if (kind === "quote" && input.exchangeRate !== undefined) {
|
|
1219
|
+
throw new CrudHttpError(400, {
|
|
1220
|
+
error: translate(
|
|
1221
|
+
"sales.quotes.exchange_rate_unsupported",
|
|
1222
|
+
"Exchange rate is not available on quotes.",
|
|
1223
|
+
),
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
if (kind === "order" && input.exchangeRate !== undefined) {
|
|
1227
|
+
(entity as SalesOrder).exchangeRate = toNumericString(input.exchangeRate);
|
|
1228
|
+
}
|
|
1201
1229
|
if (input.channelId !== undefined) {
|
|
1202
1230
|
if (input.channelId === null) {
|
|
1203
1231
|
entity.channelId = null;
|
|
@@ -1242,6 +1270,62 @@ async function applyDocumentUpdate({
|
|
|
1242
1270
|
(entity as any).statusEntryId = input.statusEntryId ?? null;
|
|
1243
1271
|
(entity as any).status = statusValue;
|
|
1244
1272
|
}
|
|
1273
|
+
// Orders only — SalesQuote has neither the entry-id nor the derived text
|
|
1274
|
+
// column for payment and fulfillment status, so both are rejected on a quote.
|
|
1275
|
+
if (kind === "quote" && input.paymentStatusEntryId !== undefined) {
|
|
1276
|
+
throw new CrudHttpError(400, {
|
|
1277
|
+
error: translate(
|
|
1278
|
+
"sales.quotes.payment_status_unsupported",
|
|
1279
|
+
"Payment status is not available on quotes.",
|
|
1280
|
+
),
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
if (kind === "order" && input.paymentStatusEntryId !== undefined) {
|
|
1284
|
+
const paymentStatusValue = await resolveDictionaryEntryValue(
|
|
1285
|
+
em,
|
|
1286
|
+
input.paymentStatusEntryId,
|
|
1287
|
+
{ tenantId },
|
|
1288
|
+
);
|
|
1289
|
+
if (input.paymentStatusEntryId && !paymentStatusValue) {
|
|
1290
|
+
throw new CrudHttpError(400, {
|
|
1291
|
+
error: translate(
|
|
1292
|
+
"sales.documents.detail.statusInvalid",
|
|
1293
|
+
"Selected status could not be found.",
|
|
1294
|
+
),
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
// Both columns, like the statusEntryId branch above and like order create:
|
|
1298
|
+
// moving the id without its derived text leaves a self-contradicting row.
|
|
1299
|
+
(entity as SalesOrder).paymentStatusEntryId =
|
|
1300
|
+
input.paymentStatusEntryId ?? null;
|
|
1301
|
+
(entity as SalesOrder).paymentStatus = paymentStatusValue;
|
|
1302
|
+
}
|
|
1303
|
+
if (kind === "quote" && input.fulfillmentStatusEntryId !== undefined) {
|
|
1304
|
+
throw new CrudHttpError(400, {
|
|
1305
|
+
error: translate(
|
|
1306
|
+
"sales.quotes.fulfillment_status_unsupported",
|
|
1307
|
+
"Fulfillment status is not available on quotes.",
|
|
1308
|
+
),
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
if (kind === "order" && input.fulfillmentStatusEntryId !== undefined) {
|
|
1312
|
+
const fulfillmentStatusValue = await resolveDictionaryEntryValue(
|
|
1313
|
+
em,
|
|
1314
|
+
input.fulfillmentStatusEntryId,
|
|
1315
|
+
{ tenantId },
|
|
1316
|
+
);
|
|
1317
|
+
if (input.fulfillmentStatusEntryId && !fulfillmentStatusValue) {
|
|
1318
|
+
throw new CrudHttpError(400, {
|
|
1319
|
+
error: translate(
|
|
1320
|
+
"sales.documents.detail.statusInvalid",
|
|
1321
|
+
"Selected status could not be found.",
|
|
1322
|
+
),
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
(entity as SalesOrder).fulfillmentStatusEntryId =
|
|
1326
|
+
input.fulfillmentStatusEntryId ?? null;
|
|
1327
|
+
(entity as SalesOrder).fulfillmentStatus = fulfillmentStatusValue;
|
|
1328
|
+
}
|
|
1245
1329
|
if (input.placedAt !== undefined) {
|
|
1246
1330
|
if (input.placedAt === null) {
|
|
1247
1331
|
entity.placedAt = null;
|
|
@@ -3732,6 +3816,15 @@ function buildDocumentUpdateChangeKeys(kind: SalesDocumentKind, input: DocumentU
|
|
|
3732
3816
|
if (input.internalNotes !== undefined) keys.add("internalNotes");
|
|
3733
3817
|
if (input.placedAt !== undefined) keys.add("placedAt");
|
|
3734
3818
|
if (input.expectedDeliveryAt !== undefined) keys.add("expectedDeliveryAt");
|
|
3819
|
+
if (input.exchangeRate !== undefined) keys.add("exchangeRate");
|
|
3820
|
+
if (input.paymentStatusEntryId !== undefined) {
|
|
3821
|
+
keys.add("paymentStatusEntryId");
|
|
3822
|
+
keys.add("paymentStatus");
|
|
3823
|
+
}
|
|
3824
|
+
if (input.fulfillmentStatusEntryId !== undefined) {
|
|
3825
|
+
keys.add("fulfillmentStatusEntryId");
|
|
3826
|
+
keys.add("fulfillmentStatus");
|
|
3827
|
+
}
|
|
3735
3828
|
}
|
|
3736
3829
|
if (
|
|
3737
3830
|
input.shippingAddressId !== undefined ||
|
|
@@ -1289,13 +1289,16 @@
|
|
|
1289
1289
|
"sales.quotes.email.subject": "Angebot {quoteNumber}",
|
|
1290
1290
|
"sales.quotes.email.total": "Gesamt: {amount} {currency}",
|
|
1291
1291
|
"sales.quotes.email.validUntil": "Gültig bis: {date}",
|
|
1292
|
+
"sales.quotes.exchange_rate_unsupported": "Der Wechselkurs ist für Angebote nicht verfügbar.",
|
|
1292
1293
|
"sales.quotes.form.general": "Allgemein",
|
|
1293
1294
|
"sales.quotes.form.lines": "Positionen",
|
|
1294
1295
|
"sales.quotes.form.notes": "Notizen",
|
|
1296
|
+
"sales.quotes.fulfillment_status_unsupported": "Der Fulfillment-Status ist für Angebote nicht verfügbar.",
|
|
1295
1297
|
"sales.quotes.internal_notes_unsupported": "Interne Notizen sind für Angebote nicht verfügbar.",
|
|
1296
1298
|
"sales.quotes.lines.delete": "Angebotsposition löschen",
|
|
1297
1299
|
"sales.quotes.lines.upsert": "Angebotsposition aktualisieren",
|
|
1298
1300
|
"sales.quotes.list.title": "Angebote",
|
|
1301
|
+
"sales.quotes.payment_status_unsupported": "Der Zahlungsstatus ist für Angebote nicht verfügbar.",
|
|
1299
1302
|
"sales.quotes.public.acceptButton": "Angebot annehmen",
|
|
1300
1303
|
"sales.quotes.public.acceptFailed": "Angebot konnte nicht angenommen werden.",
|
|
1301
1304
|
"sales.quotes.public.acceptedMessage": "Auftrag erstellt: {{orderNumber}}",
|
|
@@ -1289,13 +1289,16 @@
|
|
|
1289
1289
|
"sales.quotes.email.subject": "Quote {quoteNumber}",
|
|
1290
1290
|
"sales.quotes.email.total": "Total: {amount} {currency}",
|
|
1291
1291
|
"sales.quotes.email.validUntil": "Valid until: {date}",
|
|
1292
|
+
"sales.quotes.exchange_rate_unsupported": "Exchange rate is not available on quotes.",
|
|
1292
1293
|
"sales.quotes.form.general": "General",
|
|
1293
1294
|
"sales.quotes.form.lines": "Line Items",
|
|
1294
1295
|
"sales.quotes.form.notes": "Notes",
|
|
1296
|
+
"sales.quotes.fulfillment_status_unsupported": "Fulfillment status is not available on quotes.",
|
|
1295
1297
|
"sales.quotes.internal_notes_unsupported": "Internal notes are not available on quotes.",
|
|
1296
1298
|
"sales.quotes.lines.delete": "Delete quote line",
|
|
1297
1299
|
"sales.quotes.lines.upsert": "Update quote line",
|
|
1298
1300
|
"sales.quotes.list.title": "Quotes",
|
|
1301
|
+
"sales.quotes.payment_status_unsupported": "Payment status is not available on quotes.",
|
|
1299
1302
|
"sales.quotes.public.acceptButton": "Accept quote",
|
|
1300
1303
|
"sales.quotes.public.acceptFailed": "Failed to accept quote.",
|
|
1301
1304
|
"sales.quotes.public.acceptedMessage": "Order created: {{orderNumber}}",
|
|
@@ -1289,13 +1289,16 @@
|
|
|
1289
1289
|
"sales.quotes.email.subject": "Cotización {quoteNumber}",
|
|
1290
1290
|
"sales.quotes.email.total": "Total: {amount} {currency}",
|
|
1291
1291
|
"sales.quotes.email.validUntil": "Válida hasta: {date}",
|
|
1292
|
+
"sales.quotes.exchange_rate_unsupported": "El tipo de cambio no está disponible en los presupuestos.",
|
|
1292
1293
|
"sales.quotes.form.general": "General",
|
|
1293
1294
|
"sales.quotes.form.lines": "Líneas",
|
|
1294
1295
|
"sales.quotes.form.notes": "Notas",
|
|
1296
|
+
"sales.quotes.fulfillment_status_unsupported": "El estado de cumplimiento no está disponible en los presupuestos.",
|
|
1295
1297
|
"sales.quotes.internal_notes_unsupported": "Las notas internas no están disponibles en los presupuestos.",
|
|
1296
1298
|
"sales.quotes.lines.delete": "Eliminar línea de cotización",
|
|
1297
1299
|
"sales.quotes.lines.upsert": "Actualizar línea de cotización",
|
|
1298
1300
|
"sales.quotes.list.title": "Cotizaciones",
|
|
1301
|
+
"sales.quotes.payment_status_unsupported": "El estado de pago no está disponible en los presupuestos.",
|
|
1299
1302
|
"sales.quotes.public.acceptButton": "Aceptar cotización",
|
|
1300
1303
|
"sales.quotes.public.acceptFailed": "No se pudo aceptar la cotización.",
|
|
1301
1304
|
"sales.quotes.public.acceptedMessage": "Pedido creado: {{orderNumber}}",
|
|
@@ -1289,13 +1289,16 @@
|
|
|
1289
1289
|
"sales.quotes.email.subject": "견적 {quoteNumber}",
|
|
1290
1290
|
"sales.quotes.email.total": "합계: {amount} {currency}",
|
|
1291
1291
|
"sales.quotes.email.validUntil": "유효 기간: {date}까지",
|
|
1292
|
+
"sales.quotes.exchange_rate_unsupported": "환율은 견적서에서 사용할 수 없습니다.",
|
|
1292
1293
|
"sales.quotes.form.general": "일반",
|
|
1293
1294
|
"sales.quotes.form.lines": "라인 항목",
|
|
1294
1295
|
"sales.quotes.form.notes": "메모",
|
|
1296
|
+
"sales.quotes.fulfillment_status_unsupported": "이행 상태는 견적서에서 사용할 수 없습니다.",
|
|
1295
1297
|
"sales.quotes.internal_notes_unsupported": "내부 메모는 견적서에서 사용할 수 없습니다.",
|
|
1296
1298
|
"sales.quotes.lines.delete": "견적 항목 삭제",
|
|
1297
1299
|
"sales.quotes.lines.upsert": "견적 항목 업데이트",
|
|
1298
1300
|
"sales.quotes.list.title": "견적",
|
|
1301
|
+
"sales.quotes.payment_status_unsupported": "결제 상태는 견적서에서 사용할 수 없습니다.",
|
|
1299
1302
|
"sales.quotes.public.acceptButton": "견적 수락",
|
|
1300
1303
|
"sales.quotes.public.acceptFailed": "견적을 수락하지 못했습니다.",
|
|
1301
1304
|
"sales.quotes.public.acceptedMessage": "주문이 생성되었습니다: {{orderNumber}}",
|
|
@@ -1289,13 +1289,16 @@
|
|
|
1289
1289
|
"sales.quotes.email.subject": "Oferta {quoteNumber}",
|
|
1290
1290
|
"sales.quotes.email.total": "Suma: {amount} {currency}",
|
|
1291
1291
|
"sales.quotes.email.validUntil": "Ważna do: {date}",
|
|
1292
|
+
"sales.quotes.exchange_rate_unsupported": "Kurs wymiany nie jest dostępny dla ofert.",
|
|
1292
1293
|
"sales.quotes.form.general": "Ogólne",
|
|
1293
1294
|
"sales.quotes.form.lines": "Pozycje",
|
|
1294
1295
|
"sales.quotes.form.notes": "Notatki",
|
|
1296
|
+
"sales.quotes.fulfillment_status_unsupported": "Status realizacji nie jest dostępny dla ofert.",
|
|
1295
1297
|
"sales.quotes.internal_notes_unsupported": "Notatki wewnętrzne nie są dostępne dla ofert.",
|
|
1296
1298
|
"sales.quotes.lines.delete": "Usuń pozycję oferty",
|
|
1297
1299
|
"sales.quotes.lines.upsert": "Zaktualizuj pozycję oferty",
|
|
1298
1300
|
"sales.quotes.list.title": "Oferty",
|
|
1301
|
+
"sales.quotes.payment_status_unsupported": "Status płatności nie jest dostępny dla ofert.",
|
|
1299
1302
|
"sales.quotes.public.acceptButton": "Zaakceptuj ofertę",
|
|
1300
1303
|
"sales.quotes.public.acceptFailed": "Nie udało się zaakceptować oferty.",
|
|
1301
1304
|
"sales.quotes.public.acceptedMessage": "Utworzono zamówienie: {{orderNumber}}",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { FeatureToggle } from '@open-mercato/core/modules/feature_toggles/data/entities'
|
|
3
|
+
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
4
|
+
import { SALES_CHANNELS_TOGGLE_ID } from './salesChannelsToggleId'
|
|
5
|
+
|
|
6
|
+
export const SALES_CHANNELS_TOGGLE_DEFINITION = {
|
|
7
|
+
identifier: SALES_CHANNELS_TOGGLE_ID,
|
|
8
|
+
name: 'Sales Channels',
|
|
9
|
+
description: 'Show sales channel pickers, filters, and management UI. Disable for tenants that do not use sales channels.',
|
|
10
|
+
category: 'sales',
|
|
11
|
+
type: 'boolean' as const,
|
|
12
|
+
defaultValue: true,
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
// The sales module owns this toggle definition, so tenant setup registers it the
|
|
16
|
+
// same way portal, customers and wms register theirs. Shipping it only in the
|
|
17
|
+
// core defaults file left it unregistered on every database initialized before
|
|
18
|
+
// the toggle existed, and `useSalesChannelsEnabled` then 404s on every mount.
|
|
19
|
+
//
|
|
20
|
+
// The lookup deliberately does not filter on `deletedAt`. Deletion is soft
|
|
21
|
+
// (`feature_toggles.global.delete`) while `feature_toggles_identifier_unique`
|
|
22
|
+
// covers every row, so filtering would make this insert a duplicate and fail the
|
|
23
|
+
// whole seed run on any installation where the toggle was deleted. Matching the
|
|
24
|
+
// create command's own uniqueness check also leaves an operator's deletion alone
|
|
25
|
+
// instead of resurrecting the definition on every tenant seed.
|
|
26
|
+
export async function seedSalesChannelsToggle(em: EntityManager): Promise<void> {
|
|
27
|
+
const existing = await findOneWithDecryption(em, FeatureToggle, {
|
|
28
|
+
identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier,
|
|
29
|
+
})
|
|
30
|
+
if (existing) return
|
|
31
|
+
em.persist(
|
|
32
|
+
em.create(FeatureToggle, {
|
|
33
|
+
identifier: SALES_CHANNELS_TOGGLE_DEFINITION.identifier,
|
|
34
|
+
name: SALES_CHANNELS_TOGGLE_DEFINITION.name,
|
|
35
|
+
description: SALES_CHANNELS_TOGGLE_DEFINITION.description,
|
|
36
|
+
category: SALES_CHANNELS_TOGGLE_DEFINITION.category,
|
|
37
|
+
type: SALES_CHANNELS_TOGGLE_DEFINITION.type,
|
|
38
|
+
defaultValue: SALES_CHANNELS_TOGGLE_DEFINITION.defaultValue,
|
|
39
|
+
}),
|
|
40
|
+
)
|
|
41
|
+
await em.flush()
|
|
42
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
|
3
3
|
import { SalesSettings, SalesDocumentSequence, SalesTaxRate } from './data/entities'
|
|
4
4
|
import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from './lib/documentNumberTokens'
|
|
5
5
|
import { seedSalesStatusDictionaries, seedSalesAdjustmentKinds } from './lib/dictionaries'
|
|
6
|
+
import { seedSalesChannelsToggle } from './lib/salesChannelsToggleSeed'
|
|
6
7
|
import { ensureExampleShippingMethods, ensureExamplePaymentMethods } from './seed/examples-data'
|
|
7
8
|
import { seedSalesExamples } from './seed/examples'
|
|
8
9
|
|
|
@@ -116,6 +117,7 @@ export const setup: ModuleSetupConfig = {
|
|
|
116
117
|
await seedSalesAdjustmentKinds(em, scope)
|
|
117
118
|
await ensureExampleShippingMethods(em, scope)
|
|
118
119
|
await ensureExamplePaymentMethods(em, scope)
|
|
120
|
+
await seedSalesChannelsToggle(em)
|
|
119
121
|
},
|
|
120
122
|
|
|
121
123
|
async seedExamples({ em, container, tenantId, organizationId }) {
|