@open-mercato/core 0.6.6-develop.6401.1.8c07df34a7 → 0.6.6-develop.6402.1.080aab8ec9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +9 -12
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +2 -2
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js +49 -10
- package/dist/modules/customers/components/detail/ConfirmDealLostDialog.js.map +2 -2
- package/dist/modules/sales/cli.js +38 -1
- package/dist/modules/sales/cli.js.map +2 -2
- package/dist/modules/sales/lib/dictionaries.js +70 -0
- package/dist/modules/sales/lib/dictionaries.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +9 -13
- package/src/modules/customers/components/detail/ConfirmDealLostDialog.tsx +58 -8
- package/src/modules/customers/i18n/de.json +4 -0
- package/src/modules/customers/i18n/en.json +4 -0
- package/src/modules/customers/i18n/es.json +4 -0
- package/src/modules/customers/i18n/pl.json +4 -0
- package/src/modules/sales/cli.ts +39 -1
- package/src/modules/sales/lib/dictionaries.ts +90 -0
|
@@ -57,24 +57,21 @@ function useDealClosure({
|
|
|
57
57
|
async (input) => {
|
|
58
58
|
if (!currentDealId) return;
|
|
59
59
|
if (!await confirmDiscardIfDirty()) return;
|
|
60
|
+
const lossPayload = {
|
|
61
|
+
id: currentDealId,
|
|
62
|
+
closureOutcome: "lost",
|
|
63
|
+
status: "loose",
|
|
64
|
+
lossReasonId: input.lossReasonId,
|
|
65
|
+
...input.lossNotes ? { lossNotes: input.lossNotes } : {}
|
|
66
|
+
};
|
|
60
67
|
try {
|
|
61
68
|
await runMutationWithContext(
|
|
62
69
|
() => withScopedApiRequestHeaders(
|
|
63
70
|
buildOptimisticLockHeader(dealUpdatedAt),
|
|
64
|
-
() => updateCrud("customers/deals",
|
|
65
|
-
id: currentDealId,
|
|
66
|
-
closureOutcome: "lost",
|
|
67
|
-
status: "loose",
|
|
68
|
-
lossReasonId: input.lossReasonId,
|
|
69
|
-
lossNotes: input.lossNotes ?? null
|
|
70
|
-
})
|
|
71
|
+
() => updateCrud("customers/deals", lossPayload)
|
|
71
72
|
),
|
|
72
73
|
{
|
|
73
|
-
|
|
74
|
-
closureOutcome: "lost",
|
|
75
|
-
status: "loose",
|
|
76
|
-
lossReasonId: input.lossReasonId,
|
|
77
|
-
lossNotes: input.lossNotes ?? null,
|
|
74
|
+
...lossPayload,
|
|
78
75
|
operation: "closeLost"
|
|
79
76
|
}
|
|
80
77
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/%5Bid%5D/hooks/useDealClosure.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { DealStatsPayload, GuardedMutationRunner } from './types'\n\ntype UseDealClosureOptions = {\n currentDealId: string | null\n dealUpdatedAt: string | null\n runMutationWithContext: GuardedMutationRunner\n confirmDiscardIfDirty: () => Promise<boolean>\n onClosed: () => Promise<void>\n}\n\ntype UseDealClosureResult = {\n lostDialogOpen: boolean\n wonPopupOpen: boolean\n lostPopupOpen: boolean\n wonStats: DealStatsPayload | null\n lostStats: DealStatsPayload | null\n openLostDialog: () => void\n closeLostDialog: () => void\n closeWonPopup: () => void\n closeLostPopup: () => void\n handleWon: () => Promise<void>\n handleLostConfirm: (input: { lossReasonId: string; lossNotes?: string }) => Promise<void>\n}\n\nexport function useDealClosure({\n currentDealId,\n dealUpdatedAt,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onClosed,\n}: UseDealClosureOptions): UseDealClosureResult {\n const t = useT()\n const [lostDialogOpen, setLostDialogOpen] = React.useState(false)\n const [wonPopupOpen, setWonPopupOpen] = React.useState(false)\n const [lostPopupOpen, setLostPopupOpen] = React.useState(false)\n const [wonStats, setWonStats] = React.useState<DealStatsPayload | null>(null)\n const [lostStats, setLostStats] = React.useState<DealStatsPayload | null>(null)\n\n const fetchDealStats = React.useCallback(async (): Promise<DealStatsPayload | null> => {\n if (!currentDealId) return null\n try {\n return await readApiResultOrThrow<DealStatsPayload>(\n `/api/customers/deals/${encodeURIComponent(currentDealId)}/stats`,\n )\n } catch (statsError) {\n console.error('customers.deals.detail.stats failed', statsError)\n return null\n }\n }, [currentDealId])\n\n const handleWon = React.useCallback(async () => {\n if (!currentDealId) return\n if (!(await confirmDiscardIfDirty())) return\n try {\n await runMutationWithContext(\n () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(dealUpdatedAt),\n () => updateCrud('customers/deals', { id: currentDealId, closureOutcome: 'won', status: 'win' }),\n ),\n { id: currentDealId, closureOutcome: 'won', status: 'win', operation: 'closeWon' },\n )\n } catch (err) {\n if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onClosed() } })) {\n flash(t('customers.deals.detail.closeWonError', 'Failed to mark deal as won.'), 'error')\n }\n return\n }\n const stats = await fetchDealStats()\n setWonStats(stats)\n setWonPopupOpen(true)\n await onClosed()\n }, [confirmDiscardIfDirty, currentDealId, dealUpdatedAt, fetchDealStats, onClosed, runMutationWithContext, t])\n\n const handleLostConfirm = React.useCallback(\n async (input: { lossReasonId: string; lossNotes?: string }) => {\n if (!currentDealId) return\n if (!(await confirmDiscardIfDirty())) return\n
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;AACvB,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB,mCAAmC;AAClE,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AACtC,SAAS,aAAa;AACtB,SAAS,YAAY;AAyBd,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAgD;AAC9C,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,KAAK;AAChE,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,KAAK;AAC5D,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAS,KAAK;AAC9D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAkC,IAAI;AAC5E,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAkC,IAAI;AAE9E,QAAM,iBAAiB,MAAM,YAAY,YAA8C;AACrF,QAAI,CAAC,cAAe,QAAO;AAC3B,QAAI;AACF,aAAO,MAAM;AAAA,QACX,wBAAwB,mBAAmB,aAAa,CAAC;AAAA,MAC3D;AAAA,IACF,SAAS,YAAY;AACnB,cAAQ,MAAM,uCAAuC,UAAU;AAC/D,aAAO;AAAA,IACT;AAAA,EACF,GAAG,CAAC,aAAa,CAAC;AAElB,QAAM,YAAY,MAAM,YAAY,YAAY;AAC9C,QAAI,CAAC,cAAe;AACpB,QAAI,CAAE,MAAM,sBAAsB,EAAI;AACtC,QAAI;AACF,YAAM;AAAA,QACJ,MAAM;AAAA,UACJ,0BAA0B,aAAa;AAAA,UACvC,MAAM,WAAW,mBAAmB,EAAE,IAAI,eAAe,gBAAgB,OAAO,QAAQ,MAAM,CAAC;AAAA,QACjG;AAAA,QACA,EAAE,IAAI,eAAe,gBAAgB,OAAO,QAAQ,OAAO,WAAW,WAAW;AAAA,MACnF;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,CAAC,sBAAsB,KAAK,GAAG,EAAE,WAAW,MAAM;AAAE,aAAK,SAAS;AAAA,MAAE,EAAE,CAAC,GAAG;AAC5E,cAAM,EAAE,wCAAwC,6BAA6B,GAAG,OAAO;AAAA,MACzF;AACA;AAAA,IACF;AACA,UAAM,QAAQ,MAAM,eAAe;AACnC,gBAAY,KAAK;AACjB,oBAAgB,IAAI;AACpB,UAAM,SAAS;AAAA,EACjB,GAAG,CAAC,uBAAuB,eAAe,eAAe,gBAAgB,UAAU,wBAAwB,CAAC,CAAC;AAE7G,QAAM,oBAAoB,MAAM;AAAA,IAC9B,OAAO,UAAwD;AAC7D,UAAI,CAAC,cAAe;AACpB,UAAI,CAAE,MAAM,sBAAsB,EAAI;AACtC,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { updateCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { DealStatsPayload, GuardedMutationRunner } from './types'\n\ntype UseDealClosureOptions = {\n currentDealId: string | null\n dealUpdatedAt: string | null\n runMutationWithContext: GuardedMutationRunner\n confirmDiscardIfDirty: () => Promise<boolean>\n onClosed: () => Promise<void>\n}\n\ntype UseDealClosureResult = {\n lostDialogOpen: boolean\n wonPopupOpen: boolean\n lostPopupOpen: boolean\n wonStats: DealStatsPayload | null\n lostStats: DealStatsPayload | null\n openLostDialog: () => void\n closeLostDialog: () => void\n closeWonPopup: () => void\n closeLostPopup: () => void\n handleWon: () => Promise<void>\n handleLostConfirm: (input: { lossReasonId: string; lossNotes?: string }) => Promise<void>\n}\n\nexport function useDealClosure({\n currentDealId,\n dealUpdatedAt,\n runMutationWithContext,\n confirmDiscardIfDirty,\n onClosed,\n}: UseDealClosureOptions): UseDealClosureResult {\n const t = useT()\n const [lostDialogOpen, setLostDialogOpen] = React.useState(false)\n const [wonPopupOpen, setWonPopupOpen] = React.useState(false)\n const [lostPopupOpen, setLostPopupOpen] = React.useState(false)\n const [wonStats, setWonStats] = React.useState<DealStatsPayload | null>(null)\n const [lostStats, setLostStats] = React.useState<DealStatsPayload | null>(null)\n\n const fetchDealStats = React.useCallback(async (): Promise<DealStatsPayload | null> => {\n if (!currentDealId) return null\n try {\n return await readApiResultOrThrow<DealStatsPayload>(\n `/api/customers/deals/${encodeURIComponent(currentDealId)}/stats`,\n )\n } catch (statsError) {\n console.error('customers.deals.detail.stats failed', statsError)\n return null\n }\n }, [currentDealId])\n\n const handleWon = React.useCallback(async () => {\n if (!currentDealId) return\n if (!(await confirmDiscardIfDirty())) return\n try {\n await runMutationWithContext(\n () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(dealUpdatedAt),\n () => updateCrud('customers/deals', { id: currentDealId, closureOutcome: 'won', status: 'win' }),\n ),\n { id: currentDealId, closureOutcome: 'won', status: 'win', operation: 'closeWon' },\n )\n } catch (err) {\n if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onClosed() } })) {\n flash(t('customers.deals.detail.closeWonError', 'Failed to mark deal as won.'), 'error')\n }\n return\n }\n const stats = await fetchDealStats()\n setWonStats(stats)\n setWonPopupOpen(true)\n await onClosed()\n }, [confirmDiscardIfDirty, currentDealId, dealUpdatedAt, fetchDealStats, onClosed, runMutationWithContext, t])\n\n const handleLostConfirm = React.useCallback(\n async (input: { lossReasonId: string; lossNotes?: string }) => {\n if (!currentDealId) return\n if (!(await confirmDiscardIfDirty())) return\n const lossPayload = {\n id: currentDealId,\n closureOutcome: 'lost' as const,\n status: 'loose',\n lossReasonId: input.lossReasonId,\n ...(input.lossNotes ? { lossNotes: input.lossNotes } : {}),\n }\n try {\n await runMutationWithContext(\n () => withScopedApiRequestHeaders(\n buildOptimisticLockHeader(dealUpdatedAt),\n () => updateCrud('customers/deals', lossPayload),\n ),\n {\n ...lossPayload,\n operation: 'closeLost',\n },\n )\n } catch (err) {\n if (!surfaceRecordConflict(err, t, { onRefresh: () => { void onClosed() } })) {\n flash(t('customers.deals.detail.closeLostError', 'Failed to mark deal as lost.'), 'error')\n }\n return\n }\n setLostDialogOpen(false)\n const stats = await fetchDealStats()\n setLostStats(stats)\n setLostPopupOpen(true)\n await onClosed()\n },\n [confirmDiscardIfDirty, currentDealId, dealUpdatedAt, fetchDealStats, onClosed, runMutationWithContext, t],\n )\n\n const openLostDialog = React.useCallback(() => setLostDialogOpen(true), [])\n const closeLostDialog = React.useCallback(() => setLostDialogOpen(false), [])\n const closeWonPopup = React.useCallback(() => setWonPopupOpen(false), [])\n const closeLostPopup = React.useCallback(() => setLostPopupOpen(false), [])\n\n return {\n lostDialogOpen,\n wonPopupOpen,\n lostPopupOpen,\n wonStats,\n lostStats,\n openLostDialog,\n closeLostDialog,\n closeWonPopup,\n closeLostPopup,\n handleWon,\n handleLostConfirm,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -23,15 +23,24 @@ function ConfirmDealLostDialog({
|
|
|
23
23
|
const [lossReasons, setLossReasons] = React.useState([]);
|
|
24
24
|
const [reasonListOpen, setReasonListOpen] = React.useState(false);
|
|
25
25
|
const [error, setError] = React.useState("");
|
|
26
|
+
const [dictionaryLoadFailed, setDictionaryLoadFailed] = React.useState(false);
|
|
27
|
+
const [isLoadingReasons, setIsLoadingReasons] = React.useState(false);
|
|
26
28
|
const [isConfirming, setIsConfirming] = React.useState(false);
|
|
27
29
|
React.useEffect(() => {
|
|
28
30
|
if (!open) return;
|
|
29
31
|
let cancelled = false;
|
|
32
|
+
setIsLoadingReasons(true);
|
|
33
|
+
setDictionaryLoadFailed(false);
|
|
30
34
|
loadDictionaryEntriesByKey("sales.deal_loss_reason").then((items) => {
|
|
31
35
|
if (!cancelled) setLossReasons(items);
|
|
32
36
|
}).catch((loadError) => {
|
|
33
37
|
console.error("customers.deals.detail.lossReasons failed", loadError);
|
|
34
|
-
if (!cancelled)
|
|
38
|
+
if (!cancelled) {
|
|
39
|
+
setLossReasons([]);
|
|
40
|
+
setDictionaryLoadFailed(true);
|
|
41
|
+
}
|
|
42
|
+
}).finally(() => {
|
|
43
|
+
if (!cancelled) setIsLoadingReasons(false);
|
|
35
44
|
});
|
|
36
45
|
return () => {
|
|
37
46
|
cancelled = true;
|
|
@@ -43,12 +52,31 @@ function ConfirmDealLostDialog({
|
|
|
43
52
|
setLossNotes("");
|
|
44
53
|
setReasonListOpen(false);
|
|
45
54
|
setError("");
|
|
55
|
+
setDictionaryLoadFailed(false);
|
|
56
|
+
setIsConfirming(false);
|
|
46
57
|
}, [open]);
|
|
47
58
|
const selectedLossReason = React.useMemo(
|
|
48
59
|
() => lossReasons.find((reason) => reason.id === lossReasonId) ?? null,
|
|
49
60
|
[lossReasonId, lossReasons]
|
|
50
61
|
);
|
|
62
|
+
const hasLossReasons = lossReasons.length > 0;
|
|
63
|
+
const reasonUnavailable = !isLoadingReasons && !hasLossReasons;
|
|
64
|
+
const unavailableReasonText = dictionaryLoadFailed ? t("customers.deals.detail.lost.reasonLoadError", "Loss reasons could not be loaded.") : t("customers.deals.detail.lost.reasonUnavailable", "No loss reasons are configured.");
|
|
65
|
+
const reasonHelpText = React.useMemo(() => {
|
|
66
|
+
if (selectedLossReason?.description) return selectedLossReason.description;
|
|
67
|
+
if (isLoadingReasons) {
|
|
68
|
+
return t("customers.deals.detail.lost.reasonLoading", "Loading loss reasons...");
|
|
69
|
+
}
|
|
70
|
+
if (reasonUnavailable) {
|
|
71
|
+
return unavailableReasonText;
|
|
72
|
+
}
|
|
73
|
+
return t("customers.deals.detail.lost.reasonHelp", "Choose the closest reason from the dictionary.");
|
|
74
|
+
}, [isLoadingReasons, reasonUnavailable, selectedLossReason?.description, t, unavailableReasonText]);
|
|
51
75
|
const handleConfirm = React.useCallback(async () => {
|
|
76
|
+
if (isLoadingReasons || reasonUnavailable) {
|
|
77
|
+
setError(unavailableReasonText);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
52
80
|
if (!lossReasonId) {
|
|
53
81
|
setError(t("customers.deals.detail.lost.reasonRequired", "Please select a loss reason"));
|
|
54
82
|
return;
|
|
@@ -62,10 +90,11 @@ function ConfirmDealLostDialog({
|
|
|
62
90
|
} finally {
|
|
63
91
|
setIsConfirming(false);
|
|
64
92
|
}
|
|
65
|
-
}, [lossNotes, lossReasonId, onConfirm, t]);
|
|
93
|
+
}, [isLoadingReasons, lossNotes, lossReasonId, onConfirm, reasonUnavailable, t, unavailableReasonText]);
|
|
94
|
+
const confirmDisabled = isConfirming || isLoadingReasons || reasonUnavailable || !lossReasonId;
|
|
66
95
|
const handleKeyDown = useDialogKeyHandler({
|
|
67
96
|
onConfirm: () => void handleConfirm(),
|
|
68
|
-
disabled:
|
|
97
|
+
disabled: confirmDisabled
|
|
69
98
|
});
|
|
70
99
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (nextOpen) => {
|
|
71
100
|
if (!nextOpen) onClose();
|
|
@@ -101,14 +130,14 @@ function ConfirmDealLostDialog({
|
|
|
101
130
|
className: "h-auto flex w-full items-center justify-between rounded-md border-2 border-foreground bg-background px-4 py-3 text-left",
|
|
102
131
|
children: [
|
|
103
132
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
104
|
-
/* @__PURE__ */ jsx("div", { className: "truncate text-base font-semibold text-foreground", children: selectedLossReason?.label ?? t("customers.deals.detail.lost.reasonPlaceholder", "Select loss reason") }),
|
|
105
|
-
/* @__PURE__ */ jsx("div", { className: "truncate text-sm text-muted-foreground", children:
|
|
133
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-base font-semibold text-foreground", children: selectedLossReason?.label ?? (isLoadingReasons ? t("customers.deals.detail.lost.reasonLoadingShort", "Loading...") : t("customers.deals.detail.lost.reasonPlaceholder", "Select loss reason")) }),
|
|
134
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm text-muted-foreground", children: reasonHelpText })
|
|
106
135
|
] }),
|
|
107
136
|
/* @__PURE__ */ jsx(ChevronDown, { className: "ml-3 size-4 shrink-0 text-muted-foreground" })
|
|
108
137
|
]
|
|
109
138
|
}
|
|
110
139
|
),
|
|
111
|
-
reasonListOpen ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border border-border/80 bg-background", children: lossReasons.map((reason, index) => {
|
|
140
|
+
reasonListOpen ? /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-md border border-border/80 bg-background", children: hasLossReasons ? lossReasons.map((reason, index) => {
|
|
112
141
|
const isSelected = reason.id === lossReasonId;
|
|
113
142
|
return /* @__PURE__ */ jsxs(
|
|
114
143
|
Button,
|
|
@@ -131,8 +160,9 @@ function ConfirmDealLostDialog({
|
|
|
131
160
|
},
|
|
132
161
|
reason.id
|
|
133
162
|
);
|
|
134
|
-
}) }) : null
|
|
163
|
+
}) : /* @__PURE__ */ jsx("div", { className: "px-4 py-3 text-sm text-muted-foreground", children: unavailableReasonText }) }) : null
|
|
135
164
|
] }),
|
|
165
|
+
reasonUnavailable ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: unavailableReasonText }) : null,
|
|
136
166
|
error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: error }) : null
|
|
137
167
|
] }),
|
|
138
168
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
@@ -151,9 +181,18 @@ function ConfirmDealLostDialog({
|
|
|
151
181
|
] }),
|
|
152
182
|
/* @__PURE__ */ jsxs(DialogFooter, { className: "border-t border-border/70 px-7 py-4 sm:justify-end", children: [
|
|
153
183
|
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onClose, children: t("customers.deals.detail.lost.cancel", "Cancel") }),
|
|
154
|
-
/* @__PURE__ */ jsx(
|
|
155
|
-
|
|
156
|
-
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
Button,
|
|
186
|
+
{
|
|
187
|
+
type: "button",
|
|
188
|
+
variant: "destructive",
|
|
189
|
+
disabled: confirmDisabled,
|
|
190
|
+
onClick: () => {
|
|
191
|
+
void handleConfirm();
|
|
192
|
+
},
|
|
193
|
+
children: t("customers.deals.detail.lost.confirm", "Mark as Lost")
|
|
194
|
+
}
|
|
195
|
+
)
|
|
157
196
|
] })
|
|
158
197
|
] }) }) });
|
|
159
198
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/detail/ConfirmDealLostDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertTriangle, Check, ChevronDown } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { loadDictionaryEntriesByKey } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\n\ntype LossReasonOption = {\n id: string\n value: string\n label: string\n description?: string | null\n}\n\ntype ConfirmDealLostDialogProps = {\n open: boolean\n dealTitle: string\n dealValue?: string | null\n companyName?: string | null\n onClose: () => void\n onConfirm: (input: { lossReasonId: string; lossNotes?: string }) => void | Promise<void>\n}\n\nexport function ConfirmDealLostDialog({\n open,\n dealTitle,\n dealValue,\n companyName,\n onClose,\n onConfirm,\n}: ConfirmDealLostDialogProps) {\n const t = useT()\n const [lossReasonId, setLossReasonId] = React.useState('')\n const [lossNotes, setLossNotes] = React.useState('')\n const [lossReasons, setLossReasons] = React.useState<LossReasonOption[]>([])\n const [reasonListOpen, setReasonListOpen] = React.useState(false)\n const [error, setError] = React.useState('')\n const [isConfirming, setIsConfirming] = React.useState(false)\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n loadDictionaryEntriesByKey('sales.deal_loss_reason')\n .then((items) => {\n if (!cancelled) setLossReasons(items)\n })\n .catch((loadError) => {\n console.error('customers.deals.detail.lossReasons failed', loadError)\n if (!cancelled) setLossReasons([])\n })\n return () => {\n cancelled = true\n }\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n setLossReasonId('')\n setLossNotes('')\n setReasonListOpen(false)\n setError('')\n }, [open])\n\n const selectedLossReason = React.useMemo(\n () => lossReasons.find((reason) => reason.id === lossReasonId) ?? null,\n [lossReasonId, lossReasons],\n )\n\n const handleConfirm = React.useCallback(async () => {\n if (!lossReasonId) {\n setError(t('customers.deals.detail.lost.reasonRequired', 'Please select a loss reason'))\n return\n }\n setIsConfirming(true)\n try {\n await onConfirm({\n lossReasonId,\n lossNotes: lossNotes.trim() || undefined,\n })\n } finally {\n setIsConfirming(false)\n }\n }, [lossNotes, lossReasonId, onConfirm, t])\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm: () => void handleConfirm(),\n disabled: isConfirming,\n })\n\n return (\n <Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>\n <DialogContent className=\"overflow-hidden p-0 sm:max-w-[560px]\" onKeyDown={handleKeyDown}>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n <DialogHeader className=\"border-b border-border/70 px-7 py-5\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-md bg-destructive/10 text-destructive\">\n <AlertTriangle className=\"size-5\" />\n </div>\n <div className=\"min-w-0\">\n <DialogTitle className=\"text-lg font-bold leading-none tracking-tight text-foreground\">\n {t('customers.deals.detail.lost.title', 'Mark deal as Lost?')}\n </DialogTitle>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {dealTitle}\n {dealValue ? ` \u00B7 ${dealValue}` : ''}\n {companyName ? ` \u00B7 ${companyName}` : ''}\n </p>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"space-y-6 px-7 py-6\">\n <Alert variant=\"warning\" className=\"rounded-md\">\n <AlertTitle>\n {t('customers.deals.detail.lost.warningTitle', 'This action closes the deal')}\n </AlertTitle>\n <AlertDescription>\n {t('customers.deals.detail.lost.warning', \"This action sets the stage to 'Lost' and cannot be undone without 'sales.reopen' permission\")}\n </AlertDescription>\n </Alert>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.lost.reasonLabel', 'Loss reason')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <div className=\"space-y-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setReasonListOpen((current) => !current)}\n className=\"h-auto flex w-full items-center justify-between rounded-md border-2 border-foreground bg-background px-4 py-3 text-left\"\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-base font-semibold text-foreground\">\n {selectedLossReason?.label ?? t('customers.deals.detail.lost.reasonPlaceholder', 'Select loss reason')}\n </div>\n <div className=\"truncate text-sm text-muted-foreground\">\n {selectedLossReason?.description ?? t('customers.deals.detail.lost.reasonHelp', 'Choose the closest reason from the dictionary.')}\n </div>\n </div>\n <ChevronDown className=\"ml-3 size-4 shrink-0 text-muted-foreground\" />\n </Button>\n\n {reasonListOpen ? (\n <div className=\"overflow-hidden rounded-md border border-border/80 bg-background\">\n {lossReasons.map((reason, index) => {\n const isSelected = reason.id === lossReasonId\n return (\n <Button\n key={reason.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setLossReasonId(reason.id)\n setReasonListOpen(false)\n setError('')\n }}\n className={`h-auto flex w-full items-center justify-between rounded-none px-4 py-3 text-left ${\n index < lossReasons.length - 1 ? 'border-b border-border/60' : ''\n } ${isSelected ? 'bg-muted/60' : 'hover:bg-accent/50'}`}\n >\n <div className=\"min-w-0\">\n <div className=\"text-base font-semibold text-foreground\">{reason.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {reason.description ?? t('customers.deals.detail.lost.reasonFallbackDescription', 'No description available.')}\n </div>\n </div>\n {isSelected ? (\n <span className=\"ml-3 flex size-6 shrink-0 items-center justify-center rounded-full bg-foreground text-background\">\n <Check className=\"size-3.5\" />\n </span>\n ) : null}\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n {error ? <p className=\"text-xs text-destructive\">{error}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.lost.notesLabel', 'Loss notes (optional)')}\n </label>\n <Textarea\n value={lossNotes}\n onChange={(event) => setLossNotes(event.target.value)}\n placeholder={t('customers.deals.detail.lost.notesPlaceholder', 'Additional context about the loss...')}\n rows={4}\n className=\"min-h-[88px] rounded-md border-border/80 px-4 py-3 shadow-none\"\n />\n </div>\n </div>\n\n <DialogFooter className=\"border-t border-border/70 px-7 py-4 sm:justify-end\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.deals.detail.lost.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"destructive\" onClick={() => { void handleConfirm() }}>\n {t('customers.deals.detail.lost.confirm', 'Mark as Lost')}\n </Button>\n </DialogFooter>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { AlertTriangle, Check, ChevronDown } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { loadDictionaryEntriesByKey } from '@open-mercato/core/modules/dictionaries/lib/clientEntries'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\n\ntype LossReasonOption = {\n id: string\n value: string\n label: string\n description?: string | null\n}\n\ntype ConfirmDealLostDialogProps = {\n open: boolean\n dealTitle: string\n dealValue?: string | null\n companyName?: string | null\n onClose: () => void\n onConfirm: (input: { lossReasonId: string; lossNotes?: string }) => void | Promise<void>\n}\n\nexport function ConfirmDealLostDialog({\n open,\n dealTitle,\n dealValue,\n companyName,\n onClose,\n onConfirm,\n}: ConfirmDealLostDialogProps) {\n const t = useT()\n const [lossReasonId, setLossReasonId] = React.useState('')\n const [lossNotes, setLossNotes] = React.useState('')\n const [lossReasons, setLossReasons] = React.useState<LossReasonOption[]>([])\n const [reasonListOpen, setReasonListOpen] = React.useState(false)\n const [error, setError] = React.useState('')\n const [dictionaryLoadFailed, setDictionaryLoadFailed] = React.useState(false)\n const [isLoadingReasons, setIsLoadingReasons] = React.useState(false)\n const [isConfirming, setIsConfirming] = React.useState(false)\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setIsLoadingReasons(true)\n setDictionaryLoadFailed(false)\n loadDictionaryEntriesByKey('sales.deal_loss_reason')\n .then((items) => {\n if (!cancelled) setLossReasons(items)\n })\n .catch((loadError) => {\n console.error('customers.deals.detail.lossReasons failed', loadError)\n if (!cancelled) {\n setLossReasons([])\n setDictionaryLoadFailed(true)\n }\n })\n .finally(() => {\n if (!cancelled) setIsLoadingReasons(false)\n })\n return () => {\n cancelled = true\n }\n }, [open])\n\n React.useEffect(() => {\n if (!open) return\n setLossReasonId('')\n setLossNotes('')\n setReasonListOpen(false)\n setError('')\n setDictionaryLoadFailed(false)\n setIsConfirming(false)\n }, [open])\n\n const selectedLossReason = React.useMemo(\n () => lossReasons.find((reason) => reason.id === lossReasonId) ?? null,\n [lossReasonId, lossReasons],\n )\n const hasLossReasons = lossReasons.length > 0\n const reasonUnavailable = !isLoadingReasons && !hasLossReasons\n const unavailableReasonText = dictionaryLoadFailed\n ? t('customers.deals.detail.lost.reasonLoadError', 'Loss reasons could not be loaded.')\n : t('customers.deals.detail.lost.reasonUnavailable', 'No loss reasons are configured.')\n const reasonHelpText = React.useMemo(() => {\n if (selectedLossReason?.description) return selectedLossReason.description\n if (isLoadingReasons) {\n return t('customers.deals.detail.lost.reasonLoading', 'Loading loss reasons...')\n }\n if (reasonUnavailable) {\n return unavailableReasonText\n }\n return t('customers.deals.detail.lost.reasonHelp', 'Choose the closest reason from the dictionary.')\n }, [isLoadingReasons, reasonUnavailable, selectedLossReason?.description, t, unavailableReasonText])\n\n const handleConfirm = React.useCallback(async () => {\n if (isLoadingReasons || reasonUnavailable) {\n setError(unavailableReasonText)\n return\n }\n if (!lossReasonId) {\n setError(t('customers.deals.detail.lost.reasonRequired', 'Please select a loss reason'))\n return\n }\n setIsConfirming(true)\n try {\n await onConfirm({\n lossReasonId,\n lossNotes: lossNotes.trim() || undefined,\n })\n } finally {\n setIsConfirming(false)\n }\n }, [isLoadingReasons, lossNotes, lossReasonId, onConfirm, reasonUnavailable, t, unavailableReasonText])\n\n const confirmDisabled = isConfirming || isLoadingReasons || reasonUnavailable || !lossReasonId\n\n const handleKeyDown = useDialogKeyHandler({\n onConfirm: () => void handleConfirm(),\n disabled: confirmDisabled,\n })\n\n return (\n <Dialog open={open} onOpenChange={(nextOpen) => { if (!nextOpen) onClose() }}>\n <DialogContent className=\"overflow-hidden p-0 sm:max-w-[560px]\" onKeyDown={handleKeyDown}>\n <div className=\"overflow-hidden rounded-lg bg-card\">\n <DialogHeader className=\"border-b border-border/70 px-7 py-5\">\n <div className=\"flex items-start gap-4\">\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-md bg-destructive/10 text-destructive\">\n <AlertTriangle className=\"size-5\" />\n </div>\n <div className=\"min-w-0\">\n <DialogTitle className=\"text-lg font-bold leading-none tracking-tight text-foreground\">\n {t('customers.deals.detail.lost.title', 'Mark deal as Lost?')}\n </DialogTitle>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {dealTitle}\n {dealValue ? ` \u00B7 ${dealValue}` : ''}\n {companyName ? ` \u00B7 ${companyName}` : ''}\n </p>\n </div>\n </div>\n </DialogHeader>\n\n <div className=\"space-y-6 px-7 py-6\">\n <Alert variant=\"warning\" className=\"rounded-md\">\n <AlertTitle>\n {t('customers.deals.detail.lost.warningTitle', 'This action closes the deal')}\n </AlertTitle>\n <AlertDescription>\n {t('customers.deals.detail.lost.warning', \"This action sets the stage to 'Lost' and cannot be undone without 'sales.reopen' permission\")}\n </AlertDescription>\n </Alert>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.lost.reasonLabel', 'Loss reason')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <div className=\"space-y-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => setReasonListOpen((current) => !current)}\n className=\"h-auto flex w-full items-center justify-between rounded-md border-2 border-foreground bg-background px-4 py-3 text-left\"\n >\n <div className=\"min-w-0\">\n <div className=\"truncate text-base font-semibold text-foreground\">\n {selectedLossReason?.label\n ?? (isLoadingReasons\n ? t('customers.deals.detail.lost.reasonLoadingShort', 'Loading...')\n : t('customers.deals.detail.lost.reasonPlaceholder', 'Select loss reason'))}\n </div>\n <div className=\"truncate text-sm text-muted-foreground\">\n {reasonHelpText}\n </div>\n </div>\n <ChevronDown className=\"ml-3 size-4 shrink-0 text-muted-foreground\" />\n </Button>\n\n {reasonListOpen ? (\n <div className=\"overflow-hidden rounded-md border border-border/80 bg-background\">\n {hasLossReasons ? lossReasons.map((reason, index) => {\n const isSelected = reason.id === lossReasonId\n return (\n <Button\n key={reason.id}\n type=\"button\"\n variant=\"ghost\"\n onClick={() => {\n setLossReasonId(reason.id)\n setReasonListOpen(false)\n setError('')\n }}\n className={`h-auto flex w-full items-center justify-between rounded-none px-4 py-3 text-left ${\n index < lossReasons.length - 1 ? 'border-b border-border/60' : ''\n } ${isSelected ? 'bg-muted/60' : 'hover:bg-accent/50'}`}\n >\n <div className=\"min-w-0\">\n <div className=\"text-base font-semibold text-foreground\">{reason.label}</div>\n <div className=\"text-sm text-muted-foreground\">\n {reason.description ?? t('customers.deals.detail.lost.reasonFallbackDescription', 'No description available.')}\n </div>\n </div>\n {isSelected ? (\n <span className=\"ml-3 flex size-6 shrink-0 items-center justify-center rounded-full bg-foreground text-background\">\n <Check className=\"size-3.5\" />\n </span>\n ) : null}\n </Button>\n )\n }) : (\n <div className=\"px-4 py-3 text-sm text-muted-foreground\">\n {unavailableReasonText}\n </div>\n )}\n </div>\n ) : null}\n </div>\n {reasonUnavailable ? (\n <p className=\"text-xs text-destructive\">\n {unavailableReasonText}\n </p>\n ) : null}\n {error ? <p className=\"text-xs text-destructive\">{error}</p> : null}\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-semibold text-foreground\">\n {t('customers.deals.detail.lost.notesLabel', 'Loss notes (optional)')}\n </label>\n <Textarea\n value={lossNotes}\n onChange={(event) => setLossNotes(event.target.value)}\n placeholder={t('customers.deals.detail.lost.notesPlaceholder', 'Additional context about the loss...')}\n rows={4}\n className=\"min-h-[88px] rounded-md border-border/80 px-4 py-3 shadow-none\"\n />\n </div>\n </div>\n\n <DialogFooter className=\"border-t border-border/70 px-7 py-4 sm:justify-end\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('customers.deals.detail.lost.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n variant=\"destructive\"\n disabled={confirmDisabled}\n onClick={() => { void handleConfirm() }}\n >\n {t('customers.deals.detail.lost.confirm', 'Mark as Lost')}\n </Button>\n </DialogFooter>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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": "AACA,SAAS,8BAA8B;AAEvC,SAAS,0BAA0B,mCAAmC;
|
|
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": "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",
|
|
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": "AACA,SAAS,YAAY,uBAAuB;AAC5C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAmBP,MAAM,cAAsE;AAAA,EAC1E,gBAAgB;AAAA,IACd,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,qBAAqB;AAAA,IACnB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,mBAAmB;AAAA,IACjB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,kBAAkB;AAAA,IAChB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,qBAAqB;AAAA,IACnB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AAAA,EACA,mBAAmB;AAAA,IACjB,KAAK;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,IACV,aAAa;AAAA,IACb,cAAc;AAAA,IACd,eAAe;AAAA,EACjB;AACF;AAEO,SAAS,6BAA6B,MAAsD;AACjG,SAAO,YAAY,IAAI;AACzB;AAEA,eAAsB,sBAAsB,QAKpB;AACtB,QAAM,EAAE,IAAI,UAAU,gBAAgB,KAAK,IAAI;AAC/C,QAAM,MAAM,6BAA6B,IAAI;AAC7C,MAAI,aAAa,MAAM,GAAG,QAAQ,YAAY;AAAA,IAC5C;AAAA,IACA;AAAA,IACA,KAAK,IAAI;AAAA,IACT,WAAW;AAAA,EACb,CAAC;AACD,MAAI,CAAC,YAAY;AACf,iBAAa,GAAG,OAAO,YAAY;AAAA,MACjC;AAAA,MACA;AAAA,MACA,KAAK,IAAI;AAAA,MACT,MAAM,IAAI;AAAA,MACV,aAAa,IAAI;AAAA,MACjB,UAAU;AAAA,MACV,UAAU;AAAA,MACV,mBAAmB;AAAA,MACnB,WAAW,oBAAI,KAAK;AAAA,MACpB,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,OAAG,QAAQ,UAAU;AACrB,UAAM,GAAG,MAAM;AAAA,EACjB;AACA,SAAO;AACT;AAEA,eAAsB,4BACpB,IACA,SACA,OACwB;AACxB,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,MAAM,GAAG,QAAQ,iBAAiB,EAAE,IAAI,SAAS,UAAU,MAAM,SAAS,CAAC;AACzF,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,OAAO,KAAK,KAAK;AAChC;
|
|
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.
|
|
3
|
+
"version": "0.6.6-develop.6402.1.080aab8ec9",
|
|
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.
|
|
252
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
253
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
251
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6402.1.080aab8ec9",
|
|
252
|
+
"@open-mercato/shared": "0.6.6-develop.6402.1.080aab8ec9",
|
|
253
|
+
"@open-mercato/ui": "0.6.6-develop.6402.1.080aab8ec9",
|
|
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.
|
|
259
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
260
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
258
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6402.1.080aab8ec9",
|
|
259
|
+
"@open-mercato/shared": "0.6.6-develop.6402.1.080aab8ec9",
|
|
260
|
+
"@open-mercato/ui": "0.6.6-develop.6402.1.080aab8ec9",
|
|
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
|
-
|
|
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)
|
|
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:
|
|
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
|
|
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
|
-
{
|
|
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
|
|
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?",
|
package/src/modules/sales/cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ModuleCli } from '@open-mercato/shared/modules/registry'
|
|
2
2
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
3
3
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
-
import { seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from './lib/dictionaries'
|
|
4
|
+
import { backfillDealLossReasonDictionary, seedSalesAdjustmentKinds, seedSalesStatusDictionaries } from './lib/dictionaries'
|
|
5
5
|
import { seedSalesTaxRates } from './lib/seeds'
|
|
6
6
|
import { ensureExamplePaymentMethods, ensureExampleShippingMethods } from './seed/examples-data'
|
|
7
7
|
import { seedSalesExamples } from './seed/examples'
|
|
@@ -103,6 +103,43 @@ const seedAdjustmentKindsCommand: ModuleCli = {
|
|
|
103
103
|
},
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
const backfillDealLossReasonsCommand: ModuleCli = {
|
|
107
|
+
command: 'backfill-deal-loss-reasons',
|
|
108
|
+
async run(rest) {
|
|
109
|
+
const args = parseArgs(rest)
|
|
110
|
+
const tenantId = String(args.tenantId ?? args.tenant ?? '')
|
|
111
|
+
const organizationId = String(args.organizationId ?? args.org ?? args.orgId ?? '')
|
|
112
|
+
if (!tenantId || !organizationId) {
|
|
113
|
+
console.error('Usage: mercato sales backfill-deal-loss-reasons --tenant <tenantId> --org <organizationId>')
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
const container = await createRequestContainer()
|
|
117
|
+
try {
|
|
118
|
+
const em = container.resolve<EntityManager>('em')
|
|
119
|
+
const result = await em.transactional(async (tem) => {
|
|
120
|
+
const backfillResult = await backfillDealLossReasonDictionary(tem, { tenantId, organizationId })
|
|
121
|
+
await tem.flush()
|
|
122
|
+
return backfillResult
|
|
123
|
+
})
|
|
124
|
+
console.log(
|
|
125
|
+
[
|
|
126
|
+
`Deal loss reasons dictionary ready for organization ${organizationId}.`,
|
|
127
|
+
`dictionaryId=${result.dictionaryId}`,
|
|
128
|
+
`createdDictionary=${String(result.createdDictionary)}`,
|
|
129
|
+
`copiedLegacyEntries=${String(result.copiedLegacyEntries)}`,
|
|
130
|
+
`seededDefaultEntries=${String(result.seededDefaultEntries)}`,
|
|
131
|
+
`existingEntries=${String(result.existingEntries)}`,
|
|
132
|
+
].join(' '),
|
|
133
|
+
)
|
|
134
|
+
} finally {
|
|
135
|
+
const disposable = container as unknown as { dispose?: () => Promise<void> }
|
|
136
|
+
if (typeof disposable.dispose === 'function') {
|
|
137
|
+
await disposable.dispose()
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
}
|
|
142
|
+
|
|
106
143
|
const seedShippingMethodsCommand: ModuleCli = {
|
|
107
144
|
command: 'seed-shipping-methods',
|
|
108
145
|
async run(rest) {
|
|
@@ -189,6 +226,7 @@ export default [
|
|
|
189
226
|
seedTaxRatesCommand,
|
|
190
227
|
seedStatusesCommand,
|
|
191
228
|
seedAdjustmentKindsCommand,
|
|
229
|
+
backfillDealLossReasonsCommand,
|
|
192
230
|
seedShippingMethodsCommand,
|
|
193
231
|
seedPaymentMethodsCommand,
|
|
194
232
|
seedExamplesCommand,
|
|
@@ -133,6 +133,14 @@ type SalesDictionarySeed = {
|
|
|
133
133
|
|
|
134
134
|
type SeedScope = { tenantId: string; organizationId: string }
|
|
135
135
|
|
|
136
|
+
export type BackfillDealLossReasonDictionaryResult = {
|
|
137
|
+
dictionaryId: string
|
|
138
|
+
createdDictionary: boolean
|
|
139
|
+
copiedLegacyEntries: number
|
|
140
|
+
seededDefaultEntries: number
|
|
141
|
+
existingEntries: number
|
|
142
|
+
}
|
|
143
|
+
|
|
136
144
|
const ORDER_STATUS_DEFAULTS: SalesDictionarySeed[] = [
|
|
137
145
|
{ value: 'draft', label: 'Draft', color: '#94a3b8', icon: 'lucide:file-pen-line' },
|
|
138
146
|
{ value: 'pending_approval', label: 'Pending Approval', color: '#f59e0b', icon: 'lucide:hourglass' },
|
|
@@ -262,6 +270,88 @@ const DEAL_LOSS_REASON_DEFAULTS: SalesDictionarySeed[] = [
|
|
|
262
270
|
{ value: 'other', label: 'Other', color: '#71717a', icon: 'lucide:minus-circle' },
|
|
263
271
|
]
|
|
264
272
|
|
|
273
|
+
export async function backfillDealLossReasonDictionary(
|
|
274
|
+
em: EntityManager,
|
|
275
|
+
scope: SeedScope
|
|
276
|
+
): Promise<BackfillDealLossReasonDictionaryResult> {
|
|
277
|
+
const definition = getSalesDictionaryDefinition('deal-loss-reasons')
|
|
278
|
+
const existingDictionary = await em.findOne(Dictionary, {
|
|
279
|
+
tenantId: scope.tenantId,
|
|
280
|
+
organizationId: scope.organizationId,
|
|
281
|
+
key: definition.key,
|
|
282
|
+
deletedAt: null,
|
|
283
|
+
})
|
|
284
|
+
const dictionary = existingDictionary ?? await ensureSalesDictionary({
|
|
285
|
+
em,
|
|
286
|
+
tenantId: scope.tenantId,
|
|
287
|
+
organizationId: scope.organizationId,
|
|
288
|
+
kind: 'deal-loss-reasons',
|
|
289
|
+
})
|
|
290
|
+
const targetEntries = await em.find(DictionaryEntry, {
|
|
291
|
+
dictionary,
|
|
292
|
+
tenantId: scope.tenantId,
|
|
293
|
+
organizationId: scope.organizationId,
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
if (targetEntries.length > 0) {
|
|
297
|
+
return {
|
|
298
|
+
dictionaryId: dictionary.id,
|
|
299
|
+
createdDictionary: !existingDictionary,
|
|
300
|
+
copiedLegacyEntries: 0,
|
|
301
|
+
seededDefaultEntries: 0,
|
|
302
|
+
existingEntries: targetEntries.length,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const legacyDictionary = await em.findOne(Dictionary, {
|
|
307
|
+
tenantId: scope.tenantId,
|
|
308
|
+
organizationId: scope.organizationId,
|
|
309
|
+
key: 'customer_lost_reason',
|
|
310
|
+
deletedAt: null,
|
|
311
|
+
})
|
|
312
|
+
const legacyEntries = legacyDictionary
|
|
313
|
+
? await em.find(
|
|
314
|
+
DictionaryEntry,
|
|
315
|
+
{
|
|
316
|
+
dictionary: legacyDictionary,
|
|
317
|
+
tenantId: scope.tenantId,
|
|
318
|
+
organizationId: scope.organizationId,
|
|
319
|
+
},
|
|
320
|
+
{ orderBy: { position: 'asc', label: 'asc' } },
|
|
321
|
+
)
|
|
322
|
+
: []
|
|
323
|
+
|
|
324
|
+
let copiedLegacyEntries = 0
|
|
325
|
+
let seededDefaultEntries = 0
|
|
326
|
+
const processedValues = new Set<string>()
|
|
327
|
+
const seeds = legacyEntries.length
|
|
328
|
+
? legacyEntries.map((entry) => ({
|
|
329
|
+
value: entry.value,
|
|
330
|
+
label: entry.label,
|
|
331
|
+
color: entry.color,
|
|
332
|
+
icon: entry.icon,
|
|
333
|
+
}))
|
|
334
|
+
: DEAL_LOSS_REASON_DEFAULTS
|
|
335
|
+
|
|
336
|
+
for (const seed of seeds) {
|
|
337
|
+
const normalizedValue = normalizeDictionaryValue(seed.value ?? '')
|
|
338
|
+
if (!normalizedValue || processedValues.has(normalizedValue)) continue
|
|
339
|
+
const entry = await ensureSalesDictionaryEntry(em, scope, 'deal-loss-reasons', seed)
|
|
340
|
+
if (!entry) continue
|
|
341
|
+
processedValues.add(normalizedValue)
|
|
342
|
+
if (legacyEntries.length) copiedLegacyEntries += 1
|
|
343
|
+
else seededDefaultEntries += 1
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
dictionaryId: dictionary.id,
|
|
348
|
+
createdDictionary: !existingDictionary,
|
|
349
|
+
copiedLegacyEntries,
|
|
350
|
+
seededDefaultEntries,
|
|
351
|
+
existingEntries: 0,
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
265
355
|
export async function seedSalesStatusDictionaries(
|
|
266
356
|
em: EntityManager,
|
|
267
357
|
scope: SeedScope
|