@open-mercato/core 0.6.6-develop.6401.1.8c07df34a7 → 0.6.6-develop.6403.1.b57089b6fe

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.
Files changed (26) hide show
  1. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +9 -12
  2. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
  3. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +49 -10
  4. package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
  5. package/dist/modules/planner/api/availability-weekly.js +11 -1
  6. package/dist/modules/planner/api/availability-weekly.js.map +2 -2
  7. package/dist/modules/planner/commands/availability-weekly.js +34 -3
  8. package/dist/modules/planner/commands/availability-weekly.js.map +2 -2
  9. package/dist/modules/planner/components/AvailabilityRulesEditor.js +14 -6
  10. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  11. package/dist/modules/sales/cli.js +38 -1
  12. package/dist/modules/sales/cli.js.map +2 -2
  13. package/dist/modules/sales/lib/dictionaries.js +70 -0
  14. package/dist/modules/sales/lib/dictionaries.js.map +2 -2
  15. package/package.json +7 -7
  16. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +9 -13
  17. package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +58 -8
  18. package/src/modules/customers/i18n/de.json +4 -0
  19. package/src/modules/customers/i18n/en.json +4 -0
  20. package/src/modules/customers/i18n/es.json +4 -0
  21. package/src/modules/customers/i18n/pl.json +4 -0
  22. package/src/modules/planner/api/availability-weekly.ts +10 -0
  23. package/src/modules/planner/commands/availability-weekly.ts +46 -8
  24. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +15 -6
  25. package/src/modules/sales/cli.ts +39 -1
  26. package/src/modules/sales/lib/dictionaries.ts +90 -0
@@ -1,5 +1,5 @@
1
1
  import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
2
- import { seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from "./lib/dictionaries.js";
2
+ import { backfillDealLossReasonDictionary, seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from "./lib/dictionaries.js";
3
3
  import { seedSalesTaxRates } from "./lib/seeds.js";
4
4
  import { ensureExamplePaymentMethods, ensureExampleShippingMethods } from "./seed/examples-data.js";
5
5
  import { seedSalesExamples } from "./seed/examples.js";
@@ -96,6 +96,42 @@ const seedAdjustmentKindsCommand = {
96
96
  }
97
97
  }
98
98
  };
99
+ const backfillDealLossReasonsCommand = {
100
+ command: "backfill-deal-loss-reasons",
101
+ async run(rest) {
102
+ const args = parseArgs(rest);
103
+ const tenantId = String(args.tenantId ?? args.tenant ?? "");
104
+ const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? "");
105
+ if (!tenantId || !organizationId) {
106
+ console.error("Usage: mercato sales backfill-deal-loss-reasons --tenant <tenantId> --org <organizationId>");
107
+ return;
108
+ }
109
+ const container = await createRequestContainer();
110
+ try {
111
+ const em = container.resolve("em");
112
+ const result = await em.transactional(async (tem) => {
113
+ const backfillResult = await backfillDealLossReasonDictionary(tem, { tenantId, organizationId });
114
+ await tem.flush();
115
+ return backfillResult;
116
+ });
117
+ console.log(
118
+ [
119
+ `Deal loss reasons dictionary ready for organization ${organizationId}.`,
120
+ `dictionaryId=${result.dictionaryId}`,
121
+ `createdDictionary=${String(result.createdDictionary)}`,
122
+ `copiedLegacyEntries=${String(result.copiedLegacyEntries)}`,
123
+ `seededDefaultEntries=${String(result.seededDefaultEntries)}`,
124
+ `existingEntries=${String(result.existingEntries)}`
125
+ ].join(" ")
126
+ );
127
+ } finally {
128
+ const disposable = container;
129
+ if (typeof disposable.dispose === "function") {
130
+ await disposable.dispose();
131
+ }
132
+ }
133
+ }
134
+ };
99
135
  const seedShippingMethodsCommand = {
100
136
  command: "seed-shipping-methods",
101
137
  async run(rest) {
@@ -179,6 +215,7 @@ var cli_default = [
179
215
  seedTaxRatesCommand,
180
216
  seedStatusesCommand,
181
217
  seedAdjustmentKindsCommand,
218
+ backfillDealLossReasonsCommand,
182
219
  seedShippingMethodsCommand,
183
220
  seedPaymentMethodsCommand,
184
221
  seedExamplesCommand
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/sales/cli.ts"],
4
- "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from './lib/dictionaries'\nimport { seedSalesTaxRates } from './lib/seeds'\nimport { ensureExamplePaymentMethods, ensureExampleShippingMethods } from './seed/examples-data'\nimport { seedSalesExamples } from './seed/examples'\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part) continue\n if (part.startsWith('--')) {\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (rawValue !== undefined) args[rawKey] = rawValue\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n }\n }\n }\n return args\n}\n\nconst seedTaxRatesCommand: ModuleCli = {\n command: 'seed-tax-rates',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-tax-rates --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n try {\n await em.transactional(async (tem) => {\n await seedSalesTaxRates(tem, { tenantId, organizationId })\n })\n console.log('\uD83E\uDDFE Tax rates seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedStatusesCommand: ModuleCli = {\n command: 'seed-statuses',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-statuses --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await seedSalesStatusDictionaries(tem, { tenantId, organizationId })\n await tem.flush()\n })\n console.log('\uD83D\uDEA6 Sales order statuses seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedAdjustmentKindsCommand: ModuleCli = {\n command: 'seed-adjustment-kinds',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-adjustment-kinds --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await seedSalesAdjustmentKinds(tem, { tenantId, organizationId })\n await tem.flush()\n })\n console.log('\u2699\uFE0F Sales adjustment kinds seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedShippingMethodsCommand: ModuleCli = {\n command: 'seed-shipping-methods',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-shipping-methods --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await ensureExampleShippingMethods(tem, { tenantId, organizationId })\n })\n console.log('\uD83D\uDE9A Shipping methods seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedPaymentMethodsCommand: ModuleCli = {\n command: 'seed-payment-methods',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-payment-methods --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await ensureExamplePaymentMethods(tem, { tenantId, organizationId })\n })\n console.log('\uD83D\uDCB3 Payment methods seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedExamplesCommand: ModuleCli = {\n command: 'seed-examples',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-examples --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n const seeded = await em.transactional(async (tem) =>\n seedSalesExamples(tem, container, { tenantId, organizationId })\n )\n if (seeded) {\n console.log('\uD83E\uDDFE Sales example quotes and orders seeded for organization', organizationId)\n } else {\n console.log('Sales example data already present; skipping')\n }\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nexport default [\n seedTaxRatesCommand,\n seedStatusesCommand,\n seedAdjustmentKindsCommand,\n seedShippingMethodsCommand,\n seedPaymentMethodsCommand,\n seedExamplesCommand,\n]\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { backfillDealLossReasonDictionary, seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from './lib/dictionaries'\nimport { seedSalesTaxRates } from './lib/seeds'\nimport { ensureExamplePaymentMethods, ensureExampleShippingMethods } from './seed/examples-data'\nimport { seedSalesExamples } from './seed/examples'\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string> = {}\n for (let i = 0; i < rest.length; i += 1) {\n const part = rest[i]\n if (!part) continue\n if (part.startsWith('--')) {\n const [rawKey, rawValue] = part.slice(2).split('=')\n if (rawValue !== undefined) args[rawKey] = rawValue\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) {\n args[rawKey] = rest[i + 1]!\n i += 1\n }\n }\n }\n return args\n}\n\nconst seedTaxRatesCommand: ModuleCli = {\n command: 'seed-tax-rates',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-tax-rates --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n try {\n await em.transactional(async (tem) => {\n await seedSalesTaxRates(tem, { tenantId, organizationId })\n })\n console.log('\uD83E\uDDFE Tax rates seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedStatusesCommand: ModuleCli = {\n command: 'seed-statuses',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-statuses --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await seedSalesStatusDictionaries(tem, { tenantId, organizationId })\n await tem.flush()\n })\n console.log('\uD83D\uDEA6 Sales order statuses seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedAdjustmentKindsCommand: ModuleCli = {\n command: 'seed-adjustment-kinds',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-adjustment-kinds --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await seedSalesAdjustmentKinds(tem, { tenantId, organizationId })\n await tem.flush()\n })\n console.log('\u2699\uFE0F Sales adjustment kinds seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst backfillDealLossReasonsCommand: ModuleCli = {\n command: 'backfill-deal-loss-reasons',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales backfill-deal-loss-reasons --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n const result = await em.transactional(async (tem) => {\n const backfillResult = await backfillDealLossReasonDictionary(tem, { tenantId, organizationId })\n await tem.flush()\n return backfillResult\n })\n console.log(\n [\n `Deal loss reasons dictionary ready for organization ${organizationId}.`,\n `dictionaryId=${result.dictionaryId}`,\n `createdDictionary=${String(result.createdDictionary)}`,\n `copiedLegacyEntries=${String(result.copiedLegacyEntries)}`,\n `seededDefaultEntries=${String(result.seededDefaultEntries)}`,\n `existingEntries=${String(result.existingEntries)}`,\n ].join(' '),\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedShippingMethodsCommand: ModuleCli = {\n command: 'seed-shipping-methods',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-shipping-methods --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await ensureExampleShippingMethods(tem, { tenantId, organizationId })\n })\n console.log('\uD83D\uDE9A Shipping methods seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedPaymentMethodsCommand: ModuleCli = {\n command: 'seed-payment-methods',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-payment-methods --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n await em.transactional(async (tem) => {\n await ensureExamplePaymentMethods(tem, { tenantId, organizationId })\n })\n console.log('\uD83D\uDCB3 Payment methods seeded for organization', organizationId)\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nconst seedExamplesCommand: ModuleCli = {\n command: 'seed-examples',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = String(args.tenantId ?? args.tenant ?? '')\n const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')\n if (!tenantId || !organizationId) {\n console.error('Usage: mercato sales seed-examples --tenant <tenantId> --org <organizationId>')\n return\n }\n const container = await createRequestContainer()\n try {\n const em = container.resolve<EntityManager>('em')\n const seeded = await em.transactional(async (tem) =>\n seedSalesExamples(tem, container, { tenantId, organizationId })\n )\n if (seeded) {\n console.log('\uD83E\uDDFE Sales example quotes and orders seeded for organization', organizationId)\n } else {\n console.log('Sales example data already present; skipping')\n }\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n },\n}\n\nexport default [\n seedTaxRatesCommand,\n seedStatusesCommand,\n seedAdjustmentKindsCommand,\n backfillDealLossReasonsCommand,\n seedShippingMethodsCommand,\n seedPaymentMethodsCommand,\n seedExamplesCommand,\n]\n"],
5
+ "mappings": "AACA,SAAS,8BAA8B;AAEvC,SAAS,kCAAkC,0BAA0B,mCAAmC;AACxG,SAAS,yBAAyB;AAClC,SAAS,6BAA6B,oCAAoC;AAC1E,SAAS,yBAAyB;AAElC,SAAS,UAAU,MAAgB;AACjC,QAAM,OAA+B,CAAC;AACtC,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,UAAM,OAAO,KAAK,CAAC;AACnB,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,WAAW,IAAI,GAAG;AACzB,YAAM,CAAC,QAAQ,QAAQ,IAAI,KAAK,MAAM,CAAC,EAAE,MAAM,GAAG;AAClD,UAAI,aAAa,OAAW,MAAK,MAAM,IAAI;AAAA,eAClC,KAAK,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,EAAG,WAAW,IAAI,GAAG;AACtD,aAAK,MAAM,IAAI,KAAK,IAAI,CAAC;AACzB,aAAK;AAAA,MACP;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,sBAAiC;AAAA,EACrC,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,gFAAgF;AAC9F;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,KAAK,UAAU,QAAQ,IAAI;AACjC,QAAI;AACF,YAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,cAAM,kBAAkB,KAAK,EAAE,UAAU,eAAe,CAAC;AAAA,MAC3D,CAAC;AACD,cAAQ,IAAI,+CAAwC,cAAc;AAAA,IACpE,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,sBAAiC;AAAA,EACrC,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,+EAA+E;AAC7F;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,cAAM,4BAA4B,KAAK,EAAE,UAAU,eAAe,CAAC;AACnE,cAAM,IAAI,MAAM;AAAA,MAClB,CAAC;AACD,cAAQ,IAAI,0DAAmD,cAAc;AAAA,IAC/E,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,6BAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,uFAAuF;AACrG;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,cAAM,yBAAyB,KAAK,EAAE,UAAU,eAAe,CAAC;AAChE,cAAM,IAAI,MAAM;AAAA,MAClB,CAAC;AACD,cAAQ,IAAI,gEAAsD,cAAc;AAAA,IAClF,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,iCAA4C;AAAA,EAChD,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,4FAA4F;AAC1G;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,SAAS,MAAM,GAAG,cAAc,OAAO,QAAQ;AACnD,cAAM,iBAAiB,MAAM,iCAAiC,KAAK,EAAE,UAAU,eAAe,CAAC;AAC/F,cAAM,IAAI,MAAM;AAChB,eAAO;AAAA,MACT,CAAC;AACD,cAAQ;AAAA,QACN;AAAA,UACE,uDAAuD,cAAc;AAAA,UACrE,gBAAgB,OAAO,YAAY;AAAA,UACnC,qBAAqB,OAAO,OAAO,iBAAiB,CAAC;AAAA,UACrD,uBAAuB,OAAO,OAAO,mBAAmB,CAAC;AAAA,UACzD,wBAAwB,OAAO,OAAO,oBAAoB,CAAC;AAAA,UAC3D,mBAAmB,OAAO,OAAO,eAAe,CAAC;AAAA,QACnD,EAAE,KAAK,GAAG;AAAA,MACZ;AAAA,IACF,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,6BAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,uFAAuF;AACrG;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,cAAM,6BAA6B,KAAK,EAAE,UAAU,eAAe,CAAC;AAAA,MACtE,CAAC;AACD,cAAQ,IAAI,sDAA+C,cAAc;AAAA,IAC3E,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,4BAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,sFAAsF;AACpG;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,GAAG,cAAc,OAAO,QAAQ;AACpC,cAAM,4BAA4B,KAAK,EAAE,UAAU,eAAe,CAAC;AAAA,MACrE,CAAC;AACD,cAAQ,IAAI,qDAA8C,cAAc;AAAA,IAC1E,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,MAAM,sBAAiC;AAAA,EACrC,SAAS;AAAA,EACT,MAAM,IAAI,MAAM;AACd,UAAM,OAAO,UAAU,IAAI;AAC3B,UAAM,WAAW,OAAO,KAAK,YAAY,KAAK,UAAU,EAAE;AAC1D,UAAM,iBAAiB,OAAO,KAAK,kBAAkB,KAAK,OAAO,KAAK,SAAS,EAAE;AACjF,QAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,cAAQ,MAAM,+EAA+E;AAC7F;AAAA,IACF;AACA,UAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAI;AACF,YAAM,KAAK,UAAU,QAAuB,IAAI;AAChD,YAAM,SAAS,MAAM,GAAG;AAAA,QAAc,OAAO,QAC3C,kBAAkB,KAAK,WAAW,EAAE,UAAU,eAAe,CAAC;AAAA,MAChE;AACA,UAAI,QAAQ;AACV,gBAAQ,IAAI,qEAA8D,cAAc;AAAA,MAC1F,OAAO;AACL,gBAAQ,IAAI,8CAA8C;AAAA,MAC5D;AAAA,IACF,UAAE;AACA,YAAM,aAAa;AACnB,UAAI,OAAO,WAAW,YAAY,YAAY;AAC5C,cAAM,WAAW,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,cAAQ;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
6
6
  "names": []
7
7
  }
@@ -201,6 +201,75 @@ const DEAL_LOSS_REASON_DEFAULTS = [
201
201
  { value: "timing", label: "Bad timing", color: "#6366f1", icon: "lucide:clock" },
202
202
  { value: "other", label: "Other", color: "#71717a", icon: "lucide:minus-circle" }
203
203
  ];
204
+ async function backfillDealLossReasonDictionary(em, scope) {
205
+ const definition = getSalesDictionaryDefinition("deal-loss-reasons");
206
+ const existingDictionary = await em.findOne(Dictionary, {
207
+ tenantId: scope.tenantId,
208
+ organizationId: scope.organizationId,
209
+ key: definition.key,
210
+ deletedAt: null
211
+ });
212
+ const dictionary = existingDictionary ?? await ensureSalesDictionary({
213
+ em,
214
+ tenantId: scope.tenantId,
215
+ organizationId: scope.organizationId,
216
+ kind: "deal-loss-reasons"
217
+ });
218
+ const targetEntries = await em.find(DictionaryEntry, {
219
+ dictionary,
220
+ tenantId: scope.tenantId,
221
+ organizationId: scope.organizationId
222
+ });
223
+ if (targetEntries.length > 0) {
224
+ return {
225
+ dictionaryId: dictionary.id,
226
+ createdDictionary: !existingDictionary,
227
+ copiedLegacyEntries: 0,
228
+ seededDefaultEntries: 0,
229
+ existingEntries: targetEntries.length
230
+ };
231
+ }
232
+ const legacyDictionary = await em.findOne(Dictionary, {
233
+ tenantId: scope.tenantId,
234
+ organizationId: scope.organizationId,
235
+ key: "customer_lost_reason",
236
+ deletedAt: null
237
+ });
238
+ const legacyEntries = legacyDictionary ? await em.find(
239
+ DictionaryEntry,
240
+ {
241
+ dictionary: legacyDictionary,
242
+ tenantId: scope.tenantId,
243
+ organizationId: scope.organizationId
244
+ },
245
+ { orderBy: { position: "asc", label: "asc" } }
246
+ ) : [];
247
+ let copiedLegacyEntries = 0;
248
+ let seededDefaultEntries = 0;
249
+ const processedValues = /* @__PURE__ */ new Set();
250
+ const seeds = legacyEntries.length ? legacyEntries.map((entry) => ({
251
+ value: entry.value,
252
+ label: entry.label,
253
+ color: entry.color,
254
+ icon: entry.icon
255
+ })) : DEAL_LOSS_REASON_DEFAULTS;
256
+ for (const seed of seeds) {
257
+ const normalizedValue = normalizeDictionaryValue(seed.value ?? "");
258
+ if (!normalizedValue || processedValues.has(normalizedValue)) continue;
259
+ const entry = await ensureSalesDictionaryEntry(em, scope, "deal-loss-reasons", seed);
260
+ if (!entry) continue;
261
+ processedValues.add(normalizedValue);
262
+ if (legacyEntries.length) copiedLegacyEntries += 1;
263
+ else seededDefaultEntries += 1;
264
+ }
265
+ return {
266
+ dictionaryId: dictionary.id,
267
+ createdDictionary: !existingDictionary,
268
+ copiedLegacyEntries,
269
+ seededDefaultEntries,
270
+ existingEntries: 0
271
+ };
272
+ }
204
273
  async function seedSalesStatusDictionaries(em, scope) {
205
274
  await seedSalesDictionary(em, scope, "order-status", ORDER_STATUS_DEFAULTS);
206
275
  await seedSalesDictionary(em, scope, "order-line-status", ORDER_LINE_STATUS_DEFAULTS);
@@ -218,6 +287,7 @@ async function seedSalesDictionaries(em, scope) {
218
287
  const DEFAULT_ADJUSTMENT_KIND_VALUES = ADJUSTMENT_KIND_DEFAULTS.map((entry) => entry.value);
219
288
  export {
220
289
  DEFAULT_ADJUSTMENT_KIND_VALUES,
290
+ backfillDealLossReasonDictionary,
221
291
  ensureSalesDictionary,
222
292
  getSalesDictionaryDefinition,
223
293
  normalizeDictionaryValue,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sales/lib/dictionaries.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n normalizeDictionaryValue,\n sanitizeDictionaryColor,\n sanitizeDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/lib/utils'\n\nexport type SalesDictionaryKind =\n | 'order-status'\n | 'order-line-status'\n | 'shipment-status'\n | 'payment-status'\n | 'deal-loss-reasons'\n | 'adjustment-kind'\n\ntype SalesDictionaryDefinition = {\n key: string\n name: string\n singular: string\n description: string\n resourceKind: string\n commandPrefix: string\n}\n\nconst DEFINITIONS: Record<SalesDictionaryKind, SalesDictionaryDefinition> = {\n 'order-status': {\n key: 'sales.order_status',\n name: 'Sales order statuses',\n singular: 'Sales order status',\n description: 'Configurable set of statuses used by sales orders.',\n resourceKind: 'sales.order-status',\n commandPrefix: 'sales.order-statuses',\n },\n 'order-line-status': {\n key: 'sales.order_line_status',\n name: 'Sales order line statuses',\n singular: 'Sales order line status',\n description: 'Configurable set of statuses used by sales order lines.',\n resourceKind: 'sales.order-line-status',\n commandPrefix: 'sales.order-line-statuses',\n },\n 'shipment-status': {\n key: 'sales.shipment_status',\n name: 'Shipment statuses',\n singular: 'Shipment status',\n description: 'Configurable set of statuses used by shipments.',\n resourceKind: 'sales.shipment-status',\n commandPrefix: 'sales.shipment-statuses',\n },\n 'payment-status': {\n key: 'sales.payment_status',\n name: 'Payment statuses',\n singular: 'Payment status',\n description: 'Configurable set of statuses used by payments.',\n resourceKind: 'sales.payment-status',\n commandPrefix: 'sales.payment-statuses',\n },\n 'deal-loss-reasons': {\n key: 'sales.deal_loss_reason',\n name: 'Deal loss reasons',\n singular: 'Deal loss reason',\n description: 'Reusable reasons used when closing deals as lost.',\n resourceKind: 'sales.deal-loss-reason',\n commandPrefix: 'sales.deal-loss-reasons',\n },\n 'adjustment-kind': {\n key: 'sales.adjustment_kind',\n name: 'Sales adjustment kinds',\n singular: 'Sales adjustment kind',\n description: 'Reusable adjustment kinds applied to sales documents.',\n resourceKind: 'sales.adjustment-kind',\n commandPrefix: 'sales.adjustment-kinds',\n },\n}\n\nexport function getSalesDictionaryDefinition(kind: SalesDictionaryKind): SalesDictionaryDefinition {\n return DEFINITIONS[kind]\n}\n\nexport async function ensureSalesDictionary(params: {\n em: EntityManager\n tenantId: string\n organizationId: string\n kind: SalesDictionaryKind\n}): Promise<Dictionary> {\n const { em, tenantId, organizationId, kind } = params\n const def = getSalesDictionaryDefinition(kind)\n let dictionary = await em.findOne(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n deletedAt: null,\n })\n if (!dictionary) {\n dictionary = em.create(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n name: def.name,\n description: def.description,\n isSystem: true,\n isActive: true,\n managerVisibility: 'hidden',\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(dictionary)\n await em.flush()\n }\n return dictionary\n}\n\nexport async function resolveDictionaryEntryValue(\n em: EntityManager,\n entryId: string | null | undefined,\n scope: { tenantId: string }\n): Promise<string | null> {\n if (!entryId) return null\n const entry = await em.findOne(DictionaryEntry, { id: entryId, tenantId: scope.tenantId })\n if (!entry) return null\n return entry.value?.trim() || null\n}\n\nexport { normalizeDictionaryValue, sanitizeDictionaryColor, sanitizeDictionaryIcon }\n\ntype SalesDictionarySeed = {\n value: string\n label: string\n color?: string | null\n icon?: string | null\n}\n\ntype SeedScope = { tenantId: string; organizationId: string }\n\nconst ORDER_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'draft', label: 'Draft', color: '#94a3b8', icon: 'lucide:file-pen-line' },\n { value: 'pending_approval', label: 'Pending Approval', color: '#f59e0b', icon: 'lucide:hourglass' },\n { value: 'approved', label: 'Approved', color: '#16a34a', icon: 'lucide:check-circle' },\n { value: 'rejected', label: 'Rejected', color: '#ef4444', icon: 'lucide:x-circle' },\n { value: 'sent', label: 'Sent', color: '#0ea5e9', icon: 'lucide:send' },\n { value: 'confirmed', label: 'Confirmed', color: '#2563eb', icon: 'lucide:badge-check' },\n { value: 'in_fulfillment', label: 'In fulfillment', color: '#f59e0b', icon: 'lucide:loader-2' },\n { value: 'fulfilled', label: 'Fulfilled', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'on_hold', label: 'On hold', color: '#a855f7', icon: 'lucide:pause-circle' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst ORDER_LINE_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock' },\n { value: 'allocated', label: 'Allocated', color: '#6366f1', icon: 'lucide:inbox' },\n { value: 'picking', label: 'Picking', color: '#f59e0b', icon: 'lucide:hand' },\n { value: 'packed', label: 'Packed', color: '#0ea5e9', icon: 'lucide:package' },\n { value: 'shipped', label: 'Shipped', color: '#2563eb', icon: 'lucide:truck' },\n { value: 'delivered', label: 'Delivered', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'backordered', label: 'Backordered', color: '#d946ef', icon: 'lucide:alert-octagon' },\n { value: 'returned', label: 'Returned', color: '#0d9488', icon: 'lucide:undo-2' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst SHIPMENT_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock-3' },\n { value: 'packed', label: 'Packed', color: '#22c55e', icon: 'lucide:package-check' },\n { value: 'shipped', label: 'Shipped', color: '#2563eb', icon: 'lucide:truck' },\n { value: 'in_transit', label: 'In transit', color: '#0ea5e9', icon: 'lucide:loader' },\n { value: 'delivered', label: 'Delivered', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n { value: 'returned', label: 'Returned', color: '#0d9488', icon: 'lucide:undo-2' },\n]\n\nconst PAYMENT_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock-3' },\n { value: 'authorized', label: 'Authorized', color: '#6366f1', icon: 'lucide:badge-check' },\n { value: 'captured', label: 'Captured', color: '#0ea5e9', icon: 'lucide:banknote' },\n { value: 'received', label: 'Received', color: '#16a34a', icon: 'lucide:check' },\n { value: 'refunded', label: 'Refunded', color: '#f59e0b', icon: 'lucide:rotate-ccw' },\n { value: 'failed', label: 'Failed', color: '#ef4444', icon: 'lucide:triangle-alert' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst ADJUSTMENT_KIND_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'discount', label: 'Discount' },\n { value: 'tax', label: 'Tax' },\n { value: 'shipping', label: 'Shipping' },\n { value: 'surcharge', label: 'Surcharge' },\n { value: 'return', label: 'Return' },\n { value: 'custom', label: 'Custom' },\n]\n\nasync function ensureSalesDictionaryEntry(\n em: EntityManager,\n scope: SeedScope,\n kind: SalesDictionaryKind,\n seed: SalesDictionarySeed\n): Promise<DictionaryEntry | null> {\n const value = seed.value?.trim()\n if (!value) return null\n const dictionary = await ensureSalesDictionary({\n em,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n kind,\n })\n const normalizedValue = normalizeDictionaryValue(value)\n const color = seed.color === undefined ? undefined : sanitizeDictionaryColor(seed.color)\n const icon = seed.icon === undefined ? undefined : sanitizeDictionaryIcon(seed.icon)\n const existing = await em.findOne(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n normalizedValue,\n })\n if (existing) {\n let changed = false\n if (color !== undefined && existing.color !== color) {\n existing.color = color ?? null\n changed = true\n }\n if (icon !== undefined && existing.icon !== icon) {\n existing.icon = icon ?? null\n changed = true\n }\n if (changed) {\n existing.updatedAt = new Date()\n em.persist(existing)\n }\n return existing\n }\n const now = new Date()\n const entry = em.create(DictionaryEntry, {\n dictionary,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n value,\n label: seed.label?.trim() || value,\n normalizedValue,\n color: color ?? null,\n icon: icon ?? null,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entry)\n return entry\n}\n\nasync function seedSalesDictionary(\n em: EntityManager,\n scope: SeedScope,\n kind: SalesDictionaryKind,\n defaults: SalesDictionarySeed[]\n): Promise<void> {\n for (const seed of defaults) {\n await ensureSalesDictionaryEntry(em, scope, kind, seed)\n }\n}\n\nconst DEAL_LOSS_REASON_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'lost_to_competitor', label: 'Lost to competitor', color: '#ef4444', icon: 'lucide:users' },\n { value: 'no_budget', label: 'No budget', color: '#f59e0b', icon: 'lucide:wallet' },\n { value: 'no_decision', label: 'No decision made', color: '#94a3b8', icon: 'lucide:help-circle' },\n { value: 'timing', label: 'Bad timing', color: '#6366f1', icon: 'lucide:clock' },\n { value: 'other', label: 'Other', color: '#71717a', icon: 'lucide:minus-circle' },\n]\n\nexport async function seedSalesStatusDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesDictionary(em, scope, 'order-status', ORDER_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'order-line-status', ORDER_LINE_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'shipment-status', SHIPMENT_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'payment-status', PAYMENT_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'deal-loss-reasons', DEAL_LOSS_REASON_DEFAULTS)\n}\n\nexport async function seedSalesAdjustmentKinds(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesDictionary(em, scope, 'adjustment-kind', ADJUSTMENT_KIND_DEFAULTS)\n}\n\nexport async function seedSalesDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesStatusDictionaries(em, scope)\n await seedSalesAdjustmentKinds(em, scope)\n}\n\nexport const DEFAULT_ADJUSTMENT_KIND_VALUES = ADJUSTMENT_KIND_DEFAULTS.map((entry) => entry.value)\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { Dictionary, DictionaryEntry } from '@open-mercato/core/modules/dictionaries/data/entities'\nimport {\n normalizeDictionaryValue,\n sanitizeDictionaryColor,\n sanitizeDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/lib/utils'\n\nexport type SalesDictionaryKind =\n | 'order-status'\n | 'order-line-status'\n | 'shipment-status'\n | 'payment-status'\n | 'deal-loss-reasons'\n | 'adjustment-kind'\n\ntype SalesDictionaryDefinition = {\n key: string\n name: string\n singular: string\n description: string\n resourceKind: string\n commandPrefix: string\n}\n\nconst DEFINITIONS: Record<SalesDictionaryKind, SalesDictionaryDefinition> = {\n 'order-status': {\n key: 'sales.order_status',\n name: 'Sales order statuses',\n singular: 'Sales order status',\n description: 'Configurable set of statuses used by sales orders.',\n resourceKind: 'sales.order-status',\n commandPrefix: 'sales.order-statuses',\n },\n 'order-line-status': {\n key: 'sales.order_line_status',\n name: 'Sales order line statuses',\n singular: 'Sales order line status',\n description: 'Configurable set of statuses used by sales order lines.',\n resourceKind: 'sales.order-line-status',\n commandPrefix: 'sales.order-line-statuses',\n },\n 'shipment-status': {\n key: 'sales.shipment_status',\n name: 'Shipment statuses',\n singular: 'Shipment status',\n description: 'Configurable set of statuses used by shipments.',\n resourceKind: 'sales.shipment-status',\n commandPrefix: 'sales.shipment-statuses',\n },\n 'payment-status': {\n key: 'sales.payment_status',\n name: 'Payment statuses',\n singular: 'Payment status',\n description: 'Configurable set of statuses used by payments.',\n resourceKind: 'sales.payment-status',\n commandPrefix: 'sales.payment-statuses',\n },\n 'deal-loss-reasons': {\n key: 'sales.deal_loss_reason',\n name: 'Deal loss reasons',\n singular: 'Deal loss reason',\n description: 'Reusable reasons used when closing deals as lost.',\n resourceKind: 'sales.deal-loss-reason',\n commandPrefix: 'sales.deal-loss-reasons',\n },\n 'adjustment-kind': {\n key: 'sales.adjustment_kind',\n name: 'Sales adjustment kinds',\n singular: 'Sales adjustment kind',\n description: 'Reusable adjustment kinds applied to sales documents.',\n resourceKind: 'sales.adjustment-kind',\n commandPrefix: 'sales.adjustment-kinds',\n },\n}\n\nexport function getSalesDictionaryDefinition(kind: SalesDictionaryKind): SalesDictionaryDefinition {\n return DEFINITIONS[kind]\n}\n\nexport async function ensureSalesDictionary(params: {\n em: EntityManager\n tenantId: string\n organizationId: string\n kind: SalesDictionaryKind\n}): Promise<Dictionary> {\n const { em, tenantId, organizationId, kind } = params\n const def = getSalesDictionaryDefinition(kind)\n let dictionary = await em.findOne(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n deletedAt: null,\n })\n if (!dictionary) {\n dictionary = em.create(Dictionary, {\n tenantId,\n organizationId,\n key: def.key,\n name: def.name,\n description: def.description,\n isSystem: true,\n isActive: true,\n managerVisibility: 'hidden',\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(dictionary)\n await em.flush()\n }\n return dictionary\n}\n\nexport async function resolveDictionaryEntryValue(\n em: EntityManager,\n entryId: string | null | undefined,\n scope: { tenantId: string }\n): Promise<string | null> {\n if (!entryId) return null\n const entry = await em.findOne(DictionaryEntry, { id: entryId, tenantId: scope.tenantId })\n if (!entry) return null\n return entry.value?.trim() || null\n}\n\nexport { normalizeDictionaryValue, sanitizeDictionaryColor, sanitizeDictionaryIcon }\n\ntype SalesDictionarySeed = {\n value: string\n label: string\n color?: string | null\n icon?: string | null\n}\n\ntype SeedScope = { tenantId: string; organizationId: string }\n\nexport type BackfillDealLossReasonDictionaryResult = {\n dictionaryId: string\n createdDictionary: boolean\n copiedLegacyEntries: number\n seededDefaultEntries: number\n existingEntries: number\n}\n\nconst ORDER_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'draft', label: 'Draft', color: '#94a3b8', icon: 'lucide:file-pen-line' },\n { value: 'pending_approval', label: 'Pending Approval', color: '#f59e0b', icon: 'lucide:hourglass' },\n { value: 'approved', label: 'Approved', color: '#16a34a', icon: 'lucide:check-circle' },\n { value: 'rejected', label: 'Rejected', color: '#ef4444', icon: 'lucide:x-circle' },\n { value: 'sent', label: 'Sent', color: '#0ea5e9', icon: 'lucide:send' },\n { value: 'confirmed', label: 'Confirmed', color: '#2563eb', icon: 'lucide:badge-check' },\n { value: 'in_fulfillment', label: 'In fulfillment', color: '#f59e0b', icon: 'lucide:loader-2' },\n { value: 'fulfilled', label: 'Fulfilled', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'on_hold', label: 'On hold', color: '#a855f7', icon: 'lucide:pause-circle' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst ORDER_LINE_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock' },\n { value: 'allocated', label: 'Allocated', color: '#6366f1', icon: 'lucide:inbox' },\n { value: 'picking', label: 'Picking', color: '#f59e0b', icon: 'lucide:hand' },\n { value: 'packed', label: 'Packed', color: '#0ea5e9', icon: 'lucide:package' },\n { value: 'shipped', label: 'Shipped', color: '#2563eb', icon: 'lucide:truck' },\n { value: 'delivered', label: 'Delivered', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'backordered', label: 'Backordered', color: '#d946ef', icon: 'lucide:alert-octagon' },\n { value: 'returned', label: 'Returned', color: '#0d9488', icon: 'lucide:undo-2' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst SHIPMENT_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock-3' },\n { value: 'packed', label: 'Packed', color: '#22c55e', icon: 'lucide:package-check' },\n { value: 'shipped', label: 'Shipped', color: '#2563eb', icon: 'lucide:truck' },\n { value: 'in_transit', label: 'In transit', color: '#0ea5e9', icon: 'lucide:loader' },\n { value: 'delivered', label: 'Delivered', color: '#16a34a', icon: 'lucide:check-circle-2' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n { value: 'returned', label: 'Returned', color: '#0d9488', icon: 'lucide:undo-2' },\n]\n\nconst PAYMENT_STATUS_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'pending', label: 'Pending', color: '#94a3b8', icon: 'lucide:clock-3' },\n { value: 'authorized', label: 'Authorized', color: '#6366f1', icon: 'lucide:badge-check' },\n { value: 'captured', label: 'Captured', color: '#0ea5e9', icon: 'lucide:banknote' },\n { value: 'received', label: 'Received', color: '#16a34a', icon: 'lucide:check' },\n { value: 'refunded', label: 'Refunded', color: '#f59e0b', icon: 'lucide:rotate-ccw' },\n { value: 'failed', label: 'Failed', color: '#ef4444', icon: 'lucide:triangle-alert' },\n { value: 'canceled', label: 'Canceled', color: '#ef4444', icon: 'lucide:x-circle' },\n]\n\nconst ADJUSTMENT_KIND_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'discount', label: 'Discount' },\n { value: 'tax', label: 'Tax' },\n { value: 'shipping', label: 'Shipping' },\n { value: 'surcharge', label: 'Surcharge' },\n { value: 'return', label: 'Return' },\n { value: 'custom', label: 'Custom' },\n]\n\nasync function ensureSalesDictionaryEntry(\n em: EntityManager,\n scope: SeedScope,\n kind: SalesDictionaryKind,\n seed: SalesDictionarySeed\n): Promise<DictionaryEntry | null> {\n const value = seed.value?.trim()\n if (!value) return null\n const dictionary = await ensureSalesDictionary({\n em,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n kind,\n })\n const normalizedValue = normalizeDictionaryValue(value)\n const color = seed.color === undefined ? undefined : sanitizeDictionaryColor(seed.color)\n const icon = seed.icon === undefined ? undefined : sanitizeDictionaryIcon(seed.icon)\n const existing = await em.findOne(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n normalizedValue,\n })\n if (existing) {\n let changed = false\n if (color !== undefined && existing.color !== color) {\n existing.color = color ?? null\n changed = true\n }\n if (icon !== undefined && existing.icon !== icon) {\n existing.icon = icon ?? null\n changed = true\n }\n if (changed) {\n existing.updatedAt = new Date()\n em.persist(existing)\n }\n return existing\n }\n const now = new Date()\n const entry = em.create(DictionaryEntry, {\n dictionary,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n value,\n label: seed.label?.trim() || value,\n normalizedValue,\n color: color ?? null,\n icon: icon ?? null,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(entry)\n return entry\n}\n\nasync function seedSalesDictionary(\n em: EntityManager,\n scope: SeedScope,\n kind: SalesDictionaryKind,\n defaults: SalesDictionarySeed[]\n): Promise<void> {\n for (const seed of defaults) {\n await ensureSalesDictionaryEntry(em, scope, kind, seed)\n }\n}\n\nconst DEAL_LOSS_REASON_DEFAULTS: SalesDictionarySeed[] = [\n { value: 'lost_to_competitor', label: 'Lost to competitor', color: '#ef4444', icon: 'lucide:users' },\n { value: 'no_budget', label: 'No budget', color: '#f59e0b', icon: 'lucide:wallet' },\n { value: 'no_decision', label: 'No decision made', color: '#94a3b8', icon: 'lucide:help-circle' },\n { value: 'timing', label: 'Bad timing', color: '#6366f1', icon: 'lucide:clock' },\n { value: 'other', label: 'Other', color: '#71717a', icon: 'lucide:minus-circle' },\n]\n\nexport async function backfillDealLossReasonDictionary(\n em: EntityManager,\n scope: SeedScope\n): Promise<BackfillDealLossReasonDictionaryResult> {\n const definition = getSalesDictionaryDefinition('deal-loss-reasons')\n const existingDictionary = await em.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: definition.key,\n deletedAt: null,\n })\n const dictionary = existingDictionary ?? await ensureSalesDictionary({\n em,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n kind: 'deal-loss-reasons',\n })\n const targetEntries = await em.find(DictionaryEntry, {\n dictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n })\n\n if (targetEntries.length > 0) {\n return {\n dictionaryId: dictionary.id,\n createdDictionary: !existingDictionary,\n copiedLegacyEntries: 0,\n seededDefaultEntries: 0,\n existingEntries: targetEntries.length,\n }\n }\n\n const legacyDictionary = await em.findOne(Dictionary, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n key: 'customer_lost_reason',\n deletedAt: null,\n })\n const legacyEntries = legacyDictionary\n ? await em.find(\n DictionaryEntry,\n {\n dictionary: legacyDictionary,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n },\n { orderBy: { position: 'asc', label: 'asc' } },\n )\n : []\n\n let copiedLegacyEntries = 0\n let seededDefaultEntries = 0\n const processedValues = new Set<string>()\n const seeds = legacyEntries.length\n ? legacyEntries.map((entry) => ({\n value: entry.value,\n label: entry.label,\n color: entry.color,\n icon: entry.icon,\n }))\n : DEAL_LOSS_REASON_DEFAULTS\n\n for (const seed of seeds) {\n const normalizedValue = normalizeDictionaryValue(seed.value ?? '')\n if (!normalizedValue || processedValues.has(normalizedValue)) continue\n const entry = await ensureSalesDictionaryEntry(em, scope, 'deal-loss-reasons', seed)\n if (!entry) continue\n processedValues.add(normalizedValue)\n if (legacyEntries.length) copiedLegacyEntries += 1\n else seededDefaultEntries += 1\n }\n\n return {\n dictionaryId: dictionary.id,\n createdDictionary: !existingDictionary,\n copiedLegacyEntries,\n seededDefaultEntries,\n existingEntries: 0,\n }\n}\n\nexport async function seedSalesStatusDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesDictionary(em, scope, 'order-status', ORDER_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'order-line-status', ORDER_LINE_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'shipment-status', SHIPMENT_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'payment-status', PAYMENT_STATUS_DEFAULTS)\n await seedSalesDictionary(em, scope, 'deal-loss-reasons', DEAL_LOSS_REASON_DEFAULTS)\n}\n\nexport async function seedSalesAdjustmentKinds(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesDictionary(em, scope, 'adjustment-kind', ADJUSTMENT_KIND_DEFAULTS)\n}\n\nexport async function seedSalesDictionaries(\n em: EntityManager,\n scope: SeedScope\n): Promise<void> {\n await seedSalesStatusDictionaries(em, scope)\n await seedSalesAdjustmentKinds(em, scope)\n}\n\nexport const DEFAULT_ADJUSTMENT_KIND_VALUES = ADJUSTMENT_KIND_DEFAULTS.map((entry) => entry.value)\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.6-develop.6401.1.8c07df34a7",
3
+ "version": "0.6.6-develop.6403.1.b57089b6fe",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -248,16 +248,16 @@
248
248
  "zod": "^4.4.3"
249
249
  },
250
250
  "peerDependencies": {
251
- "@open-mercato/ai-assistant": "0.6.6-develop.6401.1.8c07df34a7",
252
- "@open-mercato/shared": "0.6.6-develop.6401.1.8c07df34a7",
253
- "@open-mercato/ui": "0.6.6-develop.6401.1.8c07df34a7",
251
+ "@open-mercato/ai-assistant": "0.6.6-develop.6403.1.b57089b6fe",
252
+ "@open-mercato/shared": "0.6.6-develop.6403.1.b57089b6fe",
253
+ "@open-mercato/ui": "0.6.6-develop.6403.1.b57089b6fe",
254
254
  "react": "^19.0.0",
255
255
  "react-dom": "^19.0.0"
256
256
  },
257
257
  "devDependencies": {
258
- "@open-mercato/ai-assistant": "0.6.6-develop.6401.1.8c07df34a7",
259
- "@open-mercato/shared": "0.6.6-develop.6401.1.8c07df34a7",
260
- "@open-mercato/ui": "0.6.6-develop.6401.1.8c07df34a7",
258
+ "@open-mercato/ai-assistant": "0.6.6-develop.6403.1.b57089b6fe",
259
+ "@open-mercato/shared": "0.6.6-develop.6403.1.b57089b6fe",
260
+ "@open-mercato/ui": "0.6.6-develop.6403.1.b57089b6fe",
261
261
  "@testing-library/dom": "^10.4.1",
262
262
  "@testing-library/jest-dom": "^6.9.1",
263
263
  "@testing-library/react": "^16.3.1",
@@ -82,25 +82,21 @@ export function useDealClosure({
82
82
  async (input: { lossReasonId: string; lossNotes?: string }) => {
83
83
  if (!currentDealId) return
84
84
  if (!(await confirmDiscardIfDirty())) return
85
+ const lossPayload = {
86
+ id: currentDealId,
87
+ closureOutcome: 'lost' as const,
88
+ status: 'loose',
89
+ lossReasonId: input.lossReasonId,
90
+ ...(input.lossNotes ? { lossNotes: input.lossNotes } : {}),
91
+ }
85
92
  try {
86
93
  await runMutationWithContext(
87
94
  () => withScopedApiRequestHeaders(
88
95
  buildOptimisticLockHeader(dealUpdatedAt),
89
- () =>
90
- updateCrud('customers/deals', {
91
- id: currentDealId,
92
- closureOutcome: 'lost',
93
- status: 'loose',
94
- lossReasonId: input.lossReasonId,
95
- lossNotes: input.lossNotes ?? null,
96
- }),
96
+ () => updateCrud('customers/deals', lossPayload),
97
97
  ),
98
98
  {
99
- id: currentDealId,
100
- closureOutcome: 'lost',
101
- status: 'loose',
102
- lossReasonId: input.lossReasonId,
103
- lossNotes: input.lossNotes ?? null,
99
+ ...lossPayload,
104
100
  operation: 'closeLost',
105
101
  },
106
102
  )
@@ -40,18 +40,28 @@ export function ConfirmDealLostDialog({
40
40
  const [lossReasons, setLossReasons] = React.useState<LossReasonOption[]>([])
41
41
  const [reasonListOpen, setReasonListOpen] = React.useState(false)
42
42
  const [error, setError] = React.useState('')
43
+ const [dictionaryLoadFailed, setDictionaryLoadFailed] = React.useState(false)
44
+ const [isLoadingReasons, setIsLoadingReasons] = React.useState(false)
43
45
  const [isConfirming, setIsConfirming] = React.useState(false)
44
46
 
45
47
  React.useEffect(() => {
46
48
  if (!open) return
47
49
  let cancelled = false
50
+ setIsLoadingReasons(true)
51
+ setDictionaryLoadFailed(false)
48
52
  loadDictionaryEntriesByKey('sales.deal_loss_reason')
49
53
  .then((items) => {
50
54
  if (!cancelled) setLossReasons(items)
51
55
  })
52
56
  .catch((loadError) => {
53
57
  console.error('customers.deals.detail.lossReasons failed', loadError)
54
- if (!cancelled) setLossReasons([])
58
+ if (!cancelled) {
59
+ setLossReasons([])
60
+ setDictionaryLoadFailed(true)
61
+ }
62
+ })
63
+ .finally(() => {
64
+ if (!cancelled) setIsLoadingReasons(false)
55
65
  })
56
66
  return () => {
57
67
  cancelled = true
@@ -64,14 +74,35 @@ export function ConfirmDealLostDialog({
64
74
  setLossNotes('')
65
75
  setReasonListOpen(false)
66
76
  setError('')
77
+ setDictionaryLoadFailed(false)
78
+ setIsConfirming(false)
67
79
  }, [open])
68
80
 
69
81
  const selectedLossReason = React.useMemo(
70
82
  () => lossReasons.find((reason) => reason.id === lossReasonId) ?? null,
71
83
  [lossReasonId, lossReasons],
72
84
  )
85
+ const hasLossReasons = lossReasons.length > 0
86
+ const reasonUnavailable = !isLoadingReasons && !hasLossReasons
87
+ const unavailableReasonText = dictionaryLoadFailed
88
+ ? t('customers.deals.detail.lost.reasonLoadError', 'Loss reasons could not be loaded.')
89
+ : t('customers.deals.detail.lost.reasonUnavailable', 'No loss reasons are configured.')
90
+ const reasonHelpText = React.useMemo(() => {
91
+ if (selectedLossReason?.description) return selectedLossReason.description
92
+ if (isLoadingReasons) {
93
+ return t('customers.deals.detail.lost.reasonLoading', 'Loading loss reasons...')
94
+ }
95
+ if (reasonUnavailable) {
96
+ return unavailableReasonText
97
+ }
98
+ return t('customers.deals.detail.lost.reasonHelp', 'Choose the closest reason from the dictionary.')
99
+ }, [isLoadingReasons, reasonUnavailable, selectedLossReason?.description, t, unavailableReasonText])
73
100
 
74
101
  const handleConfirm = React.useCallback(async () => {
102
+ if (isLoadingReasons || reasonUnavailable) {
103
+ setError(unavailableReasonText)
104
+ return
105
+ }
75
106
  if (!lossReasonId) {
76
107
  setError(t('customers.deals.detail.lost.reasonRequired', 'Please select a loss reason'))
77
108
  return
@@ -85,11 +116,13 @@ export function ConfirmDealLostDialog({
85
116
  } finally {
86
117
  setIsConfirming(false)
87
118
  }
88
- }, [lossNotes, lossReasonId, onConfirm, t])
119
+ }, [isLoadingReasons, lossNotes, lossReasonId, onConfirm, reasonUnavailable, t, unavailableReasonText])
120
+
121
+ const confirmDisabled = isConfirming || isLoadingReasons || reasonUnavailable || !lossReasonId
89
122
 
90
123
  const handleKeyDown = useDialogKeyHandler({
91
124
  onConfirm: () => void handleConfirm(),
92
- disabled: isConfirming,
125
+ disabled: confirmDisabled,
93
126
  })
94
127
 
95
128
  return (
@@ -138,10 +171,13 @@ export function ConfirmDealLostDialog({
138
171
  >
139
172
  <div className="min-w-0">
140
173
  <div className="truncate text-base font-semibold text-foreground">
141
- {selectedLossReason?.label ?? t('customers.deals.detail.lost.reasonPlaceholder', 'Select loss reason')}
174
+ {selectedLossReason?.label
175
+ ?? (isLoadingReasons
176
+ ? t('customers.deals.detail.lost.reasonLoadingShort', 'Loading...')
177
+ : t('customers.deals.detail.lost.reasonPlaceholder', 'Select loss reason'))}
142
178
  </div>
143
179
  <div className="truncate text-sm text-muted-foreground">
144
- {selectedLossReason?.description ?? t('customers.deals.detail.lost.reasonHelp', 'Choose the closest reason from the dictionary.')}
180
+ {reasonHelpText}
145
181
  </div>
146
182
  </div>
147
183
  <ChevronDown className="ml-3 size-4 shrink-0 text-muted-foreground" />
@@ -149,7 +185,7 @@ export function ConfirmDealLostDialog({
149
185
 
150
186
  {reasonListOpen ? (
151
187
  <div className="overflow-hidden rounded-md border border-border/80 bg-background">
152
- {lossReasons.map((reason, index) => {
188
+ {hasLossReasons ? lossReasons.map((reason, index) => {
153
189
  const isSelected = reason.id === lossReasonId
154
190
  return (
155
191
  <Button
@@ -178,10 +214,19 @@ export function ConfirmDealLostDialog({
178
214
  ) : null}
179
215
  </Button>
180
216
  )
181
- })}
217
+ }) : (
218
+ <div className="px-4 py-3 text-sm text-muted-foreground">
219
+ {unavailableReasonText}
220
+ </div>
221
+ )}
182
222
  </div>
183
223
  ) : null}
184
224
  </div>
225
+ {reasonUnavailable ? (
226
+ <p className="text-xs text-destructive">
227
+ {unavailableReasonText}
228
+ </p>
229
+ ) : null}
185
230
  {error ? <p className="text-xs text-destructive">{error}</p> : null}
186
231
  </div>
187
232
 
@@ -203,7 +248,12 @@ export function ConfirmDealLostDialog({
203
248
  <Button type="button" variant="outline" onClick={onClose}>
204
249
  {t('customers.deals.detail.lost.cancel', 'Cancel')}
205
250
  </Button>
206
- <Button type="button" variant="destructive" onClick={() => { void handleConfirm() }}>
251
+ <Button
252
+ type="button"
253
+ variant="destructive"
254
+ disabled={confirmDisabled}
255
+ onClick={() => { void handleConfirm() }}
256
+ >
207
257
  {t('customers.deals.detail.lost.confirm', 'Mark as Lost')}
208
258
  </Button>
209
259
  </DialogFooter>
@@ -992,8 +992,12 @@
992
992
  "customers.deals.detail.lost.reasonFallbackDescription": "No description available.",
993
993
  "customers.deals.detail.lost.reasonHelp": "Choose the closest reason from the dictionary.",
994
994
  "customers.deals.detail.lost.reasonLabel": "Loss reason",
995
+ "customers.deals.detail.lost.reasonLoadError": "Loss reasons could not be loaded.",
996
+ "customers.deals.detail.lost.reasonLoading": "Loading loss reasons...",
997
+ "customers.deals.detail.lost.reasonLoadingShort": "Loading...",
995
998
  "customers.deals.detail.lost.reasonPlaceholder": "Select loss reason",
996
999
  "customers.deals.detail.lost.reasonRequired": "Please select a loss reason",
1000
+ "customers.deals.detail.lost.reasonUnavailable": "No loss reasons are configured.",
997
1001
  "customers.deals.detail.lost.salesCycle": "Sales cycle",
998
1002
  "customers.deals.detail.lost.secondaryAction": "Set reminder for Q3",
999
1003
  "customers.deals.detail.lost.title": "Mark deal as Lost?",
@@ -992,8 +992,12 @@
992
992
  "customers.deals.detail.lost.reasonFallbackDescription": "No description available.",
993
993
  "customers.deals.detail.lost.reasonHelp": "Choose the closest reason from the dictionary.",
994
994
  "customers.deals.detail.lost.reasonLabel": "Loss reason",
995
+ "customers.deals.detail.lost.reasonLoadError": "Loss reasons could not be loaded.",
996
+ "customers.deals.detail.lost.reasonLoading": "Loading loss reasons...",
997
+ "customers.deals.detail.lost.reasonLoadingShort": "Loading...",
995
998
  "customers.deals.detail.lost.reasonPlaceholder": "Select loss reason",
996
999
  "customers.deals.detail.lost.reasonRequired": "Please select a loss reason",
1000
+ "customers.deals.detail.lost.reasonUnavailable": "No loss reasons are configured.",
997
1001
  "customers.deals.detail.lost.salesCycle": "Sales cycle",
998
1002
  "customers.deals.detail.lost.secondaryAction": "Set reminder for Q3",
999
1003
  "customers.deals.detail.lost.title": "Mark deal as Lost?",
@@ -992,8 +992,12 @@
992
992
  "customers.deals.detail.lost.reasonFallbackDescription": "No description available.",
993
993
  "customers.deals.detail.lost.reasonHelp": "Choose the closest reason from the dictionary.",
994
994
  "customers.deals.detail.lost.reasonLabel": "Loss reason",
995
+ "customers.deals.detail.lost.reasonLoadError": "Loss reasons could not be loaded.",
996
+ "customers.deals.detail.lost.reasonLoading": "Loading loss reasons...",
997
+ "customers.deals.detail.lost.reasonLoadingShort": "Loading...",
995
998
  "customers.deals.detail.lost.reasonPlaceholder": "Select loss reason",
996
999
  "customers.deals.detail.lost.reasonRequired": "Please select a loss reason",
1000
+ "customers.deals.detail.lost.reasonUnavailable": "No loss reasons are configured.",
997
1001
  "customers.deals.detail.lost.salesCycle": "Sales cycle",
998
1002
  "customers.deals.detail.lost.secondaryAction": "Set reminder for Q3",
999
1003
  "customers.deals.detail.lost.title": "Mark deal as Lost?",
@@ -992,8 +992,12 @@
992
992
  "customers.deals.detail.lost.reasonFallbackDescription": "No description available.",
993
993
  "customers.deals.detail.lost.reasonHelp": "Choose the closest reason from the dictionary.",
994
994
  "customers.deals.detail.lost.reasonLabel": "Loss reason",
995
+ "customers.deals.detail.lost.reasonLoadError": "Loss reasons could not be loaded.",
996
+ "customers.deals.detail.lost.reasonLoading": "Loading loss reasons...",
997
+ "customers.deals.detail.lost.reasonLoadingShort": "Loading...",
995
998
  "customers.deals.detail.lost.reasonPlaceholder": "Select loss reason",
996
999
  "customers.deals.detail.lost.reasonRequired": "Please select a loss reason",
1000
+ "customers.deals.detail.lost.reasonUnavailable": "No loss reasons are configured.",
997
1001
  "customers.deals.detail.lost.salesCycle": "Sales cycle",
998
1002
  "customers.deals.detail.lost.secondaryAction": "Set reminder for Q3",
999
1003
  "customers.deals.detail.lost.title": "Mark deal as Lost?",
@@ -123,6 +123,16 @@ export const openApi = {
123
123
  { status: 400, description: 'Invalid payload', schema: z.object({ error: z.string() }) },
124
124
  { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
125
125
  { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
126
+ {
127
+ status: 409,
128
+ description: 'Optimistic lock conflict',
129
+ schema: z.object({
130
+ error: z.string(),
131
+ code: z.literal('optimistic_lock_conflict'),
132
+ currentUpdatedAt: z.string(),
133
+ expectedUpdatedAt: z.string(),
134
+ }),
135
+ },
126
136
  ],
127
137
  },
128
138
  },