@open-mercato/core 0.4.2-canary-ccd610ad18 → 0.4.2-canary-92bc12ea91

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/dist/modules/api_docs/backend/docs/page.js +4 -1
  2. package/dist/modules/api_docs/backend/docs/page.js.map +2 -2
  3. package/dist/modules/auth/lib/setup-app.js +4 -0
  4. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  5. package/dist/modules/sales/acl.js +3 -1
  6. package/dist/modules/sales/acl.js.map +2 -2
  7. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +163 -0
  8. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +7 -0
  9. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +165 -0
  10. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +7 -0
  11. package/dist/modules/sales/api/dashboard/widgets/utils.js +38 -0
  12. package/dist/modules/sales/api/dashboard/widgets/utils.js.map +7 -0
  13. package/dist/modules/sales/lib/customerSnapshot.js +21 -0
  14. package/dist/modules/sales/lib/customerSnapshot.js.map +7 -0
  15. package/dist/modules/sales/lib/dateRange.js +39 -0
  16. package/dist/modules/sales/lib/dateRange.js.map +7 -0
  17. package/dist/modules/sales/widgets/dashboard/new-orders/config.js +32 -0
  18. package/dist/modules/sales/widgets/dashboard/new-orders/config.js.map +7 -0
  19. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +252 -0
  20. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +7 -0
  21. package/dist/modules/sales/widgets/dashboard/new-orders/widget.js +33 -0
  22. package/dist/modules/sales/widgets/dashboard/new-orders/widget.js.map +7 -0
  23. package/dist/modules/sales/widgets/dashboard/new-quotes/config.js +32 -0
  24. package/dist/modules/sales/widgets/dashboard/new-quotes/config.js.map +7 -0
  25. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +272 -0
  26. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +7 -0
  27. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.js +33 -0
  28. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.js.map +7 -0
  29. package/package.json +2 -2
  30. package/src/modules/api_docs/backend/docs/page.tsx +2 -1
  31. package/src/modules/auth/lib/setup-app.ts +4 -0
  32. package/src/modules/customers/README.md +2 -1
  33. package/src/modules/entities/README.md +1 -1
  34. package/src/modules/sales/acl.ts +2 -0
  35. package/src/modules/sales/api/dashboard/widgets/new-orders/__tests__/route.test.ts +60 -0
  36. package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +192 -0
  37. package/src/modules/sales/api/dashboard/widgets/new-quotes/__tests__/route.test.ts +61 -0
  38. package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +194 -0
  39. package/src/modules/sales/api/dashboard/widgets/utils.ts +53 -0
  40. package/src/modules/sales/i18n/de.json +32 -1
  41. package/src/modules/sales/i18n/en.json +32 -1
  42. package/src/modules/sales/i18n/es.json +32 -1
  43. package/src/modules/sales/i18n/pl.json +32 -1
  44. package/src/modules/sales/lib/__tests__/dateRange.test.ts +26 -0
  45. package/src/modules/sales/lib/customerSnapshot.ts +17 -0
  46. package/src/modules/sales/lib/dateRange.ts +42 -0
  47. package/src/modules/sales/widgets/dashboard/new-orders/__tests__/config.test.ts +28 -0
  48. package/src/modules/sales/widgets/dashboard/new-orders/config.ts +49 -0
  49. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +295 -0
  50. package/src/modules/sales/widgets/dashboard/new-orders/widget.ts +33 -0
  51. package/src/modules/sales/widgets/dashboard/new-quotes/__tests__/config.test.ts +28 -0
  52. package/src/modules/sales/widgets/dashboard/new-quotes/config.ts +49 -0
  53. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +322 -0
  54. package/src/modules/sales/widgets/dashboard/new-quotes/widget.ts +33 -0
@@ -0,0 +1,192 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { FilterQuery } from '@mikro-orm/core'
4
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
7
+ import { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
+ import { SalesOrder } from '../../../../data/entities'
9
+ import { resolveWidgetScope, type WidgetScopeContext } from '../utils'
10
+ import { extractCustomerName } from '../../../../lib/customerSnapshot'
11
+ import { parseDateInput, resolveDateRange, type DatePeriodOption } from '../../../../lib/dateRange'
12
+
13
+ const querySchema = z.object({
14
+ limit: z.coerce.number().min(1).max(20).default(5),
15
+ datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),
16
+ customFrom: z.string().optional(),
17
+ customTo: z.string().optional(),
18
+ tenantId: z.string().uuid().optional(),
19
+ organizationId: z.string().uuid().optional(),
20
+ })
21
+
22
+ export const metadata = {
23
+ GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-orders'] },
24
+ }
25
+
26
+ type WidgetContext = WidgetScopeContext & {
27
+ limit: number
28
+ datePeriod: DatePeriodOption
29
+ customFrom?: string
30
+ customTo?: string
31
+ }
32
+
33
+ async function resolveContext(
34
+ req: Request,
35
+ translate: (key: string, fallback?: string) => string,
36
+ ): Promise<WidgetContext> {
37
+ const url = new URL(req.url)
38
+ const rawQuery: Record<string, string> = {}
39
+ for (const [key, value] of url.searchParams.entries()) {
40
+ rawQuery[key] = value
41
+ }
42
+ const parsed = querySchema.safeParse(rawQuery)
43
+ if (!parsed.success) {
44
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })
45
+ }
46
+
47
+ const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {
48
+ tenantId: parsed.data.tenantId ?? null,
49
+ organizationId: parsed.data.organizationId ?? null,
50
+ })
51
+
52
+ return {
53
+ container,
54
+ em,
55
+ tenantId,
56
+ organizationIds,
57
+ limit: parsed.data.limit,
58
+ datePeriod: parsed.data.datePeriod,
59
+ customFrom: parsed.data.customFrom,
60
+ customTo: parsed.data.customTo,
61
+ }
62
+ }
63
+
64
+ function resolveDateRangeOrThrow(
65
+ period: DatePeriodOption,
66
+ customFrom: string | undefined,
67
+ customTo: string | undefined,
68
+ translate: (key: string, fallback?: string) => string,
69
+ ): { from: Date; to: Date } {
70
+ const parsedFrom = parseDateInput(customFrom)
71
+ const parsedTo = parseDateInput(customTo)
72
+ if (customFrom && !parsedFrom) {
73
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })
74
+ }
75
+ if (customTo && !parsedTo) {
76
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })
77
+ }
78
+ return resolveDateRange(period, parsedFrom, parsedTo)
79
+ }
80
+
81
+ export async function GET(req: Request) {
82
+ const { translate } = await resolveTranslations()
83
+ try {
84
+ const { em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(
85
+ req,
86
+ translate,
87
+ )
88
+
89
+ const { from, to } = resolveDateRangeOrThrow(datePeriod, customFrom, customTo, translate)
90
+
91
+ const where: FilterQuery<SalesOrder> = {
92
+ tenantId,
93
+ deletedAt: null,
94
+ createdAt: { $gte: from, $lte: to },
95
+ }
96
+
97
+ if (Array.isArray(organizationIds)) {
98
+ where.organizationId =
99
+ organizationIds.length === 1 ? organizationIds[0] : { $in: Array.from(new Set(organizationIds)) }
100
+ }
101
+
102
+ const [items, total] = await findAndCountWithDecryption(
103
+ em,
104
+ SalesOrder,
105
+ where,
106
+ {
107
+ limit,
108
+ orderBy: { createdAt: 'desc' as const },
109
+ },
110
+ { tenantId },
111
+ )
112
+
113
+ const responseItems = items.map((order) => ({
114
+ id: order.id,
115
+ orderNumber: order.orderNumber,
116
+ status: order.status ?? null,
117
+ fulfillmentStatus: order.fulfillmentStatus ?? null,
118
+ paymentStatus: order.paymentStatus ?? null,
119
+ customerName: extractCustomerName(order.customerSnapshot) ?? null,
120
+ customerEntityId: order.customerEntityId ?? null,
121
+ netAmount: order.grandTotalNetAmount,
122
+ grossAmount: order.grandTotalGrossAmount,
123
+ currency: order.currencyCode ?? null,
124
+ createdAt: order.createdAt.toISOString(),
125
+ }))
126
+
127
+ return NextResponse.json({
128
+ items: responseItems,
129
+ total,
130
+ dateRange: {
131
+ from: from.toISOString(),
132
+ to: to.toISOString(),
133
+ },
134
+ })
135
+ } catch (err) {
136
+ if (err instanceof CrudHttpError) {
137
+ return NextResponse.json(err.body, { status: err.status })
138
+ }
139
+ console.error('sales.widgets.newOrders failed', err)
140
+ return NextResponse.json(
141
+ { error: translate('sales.widgets.newOrders.error', 'Failed to load recent orders') },
142
+ { status: 500 },
143
+ )
144
+ }
145
+ }
146
+
147
+ const orderItemSchema = z.object({
148
+ id: z.string().uuid(),
149
+ orderNumber: z.string(),
150
+ status: z.string().nullable(),
151
+ fulfillmentStatus: z.string().nullable(),
152
+ paymentStatus: z.string().nullable(),
153
+ customerName: z.string().nullable(),
154
+ customerEntityId: z.string().uuid().nullable(),
155
+ netAmount: z.string(),
156
+ grossAmount: z.string(),
157
+ currency: z.string().nullable(),
158
+ createdAt: z.string(),
159
+ })
160
+
161
+ const responseSchema = z.object({
162
+ items: z.array(orderItemSchema),
163
+ total: z.number(),
164
+ dateRange: z.object({
165
+ from: z.string(),
166
+ to: z.string(),
167
+ }),
168
+ })
169
+
170
+ const widgetErrorSchema = z.object({
171
+ error: z.string(),
172
+ })
173
+
174
+ export const openApi: OpenApiRouteDoc = {
175
+ tag: 'Sales',
176
+ summary: 'New orders widget',
177
+ methods: {
178
+ GET: {
179
+ summary: 'Fetch recently created sales orders',
180
+ description: 'Returns the most recent sales orders within the scoped tenant/organization.',
181
+ query: querySchema,
182
+ responses: [
183
+ { status: 200, description: 'Widget payload', schema: responseSchema },
184
+ ],
185
+ errors: [
186
+ { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },
187
+ { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },
188
+ { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },
189
+ ],
190
+ },
191
+ },
192
+ }
@@ -0,0 +1,61 @@
1
+ import { GET } from '../route'
2
+
3
+ jest.mock('../../utils', () => ({
4
+ resolveWidgetScope: jest.fn(async () => ({
5
+ container: {},
6
+ em: {},
7
+ tenantId: '33333333-3333-3333-3333-333333333333',
8
+ organizationIds: ['22222222-2222-2222-2222-222222222222'],
9
+ })),
10
+ }))
11
+
12
+ jest.mock('@open-mercato/shared/lib/i18n/server', () => ({
13
+ resolveTranslations: async () => ({
14
+ translate: (k: string, fb?: string) => fb ?? k,
15
+ }),
16
+ }))
17
+
18
+ jest.mock('@open-mercato/shared/lib/encryption/find', () => ({
19
+ findAndCountWithDecryption: jest.fn(async () => [
20
+ [
21
+ {
22
+ id: '11111111-1111-1111-1111-111111111111',
23
+ quoteNumber: 'QT-2001',
24
+ status: 'draft',
25
+ customerSnapshot: { displayName: 'Acme Corp' },
26
+ customerEntityId: '44444444-4444-4444-4444-444444444444',
27
+ validFrom: new Date('2026-01-20T00:00:00.000Z'),
28
+ validUntil: new Date('2026-02-01T00:00:00.000Z'),
29
+ grandTotalNetAmount: '80.00',
30
+ grandTotalGrossAmount: '96.00',
31
+ currencyCode: 'USD',
32
+ createdAt: new Date('2026-01-27T10:00:00.000Z'),
33
+ convertedOrderId: null,
34
+ },
35
+ ],
36
+ 1,
37
+ ]),
38
+ }))
39
+
40
+ describe('sales new-quotes widget route', () => {
41
+ it('returns 200 with items on happy path', async () => {
42
+ const req = new Request('http://localhost/api?limit=5')
43
+ const res = await GET(req)
44
+ expect(res.status).toBe(200)
45
+ const body = await res.json()
46
+ expect(Array.isArray(body.items)).toBe(true)
47
+ expect(body.items[0]).toMatchObject({
48
+ id: '11111111-1111-1111-1111-111111111111',
49
+ quoteNumber: 'QT-2001',
50
+ customerName: 'Acme Corp',
51
+ })
52
+ })
53
+
54
+ it('returns 400 on invalid limit', async () => {
55
+ const req = new Request('http://localhost/api?limit=0')
56
+ const res = await GET(req)
57
+ expect(res.status).toBe(400)
58
+ const body = await res.json()
59
+ expect(body).toHaveProperty('error')
60
+ })
61
+ })
@@ -0,0 +1,194 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import type { FilterQuery } from '@mikro-orm/core'
4
+ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
7
+ import { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'
8
+ import { SalesQuote } from '../../../../data/entities'
9
+ import { resolveWidgetScope, type WidgetScopeContext } from '../utils'
10
+ import { extractCustomerName } from '../../../../lib/customerSnapshot'
11
+ import { parseDateInput, resolveDateRange, type DatePeriodOption } from '../../../../lib/dateRange'
12
+
13
+ const querySchema = z.object({
14
+ limit: z.coerce.number().min(1).max(20).default(5),
15
+ datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),
16
+ customFrom: z.string().optional(),
17
+ customTo: z.string().optional(),
18
+ tenantId: z.string().uuid().optional(),
19
+ organizationId: z.string().uuid().optional(),
20
+ })
21
+
22
+ export const metadata = {
23
+ GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-quotes'] },
24
+ }
25
+
26
+ type WidgetContext = WidgetScopeContext & {
27
+ limit: number
28
+ datePeriod: DatePeriodOption
29
+ customFrom?: string
30
+ customTo?: string
31
+ }
32
+
33
+ async function resolveContext(
34
+ req: Request,
35
+ translate: (key: string, fallback?: string) => string,
36
+ ): Promise<WidgetContext> {
37
+ const url = new URL(req.url)
38
+ const rawQuery: Record<string, string> = {}
39
+ for (const [key, value] of url.searchParams.entries()) {
40
+ rawQuery[key] = value
41
+ }
42
+ const parsed = querySchema.safeParse(rawQuery)
43
+ if (!parsed.success) {
44
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })
45
+ }
46
+
47
+ const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {
48
+ tenantId: parsed.data.tenantId ?? null,
49
+ organizationId: parsed.data.organizationId ?? null,
50
+ })
51
+
52
+ return {
53
+ container,
54
+ em,
55
+ tenantId,
56
+ organizationIds,
57
+ limit: parsed.data.limit,
58
+ datePeriod: parsed.data.datePeriod,
59
+ customFrom: parsed.data.customFrom,
60
+ customTo: parsed.data.customTo,
61
+ }
62
+ }
63
+
64
+ function resolveDateRangeOrThrow(
65
+ period: DatePeriodOption,
66
+ customFrom: string | undefined,
67
+ customTo: string | undefined,
68
+ translate: (key: string, fallback?: string) => string,
69
+ ): { from: Date; to: Date } {
70
+ const parsedFrom = parseDateInput(customFrom)
71
+ const parsedTo = parseDateInput(customTo)
72
+ if (customFrom && !parsedFrom) {
73
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })
74
+ }
75
+ if (customTo && !parsedTo) {
76
+ throw new CrudHttpError(400, { error: translate('sales.errors.invalid_date', 'Invalid date range') })
77
+ }
78
+ return resolveDateRange(period, parsedFrom, parsedTo)
79
+ }
80
+
81
+ export async function GET(req: Request) {
82
+ const { translate } = await resolveTranslations()
83
+ try {
84
+ const { em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(
85
+ req,
86
+ translate,
87
+ )
88
+
89
+ const { from, to } = resolveDateRangeOrThrow(datePeriod, customFrom, customTo, translate)
90
+
91
+ const where: FilterQuery<SalesQuote> = {
92
+ tenantId,
93
+ deletedAt: null,
94
+ createdAt: { $gte: from, $lte: to },
95
+ }
96
+
97
+ if (Array.isArray(organizationIds)) {
98
+ where.organizationId =
99
+ organizationIds.length === 1 ? organizationIds[0] : { $in: Array.from(new Set(organizationIds)) }
100
+ }
101
+
102
+ const [items, total] = await findAndCountWithDecryption(
103
+ em,
104
+ SalesQuote,
105
+ where,
106
+ {
107
+ limit,
108
+ orderBy: { createdAt: 'desc' as const },
109
+ },
110
+ { tenantId },
111
+ )
112
+
113
+ const responseItems = items.map((quote) => ({
114
+ id: quote.id,
115
+ quoteNumber: quote.quoteNumber,
116
+ status: quote.status ?? null,
117
+ customerName: extractCustomerName(quote.customerSnapshot) ?? null,
118
+ customerEntityId: quote.customerEntityId ?? null,
119
+ validFrom: quote.validFrom ? quote.validFrom.toISOString() : null,
120
+ validUntil: quote.validUntil ? quote.validUntil.toISOString() : null,
121
+ netAmount: quote.grandTotalNetAmount,
122
+ grossAmount: quote.grandTotalGrossAmount,
123
+ currency: quote.currencyCode ?? null,
124
+ createdAt: quote.createdAt.toISOString(),
125
+ convertedOrderId: quote.convertedOrderId ?? null,
126
+ }))
127
+
128
+ return NextResponse.json({
129
+ items: responseItems,
130
+ total,
131
+ dateRange: {
132
+ from: from.toISOString(),
133
+ to: to.toISOString(),
134
+ },
135
+ })
136
+ } catch (err) {
137
+ if (err instanceof CrudHttpError) {
138
+ return NextResponse.json(err.body, { status: err.status })
139
+ }
140
+ console.error('sales.widgets.newQuotes failed', err)
141
+ return NextResponse.json(
142
+ { error: translate('sales.widgets.newQuotes.error', 'Failed to load recent quotes') },
143
+ { status: 500 },
144
+ )
145
+ }
146
+ }
147
+
148
+ const quoteItemSchema = z.object({
149
+ id: z.string().uuid(),
150
+ quoteNumber: z.string(),
151
+ status: z.string().nullable(),
152
+ customerName: z.string().nullable(),
153
+ customerEntityId: z.string().uuid().nullable(),
154
+ validFrom: z.string().nullable(),
155
+ validUntil: z.string().nullable(),
156
+ netAmount: z.string(),
157
+ grossAmount: z.string(),
158
+ currency: z.string().nullable(),
159
+ createdAt: z.string(),
160
+ convertedOrderId: z.string().uuid().nullable(),
161
+ })
162
+
163
+ const responseSchema = z.object({
164
+ items: z.array(quoteItemSchema),
165
+ total: z.number(),
166
+ dateRange: z.object({
167
+ from: z.string(),
168
+ to: z.string(),
169
+ }),
170
+ })
171
+
172
+ const widgetErrorSchema = z.object({
173
+ error: z.string(),
174
+ })
175
+
176
+ export const openApi: OpenApiRouteDoc = {
177
+ tag: 'Sales',
178
+ summary: 'New quotes widget',
179
+ methods: {
180
+ GET: {
181
+ summary: 'Fetch recently created sales quotes',
182
+ description: 'Returns the most recent sales quotes within the scoped tenant/organization.',
183
+ query: querySchema,
184
+ responses: [
185
+ { status: 200, description: 'Widget payload', schema: responseSchema },
186
+ ],
187
+ errors: [
188
+ { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },
189
+ { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },
190
+ { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },
191
+ ],
192
+ },
193
+ },
194
+ }
@@ -0,0 +1,53 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { createRequestContainer, type AppContainer } from '@open-mercato/shared/lib/di/container'
3
+ import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
5
+ import { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'
6
+
7
+ export type WidgetScopeContext = {
8
+ container: AppContainer
9
+ em: EntityManager
10
+ tenantId: string
11
+ organizationIds: string[] | null
12
+ }
13
+
14
+ export async function resolveWidgetScope(
15
+ req: Request,
16
+ translate: (key: string, fallback?: string) => string,
17
+ overrides?: { tenantId?: string | null; organizationId?: string | null },
18
+ ): Promise<WidgetScopeContext> {
19
+ const auth = await getAuthFromRequest(req)
20
+ if (!auth) {
21
+ throw new CrudHttpError(401, { error: translate('sales.errors.unauthorized', 'Unauthorized') })
22
+ }
23
+
24
+ const container = await createRequestContainer()
25
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })
26
+
27
+ const tenantId = overrides?.tenantId ?? auth.tenantId ?? null
28
+ if (!tenantId) {
29
+ throw new CrudHttpError(400, { error: translate('sales.errors.tenant_required', 'Tenant context is required') })
30
+ }
31
+
32
+ const organizationIds = (() => {
33
+ if (overrides?.organizationId) return [overrides.organizationId]
34
+ if (scope?.selectedId) return [scope.selectedId]
35
+ if (Array.isArray(scope?.filterIds) && scope.filterIds.length > 0) return scope.filterIds
36
+ if (scope?.allowedIds === null) return null
37
+ if (auth.orgId) return [auth.orgId]
38
+ return []
39
+ })()
40
+
41
+ if (organizationIds !== null && organizationIds.length === 0) {
42
+ throw new CrudHttpError(400, { error: translate('sales.errors.organization_required', 'Organization context is required') })
43
+ }
44
+
45
+ const em = container.resolve('em') as EntityManager
46
+
47
+ return {
48
+ container,
49
+ em,
50
+ tenantId,
51
+ organizationIds,
52
+ }
53
+ }
@@ -1534,5 +1534,36 @@
1534
1534
  "sales.search.badge.deliveryWindow": "Lieferfenster",
1535
1535
  "sales.search.badge.paymentMethod": "Zahlungsart",
1536
1536
  "sales.search.badge.taxRate": "Steuersatz",
1537
- "sales.search.badge.documentTag": "Dokumenten-Tag"
1537
+ "sales.search.badge.documentTag": "Dokumenten-Tag",
1538
+ "sales.errors.invalid_query": "Ungültige Abfrageparameter",
1539
+ "sales.errors.tenant_required": "Mandantenkontext ist erforderlich",
1540
+ "sales.errors.organization_required": "Organisationskontext ist erforderlich",
1541
+ "sales.errors.unauthorized": "Nicht autorisiert",
1542
+ "sales.errors.invalid_date": "Ungültiger Datumsbereich",
1543
+ "sales.widgets.newOrders.error": "Bestellungen konnten nicht geladen werden",
1544
+ "sales.widgets.newOrders.empty": "Keine Bestellungen in diesem Zeitraum gefunden",
1545
+ "sales.widgets.newOrders.noCustomer": "Kein Kunde",
1546
+ "sales.widgets.newOrders.unknownDate": "Unbekanntes Datum",
1547
+ "sales.widgets.newOrders.settings.pageSize": "Anzahl der Bestellungen",
1548
+ "sales.widgets.newOrders.settings.datePeriod": "Zeitraum",
1549
+ "sales.widgets.newOrders.settings.last24h": "Letzte 24 Stunden",
1550
+ "sales.widgets.newOrders.settings.last7d": "Letzte 7 Tage",
1551
+ "sales.widgets.newOrders.settings.last30d": "Letzte 30 Tage",
1552
+ "sales.widgets.newOrders.settings.custom": "Benutzerdefinierter Zeitraum",
1553
+ "sales.widgets.newOrders.settings.customFrom": "Von",
1554
+ "sales.widgets.newOrders.settings.customTo": "Bis",
1555
+ "sales.widgets.newQuotes.error": "Angebote konnten nicht geladen werden",
1556
+ "sales.widgets.newQuotes.empty": "Keine Angebote in diesem Zeitraum gefunden",
1557
+ "sales.widgets.newQuotes.noCustomer": "Kein Kunde",
1558
+ "sales.widgets.newQuotes.validUntil": "Gültig bis {{date}}",
1559
+ "sales.widgets.newQuotes.converted": "Konvertiert",
1560
+ "sales.widgets.newQuotes.unknownDate": "Unbekanntes Datum",
1561
+ "sales.widgets.newQuotes.settings.pageSize": "Anzahl der Angebote",
1562
+ "sales.widgets.newQuotes.settings.datePeriod": "Zeitraum",
1563
+ "sales.widgets.newQuotes.settings.last24h": "Letzte 24 Stunden",
1564
+ "sales.widgets.newQuotes.settings.last7d": "Letzte 7 Tage",
1565
+ "sales.widgets.newQuotes.settings.last30d": "Letzte 30 Tage",
1566
+ "sales.widgets.newQuotes.settings.custom": "Benutzerdefinierter Zeitraum",
1567
+ "sales.widgets.newQuotes.settings.customFrom": "Von",
1568
+ "sales.widgets.newQuotes.settings.customTo": "Bis"
1538
1569
  }
@@ -1516,5 +1516,36 @@
1516
1516
  "sales.search.badge.deliveryWindow": "Delivery window",
1517
1517
  "sales.search.badge.paymentMethod": "Payment method",
1518
1518
  "sales.search.badge.taxRate": "Tax rate",
1519
- "sales.search.badge.documentTag": "Document tag"
1519
+ "sales.search.badge.documentTag": "Document tag",
1520
+ "sales.errors.invalid_query": "Invalid query parameters",
1521
+ "sales.errors.tenant_required": "Tenant context is required",
1522
+ "sales.errors.organization_required": "Organization context is required",
1523
+ "sales.errors.unauthorized": "Unauthorized",
1524
+ "sales.errors.invalid_date": "Invalid date range",
1525
+ "sales.widgets.newOrders.error": "Failed to load orders",
1526
+ "sales.widgets.newOrders.empty": "No orders found in this period",
1527
+ "sales.widgets.newOrders.noCustomer": "No customer",
1528
+ "sales.widgets.newOrders.unknownDate": "Unknown date",
1529
+ "sales.widgets.newOrders.settings.pageSize": "Number of Orders",
1530
+ "sales.widgets.newOrders.settings.datePeriod": "Date Period",
1531
+ "sales.widgets.newOrders.settings.last24h": "Last 24 hours",
1532
+ "sales.widgets.newOrders.settings.last7d": "Last 7 days",
1533
+ "sales.widgets.newOrders.settings.last30d": "Last 30 days",
1534
+ "sales.widgets.newOrders.settings.custom": "Custom range",
1535
+ "sales.widgets.newOrders.settings.customFrom": "From",
1536
+ "sales.widgets.newOrders.settings.customTo": "To",
1537
+ "sales.widgets.newQuotes.error": "Failed to load quotes",
1538
+ "sales.widgets.newQuotes.empty": "No quotes found in this period",
1539
+ "sales.widgets.newQuotes.noCustomer": "No customer",
1540
+ "sales.widgets.newQuotes.validUntil": "Valid until {{date}}",
1541
+ "sales.widgets.newQuotes.converted": "Converted",
1542
+ "sales.widgets.newQuotes.unknownDate": "Unknown date",
1543
+ "sales.widgets.newQuotes.settings.pageSize": "Number of Quotes",
1544
+ "sales.widgets.newQuotes.settings.datePeriod": "Date Period",
1545
+ "sales.widgets.newQuotes.settings.last24h": "Last 24 hours",
1546
+ "sales.widgets.newQuotes.settings.last7d": "Last 7 days",
1547
+ "sales.widgets.newQuotes.settings.last30d": "Last 30 days",
1548
+ "sales.widgets.newQuotes.settings.custom": "Custom range",
1549
+ "sales.widgets.newQuotes.settings.customFrom": "From",
1550
+ "sales.widgets.newQuotes.settings.customTo": "To"
1520
1551
  }
@@ -1517,5 +1517,36 @@
1517
1517
  "sales.search.badge.deliveryWindow": "Ventana de entrega",
1518
1518
  "sales.search.badge.paymentMethod": "Método de pago",
1519
1519
  "sales.search.badge.taxRate": "Tasa de impuesto",
1520
- "sales.search.badge.documentTag": "Etiqueta de documento"
1520
+ "sales.search.badge.documentTag": "Etiqueta de documento",
1521
+ "sales.errors.invalid_query": "Parámetros de consulta inválidos",
1522
+ "sales.errors.tenant_required": "Se requiere el contexto del inquilino",
1523
+ "sales.errors.organization_required": "Se requiere el contexto de la organización",
1524
+ "sales.errors.unauthorized": "No autorizado",
1525
+ "sales.errors.invalid_date": "Rango de fechas inválido",
1526
+ "sales.widgets.newOrders.error": "No se pudieron cargar los pedidos",
1527
+ "sales.widgets.newOrders.empty": "No se encontraron pedidos en este período",
1528
+ "sales.widgets.newOrders.noCustomer": "Sin cliente",
1529
+ "sales.widgets.newOrders.unknownDate": "Fecha desconocida",
1530
+ "sales.widgets.newOrders.settings.pageSize": "Número de pedidos",
1531
+ "sales.widgets.newOrders.settings.datePeriod": "Período",
1532
+ "sales.widgets.newOrders.settings.last24h": "Últimas 24 horas",
1533
+ "sales.widgets.newOrders.settings.last7d": "Últimos 7 días",
1534
+ "sales.widgets.newOrders.settings.last30d": "Últimos 30 días",
1535
+ "sales.widgets.newOrders.settings.custom": "Rango personalizado",
1536
+ "sales.widgets.newOrders.settings.customFrom": "Desde",
1537
+ "sales.widgets.newOrders.settings.customTo": "Hasta",
1538
+ "sales.widgets.newQuotes.error": "No se pudieron cargar las cotizaciones",
1539
+ "sales.widgets.newQuotes.empty": "No se encontraron cotizaciones en este período",
1540
+ "sales.widgets.newQuotes.noCustomer": "Sin cliente",
1541
+ "sales.widgets.newQuotes.validUntil": "Válido hasta {{date}}",
1542
+ "sales.widgets.newQuotes.converted": "Convertido",
1543
+ "sales.widgets.newQuotes.unknownDate": "Fecha desconocida",
1544
+ "sales.widgets.newQuotes.settings.pageSize": "Número de cotizaciones",
1545
+ "sales.widgets.newQuotes.settings.datePeriod": "Período",
1546
+ "sales.widgets.newQuotes.settings.last24h": "Últimas 24 horas",
1547
+ "sales.widgets.newQuotes.settings.last7d": "Últimos 7 días",
1548
+ "sales.widgets.newQuotes.settings.last30d": "Últimos 30 días",
1549
+ "sales.widgets.newQuotes.settings.custom": "Rango personalizado",
1550
+ "sales.widgets.newQuotes.settings.customFrom": "Desde",
1551
+ "sales.widgets.newQuotes.settings.customTo": "Hasta"
1521
1552
  }
@@ -1532,5 +1532,36 @@
1532
1532
  "sales.search.badge.deliveryWindow": "Okno dostawy",
1533
1533
  "sales.search.badge.paymentMethod": "Metoda płatności",
1534
1534
  "sales.search.badge.taxRate": "Stawka podatku",
1535
- "sales.search.badge.documentTag": "Tag dokumentu"
1535
+ "sales.search.badge.documentTag": "Tag dokumentu",
1536
+ "sales.errors.invalid_query": "Nieprawidłowe parametry zapytania",
1537
+ "sales.errors.tenant_required": "Wymagany kontekst najemcy",
1538
+ "sales.errors.organization_required": "Wymagany kontekst organizacji",
1539
+ "sales.errors.unauthorized": "Brak autoryzacji",
1540
+ "sales.errors.invalid_date": "Nieprawidłowy zakres dat",
1541
+ "sales.widgets.newOrders.error": "Nie udało się wczytać zamówień",
1542
+ "sales.widgets.newOrders.empty": "Brak zamówień w tym okresie",
1543
+ "sales.widgets.newOrders.noCustomer": "Brak klienta",
1544
+ "sales.widgets.newOrders.unknownDate": "Nieznana data",
1545
+ "sales.widgets.newOrders.settings.pageSize": "Liczba zamówień",
1546
+ "sales.widgets.newOrders.settings.datePeriod": "Okres",
1547
+ "sales.widgets.newOrders.settings.last24h": "Ostatnie 24 godziny",
1548
+ "sales.widgets.newOrders.settings.last7d": "Ostatnie 7 dni",
1549
+ "sales.widgets.newOrders.settings.last30d": "Ostatnie 30 dni",
1550
+ "sales.widgets.newOrders.settings.custom": "Zakres niestandardowy",
1551
+ "sales.widgets.newOrders.settings.customFrom": "Od",
1552
+ "sales.widgets.newOrders.settings.customTo": "Do",
1553
+ "sales.widgets.newQuotes.error": "Nie udało się wczytać ofert",
1554
+ "sales.widgets.newQuotes.empty": "Brak ofert w tym okresie",
1555
+ "sales.widgets.newQuotes.noCustomer": "Brak klienta",
1556
+ "sales.widgets.newQuotes.validUntil": "Ważne do {{date}}",
1557
+ "sales.widgets.newQuotes.converted": "Przekształcona",
1558
+ "sales.widgets.newQuotes.unknownDate": "Nieznana data",
1559
+ "sales.widgets.newQuotes.settings.pageSize": "Liczba ofert",
1560
+ "sales.widgets.newQuotes.settings.datePeriod": "Okres",
1561
+ "sales.widgets.newQuotes.settings.last24h": "Ostatnie 24 godziny",
1562
+ "sales.widgets.newQuotes.settings.last7d": "Ostatnie 7 dni",
1563
+ "sales.widgets.newQuotes.settings.last30d": "Ostatnie 30 dni",
1564
+ "sales.widgets.newQuotes.settings.custom": "Zakres niestandardowy",
1565
+ "sales.widgets.newQuotes.settings.customFrom": "Od",
1566
+ "sales.widgets.newQuotes.settings.customTo": "Do"
1536
1567
  }