@open-mercato/core 0.4.6-develop-a96241c478 → 0.4.6-develop-7722ab3d39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/api_docs/frontend/docs/api/page.js +1 -1
- package/dist/modules/api_docs/frontend/docs/api/page.js.map +2 -2
- package/dist/modules/attachments/api/library/[id]/route.js +0 -1
- package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentLibrary.js +1 -1
- package/dist/modules/attachments/components/AttachmentLibrary.js.map +2 -2
- package/dist/modules/attachments/lib/partitionEnv.js +1 -1
- package/dist/modules/attachments/lib/partitionEnv.js.map +2 -2
- package/dist/modules/auth/backend/users/page.js +1 -1
- package/dist/modules/auth/backend/users/page.js.map +2 -2
- package/dist/modules/auth/cli.js +1 -1
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/auth/commands/users.js +1 -1
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/business_rules/components/utils/formHelpers.js +1 -1
- package/dist/modules/business_rules/components/utils/formHelpers.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +1 -1
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +1 -1
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/shared.js +1 -1
- package/dist/modules/catalog/commands/shared.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +1 -1
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/components/products/productForm.js +1 -1
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js.map +1 -1
- package/dist/modules/currencies/services/providers/raiffeisen.js +1 -1
- package/dist/modules/currencies/services/providers/raiffeisen.js.map +2 -2
- package/dist/modules/customers/cli.js +2 -2
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/lib/detailHelpers.js +1 -1
- package/dist/modules/customers/lib/detailHelpers.js.map +2 -2
- package/dist/modules/entities/cli.js +1 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/entities/lib/field-definitions.js +1 -1
- package/dist/modules/entities/lib/field-definitions.js.map +2 -2
- package/dist/modules/entities/lib/install-from-ce.js +1 -1
- package/dist/modules/entities/lib/install-from-ce.js.map +2 -2
- package/dist/modules/inbox_ops/lib/emailParser.js +1 -1
- package/dist/modules/inbox_ops/lib/emailParser.js.map +2 -2
- package/dist/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.js +8 -0
- package/dist/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +1 -1
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +7 -7
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +1 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/dist/modules/resources/commands/resources.js +1 -1
- package/dist/modules/resources/commands/resources.js.map +2 -2
- package/dist/modules/resources/lib/seeds.js +1 -1
- package/dist/modules/resources/lib/seeds.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +1 -1
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +1 -1
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +2 -2
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js +1 -1
- package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
- package/dist/modules/sales/components/documents/ShipmentDialog.js +1 -1
- package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
- package/dist/modules/sales/lib/shipments/snapshots.js.map +1 -1
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js +8 -0
- package/dist/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.js.map +2 -2
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js +8 -0
- package/dist/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.js.map +2 -2
- package/dist/modules/workflows/backend/instances/page.js +2 -2
- package/dist/modules/workflows/backend/instances/page.js.map +2 -2
- package/dist/modules/workflows/backend/tasks/page.js +1 -1
- package/dist/modules/workflows/backend/tasks/page.js.map +2 -2
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
- package/dist/modules/workflows/lib/graph-utils.js +1 -1
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/api_docs/frontend/docs/api/page.tsx +1 -1
- package/src/modules/attachments/api/library/[id]/route.ts +0 -1
- package/src/modules/attachments/components/AttachmentLibrary.tsx +1 -1
- package/src/modules/attachments/lib/partitionEnv.ts +1 -1
- package/src/modules/auth/backend/users/page.tsx +1 -1
- package/src/modules/auth/cli.ts +1 -1
- package/src/modules/auth/commands/users.ts +1 -1
- package/src/modules/business_rules/components/utils/formHelpers.ts +1 -1
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +1 -1
- package/src/modules/catalog/commands/products.ts +1 -1
- package/src/modules/catalog/commands/shared.ts +1 -1
- package/src/modules/catalog/components/PriceKindSettings.tsx +1 -1
- package/src/modules/catalog/components/products/productForm.ts +1 -1
- package/src/modules/configs/lib/upgrade-actions.ts +1 -1
- package/src/modules/currencies/services/providers/raiffeisen.ts +1 -1
- package/src/modules/customers/cli.ts +2 -2
- package/src/modules/customers/lib/detailHelpers.ts +1 -1
- package/src/modules/entities/cli.ts +1 -1
- package/src/modules/entities/lib/field-definitions.ts +1 -1
- package/src/modules/entities/lib/install-from-ce.ts +1 -1
- package/src/modules/inbox_ops/lib/emailParser.ts +1 -1
- package/src/modules/inbox_ops/widgets/notifications/ProposalCreatedRenderer.tsx +8 -0
- package/src/modules/perspectives/services/perspectiveService.ts +1 -1
- package/src/modules/query_index/components/QueryIndexesTable.tsx +7 -7
- package/src/modules/query_index/lib/engine.ts +1 -1
- package/src/modules/resources/commands/resources.ts +1 -1
- package/src/modules/resources/lib/seeds.ts +1 -1
- package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +1 -1
- package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +1 -1
- package/src/modules/sales/commands/documents.ts +2 -2
- package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +1 -1
- package/src/modules/sales/components/documents/ShipmentDialog.tsx +1 -1
- package/src/modules/sales/lib/shipments/snapshots.ts +1 -1
- package/src/modules/sales/widgets/notifications/SalesOrderCreatedRenderer.tsx +8 -0
- package/src/modules/sales/widgets/notifications/SalesQuoteCreatedRenderer.tsx +8 -0
- package/src/modules/workflows/backend/instances/page.tsx +2 -2
- package/src/modules/workflows/backend/tasks/page.tsx +1 -1
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
- package/src/modules/workflows/lib/graph-utils.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/sales/api/dashboard/widgets/new-quotes/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { createHash } from 'node:crypto'\r\nimport { NextResponse } from 'next/server'\r\nimport { z } from 'zod'\r\nimport type { FilterQuery } from '@mikro-orm/core'\r\nimport type { CacheStrategy } from '@open-mercato/cache'\r\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\r\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\r\nimport { runWithCacheTenant } from '@open-mercato/cache'\r\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\r\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\r\nimport { resolveDateRange } from '@open-mercato/ui/backend/date-range'\r\nimport { SalesQuote } from '../../../../data/entities'\r\nimport { extractCustomerName, type DatePeriodOption } from '../helpers'\r\nimport { resolveWidgetScope, type WidgetScopeContext } from '../../../../../customers/api/dashboard/widgets/utils'\r\n\r\nconst WIDGET_CACHE_TTL = 120_000\r\nconst WIDGET_CACHE_SEGMENT_TTL = 86_400_000\r\nconst WIDGET_CACHE_SEGMENT_KEY = 'widget-data:__segment__'\r\nconst WIDGET_CACHE_TAGS = ['widget-data', 'widget-data:sales:quotes']\r\nconst WIDGET_CACHE_ID = 'sales:new-quotes'\r\n\r\nconst querySchema = z.object({\r\n limit: z.coerce.number().min(1).max(20).default(5),\r\n datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),\r\n customFrom: z.string().optional(),\r\n customTo: z.string().optional(),\r\n tenantId: z.string().uuid().optional(),\r\n organizationId: z.string().uuid().optional(),\r\n})\r\n\r\nexport const metadata = {\r\n GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-quotes'] },\r\n}\r\n\r\ntype WidgetContext = WidgetScopeContext & {\r\n limit: number\r\n datePeriod: DatePeriodOption\r\n customFrom?: string\r\n customTo?: string\r\n}\r\n\r\ntype NewQuotesWidgetResponse = {\r\n items: Array<{\r\n id: string\r\n quoteNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n validFrom: string | null\r\n validUntil: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n convertedOrderId: string | null\r\n }>\r\n total: number\r\n dateRange: {\r\n from: string\r\n to: string\r\n }\r\n}\r\n\r\nfunction normalizeOrganizationIds(organizationIds: string[] | null): string[] | null {\r\n if (organizationIds === null) return null\r\n const set = new Set(organizationIds)\r\n return Array.from(set).sort()\r\n}\r\n\r\nfunction buildCacheKey(params: {\r\n tenantId: string\r\n organizationIds: string[] | null\r\n limit: number\r\n datePeriod: DatePeriodOption\r\n customFrom?: string\r\n customTo?: string\r\n}): string {\r\n const hash = createHash('sha256')\r\n hash.update(\r\n JSON.stringify({\r\n widget: WIDGET_CACHE_ID,\r\n ...params,\r\n organizationIds: normalizeOrganizationIds(params.organizationIds),\r\n })\r\n )\r\n return `widget-data:${hash.digest('hex').slice(0, 16)}`\r\n}\r\n\r\nasync function resolveContext(req: Request, translate: (key: string, fallback?: string) => string): Promise<WidgetContext> {\r\n const url = new URL(req.url)\r\n const rawQuery: Record<string, string> = {}\r\n for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value\r\n const parsed = querySchema.safeParse(rawQuery)\r\n if (!parsed.success) {\r\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })\r\n }\r\n\r\n const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {\r\n tenantId: parsed.data.tenantId ?? null,\r\n organizationId: parsed.data.organizationId ?? null,\r\n })\r\n\r\n return {\r\n container,\r\n em,\r\n tenantId,\r\n organizationIds,\r\n limit: parsed.data.limit,\r\n datePeriod: parsed.data.datePeriod,\r\n customFrom: parsed.data.customFrom,\r\n customTo: parsed.data.customTo,\r\n }\r\n}\r\n\r\nexport async function GET(req: Request) {\r\n const { translate } = await resolveTranslations()\r\n try {\r\n const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(\r\n req,\r\n translate\r\n )\r\n const range = (() => {\r\n if (datePeriod === 'custom') {\r\n const from = customFrom ? new Date(customFrom) : new Date(0)\r\n const to = customTo ? new Date(customTo) : new Date()\r\n return { start: from, end: to }\r\n }\r\n const preset = datePeriod === 'last7d' ? 'last_7_days' : datePeriod === 'last30d' ? 'last_30_days' : 'today'\r\n return resolveDateRange(preset)\r\n })()\r\n\r\n let cache: CacheStrategy | null = null\r\n try {\r\n cache = container.resolve<CacheStrategy>('cache')\r\n } catch {\r\n cache = null\r\n }\r\n\r\n \r\n\r\n const cacheKey = buildCacheKey({ tenantId, organizationIds, limit, datePeriod, customFrom, customTo })\r\n const tenantScope = tenantId ?? null\r\n\r\n if (cache) {\r\n try {\r\n const cached = await runWithCacheTenant(tenantScope, () => cache!.get(cacheKey))\r\n if (cached && typeof cached === 'object' && 'items' in (cached as object)) {\r\n return NextResponse.json(cached)\r\n }\r\n } catch {\r\n }\r\n }\r\n\r\n const where: FilterQuery<SalesQuote> = {\r\n tenantId,\r\n deletedAt: null,\r\n createdAt: { $gte: range.start, $lte: range.end },\r\n }\r\n\r\n if (Array.isArray(organizationIds)) {\r\n const unique = Array.from(new Set(organizationIds))\r\n where.organizationId = unique.length === 1 ? unique[0] : { $in: unique }\r\n }\r\n\r\n const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null\r\n const [quotes, total] = await findAndCountWithDecryption(\r\n em,\r\n SalesQuote,\r\n where,\r\n {\r\n limit,\r\n orderBy: { createdAt: 'desc' as const },\r\n },\r\n { tenantId, organizationId: organizationIdScope },\r\n )\r\n\r\n const items = quotes.map((quote) => ({\r\n id: quote.id,\r\n quoteNumber: quote.quoteNumber,\r\n status: quote.status ?? null,\r\n customerName: extractCustomerName(quote.customerSnapshot) ?? null,\r\n customerEntityId: quote.customerEntityId ?? null,\r\n validFrom: quote.validFrom ? quote.validFrom.toISOString() : null,\r\n validUntil: quote.validUntil ? quote.validUntil.toISOString() : null,\r\n netAmount: quote.grandTotalNetAmount ?? '0',\r\n grossAmount: quote.grandTotalGrossAmount ?? '0',\r\n currency: quote.currencyCode ?? null,\r\n createdAt: quote.createdAt.toISOString(),\r\n convertedOrderId: quote.convertedOrderId ?? null,\r\n }))\r\n\r\n const response: NewQuotesWidgetResponse = {\r\n items,\r\n total,\r\n dateRange: { from: range.start.toISOString(), to: range.end.toISOString() },\r\n }\r\n\r\n if (cache) {\r\n try {\r\n await runWithCacheTenant(tenantScope, () => cache!.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: WIDGET_CACHE_TAGS }))\r\n await runWithCacheTenant(tenantScope, () => cache!.set(\r\n WIDGET_CACHE_SEGMENT_KEY,\r\n { updatedAt: response.dateRange.to },\r\n { ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ['widget-data'] },\r\n ))\r\n } catch {\r\n }\r\n }\r\n\r\n return NextResponse.json(response)\r\n } catch (err) {\r\n if (err instanceof CrudHttpError) {\r\n return NextResponse.json(err.body, { status: err.status })\r\n }\r\n console.error('sales.widgets.newQuotes failed', err)\r\n return NextResponse.json(\r\n { error: translate('sales.widgets.newQuotes.error', 'Failed to load quotes') },\r\n { status: 500 },\r\n )\r\n }\r\n}\r\n\r\nconst quoteItemSchema = z.object({\r\n id: z.string().uuid(),\r\n quoteNumber: z.string(),\r\n status: z.string().nullable(),\r\n customerName: z.string().nullable(),\r\n customerEntityId: z.string().uuid().nullable(),\r\n validFrom: z.string().nullable(),\r\n validUntil: z.string().nullable(),\r\n netAmount: z.string(),\r\n grossAmount: z.string(),\r\n currency: z.string().nullable(),\r\n createdAt: z.string(),\r\n convertedOrderId: z.string().uuid().nullable(),\r\n})\r\n\r\nconst responseSchema = z.object({\r\n items: z.array(quoteItemSchema),\r\n total: z.number(),\r\n dateRange: z.object({\r\n from: z.string(),\r\n to: z.string(),\r\n }),\r\n})\r\n\r\nconst widgetErrorSchema = z.object({ error: z.string() })\r\n\r\nexport const openApi: OpenApiRouteDoc = {\r\n tag: 'Sales',\r\n summary: 'New quotes dashboard widget',\r\n description: 'Fetches recently created sales quotes for the dashboard widget with a configurable date period.',\r\n methods: {\r\n GET: {\r\n summary: 'Fetch recently created sales quotes',\r\n query: querySchema,\r\n responses: [{ status: 200, description: 'List of recent quotes', schema: responseSchema }],\r\n errors: [\r\n { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },\r\n { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },\r\n { status: 403, description: 'Forbidden', schema: widgetErrorSchema },\r\n { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },\r\n ],\r\n },\r\n },\r\n}\r\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,kBAAkB;AAC3B,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAIlB,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,kCAAkC;AAC3C,SAAS,wBAAwB;AACjC,SAAS,kBAAkB;AAC3B,SAAS,2BAAkD;AAC3D,SAAS,0BAAmD;AAE5D,MAAM,mBAAmB;AACzB,MAAM,2BAA2B;AACjC,MAAM,2BAA2B;AACjC,MAAM,oBAAoB,CAAC,eAAe,0BAA0B;AACpE,MAAM,kBAAkB;AAExB,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,OAAO,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC;AAAA,EACjD,YAAY,EAAE,KAAK,CAAC,WAAW,UAAU,WAAW,QAAQ,CAAC,EAAE,QAAQ,SAAS;AAAA,EAChF,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EACrC,gBAAgB,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAC7C,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,0BAA0B,EAAE;AAC7F;AA+BA,SAAS,yBAAyB,iBAAmD;AACnF,MAAI,oBAAoB,KAAM,QAAO;AACrC,QAAM,MAAM,IAAI,IAAI,eAAe;AACnC,SAAO,MAAM,KAAK,GAAG,EAAE,KAAK;
|
|
4
|
+
"sourcesContent": ["import { createHash } from 'node:crypto'\r\nimport { NextResponse } from 'next/server'\r\nimport { z } from 'zod'\r\nimport type { FilterQuery } from '@mikro-orm/core'\r\nimport type { CacheStrategy } from '@open-mercato/cache'\r\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\r\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\r\nimport { runWithCacheTenant } from '@open-mercato/cache'\r\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\r\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\r\nimport { resolveDateRange } from '@open-mercato/ui/backend/date-range'\r\nimport { SalesQuote } from '../../../../data/entities'\r\nimport { extractCustomerName, type DatePeriodOption } from '../helpers'\r\nimport { resolveWidgetScope, type WidgetScopeContext } from '../../../../../customers/api/dashboard/widgets/utils'\r\n\r\nconst WIDGET_CACHE_TTL = 120_000\r\nconst WIDGET_CACHE_SEGMENT_TTL = 86_400_000\r\nconst WIDGET_CACHE_SEGMENT_KEY = 'widget-data:__segment__'\r\nconst WIDGET_CACHE_TAGS = ['widget-data', 'widget-data:sales:quotes']\r\nconst WIDGET_CACHE_ID = 'sales:new-quotes'\r\n\r\nconst querySchema = z.object({\r\n limit: z.coerce.number().min(1).max(20).default(5),\r\n datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),\r\n customFrom: z.string().optional(),\r\n customTo: z.string().optional(),\r\n tenantId: z.string().uuid().optional(),\r\n organizationId: z.string().uuid().optional(),\r\n})\r\n\r\nexport const metadata = {\r\n GET: { requireAuth: true, requireFeatures: ['dashboards.view', 'sales.widgets.new-quotes'] },\r\n}\r\n\r\ntype WidgetContext = WidgetScopeContext & {\r\n limit: number\r\n datePeriod: DatePeriodOption\r\n customFrom?: string\r\n customTo?: string\r\n}\r\n\r\ntype NewQuotesWidgetResponse = {\r\n items: Array<{\r\n id: string\r\n quoteNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n validFrom: string | null\r\n validUntil: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n convertedOrderId: string | null\r\n }>\r\n total: number\r\n dateRange: {\r\n from: string\r\n to: string\r\n }\r\n}\r\n\r\nfunction normalizeOrganizationIds(organizationIds: string[] | null): string[] | null {\r\n if (organizationIds === null) return null\r\n const set = new Set(organizationIds)\r\n return Array.from(set).sort((a, b) => a.localeCompare(b))\r\n}\r\n\r\nfunction buildCacheKey(params: {\r\n tenantId: string\r\n organizationIds: string[] | null\r\n limit: number\r\n datePeriod: DatePeriodOption\r\n customFrom?: string\r\n customTo?: string\r\n}): string {\r\n const hash = createHash('sha256')\r\n hash.update(\r\n JSON.stringify({\r\n widget: WIDGET_CACHE_ID,\r\n ...params,\r\n organizationIds: normalizeOrganizationIds(params.organizationIds),\r\n })\r\n )\r\n return `widget-data:${hash.digest('hex').slice(0, 16)}`\r\n}\r\n\r\nasync function resolveContext(req: Request, translate: (key: string, fallback?: string) => string): Promise<WidgetContext> {\r\n const url = new URL(req.url)\r\n const rawQuery: Record<string, string> = {}\r\n for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value\r\n const parsed = querySchema.safeParse(rawQuery)\r\n if (!parsed.success) {\r\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })\r\n }\r\n\r\n const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {\r\n tenantId: parsed.data.tenantId ?? null,\r\n organizationId: parsed.data.organizationId ?? null,\r\n })\r\n\r\n return {\r\n container,\r\n em,\r\n tenantId,\r\n organizationIds,\r\n limit: parsed.data.limit,\r\n datePeriod: parsed.data.datePeriod,\r\n customFrom: parsed.data.customFrom,\r\n customTo: parsed.data.customTo,\r\n }\r\n}\r\n\r\nexport async function GET(req: Request) {\r\n const { translate } = await resolveTranslations()\r\n try {\r\n const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(\r\n req,\r\n translate\r\n )\r\n const range = (() => {\r\n if (datePeriod === 'custom') {\r\n const from = customFrom ? new Date(customFrom) : new Date(0)\r\n const to = customTo ? new Date(customTo) : new Date()\r\n return { start: from, end: to }\r\n }\r\n const preset = datePeriod === 'last7d' ? 'last_7_days' : datePeriod === 'last30d' ? 'last_30_days' : 'today'\r\n return resolveDateRange(preset)\r\n })()\r\n\r\n let cache: CacheStrategy | null = null\r\n try {\r\n cache = container.resolve<CacheStrategy>('cache')\r\n } catch {\r\n cache = null\r\n }\r\n\r\n \r\n\r\n const cacheKey = buildCacheKey({ tenantId, organizationIds, limit, datePeriod, customFrom, customTo })\r\n const tenantScope = tenantId ?? null\r\n\r\n if (cache) {\r\n try {\r\n const cached = await runWithCacheTenant(tenantScope, () => cache!.get(cacheKey))\r\n if (cached && typeof cached === 'object' && 'items' in (cached as object)) {\r\n return NextResponse.json(cached)\r\n }\r\n } catch {\r\n }\r\n }\r\n\r\n const where: FilterQuery<SalesQuote> = {\r\n tenantId,\r\n deletedAt: null,\r\n createdAt: { $gte: range.start, $lte: range.end },\r\n }\r\n\r\n if (Array.isArray(organizationIds)) {\r\n const unique = Array.from(new Set(organizationIds))\r\n where.organizationId = unique.length === 1 ? unique[0] : { $in: unique }\r\n }\r\n\r\n const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null\r\n const [quotes, total] = await findAndCountWithDecryption(\r\n em,\r\n SalesQuote,\r\n where,\r\n {\r\n limit,\r\n orderBy: { createdAt: 'desc' as const },\r\n },\r\n { tenantId, organizationId: organizationIdScope },\r\n )\r\n\r\n const items = quotes.map((quote) => ({\r\n id: quote.id,\r\n quoteNumber: quote.quoteNumber,\r\n status: quote.status ?? null,\r\n customerName: extractCustomerName(quote.customerSnapshot) ?? null,\r\n customerEntityId: quote.customerEntityId ?? null,\r\n validFrom: quote.validFrom ? quote.validFrom.toISOString() : null,\r\n validUntil: quote.validUntil ? quote.validUntil.toISOString() : null,\r\n netAmount: quote.grandTotalNetAmount ?? '0',\r\n grossAmount: quote.grandTotalGrossAmount ?? '0',\r\n currency: quote.currencyCode ?? null,\r\n createdAt: quote.createdAt.toISOString(),\r\n convertedOrderId: quote.convertedOrderId ?? null,\r\n }))\r\n\r\n const response: NewQuotesWidgetResponse = {\r\n items,\r\n total,\r\n dateRange: { from: range.start.toISOString(), to: range.end.toISOString() },\r\n }\r\n\r\n if (cache) {\r\n try {\r\n await runWithCacheTenant(tenantScope, () => cache!.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: WIDGET_CACHE_TAGS }))\r\n await runWithCacheTenant(tenantScope, () => cache!.set(\r\n WIDGET_CACHE_SEGMENT_KEY,\r\n { updatedAt: response.dateRange.to },\r\n { ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ['widget-data'] },\r\n ))\r\n } catch {\r\n }\r\n }\r\n\r\n return NextResponse.json(response)\r\n } catch (err) {\r\n if (err instanceof CrudHttpError) {\r\n return NextResponse.json(err.body, { status: err.status })\r\n }\r\n console.error('sales.widgets.newQuotes failed', err)\r\n return NextResponse.json(\r\n { error: translate('sales.widgets.newQuotes.error', 'Failed to load quotes') },\r\n { status: 500 },\r\n )\r\n }\r\n}\r\n\r\nconst quoteItemSchema = z.object({\r\n id: z.string().uuid(),\r\n quoteNumber: z.string(),\r\n status: z.string().nullable(),\r\n customerName: z.string().nullable(),\r\n customerEntityId: z.string().uuid().nullable(),\r\n validFrom: z.string().nullable(),\r\n validUntil: z.string().nullable(),\r\n netAmount: z.string(),\r\n grossAmount: z.string(),\r\n currency: z.string().nullable(),\r\n createdAt: z.string(),\r\n convertedOrderId: z.string().uuid().nullable(),\r\n})\r\n\r\nconst responseSchema = z.object({\r\n items: z.array(quoteItemSchema),\r\n total: z.number(),\r\n dateRange: z.object({\r\n from: z.string(),\r\n to: z.string(),\r\n }),\r\n})\r\n\r\nconst widgetErrorSchema = z.object({ error: z.string() })\r\n\r\nexport const openApi: OpenApiRouteDoc = {\r\n tag: 'Sales',\r\n summary: 'New quotes dashboard widget',\r\n description: 'Fetches recently created sales quotes for the dashboard widget with a configurable date period.',\r\n methods: {\r\n GET: {\r\n summary: 'Fetch recently created sales quotes',\r\n query: querySchema,\r\n responses: [{ status: 200, description: 'List of recent quotes', schema: responseSchema }],\r\n errors: [\r\n { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },\r\n { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },\r\n { status: 403, description: 'Forbidden', schema: widgetErrorSchema },\r\n { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },\r\n ],\r\n },\r\n },\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2136,8 +2136,8 @@ function normalizeTagIds(tags) {
|
|
|
2136
2136
|
function buildTagChange(beforeTags, afterTags) {
|
|
2137
2137
|
const beforeIds = normalizeTagIds(beforeTags?.map((tag) => tag.tagId));
|
|
2138
2138
|
const afterIds = normalizeTagIds(afterTags?.map((tag) => tag.tagId));
|
|
2139
|
-
beforeIds.sort();
|
|
2140
|
-
afterIds.sort();
|
|
2139
|
+
beforeIds.sort((a, b) => a.localeCompare(b));
|
|
2140
|
+
afterIds.sort((a, b) => a.localeCompare(b));
|
|
2141
2141
|
if (beforeIds.length === afterIds.length && beforeIds.every((id, index) => id === afterIds[index])) {
|
|
2142
2142
|
return null;
|
|
2143
2143
|
}
|