@open-mercato/core 0.6.2-develop.3372.1.de9a169973 → 0.6.2-develop.3380.1.bb14509041
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/sales/api/order-adjustments/route.js +2 -2
- package/dist/modules/sales/api/order-adjustments/route.js.map +2 -2
- package/dist/modules/sales/api/quote-adjustments/route.js +2 -2
- package/dist/modules/sales/api/quote-adjustments/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +3 -3
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/data/validators.js +61 -6
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +6 -3
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +84 -62
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/notifications/i18n/de.json +6 -1
- package/src/modules/notifications/i18n/en.json +6 -1
- package/src/modules/notifications/i18n/es.json +6 -1
- package/src/modules/notifications/i18n/pl.json +6 -1
- package/src/modules/sales/api/order-adjustments/route.ts +2 -2
- package/src/modules/sales/api/quote-adjustments/route.ts +2 -2
- package/src/modules/sales/commands/documents.ts +3 -3
- package/src/modules/sales/data/validators.ts +78 -9
- package/src/modules/sales/lib/calculations.ts +16 -3
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +87 -69
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { ShoppingCart, ExternalLink,
|
|
4
|
+
import { ShoppingCart, ExternalLink, Loader2, X } from "lucide-react";
|
|
5
5
|
import { useRouter } from "next/navigation";
|
|
6
6
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
7
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
7
8
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
8
9
|
import { formatRelativeTime } from "@open-mercato/shared/lib/time";
|
|
9
10
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -44,12 +45,13 @@ function SalesOrderCreatedRenderer({
|
|
|
44
45
|
setExecuting(false);
|
|
45
46
|
}
|
|
46
47
|
};
|
|
48
|
+
const timeAgo = formatRelativeTime(notification.createdAt, { translate: t }) ?? "";
|
|
47
49
|
return /* @__PURE__ */ jsxs(
|
|
48
50
|
"div",
|
|
49
51
|
{
|
|
50
52
|
className: cn(
|
|
51
|
-
"group relative
|
|
52
|
-
isUnread && "bg-
|
|
53
|
+
"group relative flex gap-4 items-start rounded-xl p-3 transition-colors hover:bg-muted/40 cursor-pointer",
|
|
54
|
+
isUnread && "bg-muted/20"
|
|
53
55
|
),
|
|
54
56
|
onClick: handleView,
|
|
55
57
|
onKeyDown: (e) => {
|
|
@@ -61,66 +63,86 @@ function SalesOrderCreatedRenderer({
|
|
|
61
63
|
role: "button",
|
|
62
64
|
tabIndex: 0,
|
|
63
65
|
children: [
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { className: "relative shrink-0 flex size-10 items-center justify-center rounded-full bg-status-info-bg", children: [
|
|
67
|
+
/* @__PURE__ */ jsx(ShoppingCart, { className: "size-5 text-status-info-icon", "aria-hidden": "true" }),
|
|
68
|
+
isUnread ? /* @__PURE__ */ jsx(
|
|
69
|
+
"span",
|
|
70
|
+
{
|
|
71
|
+
className: "absolute -right-1 -top-1 size-3 rounded-full bg-accent-indigo ring-2 ring-background",
|
|
72
|
+
"aria-hidden": "true"
|
|
73
|
+
}
|
|
74
|
+
) : null
|
|
75
|
+
] }),
|
|
76
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
77
|
+
/* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium leading-5 tracking-tight text-foreground", children: notification.title }),
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs leading-4 text-muted-foreground", children: [
|
|
79
|
+
timeAgo ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
80
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: timeAgo }),
|
|
81
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "mx-1 text-text-disabled", children: "\xB7" })
|
|
82
|
+
] }) : null,
|
|
83
|
+
orderNumber ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
84
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center rounded bg-muted px-1.5 py-0.5 font-mono text-overline text-foreground", children: [
|
|
85
|
+
"#",
|
|
86
|
+
orderNumber
|
|
75
87
|
] }),
|
|
76
|
-
/* @__PURE__ */
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
children: t("notifications.actions.dismiss", "Dismiss")
|
|
119
|
-
}
|
|
120
|
-
)
|
|
121
|
-
] })
|
|
88
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "mx-1 text-text-disabled", children: "\xB7" })
|
|
89
|
+
] }) : null,
|
|
90
|
+
currentTotal ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap font-medium text-foreground", children: currentTotal }),
|
|
92
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "mx-1 text-text-disabled", children: "\xB7" })
|
|
93
|
+
] }) : null,
|
|
94
|
+
/* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: t("sales.notifications.renderer.assignedToYou", "Assigned to you") })
|
|
95
|
+
] }),
|
|
96
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center gap-2", children: [
|
|
97
|
+
/* @__PURE__ */ jsxs(
|
|
98
|
+
Button,
|
|
99
|
+
{
|
|
100
|
+
type: "button",
|
|
101
|
+
size: "sm",
|
|
102
|
+
className: "h-8 rounded-md px-2.5 bg-accent-indigo text-accent-indigo-foreground hover:bg-accent-indigo/90",
|
|
103
|
+
onClick: (e) => {
|
|
104
|
+
e.stopPropagation();
|
|
105
|
+
handleView();
|
|
106
|
+
},
|
|
107
|
+
disabled: executing || !viewAction && !notification.linkHref,
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "size-3.5", "aria-hidden": "true" }),
|
|
110
|
+
t("sales.notifications.renderer.viewOrder", "View Order"),
|
|
111
|
+
executing ? /* @__PURE__ */ jsx(Loader2, { className: "ml-1 size-3 animate-spin" }) : null
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
Button,
|
|
117
|
+
{
|
|
118
|
+
type: "button",
|
|
119
|
+
variant: "outline",
|
|
120
|
+
size: "sm",
|
|
121
|
+
className: "h-8 rounded-md px-2.5",
|
|
122
|
+
onClick: (e) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
onDismiss();
|
|
125
|
+
},
|
|
126
|
+
children: t("notifications.actions.dismiss", "Dismiss")
|
|
127
|
+
}
|
|
128
|
+
)
|
|
122
129
|
] })
|
|
123
|
-
] })
|
|
130
|
+
] }),
|
|
131
|
+
/* @__PURE__ */ jsx(
|
|
132
|
+
IconButton,
|
|
133
|
+
{
|
|
134
|
+
type: "button",
|
|
135
|
+
variant: "ghost",
|
|
136
|
+
size: "xs",
|
|
137
|
+
className: "absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100",
|
|
138
|
+
onClick: (e) => {
|
|
139
|
+
e.stopPropagation();
|
|
140
|
+
onDismiss();
|
|
141
|
+
},
|
|
142
|
+
"aria-label": t("notifications.actions.dismiss", "Dismiss"),
|
|
143
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3.5" })
|
|
144
|
+
}
|
|
145
|
+
)
|
|
124
146
|
]
|
|
125
147
|
}
|
|
126
148
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ShoppingCart, ExternalLink,
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ShoppingCart, ExternalLink, Loader2, X } from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { formatRelativeTime } from '@open-mercato/shared/lib/time'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { NotificationRendererProps } from '@open-mercato/shared/modules/notifications/types'\nimport { formatMoney } from '../../components/documents/lineItemUtils'\nimport { useSalesDocumentTotals } from './useSalesDocumentTotals'\n\nfunction normalizeTotal(value?: string | null): string | null {\n if (!value) return null\n let trimmed = value.trim()\n if (trimmed.startsWith('(') && trimmed.endsWith(')')) {\n trimmed = trimmed.slice(1, -1).trim()\n }\n return trimmed.length ? trimmed : null\n}\n\nexport function SalesOrderCreatedRenderer({\n notification,\n onAction,\n onDismiss,\n actions = [],\n}: NotificationRendererProps) {\n const t = useT()\n const router = useRouter()\n const [executing, setExecuting] = React.useState(false)\n const isUnread = notification.status === 'unread'\n const orderNumber = notification.bodyVariables?.orderNumber ?? notification.titleVariables?.orderNumber\n const fallbackTotal =\n normalizeTotal(notification.bodyVariables?.totalAmount ?? null) ??\n normalizeTotal(notification.bodyVariables?.total ?? null)\n const { totals } = useSalesDocumentTotals('order', notification.sourceEntityId)\n\n const currentTotal =\n totals && typeof totals.grandTotalGrossAmount === 'number'\n ? formatMoney(totals.grandTotalGrossAmount, totals.currencyCode)\n : fallbackTotal\n\n const viewAction = actions.find((action) => action.id === 'view') ?? actions[0] ?? null\n\n const handleView = async () => {\n if (!viewAction) {\n if (notification.linkHref) router.push(notification.linkHref)\n return\n }\n setExecuting(true)\n try {\n await onAction(viewAction.id)\n } finally {\n setExecuting(false)\n }\n }\n\n const timeAgo = formatRelativeTime(notification.createdAt, { translate: t }) ?? ''\n\n return (\n <div\n className={cn(\n 'group relative flex gap-4 items-start rounded-xl p-3 transition-colors hover:bg-muted/40 cursor-pointer',\n isUnread && 'bg-muted/20',\n )}\n onClick={handleView}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleView()\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {/* Avatar: indigo-tinted circle with shopping-cart icon */}\n <div className=\"relative shrink-0 flex size-10 items-center justify-center rounded-full bg-status-info-bg\">\n <ShoppingCart className=\"size-5 text-status-info-icon\" aria-hidden=\"true\" />\n {isUnread ? (\n <span\n className=\"absolute -right-1 -top-1 size-3 rounded-full bg-accent-indigo ring-2 ring-background\"\n aria-hidden=\"true\"\n />\n ) : null}\n </div>\n\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {/* Title row */}\n <p className=\"truncate text-sm font-medium leading-5 tracking-tight text-foreground\">\n {notification.title}\n </p>\n\n {/* Description row: time \u00B7 order# \u00B7 total \u00B7 assigned \u2014 inline so it wraps naturally per line */}\n <div className=\"text-xs leading-4 text-muted-foreground\">\n {timeAgo ? (\n <>\n <span className=\"whitespace-nowrap\">{timeAgo}</span>\n <span aria-hidden=\"true\" className=\"mx-1 text-text-disabled\">\u00B7</span>\n </>\n ) : null}\n {orderNumber ? (\n <>\n <span className=\"inline-flex items-center rounded bg-muted px-1.5 py-0.5 font-mono text-overline text-foreground\">\n #{orderNumber}\n </span>\n <span aria-hidden=\"true\" className=\"mx-1 text-text-disabled\">\u00B7</span>\n </>\n ) : null}\n {currentTotal ? (\n <>\n <span className=\"whitespace-nowrap font-medium text-foreground\">{currentTotal}</span>\n <span aria-hidden=\"true\" className=\"mx-1 text-text-disabled\">\u00B7</span>\n </>\n ) : null}\n <span className=\"whitespace-nowrap\">{t('sales.notifications.renderer.assignedToYou', 'Assigned to you')}</span>\n </div>\n\n {/* Actions row */}\n <div className=\"mt-2 flex items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-8 rounded-md px-2.5 bg-accent-indigo text-accent-indigo-foreground hover:bg-accent-indigo/90\"\n onClick={(e) => {\n e.stopPropagation()\n handleView()\n }}\n disabled={executing || (!viewAction && !notification.linkHref)}\n >\n <ExternalLink className=\"size-3.5\" aria-hidden=\"true\" />\n {t('sales.notifications.renderer.viewOrder', 'View Order')}\n {executing ? <Loader2 className=\"ml-1 size-3 animate-spin\" /> : null}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 rounded-md px-2.5\"\n onClick={(e) => {\n e.stopPropagation()\n onDismiss()\n }}\n >\n {t('notifications.actions.dismiss', 'Dismiss')}\n </Button>\n </div>\n </div>\n\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n className=\"absolute right-2 top-2 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100\"\n onClick={(e) => {\n e.stopPropagation()\n onDismiss()\n }}\n aria-label={t('notifications.actions.dismiss', 'Dismiss')}\n >\n <X className=\"size-3.5\" />\n </IconButton>\n </div>\n )\n}\n\nexport default SalesOrderCreatedRenderer\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.2-develop.
|
|
3
|
+
"version": "0.6.2-develop.3380.1.bb14509041",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -238,16 +238,16 @@
|
|
|
238
238
|
"zod": "^4.3.6"
|
|
239
239
|
},
|
|
240
240
|
"peerDependencies": {
|
|
241
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
242
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
243
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
241
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3380.1.bb14509041",
|
|
242
|
+
"@open-mercato/shared": "0.6.2-develop.3380.1.bb14509041",
|
|
243
|
+
"@open-mercato/ui": "0.6.2-develop.3380.1.bb14509041",
|
|
244
244
|
"react": "^19.0.0",
|
|
245
245
|
"react-dom": "^19.0.0"
|
|
246
246
|
},
|
|
247
247
|
"devDependencies": {
|
|
248
|
-
"@open-mercato/ai-assistant": "0.6.2-develop.
|
|
249
|
-
"@open-mercato/shared": "0.6.2-develop.
|
|
250
|
-
"@open-mercato/ui": "0.6.2-develop.
|
|
248
|
+
"@open-mercato/ai-assistant": "0.6.2-develop.3380.1.bb14509041",
|
|
249
|
+
"@open-mercato/shared": "0.6.2-develop.3380.1.bb14509041",
|
|
250
|
+
"@open-mercato/ui": "0.6.2-develop.3380.1.bb14509041",
|
|
251
251
|
"@testing-library/dom": "^10.4.1",
|
|
252
252
|
"@testing-library/jest-dom": "^6.9.1",
|
|
253
253
|
"@testing-library/react": "^16.3.1",
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"notifications.actions.markRead": "Als gelesen markieren",
|
|
5
5
|
"notifications.actions.undo": "Rückgängig",
|
|
6
6
|
"notifications.badge.unread": "{count} ungelesene Benachrichtigungen",
|
|
7
|
+
"notifications.close": "Benachrichtigungen schließen",
|
|
7
8
|
"notifications.delivery.email.actionNotice": "Aktionen sind in Open Mercato verfügbar und in dieser E-Mail schreibgeschützt.",
|
|
8
9
|
"notifications.delivery.email.bodyIntro": "Prüfen Sie die Benachrichtigung und führen Sie bei Bedarf Aktionen aus.",
|
|
9
10
|
"notifications.delivery.email.footer": "Open Mercato Benachrichtigungen",
|
|
@@ -17,6 +18,9 @@
|
|
|
17
18
|
"notifications.filters.actionRequired": "Aktion erforderlich",
|
|
18
19
|
"notifications.filters.all": "Alle",
|
|
19
20
|
"notifications.filters.unread": "Ungelesen",
|
|
21
|
+
"notifications.footer.manage": "Benachrichtigungen verwalten",
|
|
22
|
+
"notifications.footer.toNavigate": "zum Navigieren",
|
|
23
|
+
"notifications.footer.useHint": "Verwende",
|
|
20
24
|
"notifications.markAllRead": "Alle als gelesen markieren",
|
|
21
25
|
"notifications.nav.title": "Benachrichtigungen",
|
|
22
26
|
"notifications.settings.core.appUrl": "Anwendungs-URL",
|
|
@@ -46,5 +50,6 @@
|
|
|
46
50
|
"notifications.toast.actionError": "Aktion konnte nicht ausgeführt werden",
|
|
47
51
|
"notifications.toast.actionSuccess": "Aktion erfolgreich ausgeführt",
|
|
48
52
|
"notifications.toast.dismissed": "Benachrichtigung verworfen",
|
|
49
|
-
"notifications.toast.new": "Neue Benachrichtigung"
|
|
53
|
+
"notifications.toast.new": "Neue Benachrichtigung",
|
|
54
|
+
"notifications.unreadSummary": "{count} ungelesen"
|
|
50
55
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"notifications.actions.markRead": "Mark as read",
|
|
5
5
|
"notifications.actions.undo": "Undo",
|
|
6
6
|
"notifications.badge.unread": "{count} unread notifications",
|
|
7
|
+
"notifications.close": "Close notifications",
|
|
7
8
|
"notifications.delivery.email.actionNotice": "Actions are available in Open Mercato and are read-only in this email.",
|
|
8
9
|
"notifications.delivery.email.bodyIntro": "Review the notification details and take any required actions.",
|
|
9
10
|
"notifications.delivery.email.footer": "Open Mercato notifications",
|
|
@@ -17,6 +18,9 @@
|
|
|
17
18
|
"notifications.filters.actionRequired": "Action Required",
|
|
18
19
|
"notifications.filters.all": "All",
|
|
19
20
|
"notifications.filters.unread": "Unread",
|
|
21
|
+
"notifications.footer.manage": "Manage notifications",
|
|
22
|
+
"notifications.footer.toNavigate": "to navigate",
|
|
23
|
+
"notifications.footer.useHint": "Use",
|
|
20
24
|
"notifications.markAllRead": "Mark all read",
|
|
21
25
|
"notifications.nav.title": "Notifications",
|
|
22
26
|
"notifications.settings.core.appUrl": "Application URL",
|
|
@@ -46,5 +50,6 @@
|
|
|
46
50
|
"notifications.toast.actionError": "Failed to execute action",
|
|
47
51
|
"notifications.toast.actionSuccess": "Action completed successfully",
|
|
48
52
|
"notifications.toast.dismissed": "Notification dismissed",
|
|
49
|
-
"notifications.toast.new": "New notification"
|
|
53
|
+
"notifications.toast.new": "New notification",
|
|
54
|
+
"notifications.unreadSummary": "{count} unread"
|
|
50
55
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"notifications.actions.markRead": "Marcar como leída",
|
|
5
5
|
"notifications.actions.undo": "Deshacer",
|
|
6
6
|
"notifications.badge.unread": "{count} notificaciones no leídas",
|
|
7
|
+
"notifications.close": "Cerrar notificaciones",
|
|
7
8
|
"notifications.delivery.email.actionNotice": "Las acciones están disponibles en Open Mercato y son de solo lectura en este correo.",
|
|
8
9
|
"notifications.delivery.email.bodyIntro": "Revisa los detalles de la notificación y realiza las acciones necesarias.",
|
|
9
10
|
"notifications.delivery.email.footer": "Notificaciones de Open Mercato",
|
|
@@ -17,6 +18,9 @@
|
|
|
17
18
|
"notifications.filters.actionRequired": "Acción requerida",
|
|
18
19
|
"notifications.filters.all": "Todas",
|
|
19
20
|
"notifications.filters.unread": "No leídas",
|
|
21
|
+
"notifications.footer.manage": "Gestionar notificaciones",
|
|
22
|
+
"notifications.footer.toNavigate": "para navegar",
|
|
23
|
+
"notifications.footer.useHint": "Usa",
|
|
20
24
|
"notifications.markAllRead": "Marcar todo como leído",
|
|
21
25
|
"notifications.nav.title": "Notificaciones",
|
|
22
26
|
"notifications.settings.core.appUrl": "URL de la aplicación",
|
|
@@ -46,5 +50,6 @@
|
|
|
46
50
|
"notifications.toast.actionError": "No se pudo ejecutar la acción",
|
|
47
51
|
"notifications.toast.actionSuccess": "Acción completada correctamente",
|
|
48
52
|
"notifications.toast.dismissed": "Notificación descartada",
|
|
49
|
-
"notifications.toast.new": "Nueva notificación"
|
|
53
|
+
"notifications.toast.new": "Nueva notificación",
|
|
54
|
+
"notifications.unreadSummary": "{count} sin leer"
|
|
50
55
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
"notifications.actions.markRead": "Oznacz jako przeczytane",
|
|
5
5
|
"notifications.actions.undo": "Cofnij",
|
|
6
6
|
"notifications.badge.unread": "{count} nieprzeczytanych powiadomień",
|
|
7
|
+
"notifications.close": "Zamknij powiadomienia",
|
|
7
8
|
"notifications.delivery.email.actionNotice": "Akcje są dostępne w Open Mercato i w tym e-mailu są tylko do odczytu.",
|
|
8
9
|
"notifications.delivery.email.bodyIntro": "Sprawdź szczegóły powiadomienia i wykonaj wymagane działania.",
|
|
9
10
|
"notifications.delivery.email.footer": "Powiadomienia Open Mercato",
|
|
@@ -17,6 +18,9 @@
|
|
|
17
18
|
"notifications.filters.actionRequired": "Wymagana akcja",
|
|
18
19
|
"notifications.filters.all": "Wszystkie",
|
|
19
20
|
"notifications.filters.unread": "Nieprzeczytane",
|
|
21
|
+
"notifications.footer.manage": "Zarządzaj powiadomieniami",
|
|
22
|
+
"notifications.footer.toNavigate": "do nawigacji",
|
|
23
|
+
"notifications.footer.useHint": "Użyj",
|
|
20
24
|
"notifications.markAllRead": "Oznacz wszystkie jako przeczytane",
|
|
21
25
|
"notifications.nav.title": "Powiadomienia",
|
|
22
26
|
"notifications.settings.core.appUrl": "URL aplikacji",
|
|
@@ -46,5 +50,6 @@
|
|
|
46
50
|
"notifications.toast.actionError": "Nie udało się wykonać akcji",
|
|
47
51
|
"notifications.toast.actionSuccess": "Akcja wykonana pomyślnie",
|
|
48
52
|
"notifications.toast.dismissed": "Powiadomienie odrzucone",
|
|
49
|
-
"notifications.toast.new": "Nowe powiadomienie"
|
|
53
|
+
"notifications.toast.new": "Nowe powiadomienie",
|
|
54
|
+
"notifications.unreadSummary": "{count} nieprzeczytanych"
|
|
50
55
|
}
|
|
@@ -4,7 +4,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
4
4
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
5
|
import { SalesOrderAdjustment } from '../../data/entities'
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
enforceAdjustmentSign,
|
|
8
8
|
orderAdjustmentCreateSchema,
|
|
9
9
|
} from '../../data/validators'
|
|
10
10
|
import { createPagedListResponseSchema, createSalesCrudOpenApi, defaultOkResponseSchema } from '../openapi'
|
|
@@ -34,7 +34,7 @@ const routeMetadata = {
|
|
|
34
34
|
|
|
35
35
|
const upsertSchema = orderAdjustmentCreateSchema
|
|
36
36
|
.extend({ id: z.string().uuid().optional() })
|
|
37
|
-
.superRefine(
|
|
37
|
+
.superRefine(enforceAdjustmentSign)
|
|
38
38
|
|
|
39
39
|
const deleteSchema = z.object({
|
|
40
40
|
id: z.string().uuid(),
|
|
@@ -4,7 +4,7 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
4
4
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
5
5
|
import { SalesQuoteAdjustment } from '../../data/entities'
|
|
6
6
|
import {
|
|
7
|
-
|
|
7
|
+
enforceAdjustmentSign,
|
|
8
8
|
quoteAdjustmentCreateSchema,
|
|
9
9
|
} from '../../data/validators'
|
|
10
10
|
import { createPagedListResponseSchema, createSalesCrudOpenApi, defaultOkResponseSchema } from '../openapi'
|
|
@@ -34,7 +34,7 @@ const routeMetadata = {
|
|
|
34
34
|
|
|
35
35
|
const upsertSchema = quoteAdjustmentCreateSchema
|
|
36
36
|
.extend({ id: z.string().uuid().optional() })
|
|
37
|
-
.superRefine(
|
|
37
|
+
.superRefine(enforceAdjustmentSign)
|
|
38
38
|
|
|
39
39
|
const deleteSchema = z.object({
|
|
40
40
|
id: z.string().uuid(),
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
CustomerPersonProfile,
|
|
67
67
|
} from "../../customers/data/entities";
|
|
68
68
|
import {
|
|
69
|
-
|
|
69
|
+
enforceAdjustmentSign,
|
|
70
70
|
quoteCreateSchema,
|
|
71
71
|
quoteLineCreateSchema,
|
|
72
72
|
quoteAdjustmentCreateSchema,
|
|
@@ -6304,7 +6304,7 @@ const orderAdjustmentUpsertSchema = orderAdjustmentCreateSchema
|
|
|
6304
6304
|
.extend({
|
|
6305
6305
|
id: z.string().uuid().optional(),
|
|
6306
6306
|
})
|
|
6307
|
-
.superRefine(
|
|
6307
|
+
.superRefine(enforceAdjustmentSign);
|
|
6308
6308
|
|
|
6309
6309
|
const orderAdjustmentDeleteSchema = z.object({
|
|
6310
6310
|
id: z.string().uuid(),
|
|
@@ -6315,7 +6315,7 @@ const quoteAdjustmentUpsertSchema = quoteAdjustmentCreateSchema
|
|
|
6315
6315
|
.extend({
|
|
6316
6316
|
id: z.string().uuid().optional(),
|
|
6317
6317
|
})
|
|
6318
|
-
.superRefine(
|
|
6318
|
+
.superRefine(enforceAdjustmentSign);
|
|
6319
6319
|
|
|
6320
6320
|
const quoteAdjustmentDeleteSchema = z.object({
|
|
6321
6321
|
id: z.string().uuid(),
|
|
@@ -419,13 +419,37 @@ export const quoteLineUpdateSchema = z
|
|
|
419
419
|
})
|
|
420
420
|
.merge(quoteLineCreateSchema.partial())
|
|
421
421
|
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
422
|
+
// Each adjustment kind has an intrinsic sign convention so the grand total
|
|
423
|
+
// stays semantically correct regardless of operator input. The calculation
|
|
424
|
+
// engine normalizes signs defensively, but the API edge rejects values that
|
|
425
|
+
// would invert the kind's meaning so bad data never reaches storage.
|
|
426
|
+
//
|
|
427
|
+
// - `return`: non-positive — returns reduce the total (see #1705).
|
|
428
|
+
// - `discount`: non-negative — discounts reduce the total.
|
|
429
|
+
// - `surcharge`/`shipping`/`tax`: non-negative — they add to the total.
|
|
430
|
+
// - `custom` and unknown kinds: unconstrained (operator-controlled).
|
|
431
|
+
//
|
|
432
|
+
// See #1905.
|
|
425
433
|
export const RETURN_ADJUSTMENT_POSITIVE_NET_MESSAGE =
|
|
426
434
|
'Return adjustments must use a non-positive amountNet (returns reduce the total).'
|
|
427
435
|
export const RETURN_ADJUSTMENT_POSITIVE_GROSS_MESSAGE =
|
|
428
436
|
'Return adjustments must use a non-positive amountGross (returns reduce the total).'
|
|
437
|
+
export const DISCOUNT_ADJUSTMENT_NEGATIVE_NET_MESSAGE =
|
|
438
|
+
'Discount adjustments must use a non-negative amountNet (discounts reduce the total).'
|
|
439
|
+
export const DISCOUNT_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE =
|
|
440
|
+
'Discount adjustments must use a non-negative amountGross (discounts reduce the total).'
|
|
441
|
+
export const SURCHARGE_ADJUSTMENT_NEGATIVE_NET_MESSAGE =
|
|
442
|
+
'Surcharge adjustments must use a non-negative amountNet (surcharges add to the total).'
|
|
443
|
+
export const SURCHARGE_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE =
|
|
444
|
+
'Surcharge adjustments must use a non-negative amountGross (surcharges add to the total).'
|
|
445
|
+
export const SHIPPING_ADJUSTMENT_NEGATIVE_NET_MESSAGE =
|
|
446
|
+
'Shipping adjustments must use a non-negative amountNet (shipping adds to the total).'
|
|
447
|
+
export const SHIPPING_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE =
|
|
448
|
+
'Shipping adjustments must use a non-negative amountGross (shipping adds to the total).'
|
|
449
|
+
export const TAX_ADJUSTMENT_NEGATIVE_NET_MESSAGE =
|
|
450
|
+
'Tax adjustments must use a non-negative amountNet (taxes add to the total).'
|
|
451
|
+
export const TAX_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE =
|
|
452
|
+
'Tax adjustments must use a non-negative amountGross (taxes add to the total).'
|
|
429
453
|
|
|
430
454
|
type AdjustmentSignInput = {
|
|
431
455
|
kind?: string
|
|
@@ -433,27 +457,72 @@ type AdjustmentSignInput = {
|
|
|
433
457
|
amountGross?: number
|
|
434
458
|
}
|
|
435
459
|
|
|
436
|
-
|
|
460
|
+
const NON_NEGATIVE_SIGN_MESSAGES: Record<string, { net: string; gross: string }> = {
|
|
461
|
+
discount: {
|
|
462
|
+
net: DISCOUNT_ADJUSTMENT_NEGATIVE_NET_MESSAGE,
|
|
463
|
+
gross: DISCOUNT_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE,
|
|
464
|
+
},
|
|
465
|
+
surcharge: {
|
|
466
|
+
net: SURCHARGE_ADJUSTMENT_NEGATIVE_NET_MESSAGE,
|
|
467
|
+
gross: SURCHARGE_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE,
|
|
468
|
+
},
|
|
469
|
+
shipping: {
|
|
470
|
+
net: SHIPPING_ADJUSTMENT_NEGATIVE_NET_MESSAGE,
|
|
471
|
+
gross: SHIPPING_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE,
|
|
472
|
+
},
|
|
473
|
+
tax: {
|
|
474
|
+
net: TAX_ADJUSTMENT_NEGATIVE_NET_MESSAGE,
|
|
475
|
+
gross: TAX_ADJUSTMENT_NEGATIVE_GROSS_MESSAGE,
|
|
476
|
+
},
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export const enforceAdjustmentSign = (
|
|
437
480
|
value: AdjustmentSignInput,
|
|
438
481
|
ctx: z.RefinementCtx
|
|
439
482
|
) => {
|
|
440
|
-
if (value.kind
|
|
441
|
-
if (
|
|
483
|
+
if (!value.kind) return
|
|
484
|
+
if (value.kind === 'return') {
|
|
485
|
+
if (typeof value.amountNet === 'number' && value.amountNet > 0) {
|
|
486
|
+
ctx.addIssue({
|
|
487
|
+
code: z.ZodIssueCode.custom,
|
|
488
|
+
message: RETURN_ADJUSTMENT_POSITIVE_NET_MESSAGE,
|
|
489
|
+
path: ['amountNet'],
|
|
490
|
+
})
|
|
491
|
+
}
|
|
492
|
+
if (typeof value.amountGross === 'number' && value.amountGross > 0) {
|
|
493
|
+
ctx.addIssue({
|
|
494
|
+
code: z.ZodIssueCode.custom,
|
|
495
|
+
message: RETURN_ADJUSTMENT_POSITIVE_GROSS_MESSAGE,
|
|
496
|
+
path: ['amountGross'],
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
return
|
|
500
|
+
}
|
|
501
|
+
const messages = NON_NEGATIVE_SIGN_MESSAGES[value.kind]
|
|
502
|
+
if (!messages) return
|
|
503
|
+
if (typeof value.amountNet === 'number' && value.amountNet < 0) {
|
|
442
504
|
ctx.addIssue({
|
|
443
505
|
code: z.ZodIssueCode.custom,
|
|
444
|
-
message:
|
|
506
|
+
message: messages.net,
|
|
445
507
|
path: ['amountNet'],
|
|
446
508
|
})
|
|
447
509
|
}
|
|
448
|
-
if (typeof value.amountGross === 'number' && value.amountGross
|
|
510
|
+
if (typeof value.amountGross === 'number' && value.amountGross < 0) {
|
|
449
511
|
ctx.addIssue({
|
|
450
512
|
code: z.ZodIssueCode.custom,
|
|
451
|
-
message:
|
|
513
|
+
message: messages.gross,
|
|
452
514
|
path: ['amountGross'],
|
|
453
515
|
})
|
|
454
516
|
}
|
|
455
517
|
}
|
|
456
518
|
|
|
519
|
+
/**
|
|
520
|
+
* @deprecated Use `enforceAdjustmentSign` — it enforces the sign convention
|
|
521
|
+
* for every adjustment kind, not just `return`. Kept for backward compatibility
|
|
522
|
+
* with third-party code that imported the original helper.
|
|
523
|
+
*/
|
|
524
|
+
export const enforceReturnAdjustmentSign = enforceAdjustmentSign
|
|
525
|
+
|
|
457
526
|
export const orderAdjustmentCreateSchema = scoped.extend({
|
|
458
527
|
orderId: uuid(),
|
|
459
528
|
orderLineId: uuid().optional(),
|
|
@@ -151,13 +151,26 @@ function buildBaseDocumentResult(params: {
|
|
|
151
151
|
)
|
|
152
152
|
|
|
153
153
|
for (const adj of scopedAdjustments) {
|
|
154
|
-
const
|
|
155
|
-
const
|
|
154
|
+
const rawNet = toNumber(adj.amountNet, toNumber(adj.amountGross))
|
|
155
|
+
const rawGross = toNumber(adj.amountGross, rawNet)
|
|
156
|
+
// Each adjustment kind has an intrinsic sign convention. The API edge
|
|
157
|
+
// (enforceAdjustmentSign) rejects values that would invert the kind's
|
|
158
|
+
// semantic effect, but the calculation engine normalizes defensively so
|
|
159
|
+
// direct DB writes or seeded data can't inflate the grand total either.
|
|
160
|
+
// See #1905 (mirrors the existing return normalization a few lines below
|
|
161
|
+
// introduced for #1705).
|
|
162
|
+
const isNonNegativeKind =
|
|
163
|
+
adj.kind === 'discount' ||
|
|
164
|
+
adj.kind === 'surcharge' ||
|
|
165
|
+
adj.kind === 'shipping' ||
|
|
166
|
+
adj.kind === 'tax'
|
|
167
|
+
const net = isNonNegativeKind ? Math.abs(rawNet) : rawNet
|
|
168
|
+
const gross = isNonNegativeKind ? Math.abs(rawGross) : rawGross
|
|
156
169
|
const taxRate = extractAdjustmentTaxRate(adj)
|
|
157
170
|
const taxPortion = taxRate !== null ? round(gross - net) : 0
|
|
158
171
|
switch (adj.kind) {
|
|
159
172
|
case 'discount':
|
|
160
|
-
discountTotal +=
|
|
173
|
+
discountTotal += net
|
|
161
174
|
subtotalNet = Math.max(subtotalNet - net, 0)
|
|
162
175
|
subtotalGross = Math.max(subtotalGross - gross, 0)
|
|
163
176
|
if (taxPortion) {
|