@open-mercato/core 0.4.8-develop-d16e2f51dc → 0.4.8-develop-4b58cde65d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/sales_return/index.js +31 -0
- package/dist/generated/entities/sales_return/index.js.map +7 -0
- package/dist/generated/entities/sales_return_line/index.js +29 -0
- package/dist/generated/entities/sales_return_line/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +4 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/sales/acl.js +2 -0
- package/dist/modules/sales/acl.js.map +2 -2
- package/dist/modules/sales/api/document-history/route.js +20 -2
- package/dist/modules/sales/api/document-history/route.js.map +2 -2
- package/dist/modules/sales/api/documents/factory.js +34 -0
- package/dist/modules/sales/api/documents/factory.js.map +2 -2
- package/dist/modules/sales/api/returns/[id]/route.js +147 -0
- package/dist/modules/sales/api/returns/[id]/route.js.map +7 -0
- package/dist/modules/sales/api/returns/route.js +158 -0
- package/dist/modules/sales/api/returns/route.js.map +7 -0
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +20 -1
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/commands/index.js +1 -0
- package/dist/modules/sales/commands/index.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +467 -0
- package/dist/modules/sales/commands/returns.js.map +7 -0
- package/dist/modules/sales/components/documents/ReturnDialog.js +176 -0
- package/dist/modules/sales/components/documents/ReturnDialog.js.map +7 -0
- package/dist/modules/sales/components/documents/ReturnsSection.js +188 -0
- package/dist/modules/sales/components/documents/ReturnsSection.js.map +7 -0
- package/dist/modules/sales/data/entities.js +115 -1
- package/dist/modules/sales/data/entities.js.map +2 -2
- package/dist/modules/sales/data/validators.js +13 -0
- package/dist/modules/sales/data/validators.js.map +2 -2
- package/dist/modules/sales/events.js +4 -0
- package/dist/modules/sales/events.js.map +2 -2
- package/dist/modules/sales/lib/calculations.js +7 -0
- package/dist/modules/sales/lib/calculations.js.map +2 -2
- package/dist/modules/sales/lib/dictionaries.js +1 -0
- package/dist/modules/sales/lib/dictionaries.js.map +2 -2
- package/dist/modules/sales/lib/documentNumberTokens.js +2 -0
- package/dist/modules/sales/lib/documentNumberTokens.js.map +2 -2
- package/dist/modules/sales/lib/historyHelpers.js +15 -7
- package/dist/modules/sales/lib/historyHelpers.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +42 -37
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +2 -2
- package/dist/modules/sales/migrations/Migration20260309073310.js +23 -0
- package/dist/modules/sales/migrations/Migration20260309073310.js.map +7 -0
- package/dist/modules/sales/services/salesDocumentNumberGenerator.js +8 -6
- package/dist/modules/sales/services/salesDocumentNumberGenerator.js.map +2 -2
- package/dist/modules/sales/setup.js +1 -1
- package/dist/modules/sales/setup.js.map +2 -2
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js +25 -16
- package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
- package/generated/entities/sales_return/index.ts +14 -0
- package/generated/entities/sales_return_line/index.ts +13 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +4 -0
- package/package.json +3 -3
- package/src/modules/sales/AGENTS.md +1 -0
- package/src/modules/sales/acl.ts +2 -0
- package/src/modules/sales/api/document-history/route.ts +25 -1
- package/src/modules/sales/api/documents/factory.ts +35 -0
- package/src/modules/sales/api/returns/[id]/route.ts +156 -0
- package/src/modules/sales/api/returns/route.ts +171 -0
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +18 -0
- package/src/modules/sales/commands/index.ts +1 -0
- package/src/modules/sales/commands/returns.ts +540 -0
- package/src/modules/sales/components/documents/ReturnDialog.tsx +216 -0
- package/src/modules/sales/components/documents/ReturnsSection.tsx +270 -0
- package/src/modules/sales/data/entities.ts +99 -3
- package/src/modules/sales/data/validators.ts +16 -0
- package/src/modules/sales/events.ts +5 -0
- package/src/modules/sales/i18n/de.json +32 -0
- package/src/modules/sales/i18n/en.json +32 -0
- package/src/modules/sales/i18n/es.json +32 -0
- package/src/modules/sales/i18n/pl.json +32 -0
- package/src/modules/sales/lib/calculations.ts +9 -0
- package/src/modules/sales/lib/dictionaries.ts +1 -0
- package/src/modules/sales/lib/documentNumberTokens.ts +2 -1
- package/src/modules/sales/lib/historyHelpers.ts +20 -9
- package/src/modules/sales/lib/makeSalesLineRoute.ts +42 -37
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +398 -0
- package/src/modules/sales/migrations/Migration20260309073310.ts +26 -0
- package/src/modules/sales/services/salesDocumentNumberGenerator.ts +15 -4
- package/src/modules/sales/setup.ts +1 -1
- package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +26 -17
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
|
|
5
|
+
import { Input } from "@open-mercato/ui/primitives/input";
|
|
6
|
+
import { Textarea } from "@open-mercato/ui/primitives/textarea";
|
|
7
|
+
import { Label } from "@open-mercato/ui/primitives/label";
|
|
8
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
10
|
+
import { apiCallOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
11
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
12
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
13
|
+
const normalizeNumber = (value) => {
|
|
14
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
15
|
+
if (typeof value === "string" && value.trim().length) {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
if (Number.isFinite(parsed)) return parsed;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
};
|
|
21
|
+
function ReturnDialog({ open, orderId, lines, onClose, onSaved }) {
|
|
22
|
+
const t = useT();
|
|
23
|
+
const { runMutation } = useGuardedMutation({ contextId: `sales-returns-${orderId}` });
|
|
24
|
+
const [reason, setReason] = React.useState("");
|
|
25
|
+
const [notes, setNotes] = React.useState("");
|
|
26
|
+
const [quantities, setQuantities] = React.useState({});
|
|
27
|
+
const [saving, setSaving] = React.useState(false);
|
|
28
|
+
const availableLines = React.useMemo(() => {
|
|
29
|
+
return lines.map((line) => {
|
|
30
|
+
const available = Math.max(0, line.quantity - line.returnedQuantity);
|
|
31
|
+
return { ...line, available };
|
|
32
|
+
}).filter((line) => line.available > 0);
|
|
33
|
+
}, [lines]);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
if (!open) return;
|
|
36
|
+
setReason("");
|
|
37
|
+
setNotes("");
|
|
38
|
+
setQuantities({});
|
|
39
|
+
}, [open]);
|
|
40
|
+
const submit = React.useCallback(async () => {
|
|
41
|
+
if (saving) return;
|
|
42
|
+
let hasInvalidQuantity = false;
|
|
43
|
+
const linesForRequest = [];
|
|
44
|
+
availableLines.forEach((line) => {
|
|
45
|
+
const raw = quantities[line.id];
|
|
46
|
+
const qty = normalizeNumber(raw);
|
|
47
|
+
if (!Number.isFinite(qty) || qty <= 0) return;
|
|
48
|
+
if (qty - 1e-6 > line.available) {
|
|
49
|
+
hasInvalidQuantity = true;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
linesForRequest.push({ orderLineId: line.id, quantity: qty.toString() });
|
|
53
|
+
});
|
|
54
|
+
if (hasInvalidQuantity) {
|
|
55
|
+
flash(t("sales.returns.errors.quantityExceeded", "Cannot return more than available quantity."), "error");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!linesForRequest.length) {
|
|
59
|
+
flash(t("sales.returns.errors.linesRequired", "Select at least one line to return."), "error");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
setSaving(true);
|
|
63
|
+
try {
|
|
64
|
+
await runMutation({
|
|
65
|
+
context: { kind: "order", record: { id: orderId } },
|
|
66
|
+
mutationPayload: { orderId, lines: linesForRequest, reason, notes },
|
|
67
|
+
operation: async () => {
|
|
68
|
+
const response = await apiCallOrThrow(
|
|
69
|
+
"/api/sales/returns",
|
|
70
|
+
{
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: { "content-type": "application/json" },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
orderId,
|
|
75
|
+
lines: linesForRequest,
|
|
76
|
+
...reason.trim().length ? { reason: reason.trim() } : {},
|
|
77
|
+
...notes.trim().length ? { notes: notes.trim() } : {}
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
{ errorMessage: t("sales.returns.errors.create", "Failed to create return.") }
|
|
81
|
+
);
|
|
82
|
+
return response.result?.id ?? null;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
flash(t("sales.returns.created", "Return created."), "success");
|
|
86
|
+
onClose();
|
|
87
|
+
await onSaved();
|
|
88
|
+
} catch {
|
|
89
|
+
flash(t("sales.returns.errors.create", "Failed to create return."), "error");
|
|
90
|
+
} finally {
|
|
91
|
+
setSaving(false);
|
|
92
|
+
}
|
|
93
|
+
}, [availableLines, notes, onClose, onSaved, orderId, quantities, reason, runMutation, saving, t]);
|
|
94
|
+
const onKeyDown = React.useCallback(
|
|
95
|
+
(e) => {
|
|
96
|
+
if (e.key === "Escape") onClose();
|
|
97
|
+
if ((e.key === "Enter" || e.key === "NumpadEnter") && (e.metaKey || e.ctrlKey)) {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
submit();
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
[onClose, submit]
|
|
103
|
+
);
|
|
104
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => !next ? onClose() : void 0, children: /* @__PURE__ */ jsxs(DialogContent, { onKeyDown, className: "max-w-2xl", children: [
|
|
105
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: t("sales.returns.create.title", "Create return") }) }),
|
|
106
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
107
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
108
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: t("sales.returns.create.lines", "Lines") }),
|
|
109
|
+
!availableLines.length ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("sales.returns.empty.available", "No items available to return.") }) : /* @__PURE__ */ jsx("div", { className: "max-h-[280px] overflow-auto rounded-md border", children: /* @__PURE__ */ jsx("div", { className: "divide-y", children: availableLines.map((line) => {
|
|
110
|
+
const value = quantities[line.id] ?? "";
|
|
111
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3", children: [
|
|
112
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { className: "truncate text-sm font-medium", children: [
|
|
114
|
+
line.lineNumber ? `#${line.lineNumber} \xB7 ` : "",
|
|
115
|
+
line.title
|
|
116
|
+
] }),
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
|
|
118
|
+
t("sales.returns.available", "Available"),
|
|
119
|
+
": ",
|
|
120
|
+
line.available
|
|
121
|
+
] })
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { className: "w-32", children: [
|
|
124
|
+
/* @__PURE__ */ jsx(Label, { className: "sr-only", htmlFor: `return-qty-${line.id}`, children: t("sales.returns.quantity", "Quantity") }),
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
Input,
|
|
127
|
+
{
|
|
128
|
+
id: `return-qty-${line.id}`,
|
|
129
|
+
inputMode: "decimal",
|
|
130
|
+
placeholder: "0",
|
|
131
|
+
value,
|
|
132
|
+
onChange: (e) => setQuantities((prev) => ({ ...prev, [line.id]: e.target.value }))
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
] })
|
|
136
|
+
] }, line.id);
|
|
137
|
+
}) }) })
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
|
|
140
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
141
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "return-reason", children: t("sales.returns.reason", "Reason") }),
|
|
142
|
+
/* @__PURE__ */ jsx(
|
|
143
|
+
Input,
|
|
144
|
+
{
|
|
145
|
+
id: "return-reason",
|
|
146
|
+
value: reason,
|
|
147
|
+
onChange: (e) => setReason(e.target.value),
|
|
148
|
+
placeholder: t("sales.returns.reason.placeholder", "Optional")
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5 md:col-span-2", children: [
|
|
153
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "return-notes", children: t("sales.returns.notes", "Notes") }),
|
|
154
|
+
/* @__PURE__ */ jsx(
|
|
155
|
+
Textarea,
|
|
156
|
+
{
|
|
157
|
+
id: "return-notes",
|
|
158
|
+
value: notes,
|
|
159
|
+
onChange: (e) => setNotes(e.target.value),
|
|
160
|
+
placeholder: t("sales.returns.notes.placeholder", "Optional"),
|
|
161
|
+
rows: 3
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
] })
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
167
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onClose, disabled: saving, children: t("common.cancel", "Cancel") }),
|
|
168
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: submit, disabled: saving || !availableLines.length, children: saving ? t("common.saving", "Saving\u2026") : t("sales.returns.create.submit", "Create return") })
|
|
169
|
+
] })
|
|
170
|
+
] })
|
|
171
|
+
] }) });
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
ReturnDialog
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=ReturnDialog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/sales/components/documents/ReturnDialog.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\n\nexport type ReturnOrderLine = {\n id: string\n title: string\n lineNumber: number | null\n quantity: number\n returnedQuantity: number\n thumbnail?: string | null\n}\n\ntype ReturnDialogProps = {\n open: boolean\n orderId: string\n lines: ReturnOrderLine[]\n onClose: () => void\n onSaved: () => Promise<void>\n}\n\nconst normalizeNumber = (value: unknown): number => {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n if (Number.isFinite(parsed)) return parsed\n }\n return 0\n}\n\nexport function ReturnDialog({ open, orderId, lines, onClose, onSaved }: ReturnDialogProps) {\n const t = useT()\n const { runMutation } = useGuardedMutation({ contextId: `sales-returns-${orderId}` })\n const [reason, setReason] = React.useState('')\n const [notes, setNotes] = React.useState('')\n const [quantities, setQuantities] = React.useState<Record<string, string>>({})\n const [saving, setSaving] = React.useState(false)\n\n const availableLines = React.useMemo(() => {\n return lines\n .map((line) => {\n const available = Math.max(0, line.quantity - line.returnedQuantity)\n return { ...line, available }\n })\n .filter((line) => line.available > 0)\n }, [lines])\n\n React.useEffect(() => {\n if (!open) return\n setReason('')\n setNotes('')\n setQuantities({})\n }, [open])\n\n const submit = React.useCallback(async () => {\n if (saving) return\n let hasInvalidQuantity = false\n const linesForRequest: Array<{ orderLineId: string; quantity: string }> = []\n availableLines.forEach((line) => {\n const raw = quantities[line.id]\n const qty = normalizeNumber(raw)\n if (!Number.isFinite(qty) || qty <= 0) return\n if (qty - 1e-6 > line.available) {\n hasInvalidQuantity = true\n return\n }\n linesForRequest.push({ orderLineId: line.id, quantity: qty.toString() })\n })\n\n if (hasInvalidQuantity) {\n flash(t('sales.returns.errors.quantityExceeded', 'Cannot return more than available quantity.'), 'error')\n return\n }\n\n if (!linesForRequest.length) {\n flash(t('sales.returns.errors.linesRequired', 'Select at least one line to return.'), 'error')\n return\n }\n\n setSaving(true)\n try {\n await runMutation({\n context: { kind: 'order', record: { id: orderId } },\n mutationPayload: { orderId, lines: linesForRequest, reason, notes },\n operation: async () => {\n const response = await apiCallOrThrow<{ id: string | null }>(\n '/api/sales/returns',\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({\n orderId,\n lines: linesForRequest,\n ...(reason.trim().length ? { reason: reason.trim() } : {}),\n ...(notes.trim().length ? { notes: notes.trim() } : {}),\n }),\n },\n { errorMessage: t('sales.returns.errors.create', 'Failed to create return.') },\n )\n return response.result?.id ?? null\n },\n })\n flash(t('sales.returns.created', 'Return created.'), 'success')\n onClose()\n await onSaved()\n } catch {\n flash(t('sales.returns.errors.create', 'Failed to create return.'), 'error')\n } finally {\n setSaving(false)\n }\n }, [availableLines, notes, onClose, onSaved, orderId, quantities, reason, runMutation, saving, t])\n\n const onKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (e.key === 'Escape') onClose()\n if ((e.key === 'Enter' || e.key === 'NumpadEnter') && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n submit()\n }\n },\n [onClose, submit],\n )\n\n return (\n <Dialog open={open} onOpenChange={(next) => (!next ? onClose() : undefined)}>\n <DialogContent onKeyDown={onKeyDown} className=\"max-w-2xl\">\n <DialogHeader>\n <DialogTitle>{t('sales.returns.create.title', 'Create return')}</DialogTitle>\n </DialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <div className=\"text-sm font-medium\">{t('sales.returns.create.lines', 'Lines')}</div>\n {!availableLines.length ? (\n <div className=\"text-sm text-muted-foreground\">{t('sales.returns.empty.available', 'No items available to return.')}</div>\n ) : (\n <div className=\"max-h-[280px] overflow-auto rounded-md border\">\n <div className=\"divide-y\">\n {availableLines.map((line) => {\n const value = quantities[line.id] ?? ''\n return (\n <div key={line.id} className=\"flex items-center gap-3 p-3\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-sm font-medium\">\n {line.lineNumber ? `#${line.lineNumber} \u00B7 ` : ''}\n {line.title}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t('sales.returns.available', 'Available')}: {line.available}\n </div>\n </div>\n <div className=\"w-32\">\n <Label className=\"sr-only\" htmlFor={`return-qty-${line.id}`}>\n {t('sales.returns.quantity', 'Quantity')}\n </Label>\n <Input\n id={`return-qty-${line.id}`}\n inputMode=\"decimal\"\n placeholder=\"0\"\n value={value}\n onChange={(e) => setQuantities((prev) => ({ ...prev, [line.id]: e.target.value }))}\n />\n </div>\n </div>\n )\n })}\n </div>\n </div>\n )}\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"return-reason\">{t('sales.returns.reason', 'Reason')}</Label>\n <Input\n id=\"return-reason\"\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n placeholder={t('sales.returns.reason.placeholder', 'Optional')}\n />\n </div>\n <div className=\"space-y-1.5 md:col-span-2\">\n <Label htmlFor=\"return-notes\">{t('sales.returns.notes', 'Notes')}</Label>\n <Textarea\n id=\"return-notes\"\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder={t('sales.returns.notes.placeholder', 'Optional')}\n rows={3}\n />\n </div>\n </div>\n\n <div className=\"flex items-center justify-end gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose} disabled={saving}>\n {t('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={submit} disabled={saving || !availableLines.length}>\n {saving ? t('common.saving', 'Saving\u2026') : t('sales.returns.create.submit', 'Create return')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Undo2, Plus } from "lucide-react";
|
|
5
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
6
|
+
import { Badge } from "@open-mercato/ui/primitives/badge";
|
|
7
|
+
import { ErrorMessage, LoadingMessage, TabEmptyState } from "@open-mercato/ui/backend/detail";
|
|
8
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
9
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
10
|
+
import {
|
|
11
|
+
emitSalesDocumentTotalsRefresh,
|
|
12
|
+
subscribeSalesDocumentTotalsRefresh
|
|
13
|
+
} from "@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents";
|
|
14
|
+
import { formatMoney, normalizeNumber } from "./lineItemUtils.js";
|
|
15
|
+
import { ReturnDialog } from "./ReturnDialog.js";
|
|
16
|
+
function formatDisplayDate(value) {
|
|
17
|
+
if (!value) return null;
|
|
18
|
+
const date = new Date(value);
|
|
19
|
+
if (Number.isNaN(date.getTime())) return null;
|
|
20
|
+
return new Intl.DateTimeFormat(void 0, { dateStyle: "medium" }).format(date);
|
|
21
|
+
}
|
|
22
|
+
function SalesReturnsSection({ orderId, currencyCode }) {
|
|
23
|
+
const t = useT();
|
|
24
|
+
const [returns, setReturns] = React.useState([]);
|
|
25
|
+
const [lines, setLines] = React.useState([]);
|
|
26
|
+
const [loading, setLoading] = React.useState(false);
|
|
27
|
+
const [error, setError] = React.useState(null);
|
|
28
|
+
const [dialogOpen, setDialogOpen] = React.useState(false);
|
|
29
|
+
const loadLines = React.useCallback(async () => {
|
|
30
|
+
const params = new URLSearchParams({ page: "1", pageSize: "100", orderId });
|
|
31
|
+
const response = await apiCall(
|
|
32
|
+
`/api/sales/order-lines?${params.toString()}`,
|
|
33
|
+
void 0,
|
|
34
|
+
{ fallback: { items: [] } }
|
|
35
|
+
);
|
|
36
|
+
const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : [];
|
|
37
|
+
const mapped = items.map((item) => {
|
|
38
|
+
const map = item;
|
|
39
|
+
const id = typeof map.id === "string" ? map.id : null;
|
|
40
|
+
if (!id) return null;
|
|
41
|
+
const snapshot = map["catalog_snapshot"];
|
|
42
|
+
const snapshotName = snapshot && typeof snapshot === "object" && !Array.isArray(snapshot) ? snapshot["name"] : null;
|
|
43
|
+
const name = typeof map.name === "string" ? map.name : typeof snapshotName === "string" ? snapshotName : null;
|
|
44
|
+
const lineNumber = typeof map["line_number"] === "number" ? map["line_number"] : typeof map.lineNumber === "number" ? map.lineNumber : null;
|
|
45
|
+
const quantity = typeof map.quantity === "number" ? map.quantity : typeof map.quantity === "string" ? Number(map.quantity) : 0;
|
|
46
|
+
const returnedQuantity = typeof map["returned_quantity"] === "number" ? map["returned_quantity"] : typeof map.returnedQuantity === "number" ? map.returnedQuantity : typeof map["returned_quantity"] === "string" ? Number(map["returned_quantity"]) : typeof map.returnedQuantity === "string" ? Number(map.returnedQuantity) : 0;
|
|
47
|
+
return {
|
|
48
|
+
id,
|
|
49
|
+
title: name ?? id,
|
|
50
|
+
lineNumber,
|
|
51
|
+
quantity: Number.isFinite(quantity) ? quantity : 0,
|
|
52
|
+
returnedQuantity: Number.isFinite(returnedQuantity) ? returnedQuantity : 0
|
|
53
|
+
};
|
|
54
|
+
}).filter((entry) => Boolean(entry?.id));
|
|
55
|
+
setLines(mapped);
|
|
56
|
+
}, [orderId]);
|
|
57
|
+
const loadReturns = React.useCallback(async () => {
|
|
58
|
+
setLoading(true);
|
|
59
|
+
setError(null);
|
|
60
|
+
try {
|
|
61
|
+
const params = new URLSearchParams({ page: "1", pageSize: "100", orderId });
|
|
62
|
+
const response = await apiCall(
|
|
63
|
+
`/api/sales/returns?${params.toString()}`,
|
|
64
|
+
void 0,
|
|
65
|
+
{ fallback: { items: [] } }
|
|
66
|
+
);
|
|
67
|
+
const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : [];
|
|
68
|
+
const mapped = items.map((item) => {
|
|
69
|
+
const map = item;
|
|
70
|
+
const id = typeof map.id === "string" ? map.id : null;
|
|
71
|
+
if (!id) return null;
|
|
72
|
+
const returnNumber = typeof map["return_number"] === "string" ? map["return_number"] : typeof map.returnNumber === "string" ? map.returnNumber : id;
|
|
73
|
+
const returnedAt = typeof map["returned_at"] === "string" ? map["returned_at"] : typeof map.returnedAt === "string" ? map.returnedAt : null;
|
|
74
|
+
const totalNetAmount = typeof map["total_net_amount"] === "number" ? map["total_net_amount"] : typeof map.totalNetAmount === "number" ? map.totalNetAmount : null;
|
|
75
|
+
const totalGrossAmount = typeof map["total_gross_amount"] === "number" ? map["total_gross_amount"] : typeof map.totalGrossAmount === "number" ? map.totalGrossAmount : null;
|
|
76
|
+
return {
|
|
77
|
+
id,
|
|
78
|
+
returnNumber,
|
|
79
|
+
status: typeof map.status === "string" ? map.status : null,
|
|
80
|
+
returnedAt,
|
|
81
|
+
totalNetAmount,
|
|
82
|
+
totalGrossAmount
|
|
83
|
+
};
|
|
84
|
+
}).filter((entry) => Boolean(entry?.id));
|
|
85
|
+
setReturns(mapped);
|
|
86
|
+
await loadLines();
|
|
87
|
+
} catch {
|
|
88
|
+
setError(t("sales.returns.errors.load", "Failed to load returns."));
|
|
89
|
+
} finally {
|
|
90
|
+
setLoading(false);
|
|
91
|
+
}
|
|
92
|
+
}, [loadLines, orderId, t]);
|
|
93
|
+
React.useEffect(() => {
|
|
94
|
+
loadReturns();
|
|
95
|
+
}, [loadReturns]);
|
|
96
|
+
React.useEffect(() => {
|
|
97
|
+
return subscribeSalesDocumentTotalsRefresh((detail) => {
|
|
98
|
+
if (detail.documentId !== orderId) return;
|
|
99
|
+
loadReturns();
|
|
100
|
+
});
|
|
101
|
+
}, [loadReturns, orderId]);
|
|
102
|
+
const emptyState = React.useMemo(
|
|
103
|
+
() => ({
|
|
104
|
+
title: t("sales.returns.empty.title", "No returns yet."),
|
|
105
|
+
description: t("sales.returns.empty.description", "Create a return to generate credit adjustments for returned items.")
|
|
106
|
+
}),
|
|
107
|
+
[t]
|
|
108
|
+
);
|
|
109
|
+
const rows = React.useMemo(() => {
|
|
110
|
+
return returns.map((ret) => {
|
|
111
|
+
const total = normalizeNumber(ret.totalGrossAmount ?? ret.totalNetAmount ?? 0, 0);
|
|
112
|
+
return {
|
|
113
|
+
...ret,
|
|
114
|
+
total
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
}, [returns]);
|
|
118
|
+
if (loading) return /* @__PURE__ */ jsx(LoadingMessage, { label: t("sales.returns.loading", "Loading returns\u2026") });
|
|
119
|
+
if (error) return /* @__PURE__ */ jsx(ErrorMessage, { label: error });
|
|
120
|
+
if (!rows.length) {
|
|
121
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
122
|
+
/* @__PURE__ */ jsx(
|
|
123
|
+
TabEmptyState,
|
|
124
|
+
{
|
|
125
|
+
title: emptyState.title,
|
|
126
|
+
description: emptyState.description,
|
|
127
|
+
action: {
|
|
128
|
+
label: t("sales.returns.create", "Create return"),
|
|
129
|
+
icon: /* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4", "aria-hidden": true }),
|
|
130
|
+
onClick: () => setDialogOpen(true)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
),
|
|
134
|
+
/* @__PURE__ */ jsx(
|
|
135
|
+
ReturnDialog,
|
|
136
|
+
{
|
|
137
|
+
open: dialogOpen,
|
|
138
|
+
orderId,
|
|
139
|
+
lines,
|
|
140
|
+
onClose: () => setDialogOpen(false),
|
|
141
|
+
onSaved: async () => {
|
|
142
|
+
emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: "order" });
|
|
143
|
+
await loadReturns();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
] });
|
|
148
|
+
}
|
|
149
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
150
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-end", children: /* @__PURE__ */ jsxs(Button, { type: "button", onClick: () => setDialogOpen(true), children: [
|
|
151
|
+
/* @__PURE__ */ jsx(Plus, { className: "mr-2 h-4 w-4", "aria-hidden": true }),
|
|
152
|
+
t("sales.returns.create", "Create return")
|
|
153
|
+
] }) }),
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-md border", children: [
|
|
155
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_auto_auto] gap-3 border-b bg-muted/30 px-4 py-2 text-xs font-medium text-muted-foreground", children: [
|
|
156
|
+
/* @__PURE__ */ jsx("div", { children: t("sales.returns.returnNumber", "Return") }),
|
|
157
|
+
/* @__PURE__ */ jsx("div", { className: "text-right", children: t("sales.returns.returnedAt", "Returned at") }),
|
|
158
|
+
/* @__PURE__ */ jsx("div", { className: "text-right", children: t("sales.returns.total", "Total") })
|
|
159
|
+
] }),
|
|
160
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y", children: rows.map((ret) => /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-[1fr_auto_auto] items-center gap-3 px-4 py-3", children: [
|
|
161
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
162
|
+
/* @__PURE__ */ jsx(Undo2, { className: "h-4 w-4 text-muted-foreground", "aria-hidden": true }),
|
|
163
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: ret.returnNumber }),
|
|
164
|
+
ret.status ? /* @__PURE__ */ jsx(Badge, { variant: "secondary", children: ret.status }) : null
|
|
165
|
+
] }) }),
|
|
166
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-nowrap text-right text-sm text-muted-foreground", children: formatDisplayDate(ret.returnedAt) ?? t("sales.returns.notSet", "Not set") }),
|
|
167
|
+
/* @__PURE__ */ jsx("div", { className: "whitespace-nowrap text-right text-sm font-medium", children: formatMoney(ret.total, currencyCode ?? null) })
|
|
168
|
+
] }, ret.id)) })
|
|
169
|
+
] }),
|
|
170
|
+
/* @__PURE__ */ jsx(
|
|
171
|
+
ReturnDialog,
|
|
172
|
+
{
|
|
173
|
+
open: dialogOpen,
|
|
174
|
+
orderId,
|
|
175
|
+
lines,
|
|
176
|
+
onClose: () => setDialogOpen(false),
|
|
177
|
+
onSaved: async () => {
|
|
178
|
+
emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: "order" });
|
|
179
|
+
await loadReturns();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
)
|
|
183
|
+
] });
|
|
184
|
+
}
|
|
185
|
+
export {
|
|
186
|
+
SalesReturnsSection
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=ReturnsSection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/sales/components/documents/ReturnsSection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Undo2, Plus } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { ErrorMessage, LoadingMessage, TabEmptyState } from '@open-mercato/ui/backend/detail'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n emitSalesDocumentTotalsRefresh,\n subscribeSalesDocumentTotalsRefresh,\n} from '@open-mercato/core/modules/sales/lib/frontend/documentTotalsEvents'\nimport { formatMoney, normalizeNumber } from './lineItemUtils'\nimport { ReturnDialog, type ReturnOrderLine } from './ReturnDialog'\n\ntype ReturnRow = {\n id: string\n returnNumber: string\n status: string | null\n returnedAt: string | null\n totalNetAmount: number | null\n totalGrossAmount: number | null\n}\n\ntype SalesReturnsSectionProps = {\n orderId: string\n currencyCode?: string | null\n}\n\nfunction formatDisplayDate(value: string | null | undefined): string | null {\n if (!value) return null\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return null\n return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium' }).format(date)\n}\n\nexport function SalesReturnsSection({ orderId, currencyCode }: SalesReturnsSectionProps) {\n const t = useT()\n const [returns, setReturns] = React.useState<ReturnRow[]>([])\n const [lines, setLines] = React.useState<ReturnOrderLine[]>([])\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [dialogOpen, setDialogOpen] = React.useState(false)\n\n const loadLines = React.useCallback(async () => {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/order-lines?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: ReturnOrderLine[] = items\n .map((item) => {\n const map = item as Record<string, unknown>\n const id = typeof map.id === 'string' ? map.id : null\n if (!id) return null\n const snapshot = map['catalog_snapshot']\n const snapshotName =\n snapshot && typeof snapshot === 'object' && !Array.isArray(snapshot)\n ? (snapshot as Record<string, unknown>)['name']\n : null\n const name =\n typeof map.name === 'string'\n ? map.name\n : typeof snapshotName === 'string'\n ? snapshotName\n : null\n const lineNumber =\n typeof map['line_number'] === 'number'\n ? (map['line_number'] as number)\n : typeof map.lineNumber === 'number'\n ? (map.lineNumber as number)\n : null\n const quantity =\n typeof map.quantity === 'number'\n ? (map.quantity as number)\n : typeof map.quantity === 'string'\n ? Number(map.quantity)\n : 0\n const returnedQuantity =\n typeof map['returned_quantity'] === 'number'\n ? (map['returned_quantity'] as number)\n : typeof map.returnedQuantity === 'number'\n ? (map.returnedQuantity as number)\n : typeof map['returned_quantity'] === 'string'\n ? Number(map['returned_quantity'])\n : typeof map.returnedQuantity === 'string'\n ? Number(map.returnedQuantity)\n : 0\n return {\n id,\n title: name ?? id,\n lineNumber,\n quantity: Number.isFinite(quantity) ? quantity : 0,\n returnedQuantity: Number.isFinite(returnedQuantity) ? returnedQuantity : 0,\n }\n })\n .filter((entry): entry is ReturnOrderLine => Boolean(entry?.id))\n setLines(mapped)\n }, [orderId])\n\n const loadReturns = React.useCallback(async () => {\n setLoading(true)\n setError(null)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100', orderId })\n const response = await apiCall<{ items?: Array<Record<string, unknown>> }>(\n `/api/sales/returns?${params.toString()}`,\n undefined,\n { fallback: { items: [] } },\n )\n const items = Array.isArray(response.result?.items) ? response.result?.items ?? [] : []\n const mapped: ReturnRow[] = items\n .map((item) => {\n const map = item as Record<string, unknown>\n const id = typeof map.id === 'string' ? map.id : null\n if (!id) return null\n const returnNumber =\n typeof map['return_number'] === 'string'\n ? (map['return_number'] as string)\n : typeof map.returnNumber === 'string'\n ? (map.returnNumber as string)\n : id\n const returnedAt =\n typeof map['returned_at'] === 'string'\n ? (map['returned_at'] as string)\n : typeof map.returnedAt === 'string'\n ? (map.returnedAt as string)\n : null\n const totalNetAmount =\n typeof map['total_net_amount'] === 'number'\n ? (map['total_net_amount'] as number)\n : typeof map.totalNetAmount === 'number'\n ? (map.totalNetAmount as number)\n : null\n const totalGrossAmount =\n typeof map['total_gross_amount'] === 'number'\n ? (map['total_gross_amount'] as number)\n : typeof map.totalGrossAmount === 'number'\n ? (map.totalGrossAmount as number)\n : null\n return {\n id,\n returnNumber,\n status: typeof map.status === 'string' ? (map.status as string) : null,\n returnedAt,\n totalNetAmount,\n totalGrossAmount,\n }\n })\n .filter((entry): entry is ReturnRow => Boolean(entry?.id))\n setReturns(mapped)\n await loadLines()\n } catch {\n setError(t('sales.returns.errors.load', 'Failed to load returns.'))\n } finally {\n setLoading(false)\n }\n }, [loadLines, orderId, t])\n\n React.useEffect(() => {\n loadReturns()\n }, [loadReturns])\n\n React.useEffect(() => {\n return subscribeSalesDocumentTotalsRefresh((detail) => {\n if (detail.documentId !== orderId) return\n loadReturns()\n })\n }, [loadReturns, orderId])\n\n const emptyState = React.useMemo(\n () => ({\n title: t('sales.returns.empty.title', 'No returns yet.'),\n description: t('sales.returns.empty.description', 'Create a return to generate credit adjustments for returned items.'),\n }),\n [t],\n )\n\n const rows = React.useMemo(() => {\n return returns.map((ret) => {\n const total = normalizeNumber(ret.totalGrossAmount ?? ret.totalNetAmount ?? 0, 0)\n return {\n ...ret,\n total,\n }\n })\n }, [returns])\n\n if (loading) return <LoadingMessage label={t('sales.returns.loading', 'Loading returns\u2026')} />\n if (error) return <ErrorMessage label={error} />\n\n if (!rows.length) {\n return (\n <div className=\"space-y-4\">\n <TabEmptyState\n title={emptyState.title}\n description={emptyState.description}\n action={{\n label: t('sales.returns.create', 'Create return'),\n icon: <Plus className=\"mr-2 h-4 w-4\" aria-hidden />,\n onClick: () => setDialogOpen(true),\n }}\n />\n <ReturnDialog\n open={dialogOpen}\n orderId={orderId}\n lines={lines}\n onClose={() => setDialogOpen(false)}\n onSaved={async () => {\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n await loadReturns()\n }}\n />\n </div>\n )\n }\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-end\">\n <Button type=\"button\" onClick={() => setDialogOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" aria-hidden />\n {t('sales.returns.create', 'Create return')}\n </Button>\n </div>\n\n <div className=\"overflow-hidden rounded-md border\">\n <div className=\"grid grid-cols-[1fr_auto_auto] gap-3 border-b bg-muted/30 px-4 py-2 text-xs font-medium text-muted-foreground\">\n <div>{t('sales.returns.returnNumber', 'Return')}</div>\n <div className=\"text-right\">{t('sales.returns.returnedAt', 'Returned at')}</div>\n <div className=\"text-right\">{t('sales.returns.total', 'Total')}</div>\n </div>\n <div className=\"divide-y\">\n {rows.map((ret) => (\n <div key={ret.id} className=\"grid grid-cols-[1fr_auto_auto] items-center gap-3 px-4 py-3\">\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <Undo2 className=\"h-4 w-4 text-muted-foreground\" aria-hidden />\n <div className=\"truncate text-sm font-medium\">{ret.returnNumber}</div>\n {ret.status ? <Badge variant=\"secondary\">{ret.status}</Badge> : null}\n </div>\n </div>\n <div className=\"whitespace-nowrap text-right text-sm text-muted-foreground\">\n {formatDisplayDate(ret.returnedAt) ?? t('sales.returns.notSet', 'Not set')}\n </div>\n <div className=\"whitespace-nowrap text-right text-sm font-medium\">\n {formatMoney(ret.total, currencyCode ?? null)}\n </div>\n </div>\n ))}\n </div>\n </div>\n\n <ReturnDialog\n open={dialogOpen}\n orderId={orderId}\n lines={lines}\n onClose={() => setDialogOpen(false)}\n onSaved={async () => {\n emitSalesDocumentTotalsRefresh({ documentId: orderId, kind: 'order' })\n await loadReturns()\n }}\n />\n </div>\n )\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
Unique
|
|
21
21
|
} from "@mikro-orm/core";
|
|
22
22
|
import { DEFAULT_ORDER_NUMBER_FORMAT, DEFAULT_QUOTE_NUMBER_FORMAT } from "../lib/documentNumberTokens.js";
|
|
23
|
-
const DEFAULT_SALES_ADJUSTMENT_KINDS = ["discount", "tax", "shipping", "surcharge", "custom"];
|
|
23
|
+
const DEFAULT_SALES_ADJUSTMENT_KINDS = ["discount", "tax", "shipping", "surcharge", "return", "custom"];
|
|
24
24
|
let SalesChannel = class {
|
|
25
25
|
constructor() {
|
|
26
26
|
this.isActive = true;
|
|
@@ -1476,6 +1476,118 @@ SalesShipmentItem = __decorateClass([
|
|
|
1476
1476
|
Entity({ tableName: "sales_shipment_items" }),
|
|
1477
1477
|
Index({ name: "sales_shipment_items_scope_idx", properties: ["shipment", "organizationId", "tenantId"] })
|
|
1478
1478
|
], SalesShipmentItem);
|
|
1479
|
+
OptionalProps;
|
|
1480
|
+
let SalesReturn = class {
|
|
1481
|
+
constructor() {
|
|
1482
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
1483
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
1484
|
+
this.lines = new Collection(this);
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
__decorateClass([
|
|
1488
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
1489
|
+
], SalesReturn.prototype, "id", 2);
|
|
1490
|
+
__decorateClass([
|
|
1491
|
+
ManyToOne(() => SalesOrder, { fieldName: "order_id" })
|
|
1492
|
+
], SalesReturn.prototype, "order", 2);
|
|
1493
|
+
__decorateClass([
|
|
1494
|
+
Property({ name: "organization_id", type: "uuid" })
|
|
1495
|
+
], SalesReturn.prototype, "organizationId", 2);
|
|
1496
|
+
__decorateClass([
|
|
1497
|
+
Property({ name: "tenant_id", type: "uuid" })
|
|
1498
|
+
], SalesReturn.prototype, "tenantId", 2);
|
|
1499
|
+
__decorateClass([
|
|
1500
|
+
Property({ name: "return_number", type: "text" })
|
|
1501
|
+
], SalesReturn.prototype, "returnNumber", 2);
|
|
1502
|
+
__decorateClass([
|
|
1503
|
+
Property({ name: "status_entry_id", type: "uuid", nullable: true })
|
|
1504
|
+
], SalesReturn.prototype, "statusEntryId", 2);
|
|
1505
|
+
__decorateClass([
|
|
1506
|
+
Property({ name: "status", type: "text", nullable: true })
|
|
1507
|
+
], SalesReturn.prototype, "status", 2);
|
|
1508
|
+
__decorateClass([
|
|
1509
|
+
Property({ name: "reason", type: "text", nullable: true })
|
|
1510
|
+
], SalesReturn.prototype, "reason", 2);
|
|
1511
|
+
__decorateClass([
|
|
1512
|
+
Property({ name: "notes", type: "text", nullable: true })
|
|
1513
|
+
], SalesReturn.prototype, "notes", 2);
|
|
1514
|
+
__decorateClass([
|
|
1515
|
+
Property({ name: "returned_at", type: Date, nullable: true })
|
|
1516
|
+
], SalesReturn.prototype, "returnedAt", 2);
|
|
1517
|
+
__decorateClass([
|
|
1518
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
1519
|
+
], SalesReturn.prototype, "createdAt", 2);
|
|
1520
|
+
__decorateClass([
|
|
1521
|
+
Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
|
|
1522
|
+
], SalesReturn.prototype, "updatedAt", 2);
|
|
1523
|
+
__decorateClass([
|
|
1524
|
+
Property({ name: "deleted_at", type: Date, nullable: true })
|
|
1525
|
+
], SalesReturn.prototype, "deletedAt", 2);
|
|
1526
|
+
__decorateClass([
|
|
1527
|
+
OneToMany(() => SalesReturnLine, (line) => line.salesReturn)
|
|
1528
|
+
], SalesReturn.prototype, "lines", 2);
|
|
1529
|
+
SalesReturn = __decorateClass([
|
|
1530
|
+
Entity({ tableName: "sales_returns" }),
|
|
1531
|
+
Index({ name: "sales_returns_scope_idx", properties: ["order", "organizationId", "tenantId"] }),
|
|
1532
|
+
Index({ name: "sales_returns_status_idx", properties: ["organizationId", "tenantId", "status"] }),
|
|
1533
|
+
Unique({ name: "sales_returns_number_unique", properties: ["organizationId", "tenantId", "returnNumber"] })
|
|
1534
|
+
], SalesReturn);
|
|
1535
|
+
OptionalProps;
|
|
1536
|
+
let SalesReturnLine = class {
|
|
1537
|
+
constructor() {
|
|
1538
|
+
this.quantityReturned = "0";
|
|
1539
|
+
this.unitPriceNet = "0";
|
|
1540
|
+
this.unitPriceGross = "0";
|
|
1541
|
+
this.totalNetAmount = "0";
|
|
1542
|
+
this.totalGrossAmount = "0";
|
|
1543
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
1544
|
+
this.updatedAt = /* @__PURE__ */ new Date();
|
|
1545
|
+
}
|
|
1546
|
+
};
|
|
1547
|
+
__decorateClass([
|
|
1548
|
+
PrimaryKey({ type: "uuid", defaultRaw: "gen_random_uuid()" })
|
|
1549
|
+
], SalesReturnLine.prototype, "id", 2);
|
|
1550
|
+
__decorateClass([
|
|
1551
|
+
ManyToOne(() => SalesReturn, { fieldName: "return_id" })
|
|
1552
|
+
], SalesReturnLine.prototype, "salesReturn", 2);
|
|
1553
|
+
__decorateClass([
|
|
1554
|
+
ManyToOne(() => SalesOrderLine, { fieldName: "order_line_id" })
|
|
1555
|
+
], SalesReturnLine.prototype, "orderLine", 2);
|
|
1556
|
+
__decorateClass([
|
|
1557
|
+
Property({ name: "organization_id", type: "uuid" })
|
|
1558
|
+
], SalesReturnLine.prototype, "organizationId", 2);
|
|
1559
|
+
__decorateClass([
|
|
1560
|
+
Property({ name: "tenant_id", type: "uuid" })
|
|
1561
|
+
], SalesReturnLine.prototype, "tenantId", 2);
|
|
1562
|
+
__decorateClass([
|
|
1563
|
+
Property({ name: "quantity_returned", type: "numeric", precision: 18, scale: 4, default: "0" })
|
|
1564
|
+
], SalesReturnLine.prototype, "quantityReturned", 2);
|
|
1565
|
+
__decorateClass([
|
|
1566
|
+
Property({ name: "unit_price_net", type: "numeric", precision: 18, scale: 4, default: "0" })
|
|
1567
|
+
], SalesReturnLine.prototype, "unitPriceNet", 2);
|
|
1568
|
+
__decorateClass([
|
|
1569
|
+
Property({ name: "unit_price_gross", type: "numeric", precision: 18, scale: 4, default: "0" })
|
|
1570
|
+
], SalesReturnLine.prototype, "unitPriceGross", 2);
|
|
1571
|
+
__decorateClass([
|
|
1572
|
+
Property({ name: "total_net_amount", type: "numeric", precision: 18, scale: 4, default: "0" })
|
|
1573
|
+
], SalesReturnLine.prototype, "totalNetAmount", 2);
|
|
1574
|
+
__decorateClass([
|
|
1575
|
+
Property({ name: "total_gross_amount", type: "numeric", precision: 18, scale: 4, default: "0" })
|
|
1576
|
+
], SalesReturnLine.prototype, "totalGrossAmount", 2);
|
|
1577
|
+
__decorateClass([
|
|
1578
|
+
Property({ name: "created_at", type: Date, onCreate: () => /* @__PURE__ */ new Date() })
|
|
1579
|
+
], SalesReturnLine.prototype, "createdAt", 2);
|
|
1580
|
+
__decorateClass([
|
|
1581
|
+
Property({ name: "updated_at", type: Date, onUpdate: () => /* @__PURE__ */ new Date() })
|
|
1582
|
+
], SalesReturnLine.prototype, "updatedAt", 2);
|
|
1583
|
+
__decorateClass([
|
|
1584
|
+
Property({ name: "deleted_at", type: Date, nullable: true })
|
|
1585
|
+
], SalesReturnLine.prototype, "deletedAt", 2);
|
|
1586
|
+
SalesReturnLine = __decorateClass([
|
|
1587
|
+
Entity({ tableName: "sales_return_lines" }),
|
|
1588
|
+
Index({ name: "sales_return_lines_return_idx", properties: ["salesReturn", "organizationId", "tenantId"] }),
|
|
1589
|
+
Index({ name: "sales_return_lines_order_line_idx", properties: ["orderLine", "organizationId", "tenantId"] })
|
|
1590
|
+
], SalesReturnLine);
|
|
1479
1591
|
let SalesInvoice = class {
|
|
1480
1592
|
constructor() {
|
|
1481
1593
|
this.subtotalNetAmount = "0";
|
|
@@ -2187,6 +2299,8 @@ export {
|
|
|
2187
2299
|
SalesQuote,
|
|
2188
2300
|
SalesQuoteAdjustment,
|
|
2189
2301
|
SalesQuoteLine,
|
|
2302
|
+
SalesReturn,
|
|
2303
|
+
SalesReturnLine,
|
|
2190
2304
|
SalesSettings,
|
|
2191
2305
|
SalesShipment,
|
|
2192
2306
|
SalesShipmentItem,
|