@open-mercato/core 0.6.6-develop.5617.1.62538c48ca → 0.6.6-develop.5619.1.29f01e2c42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/sales/acl.js +6 -0
- package/dist/modules/sales/acl.js.map +2 -2
- package/dist/modules/sales/api/returns/route.js +43 -3
- package/dist/modules/sales/api/returns/route.js.map +2 -2
- package/dist/modules/sales/commands/returns.js +473 -213
- package/dist/modules/sales/commands/returns.js.map +2 -2
- package/dist/modules/sales/commands/shared.js +2 -0
- package/dist/modules/sales/commands/shared.js.map +2 -2
- package/dist/modules/sales/components/documents/ReturnEditDialog.js +125 -0
- package/dist/modules/sales/components/documents/ReturnEditDialog.js.map +7 -0
- package/dist/modules/sales/components/documents/ReturnsSection.js +102 -6
- package/dist/modules/sales/components/documents/ReturnsSection.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/setup.js +1 -0
- package/dist/modules/sales/setup.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/sales/acl.ts +6 -0
- package/src/modules/sales/api/returns/route.ts +41 -3
- package/src/modules/sales/commands/returns.ts +561 -229
- package/src/modules/sales/commands/shared.ts +1 -0
- package/src/modules/sales/components/documents/ReturnEditDialog.tsx +157 -0
- package/src/modules/sales/components/documents/ReturnsSection.tsx +105 -3
- package/src/modules/sales/data/validators.ts +15 -0
- package/src/modules/sales/i18n/de.json +11 -0
- package/src/modules/sales/i18n/en.json +11 -0
- package/src/modules/sales/i18n/es.json +11 -0
- package/src/modules/sales/i18n/pl.json +11 -0
- package/src/modules/sales/setup.ts +1 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -82,6 +82,12 @@ const features = [
|
|
|
82
82
|
module: "sales",
|
|
83
83
|
dependsOn: ["sales.returns.view", "sales.orders.manage"]
|
|
84
84
|
},
|
|
85
|
+
{
|
|
86
|
+
id: "sales.returns.manage",
|
|
87
|
+
title: "Edit and delete order returns",
|
|
88
|
+
module: "sales",
|
|
89
|
+
dependsOn: ["sales.returns.create"]
|
|
90
|
+
},
|
|
85
91
|
{
|
|
86
92
|
id: "sales.invoices.manage",
|
|
87
93
|
title: "Manage sales invoices",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/sales/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n {\n id: 'sales.orders.view',\n title: 'View sales orders',\n module: 'sales',\n dependsOn: [\n 'sales.channels.view',\n 'sales.settings.view',\n 'customers.people.view',\n 'catalog.products.view',\n 'currencies.view',\n ],\n },\n {\n id: 'sales.orders.manage',\n title: 'Manage sales orders',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.orders.approve',\n title: 'Approve sales orders',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.widgets.new-orders',\n title: 'View new orders widget',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.widgets.new-quotes',\n title: 'View new quotes widget',\n module: 'sales',\n dependsOn: ['sales.quotes.view'],\n },\n {\n id: 'sales.quotes.view',\n title: 'View sales quotes',\n module: 'sales',\n dependsOn: [\n 'sales.channels.view',\n 'sales.settings.view',\n 'customers.people.view',\n 'catalog.products.view',\n ],\n },\n {\n id: 'sales.quotes.manage',\n title: 'Manage sales quotes',\n module: 'sales',\n dependsOn: ['sales.quotes.view'],\n },\n {\n id: 'sales.documents.number.edit',\n title: 'Edit sales document numbers',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.shipments.manage',\n title: 'Manage order shipments',\n module: 'sales',\n dependsOn: ['sales.orders.view', 'shipping_carriers.view'],\n },\n {\n id: 'sales.payments.manage',\n title: 'Manage order payments',\n module: 'sales',\n dependsOn: ['sales.orders.view', 'payment_gateways.view'],\n },\n {\n id: 'sales.returns.view',\n title: 'View order returns',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.returns.create',\n title: 'Create order returns',\n module: 'sales',\n dependsOn: ['sales.returns.view', 'sales.orders.manage'],\n },\n {\n id: 'sales.invoices.manage',\n title: 'Manage sales invoices',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.credit_memos.manage',\n title: 'Manage credit memos',\n module: 'sales',\n dependsOn: ['sales.invoices.manage'],\n },\n { id: 'sales.channels.view', title: 'View sales channels', module: 'sales' },\n {\n id: 'sales.channels.manage',\n title: 'Manage sales channels',\n module: 'sales',\n dependsOn: ['sales.channels.view'],\n },\n { id: 'sales.settings.view', title: 'View sales configuration', module: 'sales' },\n {\n id: 'sales.settings.manage',\n title: 'Manage sales configuration',\n module: 'sales',\n dependsOn: ['sales.settings.view'],\n },\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB,wBAAwB;AAAA,EAC3D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB,uBAAuB;AAAA,EAC1D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,sBAAsB,qBAAqB;AAAA,EACzD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA,EAAE,IAAI,uBAAuB,OAAO,uBAAuB,QAAQ,QAAQ;AAAA,EAC3E;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB;AAAA,EACnC;AAAA,EACA,EAAE,IAAI,uBAAuB,OAAO,4BAA4B,QAAQ,QAAQ;AAAA,EAChF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB;AAAA,EACnC;AACF;AAEA,IAAO,cAAQ;",
|
|
4
|
+
"sourcesContent": ["export const features = [\n {\n id: 'sales.orders.view',\n title: 'View sales orders',\n module: 'sales',\n dependsOn: [\n 'sales.channels.view',\n 'sales.settings.view',\n 'customers.people.view',\n 'catalog.products.view',\n 'currencies.view',\n ],\n },\n {\n id: 'sales.orders.manage',\n title: 'Manage sales orders',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.orders.approve',\n title: 'Approve sales orders',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.widgets.new-orders',\n title: 'View new orders widget',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.widgets.new-quotes',\n title: 'View new quotes widget',\n module: 'sales',\n dependsOn: ['sales.quotes.view'],\n },\n {\n id: 'sales.quotes.view',\n title: 'View sales quotes',\n module: 'sales',\n dependsOn: [\n 'sales.channels.view',\n 'sales.settings.view',\n 'customers.people.view',\n 'catalog.products.view',\n ],\n },\n {\n id: 'sales.quotes.manage',\n title: 'Manage sales quotes',\n module: 'sales',\n dependsOn: ['sales.quotes.view'],\n },\n {\n id: 'sales.documents.number.edit',\n title: 'Edit sales document numbers',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.shipments.manage',\n title: 'Manage order shipments',\n module: 'sales',\n dependsOn: ['sales.orders.view', 'shipping_carriers.view'],\n },\n {\n id: 'sales.payments.manage',\n title: 'Manage order payments',\n module: 'sales',\n dependsOn: ['sales.orders.view', 'payment_gateways.view'],\n },\n {\n id: 'sales.returns.view',\n title: 'View order returns',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.returns.create',\n title: 'Create order returns',\n module: 'sales',\n dependsOn: ['sales.returns.view', 'sales.orders.manage'],\n },\n {\n id: 'sales.returns.manage',\n title: 'Edit and delete order returns',\n module: 'sales',\n dependsOn: ['sales.returns.create'],\n },\n {\n id: 'sales.invoices.manage',\n title: 'Manage sales invoices',\n module: 'sales',\n dependsOn: ['sales.orders.view'],\n },\n {\n id: 'sales.credit_memos.manage',\n title: 'Manage credit memos',\n module: 'sales',\n dependsOn: ['sales.invoices.manage'],\n },\n { id: 'sales.channels.view', title: 'View sales channels', module: 'sales' },\n {\n id: 'sales.channels.manage',\n title: 'Manage sales channels',\n module: 'sales',\n dependsOn: ['sales.channels.view'],\n },\n { id: 'sales.settings.view', title: 'View sales configuration', module: 'sales' },\n {\n id: 'sales.settings.manage',\n title: 'Manage sales configuration',\n module: 'sales',\n dependsOn: ['sales.settings.view'],\n },\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB,wBAAwB;AAAA,EAC3D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB,uBAAuB;AAAA,EAC1D;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,sBAAsB,qBAAqB;AAAA,EACzD;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,uBAAuB;AAAA,EACrC;AAAA,EACA,EAAE,IAAI,uBAAuB,OAAO,uBAAuB,QAAQ,QAAQ;AAAA,EAC3E;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB;AAAA,EACnC;AAAA,EACA,EAAE,IAAI,uBAAuB,OAAO,4BAA4B,QAAQ,QAAQ;AAAA,EAChF;AAAA,IACE,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW,CAAC,qBAAqB;AAAA,EACnC;AACF;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,8 @@ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
|
4
4
|
import { splitCustomFieldPayload } from "@open-mercato/shared/lib/crud/custom-fields";
|
|
5
5
|
import { withScopedPayload } from "../utils.js";
|
|
6
6
|
import { SalesReturn, SalesReturnLine } from "../../data/entities.js";
|
|
7
|
-
import { returnCreateSchema } from "../../data/validators.js";
|
|
7
|
+
import { returnCreateSchema, returnUpdateSchema, returnDeleteSchema } from "../../data/validators.js";
|
|
8
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
8
9
|
import { E } from "../../../../generated/entities.ids.generated.js";
|
|
9
10
|
import * as F from "../../../../generated/entities/sales_return/index.js";
|
|
10
11
|
import { createPagedListResponseSchema } from "../openapi.js";
|
|
@@ -19,7 +20,9 @@ const listSchema = z.object({
|
|
|
19
20
|
}).passthrough();
|
|
20
21
|
const routeMetadata = {
|
|
21
22
|
GET: { requireAuth: true, requireFeatures: ["sales.returns.view"] },
|
|
22
|
-
POST: { requireAuth: true, requireFeatures: ["sales.returns.create"] }
|
|
23
|
+
POST: { requireAuth: true, requireFeatures: ["sales.returns.create"] },
|
|
24
|
+
PUT: { requireAuth: true, requireFeatures: ["sales.returns.manage"] },
|
|
25
|
+
DELETE: { requireAuth: true, requireFeatures: ["sales.returns.manage"] }
|
|
23
26
|
};
|
|
24
27
|
const toNumber = (value) => {
|
|
25
28
|
if (typeof value === "number") return value;
|
|
@@ -81,6 +84,31 @@ const crud = makeCrudRoute({
|
|
|
81
84
|
},
|
|
82
85
|
response: ({ result }) => ({ id: result?.returnId ?? null }),
|
|
83
86
|
status: 201
|
|
87
|
+
},
|
|
88
|
+
update: {
|
|
89
|
+
commandId: "sales.returns.update",
|
|
90
|
+
schema: rawBodySchema,
|
|
91
|
+
mapInput: async ({ raw, ctx }) => {
|
|
92
|
+
const { translate } = await resolveTranslations();
|
|
93
|
+
const scoped = withScopedPayload(raw ?? {}, ctx, translate);
|
|
94
|
+
const { base } = splitCustomFieldPayload(scoped);
|
|
95
|
+
return returnUpdateSchema.parse(base);
|
|
96
|
+
},
|
|
97
|
+
response: () => ({ ok: true })
|
|
98
|
+
},
|
|
99
|
+
delete: {
|
|
100
|
+
commandId: "sales.returns.delete",
|
|
101
|
+
schema: rawBodySchema,
|
|
102
|
+
mapInput: async ({ raw, ctx }) => {
|
|
103
|
+
const { translate } = await resolveTranslations();
|
|
104
|
+
const merged = { ...raw?.body ?? {}, ...raw?.query ?? {} };
|
|
105
|
+
const payload = returnDeleteSchema.parse(withScopedPayload(merged, ctx, translate));
|
|
106
|
+
if (!payload.id || !payload.orderId) {
|
|
107
|
+
throw new CrudHttpError(400, { error: translate("sales.returns.notFound", "Return not found.") });
|
|
108
|
+
}
|
|
109
|
+
return payload;
|
|
110
|
+
},
|
|
111
|
+
response: () => ({ ok: true })
|
|
84
112
|
}
|
|
85
113
|
},
|
|
86
114
|
hooks: {
|
|
@@ -119,7 +147,7 @@ const crud = makeCrudRoute({
|
|
|
119
147
|
}
|
|
120
148
|
}
|
|
121
149
|
});
|
|
122
|
-
const { GET, POST } = crud;
|
|
150
|
+
const { GET, POST, PUT, DELETE } = crud;
|
|
123
151
|
const metadata = routeMetadata;
|
|
124
152
|
const returnSchema = z.object({
|
|
125
153
|
id: z.string().uuid(),
|
|
@@ -148,12 +176,24 @@ const openApi = {
|
|
|
148
176
|
summary: "Create return",
|
|
149
177
|
requestBody: { schema: returnCreateSchema },
|
|
150
178
|
responses: [{ status: 201, description: "Return created", schema: z.object({ id: z.string().uuid().nullable() }) }]
|
|
179
|
+
},
|
|
180
|
+
PUT: {
|
|
181
|
+
summary: "Update return",
|
|
182
|
+
requestBody: { schema: returnUpdateSchema },
|
|
183
|
+
responses: [{ status: 200, description: "Return updated", schema: z.object({ ok: z.boolean() }) }]
|
|
184
|
+
},
|
|
185
|
+
DELETE: {
|
|
186
|
+
summary: "Delete return",
|
|
187
|
+
requestBody: { schema: returnDeleteSchema },
|
|
188
|
+
responses: [{ status: 200, description: "Return deleted", schema: z.object({ ok: z.boolean() }) }]
|
|
151
189
|
}
|
|
152
190
|
}
|
|
153
191
|
};
|
|
154
192
|
export {
|
|
193
|
+
DELETE,
|
|
155
194
|
GET,
|
|
156
195
|
POST,
|
|
196
|
+
PUT,
|
|
157
197
|
metadata,
|
|
158
198
|
openApi
|
|
159
199
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/returns/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { withScopedPayload } from '../utils'\nimport { SalesReturn, SalesReturnLine } from '../../data/entities'\nimport { returnCreateSchema } from '../../data/validators'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/sales_return'\nimport { createPagedListResponseSchema } from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n orderId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.returns.view'] },\n POST: { requireAuth: true, requireFeatures: ['sales.returns.create'] },\n}\n\nconst toNumber = (value: unknown): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesReturn,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.sales.sales_return,\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_return,\n fields: [\n F.id,\n 'order_id',\n F.return_number,\n F.status_entry_id,\n F.status,\n F.reason,\n F.notes,\n F.returned_at,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n returnedAt: F.returned_at,\n },\n buildFilters: async (query: Record<string, unknown>) => {\n const filters: Record<string, unknown> = {}\n if (typeof query.orderId === 'string' && query.orderId.length > 0) {\n filters.order_id = { $eq: query.orderId }\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.returns.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base } = splitCustomFieldPayload(scoped)\n return returnCreateSchema.parse(base)\n },\n response: ({ result }) => ({ id: result?.returnId ?? null }),\n status: 201,\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const returnIds = items\n .map((item: unknown) => (item && typeof item === 'object' ? (item as Record<string, unknown>).id : null))\n .filter((value: string | null): value is string => typeof value === 'string')\n if (!returnIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const lines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: { $in: returnIds }, deletedAt: null },\n {},\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n const totals = lines.reduce<Map<string, { net: number; gross: number }>>((acc, line) => {\n const returnId = typeof line.salesReturn === 'string' ? line.salesReturn : line.salesReturn?.id ?? null\n if (!returnId) return acc\n const current = acc.get(returnId) ?? { net: 0, gross: 0 }\n current.net += toNumber(line.totalNetAmount)\n current.gross += toNumber(line.totalGrossAmount)\n acc.set(returnId, current)\n return acc\n }, new Map())\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const map = item as Record<string, unknown>\n const id = map.id\n if (typeof id !== 'string') return\n const sum = totals.get(id)\n if (!sum) return\n map['total_net_amount'] = sum.net\n map['total_gross_amount'] = sum.gross\n })\n },\n },\n})\n\nconst { GET, POST } = crud\n\nexport const metadata = routeMetadata\nexport { GET, POST }\n\nconst returnSchema = z\n .object({\n id: z.string().uuid(),\n order_id: z.string().uuid(),\n return_number: z.string(),\n status_entry_id: z.string().uuid().nullable().optional(),\n status: z.string().nullable().optional(),\n reason: z.string().nullable().optional(),\n notes: z.string().nullable().optional(),\n returned_at: z.string().nullable().optional(),\n created_at: z.string(),\n updated_at: z.string(),\n total_net_amount: z.number().optional(),\n total_gross_amount: z.number().optional(),\n })\n .passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Manage order returns',\n methods: {\n GET: {\n summary: 'List returns',\n query: listSchema,\n responses: [{ status: 200, description: 'Returns list', schema: createPagedListResponseSchema(returnSchema) }],\n },\n POST: {\n summary: 'Create return',\n requestBody: { schema: returnCreateSchema },\n responses: [{ status: 201, description: 'Return created', schema: z.object({ id: z.string().uuid().nullable() }) }],\n },\n },\n}\n\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AACxC,SAAS,yBAAyB;AAClC,SAAS,aAAa,uBAAuB;AAC7C,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport { withScopedPayload } from '../utils'\nimport { SalesReturn, SalesReturnLine } from '../../data/entities'\nimport { returnCreateSchema, returnUpdateSchema, returnDeleteSchema } from '../../data/validators'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { E } from '#generated/entities.ids.generated'\nimport * as F from '#generated/entities/sales_return'\nimport { createPagedListResponseSchema } from '../openapi'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(200).default(50),\n orderId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['sales.returns.view'] },\n POST: { requireAuth: true, requireFeatures: ['sales.returns.create'] },\n PUT: { requireAuth: true, requireFeatures: ['sales.returns.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['sales.returns.manage'] },\n}\n\nconst toNumber = (value: unknown): number => {\n if (typeof value === 'number') return value\n if (typeof value === 'string') {\n const parsed = Number(value)\n if (!Number.isNaN(parsed)) return parsed\n }\n return 0\n}\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesReturn,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n softDeleteField: 'deletedAt',\n },\n indexer: {\n entityType: E.sales.sales_return,\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_return,\n fields: [\n F.id,\n 'order_id',\n F.return_number,\n F.status_entry_id,\n F.status,\n F.reason,\n F.notes,\n F.returned_at,\n F.created_at,\n F.updated_at,\n ],\n sortFieldMap: {\n createdAt: F.created_at,\n updatedAt: F.updated_at,\n returnedAt: F.returned_at,\n },\n buildFilters: async (query: Record<string, unknown>) => {\n const filters: Record<string, unknown> = {}\n if (typeof query.orderId === 'string' && query.orderId.length > 0) {\n filters.order_id = { $eq: query.orderId }\n }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.returns.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base } = splitCustomFieldPayload(scoped)\n return returnCreateSchema.parse(base)\n },\n response: ({ result }) => ({ id: result?.returnId ?? null }),\n status: 201,\n },\n update: {\n commandId: 'sales.returns.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const scoped = withScopedPayload(raw ?? {}, ctx, translate)\n const { base } = splitCustomFieldPayload(scoped)\n return returnUpdateSchema.parse(base)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'sales.returns.delete',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const merged = { ...(raw?.body ?? {}), ...(raw?.query ?? {}) }\n const payload = returnDeleteSchema.parse(withScopedPayload(merged, ctx, translate))\n if (!payload.id || !payload.orderId) {\n throw new CrudHttpError(400, { error: translate('sales.returns.notFound', 'Return not found.') })\n }\n return payload\n },\n response: () => ({ ok: true }),\n },\n },\n hooks: {\n afterList: async (payload, ctx) => {\n const items = Array.isArray(payload.items) ? payload.items : []\n if (!items.length) return\n const returnIds = items\n .map((item: unknown) => (item && typeof item === 'object' ? (item as Record<string, unknown>).id : null))\n .filter((value: string | null): value is string => typeof value === 'string')\n if (!returnIds.length) return\n const em = ctx.container.resolve('em') as EntityManager\n const lines = await findWithDecryption(\n em,\n SalesReturnLine,\n { salesReturn: { $in: returnIds }, deletedAt: null },\n {},\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.auth?.orgId ?? null },\n )\n const totals = lines.reduce<Map<string, { net: number; gross: number }>>((acc, line) => {\n const returnId = typeof line.salesReturn === 'string' ? line.salesReturn : line.salesReturn?.id ?? null\n if (!returnId) return acc\n const current = acc.get(returnId) ?? { net: 0, gross: 0 }\n current.net += toNumber(line.totalNetAmount)\n current.gross += toNumber(line.totalGrossAmount)\n acc.set(returnId, current)\n return acc\n }, new Map())\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const map = item as Record<string, unknown>\n const id = map.id\n if (typeof id !== 'string') return\n const sum = totals.get(id)\n if (!sum) return\n map['total_net_amount'] = sum.net\n map['total_gross_amount'] = sum.gross\n })\n },\n },\n})\n\nconst { GET, POST, PUT, DELETE } = crud\n\nexport const metadata = routeMetadata\nexport { GET, POST, PUT, DELETE }\n\nconst returnSchema = z\n .object({\n id: z.string().uuid(),\n order_id: z.string().uuid(),\n return_number: z.string(),\n status_entry_id: z.string().uuid().nullable().optional(),\n status: z.string().nullable().optional(),\n reason: z.string().nullable().optional(),\n notes: z.string().nullable().optional(),\n returned_at: z.string().nullable().optional(),\n created_at: z.string(),\n updated_at: z.string(),\n total_net_amount: z.number().optional(),\n total_gross_amount: z.number().optional(),\n })\n .passthrough()\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Manage order returns',\n methods: {\n GET: {\n summary: 'List returns',\n query: listSchema,\n responses: [{ status: 200, description: 'Returns list', schema: createPagedListResponseSchema(returnSchema) }],\n },\n POST: {\n summary: 'Create return',\n requestBody: { schema: returnCreateSchema },\n responses: [{ status: 201, description: 'Return created', schema: z.object({ id: z.string().uuid().nullable() }) }],\n },\n PUT: {\n summary: 'Update return',\n requestBody: { schema: returnUpdateSchema },\n responses: [{ status: 200, description: 'Return updated', schema: z.object({ ok: z.boolean() }) }],\n },\n DELETE: {\n summary: 'Delete return',\n requestBody: { schema: returnDeleteSchema },\n responses: [{ status: 200, description: 'Return deleted', schema: z.object({ ok: z.boolean() }) }],\n },\n },\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|