@open-mercato/core 0.4.6-develop-9ff1d4a9a2 → 0.4.6-develop-219dae16c5
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/currencies/backend/exchange-rates/[id]/page.js +17 -154
- package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
- package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
- package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
- package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
- package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
- package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
- package/dist/modules/customers/commands/activities.js +3 -8
- package/dist/modules/customers/commands/activities.js.map +2 -2
- package/dist/modules/customers/commands/comments.js +2 -8
- package/dist/modules/customers/commands/comments.js.map +2 -2
- package/dist/modules/dashboards/lib/widgetScope.js +38 -0
- package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
- package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
- package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
- package/dist/modules/resources/api/activities.js +24 -232
- package/dist/modules/resources/api/activities.js.map +2 -2
- package/dist/modules/resources/commands/activities.js +3 -8
- package/dist/modules/resources/commands/activities.js.map +2 -2
- package/dist/modules/resources/commands/comments.js +2 -8
- package/dist/modules/resources/commands/comments.js.map +2 -2
- package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
- 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 +28 -183
- package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
- package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/order-lines/route.js +15 -281
- package/dist/modules/sales/api/order-lines/route.js.map +2 -2
- package/dist/modules/sales/api/order-statuses/route.js +15 -194
- package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/payment-statuses/route.js +15 -194
- package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
- package/dist/modules/sales/api/quote-lines/route.js +15 -279
- package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
- package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
- package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
- package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
- package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
- package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
- package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
- package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
- package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
- package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
- package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
- package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
- package/dist/modules/staff/api/activities.js +24 -232
- package/dist/modules/staff/api/activities.js.map +2 -2
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
- package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
- package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
- package/dist/modules/staff/commands/activities.js +3 -8
- package/dist/modules/staff/commands/activities.js.map +2 -2
- package/dist/modules/staff/commands/comments.js +2 -8
- package/dist/modules/staff/commands/comments.js.map +2 -2
- package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
- package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
- package/package.json +2 -2
- package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
- package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
- package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
- package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
- package/src/modules/customers/commands/activities.ts +2 -8
- package/src/modules/customers/commands/comments.ts +2 -8
- package/src/modules/dashboards/i18n/de.json +3 -0
- package/src/modules/dashboards/i18n/en.json +3 -0
- package/src/modules/dashboards/i18n/es.json +3 -0
- package/src/modules/dashboards/i18n/pl.json +3 -0
- package/src/modules/dashboards/lib/widgetScope.ts +53 -0
- package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
- package/src/modules/resources/api/activities.ts +25 -269
- package/src/modules/resources/commands/activities.ts +2 -7
- package/src/modules/resources/commands/comments.ts +2 -8
- package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
- package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
- package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
- package/src/modules/sales/api/order-lines/route.ts +16 -300
- package/src/modules/sales/api/order-statuses/route.ts +16 -209
- package/src/modules/sales/api/payment-statuses/route.ts +16 -209
- package/src/modules/sales/api/quote-lines/route.ts +16 -298
- package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
- package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
- package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
- package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
- package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
- package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
- package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
- package/src/modules/staff/api/activities.ts +25 -269
- package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
- package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
- package/src/modules/staff/commands/activities.ts +2 -7
- package/src/modules/staff/commands/comments.ts +2 -8
- package/src/modules/staff/lib/leaveRequestHelpers.ts +78 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/resources/api/activities.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 { resolveCrudRecordId, parseScopedCommandInput } from '@open-mercato/shared/lib/api/scoped'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { ResourcesResourceActivity } from '../data/entities'\nimport {\n resourcesResourceActivityCreateSchema,\n resourcesResourceActivityUpdateSchema,\n} from '../data/validators'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { createResourcesCrudOpenApi, createPagedListResponseSchema, defaultOkResponseSchema } from './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(100).default(50),\n entityId: 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: ['resources.view'] },\n POST: { requireAuth: true, requireFeatures: ['resources.manage_resources'] },\n PUT: { requireAuth: true, requireFeatures: ['resources.manage_resources'] },\n DELETE: { requireAuth: true, requireFeatures: ['resources.manage_resources'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: ResourcesResourceActivity,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n },\n indexer: {\n entityType: E.resources.resources_resource_activity,\n },\n list: {\n schema: listSchema,\n entityId: E.resources.resources_resource_activity,\n fields: [\n 'id',\n 'resource_id',\n 'activity_type',\n 'subject',\n 'body',\n 'occurred_at',\n 'author_user_id',\n 'appearance_icon',\n 'appearance_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n decorateCustomFields: {\n entityIds: E.resources.resources_resource_activity,\n },\n sortFieldMap: {\n occurredAt: 'occurred_at',\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n buildFilters: async (query) => {\n const filters: Record<string, unknown> = {}\n if (query.entityId) filters.resource_id = { $eq: query.entityId }\n return filters\n },\n transformItem: (item: Record<string, unknown>) => {\n const record = (item ?? {}) as Record<string, unknown>\n const toIsoString = (value: unknown): string | null => {\n if (value == null) return null\n if (value instanceof Date) return value.toISOString()\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const date = new Date(trimmed)\n return Number.isNaN(date.getTime()) ? trimmed : date.toISOString()\n }\n return null\n }\n const readString = (value: unknown): string | null => (typeof value === 'string' ? value : null)\n const idValue = readString(record.id) ?? (record.id != null ? String(record.id) : '')\n const resourceId = readString(record['resource_id']) ?? readString(record['resourceId']) ?? null\n const activityType =\n readString(record['activity_type']) ??\n readString(record['activityType']) ??\n ''\n const subject =\n readString(record.subject) ??\n (record.subject == null ? null : String(record.subject))\n const body =\n readString(record.body) ??\n (record.body == null ? null : String(record.body))\n const authorUserId =\n readString(record['author_user_id']) ?? readString(record['authorUserId']) ?? null\n const appearanceIconRaw =\n readString(record['appearance_icon']) ?? readString(record['appearanceIcon'])\n const appearanceColorRaw =\n readString(record['appearance_color']) ?? readString(record['appearanceColor'])\n const organizationId =\n readString(record['organization_id']) ?? readString(record['organizationId'])\n const tenantId =\n readString(record['tenant_id']) ?? readString(record['tenantId'])\n const output: Record<string, unknown> = {\n id: idValue,\n entityId: resourceId,\n resourceId,\n activityType,\n subject,\n body,\n occurredAt: toIsoString(record['occurred_at'] ?? record['occurredAt']),\n createdAt: toIsoString(record['created_at'] ?? record['createdAt']),\n authorUserId,\n organizationId,\n tenantId,\n appearanceIcon: appearanceIconRaw && appearanceIconRaw.trim().length ? appearanceIconRaw : null,\n appearanceColor: appearanceColorRaw && appearanceColorRaw.trim().length ? appearanceColorRaw : null,\n customFields: Array.isArray(record.customFields) ? record.customFields : undefined,\n customValues: record.customValues ?? undefined,\n }\n for (const [key, value] of Object.entries(record)) {\n if (key.startsWith('cf_') || key.startsWith('cf:')) {\n output[key] = value\n }\n }\n return output\n },\n },\n actions: {\n create: {\n commandId: 'resources.resource-activities.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(resourcesResourceActivityCreateSchema, raw ?? {}, ctx, translate)\n },\n response: ({ result }) => ({\n id: result?.activityId ?? result?.id ?? null,\n authorUserId: result?.authorUserId ?? null,\n }),\n status: 201,\n },\n update: {\n commandId: 'resources.resource-activities.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n return parseScopedCommandInput(resourcesResourceActivityUpdateSchema, raw ?? {}, ctx, translate)\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'resources.resource-activities.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id = resolveCrudRecordId(parsed, ctx, translate)\n return { id }\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 userIds = new Set<string>()\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const record = item as Record<string, unknown>\n const userId =\n typeof record.author_user_id === 'string'\n ? record.author_user_id\n : typeof record.authorUserId === 'string'\n ? record.authorUserId\n : null\n if (userId) userIds.add(userId)\n })\n if (!userIds.size) return\n try {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const users = await findWithDecryption(\n em,\n User,\n { id: { $in: Array.from(userIds) } },\n undefined,\n { tenantId: ctx.auth?.tenantId ?? null, organizationId: ctx.selectedOrganizationId ?? null },\n )\n const map = new Map<string, { name: string | null; email: string | null }>()\n users.forEach((user) => {\n const name = typeof user.name === 'string' && user.name.trim().length\n ? user.name.trim()\n : null\n map.set(user.id, { name, email: user.email ?? null })\n })\n items.forEach((item: unknown) => {\n if (!item || typeof item !== 'object') return\n const record = item as Record<string, unknown>\n const userId =\n typeof record.author_user_id === 'string'\n ? record.author_user_id\n : typeof record.authorUserId === 'string'\n ? record.authorUserId\n : null\n if (!userId) return\n const meta = map.get(userId)\n if (!meta) return\n record.authorName = meta.name\n record.authorEmail = meta.email\n if (!('author_name' in record)) record.author_name = meta.name\n if (!('author_email' in record)) record.author_email = meta.email\n })\n } catch (err) {\n console.warn('[resources.activities] failed to enrich author metadata', err)\n }\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst activityListItemSchema = z\n .object({\n id: z.string().uuid(),\n resource_id: z.string().uuid().nullable().optional(),\n activity_type: z.string().nullable().optional(),\n subject: z.string().nullable().optional(),\n body: z.string().nullable().optional(),\n occurred_at: z.string().nullable().optional(),\n author_user_id: z.string().uuid().nullable(),\n appearance_icon: z.string().nullable().optional(),\n appearance_color: z.string().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n created_at: z.string().nullable(),\n updated_at: z.string().nullable().optional(),\n })\n .passthrough()\n\nconst activityCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n authorUserId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createResourcesCrudOpenApi({\n resourceName: 'ResourceActivity',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(activityListItemSchema),\n create: {\n schema: resourcesResourceActivityCreateSchema,\n responseSchema: activityCreateResponseSchema,\n description: 'Adds an activity to a resource timeline.',\n },\n update: {\n schema: resourcesResourceActivityUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a resource activity.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a resource activity.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,
|
|
4
|
+
"sourcesContent": ["import { makeActivityRoute } from '@open-mercato/core/modules/entities/lib/makeActivityRoute'\nimport { ResourcesResourceActivity } from '../data/entities'\nimport {\n resourcesResourceActivityCreateSchema,\n resourcesResourceActivityUpdateSchema,\n} from '../data/validators'\nimport { E } from '#generated/entities.ids.generated'\nimport { createResourcesCrudOpenApi } from './openapi'\n\nconst route = makeActivityRoute({\n entity: ResourcesResourceActivity,\n entityId: E.resources.resources_resource_activity,\n parentFkColumn: 'resource_id',\n parentFkParam: 'resourceId',\n features: { view: 'resources.view', manage: 'resources.manage_resources' },\n createSchema: resourcesResourceActivityCreateSchema,\n updateSchema: resourcesResourceActivityUpdateSchema,\n commandPrefix: 'resources.resource-activities',\n logPrefix: '[resources.activities]',\n openApiFactory: createResourcesCrudOpenApi,\n openApi: {\n resourceName: 'ResourceActivity',\n createDescription: 'Adds an activity to a resource timeline.',\n updateDescription: 'Updates a resource activity.',\n deleteDescription: 'Deletes a resource activity.',\n },\n})\n\nexport const metadata = route.metadata\nexport const openApi = route.openApi\nexport const GET = route.GET\nexport const POST = route.POST\nexport const PUT = route.PUT\nexport const DELETE = route.DELETE\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,yBAAyB;AAClC,SAAS,iCAAiC;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,SAAS;AAClB,SAAS,kCAAkC;AAE3C,MAAM,QAAQ,kBAAkB;AAAA,EAC9B,QAAQ;AAAA,EACR,UAAU,EAAE,UAAU;AAAA,EACtB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,UAAU,EAAE,MAAM,kBAAkB,QAAQ,6BAA6B;AAAA,EACzE,cAAc;AAAA,EACd,cAAc;AAAA,EACd,eAAe;AAAA,EACf,WAAW;AAAA,EACX,gBAAgB;AAAA,EAChB,SAAS;AAAA,IACP,cAAc;AAAA,IACd,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,EACrB;AACF,CAAC;AAEM,MAAM,WAAW,MAAM;AACvB,MAAM,UAAU,MAAM;AACtB,MAAM,MAAM,MAAM;AAClB,MAAM,OAAO,MAAM;AACnB,MAAM,MAAM,MAAM;AAClB,MAAM,SAAS,MAAM;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,7 +5,8 @@ import {
|
|
|
5
5
|
emitCrudSideEffects,
|
|
6
6
|
emitCrudUndoSideEffects,
|
|
7
7
|
buildChanges,
|
|
8
|
-
requireId
|
|
8
|
+
requireId,
|
|
9
|
+
normalizeAuthorUserId
|
|
9
10
|
} from "@open-mercato/shared/lib/commands/helpers";
|
|
10
11
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
11
12
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
@@ -70,13 +71,7 @@ const createActivityCommand = {
|
|
|
70
71
|
const { parsed, custom } = parseWithCustomFields(resourcesResourceActivityCreateSchema, rawInput);
|
|
71
72
|
ensureTenantScope(ctx, parsed.tenantId);
|
|
72
73
|
ensureOrganizationScope(ctx, parsed.organizationId);
|
|
73
|
-
const
|
|
74
|
-
const normalizedAuthor = (() => {
|
|
75
|
-
if (parsed.authorUserId) return parsed.authorUserId;
|
|
76
|
-
if (!authSub) return null;
|
|
77
|
-
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
|
|
78
|
-
return uuidRegex.test(authSub) ? authSub : null;
|
|
79
|
-
})();
|
|
74
|
+
const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth);
|
|
80
75
|
const em = ctx.container.resolve("em").fork();
|
|
81
76
|
const resource = await requireResource(em, parsed.entityId, "Resource not found");
|
|
82
77
|
ensureTenantScope(ctx, resource.tenantId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/resources/commands/activities.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport {\n loadCustomFieldSnapshot,\n diffCustomFieldChanges,\n buildCustomFieldResetMap,\n type CustomFieldChangeSet,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { ResourcesResourceActivity } from '../data/entities'\nimport {\n resourcesResourceActivityCreateSchema,\n resourcesResourceActivityUpdateSchema,\n type ResourcesResourceActivityCreateInput,\n type ResourcesResourceActivityUpdateInput,\n} from '../data/validators'\nimport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireResource } from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst ACTIVITY_ENTITY_ID = E.resources.resources_resource_activity\nconst activityCrudIndexer: CrudIndexerConfig<ResourcesResourceActivity> = {\n entityType: E.resources.resources_resource_activity,\n}\n\ntype ActivitySnapshot = {\n activity: {\n id: string\n organizationId: string\n tenantId: string\n resourceId: string\n activityType: string\n subject: string | null\n body: string | null\n occurredAt: Date | null\n authorUserId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n }\n custom?: Record<string, unknown>\n}\n\ntype ActivityUndoPayload = {\n before?: ActivitySnapshot | null\n after?: ActivitySnapshot | null\n}\n\ntype ActivityChangeMap = Record<string, { from: unknown; to: unknown }> & {\n custom?: CustomFieldChangeSet\n}\n\nasync function loadActivitySnapshot(em: EntityManager, id: string): Promise<ActivitySnapshot | null> {\n const activity = await em.findOne(ResourcesResourceActivity, { id })\n if (!activity) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n tenantId: activity.tenantId,\n organizationId: activity.organizationId,\n })\n return {\n activity: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n resourceId: typeof activity.resource === 'string' ? activity.resource : activity.resource.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: activity.occurredAt ?? null,\n authorUserId: activity.authorUserId ?? null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n },\n custom,\n }\n}\n\nasync function setActivityCustomFields(\n ctx: CommandRuntimeContext,\n activityId: string,\n organizationId: string,\n tenantId: string,\n values: Record<string, unknown>,\n) {\n if (!values || !Object.keys(values).length) return\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activityId,\n organizationId,\n tenantId,\n values,\n notify: false,\n })\n}\n\nconst createActivityCommand: CommandHandler<ResourcesResourceActivityCreateInput, { activityId: string; authorUserId: string | null }> = {\n id: 'resources.resource-activities.create',\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(resourcesResourceActivityCreateSchema, rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const authSub = ctx.auth?.isApiKey ? null : ctx.auth?.sub ?? null\n const normalizedAuthor = (() => {\n if (parsed.authorUserId) return parsed.authorUserId\n if (!authSub) return null\n const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n return uuidRegex.test(authSub) ? authSub : null\n })()\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n\n const activity = em.create(ResourcesResourceActivity, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n resource,\n activityType: parsed.activityType,\n subject: parsed.subject ?? null,\n body: parsed.body ?? null,\n occurredAt: parsed.occurredAt ?? null,\n authorUserId: normalizedAuthor,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n await em.flush()\n\n await setActivityCustomFields(ctx, activity.id, parsed.organizationId, parsed.tenantId, custom)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n return { activityId: activity.id, authorUserId: activity.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadActivitySnapshot(em, result.activityId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadActivitySnapshot(em, result.activityId)\n return {\n actionLabel: translate('resources.audit.resourceActivities.create', 'Create activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: result.activityId,\n parentResourceKind: 'resources.resource',\n parentResourceId: snapshot?.activity?.resourceId ?? null,\n tenantId: snapshot?.activity.tenantId ?? null,\n organizationId: snapshot?.activity.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const activityId = logEntry?.resourceId ?? null\n if (!activityId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(ResourcesResourceActivity, { id: activityId })\n if (existing) {\n em.remove(existing)\n await em.flush()\n }\n },\n}\n\nconst updateActivityCommand: CommandHandler<ResourcesResourceActivityUpdateInput, { activityId: string }> = {\n id: 'resources.resource-activities.update',\n async prepare(rawInput, ctx) {\n const parsed = resourcesResourceActivityUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadActivitySnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(resourcesResourceActivityUpdateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const activity = await em.findOne(ResourcesResourceActivity, { id: parsed.id })\n if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })\n ensureTenantScope(ctx, activity.tenantId)\n ensureOrganizationScope(ctx, activity.organizationId)\n\n if (parsed.entityId !== undefined) {\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n activity.resource = resource\n }\n if (parsed.activityType !== undefined) activity.activityType = parsed.activityType\n if (parsed.subject !== undefined) activity.subject = parsed.subject ?? null\n if (parsed.body !== undefined) activity.body = parsed.body ?? null\n if (parsed.occurredAt !== undefined) activity.occurredAt = parsed.occurredAt ?? null\n if (parsed.authorUserId !== undefined) activity.authorUserId = parsed.authorUserId ?? null\n if (parsed.appearanceIcon !== undefined) activity.appearanceIcon = parsed.appearanceIcon ?? null\n if (parsed.appearanceColor !== undefined) activity.appearanceColor = parsed.appearanceColor ?? null\n\n await em.flush()\n\n await setActivityCustomFields(ctx, activity.id, activity.organizationId, activity.tenantId, custom)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n return { activityId: activity.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ActivitySnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadActivitySnapshot(em, before.activity.id)\n const changes: ActivityChangeMap =\n afterSnapshot && afterSnapshot.activity\n ? buildChanges(\n before.activity as Record<string, unknown>,\n afterSnapshot.activity as Record<string, unknown>,\n ['resourceId', 'activityType', 'subject', 'body', 'occurredAt', 'authorUserId', 'appearanceIcon', 'appearanceColor'],\n )\n : {}\n const customChanges = diffCustomFieldChanges(before.custom, afterSnapshot?.custom)\n if (Object.keys(customChanges).length) changes.custom = customChanges\n return {\n actionLabel: translate('resources.audit.resourceActivities.update', 'Update activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: before.activity.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.activity.resourceId ?? null,\n tenantId: before.activity.tenantId,\n organizationId: before.activity.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ActivityUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let activity = await em.findOne(ResourcesResourceActivity, { id: before.activity.id })\n const resource = await requireResource(em, before.activity.resourceId, 'Resource not found')\n\n if (!activity) {\n activity = em.create(ResourcesResourceActivity, {\n id: before.activity.id,\n organizationId: before.activity.organizationId,\n tenantId: before.activity.tenantId,\n resource,\n activityType: before.activity.activityType,\n subject: before.activity.subject,\n body: before.activity.body,\n occurredAt: before.activity.occurredAt,\n authorUserId: before.activity.authorUserId,\n appearanceIcon: before.activity.appearanceIcon,\n appearanceColor: before.activity.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n } else {\n activity.resource = resource\n activity.activityType = before.activity.activityType\n activity.subject = before.activity.subject\n activity.body = before.activity.body\n activity.occurredAt = before.activity.occurredAt\n activity.authorUserId = before.activity.authorUserId\n activity.appearanceIcon = before.activity.appearanceIcon\n activity.appearanceColor = before.activity.appearanceColor\n }\n\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n const resetValues = buildCustomFieldResetMap(before.custom, payload?.after?.custom)\n if (Object.keys(resetValues).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n values: resetValues,\n notify: false,\n })\n }\n },\n}\n\nconst deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { activityId: string }> = {\n id: 'resources.resource-activities.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Activity id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadActivitySnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Activity id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const activity = await em.findOne(ResourcesResourceActivity, { id })\n if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })\n ensureTenantScope(ctx, activity.tenantId)\n ensureOrganizationScope(ctx, activity.organizationId)\n em.remove(activity)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n return { activityId: activity.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ActivitySnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('resources.audit.resourceActivities.delete', 'Delete activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: before.activity.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.activity.resourceId ?? null,\n tenantId: before.activity.tenantId,\n organizationId: before.activity.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ActivityUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, before.activity.resourceId, 'Resource not found')\n let activity = await em.findOne(ResourcesResourceActivity, { id: before.activity.id })\n if (!activity) {\n activity = em.create(ResourcesResourceActivity, {\n id: before.activity.id,\n organizationId: before.activity.organizationId,\n tenantId: before.activity.tenantId,\n resource,\n activityType: before.activity.activityType,\n subject: before.activity.subject,\n body: before.activity.body,\n occurredAt: before.activity.occurredAt,\n authorUserId: before.activity.authorUserId,\n appearanceIcon: before.activity.appearanceIcon,\n appearanceColor: before.activity.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n } else {\n activity.resource = resource\n activity.activityType = before.activity.activityType\n activity.subject = before.activity.subject\n activity.body = before.activity.body\n activity.occurredAt = before.activity.occurredAt\n activity.authorUserId = before.activity.authorUserId\n activity.appearanceIcon = before.activity.appearanceIcon\n activity.appearanceColor = before.activity.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n const resetValues = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(resetValues).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n values: resetValues,\n notify: false,\n })\n }\n },\n}\n\nregisterCommand(createActivityCommand)\nregisterCommand(updateActivityCommand)\nregisterCommand(deleteActivityCommand)\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,uBAAuB;AAEhC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAIP,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAEpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAS,iCAAiC;AAC1C;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP,SAAS,yBAAyB,mBAAmB,oBAAoB,uBAAuB;AAChG,SAAS,SAAS;AAElB,MAAM,qBAAqB,EAAE,UAAU;AACvC,MAAM,sBAAoE;AAAA,EACxE,YAAY,EAAE,UAAU;AAC1B;AA4BA,eAAe,qBAAqB,IAAmB,IAA8C;AACnG,QAAM,WAAW,MAAM,GAAG,QAAQ,2BAA2B,EAAE,GAAG,CAAC;AACnE,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,IAC/C,UAAU;AAAA,IACV,UAAU,SAAS;AAAA,IACnB,UAAU,SAAS;AAAA,IACnB,gBAAgB,SAAS;AAAA,EAC3B,CAAC;AACD,SAAO;AAAA,IACL,UAAU;AAAA,MACR,IAAI,SAAS;AAAA,MACb,gBAAgB,SAAS;AAAA,MACzB,UAAU,SAAS;AAAA,MACnB,YAAY,OAAO,SAAS,aAAa,WAAW,SAAS,WAAW,SAAS,SAAS;AAAA,MAC1F,cAAc,SAAS;AAAA,MACvB,SAAS,SAAS,WAAW;AAAA,MAC7B,MAAM,SAAS,QAAQ;AAAA,MACvB,YAAY,SAAS,cAAc;AAAA,MACnC,cAAc,SAAS,gBAAgB;AAAA,MACvC,gBAAgB,SAAS,kBAAkB;AAAA,MAC3C,iBAAiB,SAAS,mBAAmB;AAAA,IAC/C;AAAA,IACA;AAAA,EACF;AACF;AAEA,eAAe,wBACb,KACA,YACA,gBACA,UACA,QACA;AACA,MAAI,CAAC,UAAU,CAAC,OAAO,KAAK,MAAM,EAAE,OAAQ;AAC5C,QAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAM,qBAAqB;AAAA,IACzB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV,CAAC;AACH;AAEA,MAAM,wBAAmI;AAAA,EACvI,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,QAAQ,OAAO,IAAI,sBAAsB,uCAAuC,QAAQ;AAChG,sBAAkB,KAAK,OAAO,QAAQ;AACtC,4BAAwB,KAAK,OAAO,cAAc;AAClD,UAAM,
|
|
4
|
+
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n normalizeAuthorUserId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport {\n loadCustomFieldSnapshot,\n diffCustomFieldChanges,\n buildCustomFieldResetMap,\n type CustomFieldChangeSet,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { ResourcesResourceActivity } from '../data/entities'\nimport {\n resourcesResourceActivityCreateSchema,\n resourcesResourceActivityUpdateSchema,\n type ResourcesResourceActivityCreateInput,\n type ResourcesResourceActivityUpdateInput,\n} from '../data/validators'\nimport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireResource } from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst ACTIVITY_ENTITY_ID = E.resources.resources_resource_activity\nconst activityCrudIndexer: CrudIndexerConfig<ResourcesResourceActivity> = {\n entityType: E.resources.resources_resource_activity,\n}\n\ntype ActivitySnapshot = {\n activity: {\n id: string\n organizationId: string\n tenantId: string\n resourceId: string\n activityType: string\n subject: string | null\n body: string | null\n occurredAt: Date | null\n authorUserId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n }\n custom?: Record<string, unknown>\n}\n\ntype ActivityUndoPayload = {\n before?: ActivitySnapshot | null\n after?: ActivitySnapshot | null\n}\n\ntype ActivityChangeMap = Record<string, { from: unknown; to: unknown }> & {\n custom?: CustomFieldChangeSet\n}\n\nasync function loadActivitySnapshot(em: EntityManager, id: string): Promise<ActivitySnapshot | null> {\n const activity = await em.findOne(ResourcesResourceActivity, { id })\n if (!activity) return null\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n tenantId: activity.tenantId,\n organizationId: activity.organizationId,\n })\n return {\n activity: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n resourceId: typeof activity.resource === 'string' ? activity.resource : activity.resource.id,\n activityType: activity.activityType,\n subject: activity.subject ?? null,\n body: activity.body ?? null,\n occurredAt: activity.occurredAt ?? null,\n authorUserId: activity.authorUserId ?? null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n },\n custom,\n }\n}\n\nasync function setActivityCustomFields(\n ctx: CommandRuntimeContext,\n activityId: string,\n organizationId: string,\n tenantId: string,\n values: Record<string, unknown>,\n) {\n if (!values || !Object.keys(values).length) return\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activityId,\n organizationId,\n tenantId,\n values,\n notify: false,\n })\n}\n\nconst createActivityCommand: CommandHandler<ResourcesResourceActivityCreateInput, { activityId: string; authorUserId: string | null }> = {\n id: 'resources.resource-activities.create',\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(resourcesResourceActivityCreateSchema, rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n\n const activity = em.create(ResourcesResourceActivity, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n resource,\n activityType: parsed.activityType,\n subject: parsed.subject ?? null,\n body: parsed.body ?? null,\n occurredAt: parsed.occurredAt ?? null,\n authorUserId: normalizedAuthor,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n await em.flush()\n\n await setActivityCustomFields(ctx, activity.id, parsed.organizationId, parsed.tenantId, custom)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n return { activityId: activity.id, authorUserId: activity.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadActivitySnapshot(em, result.activityId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadActivitySnapshot(em, result.activityId)\n return {\n actionLabel: translate('resources.audit.resourceActivities.create', 'Create activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: result.activityId,\n parentResourceKind: 'resources.resource',\n parentResourceId: snapshot?.activity?.resourceId ?? null,\n tenantId: snapshot?.activity.tenantId ?? null,\n organizationId: snapshot?.activity.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const activityId = logEntry?.resourceId ?? null\n if (!activityId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(ResourcesResourceActivity, { id: activityId })\n if (existing) {\n em.remove(existing)\n await em.flush()\n }\n },\n}\n\nconst updateActivityCommand: CommandHandler<ResourcesResourceActivityUpdateInput, { activityId: string }> = {\n id: 'resources.resource-activities.update',\n async prepare(rawInput, ctx) {\n const parsed = resourcesResourceActivityUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadActivitySnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(resourcesResourceActivityUpdateSchema, rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const activity = await em.findOne(ResourcesResourceActivity, { id: parsed.id })\n if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })\n ensureTenantScope(ctx, activity.tenantId)\n ensureOrganizationScope(ctx, activity.organizationId)\n\n if (parsed.entityId !== undefined) {\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n activity.resource = resource\n }\n if (parsed.activityType !== undefined) activity.activityType = parsed.activityType\n if (parsed.subject !== undefined) activity.subject = parsed.subject ?? null\n if (parsed.body !== undefined) activity.body = parsed.body ?? null\n if (parsed.occurredAt !== undefined) activity.occurredAt = parsed.occurredAt ?? null\n if (parsed.authorUserId !== undefined) activity.authorUserId = parsed.authorUserId ?? null\n if (parsed.appearanceIcon !== undefined) activity.appearanceIcon = parsed.appearanceIcon ?? null\n if (parsed.appearanceColor !== undefined) activity.appearanceColor = parsed.appearanceColor ?? null\n\n await em.flush()\n\n await setActivityCustomFields(ctx, activity.id, activity.organizationId, activity.tenantId, custom)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n return { activityId: activity.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as ActivitySnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadActivitySnapshot(em, before.activity.id)\n const changes: ActivityChangeMap =\n afterSnapshot && afterSnapshot.activity\n ? buildChanges(\n before.activity as Record<string, unknown>,\n afterSnapshot.activity as Record<string, unknown>,\n ['resourceId', 'activityType', 'subject', 'body', 'occurredAt', 'authorUserId', 'appearanceIcon', 'appearanceColor'],\n )\n : {}\n const customChanges = diffCustomFieldChanges(before.custom, afterSnapshot?.custom)\n if (Object.keys(customChanges).length) changes.custom = customChanges\n return {\n actionLabel: translate('resources.audit.resourceActivities.update', 'Update activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: before.activity.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.activity.resourceId ?? null,\n tenantId: before.activity.tenantId,\n organizationId: before.activity.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ActivityUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let activity = await em.findOne(ResourcesResourceActivity, { id: before.activity.id })\n const resource = await requireResource(em, before.activity.resourceId, 'Resource not found')\n\n if (!activity) {\n activity = em.create(ResourcesResourceActivity, {\n id: before.activity.id,\n organizationId: before.activity.organizationId,\n tenantId: before.activity.tenantId,\n resource,\n activityType: before.activity.activityType,\n subject: before.activity.subject,\n body: before.activity.body,\n occurredAt: before.activity.occurredAt,\n authorUserId: before.activity.authorUserId,\n appearanceIcon: before.activity.appearanceIcon,\n appearanceColor: before.activity.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n } else {\n activity.resource = resource\n activity.activityType = before.activity.activityType\n activity.subject = before.activity.subject\n activity.body = before.activity.body\n activity.occurredAt = before.activity.occurredAt\n activity.authorUserId = before.activity.authorUserId\n activity.appearanceIcon = before.activity.appearanceIcon\n activity.appearanceColor = before.activity.appearanceColor\n }\n\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n const resetValues = buildCustomFieldResetMap(before.custom, payload?.after?.custom)\n if (Object.keys(resetValues).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n values: resetValues,\n notify: false,\n })\n }\n },\n}\n\nconst deleteActivityCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { activityId: string }> = {\n id: 'resources.resource-activities.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Activity id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadActivitySnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Activity id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const activity = await em.findOne(ResourcesResourceActivity, { id })\n if (!activity) throw new CrudHttpError(404, { error: 'Activity not found' })\n ensureTenantScope(ctx, activity.tenantId)\n ensureOrganizationScope(ctx, activity.organizationId)\n em.remove(activity)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n return { activityId: activity.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as ActivitySnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('resources.audit.resourceActivities.delete', 'Delete activity'),\n resourceKind: 'resources.resource_activity',\n resourceId: before.activity.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.activity.resourceId ?? null,\n tenantId: before.activity.tenantId,\n organizationId: before.activity.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies ActivityUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<ActivityUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, before.activity.resourceId, 'Resource not found')\n let activity = await em.findOne(ResourcesResourceActivity, { id: before.activity.id })\n if (!activity) {\n activity = em.create(ResourcesResourceActivity, {\n id: before.activity.id,\n organizationId: before.activity.organizationId,\n tenantId: before.activity.tenantId,\n resource,\n activityType: before.activity.activityType,\n subject: before.activity.subject,\n body: before.activity.body,\n occurredAt: before.activity.occurredAt,\n authorUserId: before.activity.authorUserId,\n appearanceIcon: before.activity.appearanceIcon,\n appearanceColor: before.activity.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(activity)\n } else {\n activity.resource = resource\n activity.activityType = before.activity.activityType\n activity.subject = before.activity.subject\n activity.body = before.activity.body\n activity.occurredAt = before.activity.occurredAt\n activity.authorUserId = before.activity.authorUserId\n activity.appearanceIcon = before.activity.appearanceIcon\n activity.appearanceColor = before.activity.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: activity,\n identifiers: {\n id: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n },\n indexer: activityCrudIndexer,\n })\n\n const resetValues = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(resetValues).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: ACTIVITY_ENTITY_ID,\n recordId: activity.id,\n organizationId: activity.organizationId,\n tenantId: activity.tenantId,\n values: resetValues,\n notify: false,\n })\n }\n },\n}\n\nregisterCommand(createActivityCommand)\nregisterCommand(updateActivityCommand)\nregisterCommand(deleteActivityCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
2
|
-
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from "@open-mercato/shared/lib/commands/helpers";
|
|
2
|
+
import { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId, normalizeAuthorUserId } from "@open-mercato/shared/lib/commands/helpers";
|
|
3
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
4
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
5
|
import { ResourcesResourceComment } from "../data/entities.js";
|
|
@@ -32,13 +32,7 @@ const createCommentCommand = {
|
|
|
32
32
|
const parsed = resourcesResourceCommentCreateSchema.parse(rawInput);
|
|
33
33
|
ensureTenantScope(ctx, parsed.tenantId);
|
|
34
34
|
ensureOrganizationScope(ctx, parsed.organizationId);
|
|
35
|
-
const
|
|
36
|
-
const normalizedAuthor = (() => {
|
|
37
|
-
if (parsed.authorUserId) return parsed.authorUserId;
|
|
38
|
-
if (!authSub) return null;
|
|
39
|
-
const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
|
|
40
|
-
return uuidRegex.test(authSub) ? authSub : null;
|
|
41
|
-
})();
|
|
35
|
+
const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth);
|
|
42
36
|
const em = ctx.container.resolve("em").fork();
|
|
43
37
|
const resource = await requireResource(em, parsed.entityId, "Resource not found");
|
|
44
38
|
ensureTenantScope(ctx, resource.tenantId);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/resources/commands/comments.ts"],
|
|
4
|
-
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { ResourcesResourceComment } from '../data/entities'\nimport {\n resourcesResourceCommentCreateSchema,\n resourcesResourceCommentUpdateSchema,\n type ResourcesResourceCommentCreateInput,\n type ResourcesResourceCommentUpdateInput,\n} from '../data/validators'\nimport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireResource } from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst commentCrudIndexer: CrudIndexerConfig<ResourcesResourceComment> = {\n entityType: E.resources.resources_resource_comment,\n}\n\ntype CommentSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n resourceId: string\n body: string\n authorUserId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n}\n\ntype CommentUndoPayload = {\n before?: CommentSnapshot | null\n after?: CommentSnapshot | null\n}\n\nasync function loadCommentSnapshot(em: EntityManager, id: string): Promise<CommentSnapshot | null> {\n const comment = await em.findOne(ResourcesResourceComment, { id })\n if (!comment) return null\n return {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n resourceId: typeof comment.resource === 'string' ? comment.resource : comment.resource.id,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n appearanceIcon: comment.appearanceIcon ?? null,\n appearanceColor: comment.appearanceColor ?? null,\n }\n}\n\nconst createCommentCommand: CommandHandler<\n ResourcesResourceCommentCreateInput,\n { commentId: string; authorUserId: string | null }\n> = {\n id: 'resources.resource-comments.create',\n async execute(rawInput, ctx) {\n const parsed = resourcesResourceCommentCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const authSub = ctx.auth?.isApiKey ? null : ctx.auth?.sub ?? null\n const normalizedAuthor = (() => {\n if (parsed.authorUserId) return parsed.authorUserId\n if (!authSub) return null\n const uuidRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/\n return uuidRegex.test(authSub) ? authSub : null\n })()\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n\n const comment = em.create(ResourcesResourceComment, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n resource,\n body: parsed.body,\n authorUserId: normalizedAuthor,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, authorUserId: comment.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadCommentSnapshot(em, result.commentId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadCommentSnapshot(em, result.commentId)\n return {\n actionLabel: translate('resources.audit.resourceComments.create', 'Create note'),\n resourceKind: 'resources.resource_comment',\n resourceId: result.commentId,\n parentResourceKind: 'resources.resource',\n parentResourceId: snapshot?.resourceId ?? null,\n tenantId: snapshot?.tenantId ?? null,\n organizationId: snapshot?.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const commentId = logEntry?.resourceId ?? null\n if (!commentId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(ResourcesResourceComment, { id: commentId })\n if (existing) {\n em.remove(existing)\n await em.flush()\n }\n },\n}\n\nconst updateCommentCommand: CommandHandler<ResourcesResourceCommentUpdateInput, { commentId: string }> = {\n id: 'resources.resource-comments.update',\n async prepare(rawInput, ctx) {\n const parsed = resourcesResourceCommentUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadCommentSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = resourcesResourceCommentUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(ResourcesResourceComment, { id: parsed.id })\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n\n if (parsed.entityId !== undefined) {\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n comment.resource = resource\n }\n if (parsed.body !== undefined) comment.body = parsed.body\n if (parsed.authorUserId !== undefined) comment.authorUserId = parsed.authorUserId ?? null\n if (parsed.appearanceIcon !== undefined) comment.appearanceIcon = parsed.appearanceIcon ?? null\n if (parsed.appearanceColor !== undefined) comment.appearanceColor = parsed.appearanceColor ?? null\n\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CommentSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadCommentSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as Record<string, unknown>,\n afterSnapshot as Record<string, unknown>,\n ['resourceId', 'body', 'authorUserId', 'appearanceIcon', 'appearanceColor'],\n )\n : {}\n return {\n actionLabel: translate('resources.audit.resourceComments.update', 'Update note'),\n resourceKind: 'resources.resource_comment',\n resourceId: before.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.resourceId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let comment = await em.findOne(ResourcesResourceComment, { id: before.id })\n const resource = await requireResource(em, before.resourceId, 'Resource not found')\n\n if (!comment) {\n comment = em.create(ResourcesResourceComment, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n resource,\n body: before.body,\n authorUserId: before.authorUserId,\n appearanceIcon: before.appearanceIcon,\n appearanceColor: before.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n } else {\n comment.resource = resource\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.appearanceIcon = before.appearanceIcon\n comment.appearanceColor = before.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { commentId: string }> = {\n id: 'resources.resource-comments.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Comment id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadCommentSnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Comment id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(ResourcesResourceComment, { id })\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n em.remove(comment)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n return { commentId: comment.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as CommentSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('resources.audit.resourceComments.delete', 'Delete note'),\n resourceKind: 'resources.resource_comment',\n resourceId: before.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.resourceId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, before.resourceId, 'Resource not found')\n let comment = await em.findOne(ResourcesResourceComment, { id: before.id })\n if (!comment) {\n comment = em.create(ResourcesResourceComment, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n resource,\n body: before.body,\n authorUserId: before.authorUserId,\n appearanceIcon: before.appearanceIcon,\n appearanceColor: before.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n } else {\n comment.resource = resource\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.appearanceIcon = before.appearanceIcon\n comment.appearanceColor = before.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n },\n}\n\nregisterCommand(createCommentCommand)\nregisterCommand(updateCommentCommand)\nregisterCommand(deleteCommentCommand)\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB,yBAAyB,cAAc,
|
|
4
|
+
"sourcesContent": ["import { registerCommand } from '@open-mercato/shared/lib/commands'\nimport type { CommandHandler } from '@open-mercato/shared/lib/commands'\nimport { emitCrudSideEffects, emitCrudUndoSideEffects, buildChanges, requireId, normalizeAuthorUserId } from '@open-mercato/shared/lib/commands/helpers'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { ResourcesResourceComment } from '../data/entities'\nimport {\n resourcesResourceCommentCreateSchema,\n resourcesResourceCommentUpdateSchema,\n type ResourcesResourceCommentCreateInput,\n type ResourcesResourceCommentUpdateInput,\n} from '../data/validators'\nimport { ensureOrganizationScope, ensureTenantScope, extractUndoPayload, requireResource } from './shared'\nimport { E } from '#generated/entities.ids.generated'\n\nconst commentCrudIndexer: CrudIndexerConfig<ResourcesResourceComment> = {\n entityType: E.resources.resources_resource_comment,\n}\n\ntype CommentSnapshot = {\n id: string\n organizationId: string\n tenantId: string\n resourceId: string\n body: string\n authorUserId: string | null\n appearanceIcon: string | null\n appearanceColor: string | null\n}\n\ntype CommentUndoPayload = {\n before?: CommentSnapshot | null\n after?: CommentSnapshot | null\n}\n\nasync function loadCommentSnapshot(em: EntityManager, id: string): Promise<CommentSnapshot | null> {\n const comment = await em.findOne(ResourcesResourceComment, { id })\n if (!comment) return null\n return {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n resourceId: typeof comment.resource === 'string' ? comment.resource : comment.resource.id,\n body: comment.body,\n authorUserId: comment.authorUserId ?? null,\n appearanceIcon: comment.appearanceIcon ?? null,\n appearanceColor: comment.appearanceColor ?? null,\n }\n}\n\nconst createCommentCommand: CommandHandler<\n ResourcesResourceCommentCreateInput,\n { commentId: string; authorUserId: string | null }\n> = {\n id: 'resources.resource-comments.create',\n async execute(rawInput, ctx) {\n const parsed = resourcesResourceCommentCreateSchema.parse(rawInput)\n ensureTenantScope(ctx, parsed.tenantId)\n ensureOrganizationScope(ctx, parsed.organizationId)\n const normalizedAuthor = normalizeAuthorUserId(parsed.authorUserId, ctx.auth)\n\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n\n const comment = em.create(ResourcesResourceComment, {\n organizationId: parsed.organizationId,\n tenantId: parsed.tenantId,\n resource,\n body: parsed.body,\n authorUserId: normalizedAuthor,\n appearanceIcon: parsed.appearanceIcon ?? null,\n appearanceColor: parsed.appearanceColor ?? null,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id, authorUserId: comment.authorUserId ?? null }\n },\n captureAfter: async (_input, result, ctx) => {\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n return await loadCommentSnapshot(em, result.commentId)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const snapshot = await loadCommentSnapshot(em, result.commentId)\n return {\n actionLabel: translate('resources.audit.resourceComments.create', 'Create note'),\n resourceKind: 'resources.resource_comment',\n resourceId: result.commentId,\n parentResourceKind: 'resources.resource',\n parentResourceId: snapshot?.resourceId ?? null,\n tenantId: snapshot?.tenantId ?? null,\n organizationId: snapshot?.organizationId ?? null,\n snapshotAfter: snapshot ?? null,\n payload: {\n undo: {\n after: snapshot ?? null,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const commentId = logEntry?.resourceId ?? null\n if (!commentId) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const existing = await em.findOne(ResourcesResourceComment, { id: commentId })\n if (existing) {\n em.remove(existing)\n await em.flush()\n }\n },\n}\n\nconst updateCommentCommand: CommandHandler<ResourcesResourceCommentUpdateInput, { commentId: string }> = {\n id: 'resources.resource-comments.update',\n async prepare(rawInput, ctx) {\n const parsed = resourcesResourceCommentUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadCommentSnapshot(em, parsed.id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(rawInput, ctx) {\n const parsed = resourcesResourceCommentUpdateSchema.parse(rawInput)\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(ResourcesResourceComment, { id: parsed.id })\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n\n if (parsed.entityId !== undefined) {\n const resource = await requireResource(em, parsed.entityId, 'Resource not found')\n ensureTenantScope(ctx, resource.tenantId)\n ensureOrganizationScope(ctx, resource.organizationId)\n comment.resource = resource\n }\n if (parsed.body !== undefined) comment.body = parsed.body\n if (parsed.authorUserId !== undefined) comment.authorUserId = parsed.authorUserId ?? null\n if (parsed.appearanceIcon !== undefined) comment.appearanceIcon = parsed.appearanceIcon ?? null\n if (parsed.appearanceColor !== undefined) comment.appearanceColor = parsed.appearanceColor ?? null\n\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n\n return { commentId: comment.id }\n },\n buildLog: async ({ snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const before = snapshots.before as CommentSnapshot | undefined\n if (!before) return null\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const afterSnapshot = await loadCommentSnapshot(em, before.id)\n const changes =\n afterSnapshot && before\n ? buildChanges(\n before as Record<string, unknown>,\n afterSnapshot as Record<string, unknown>,\n ['resourceId', 'body', 'authorUserId', 'appearanceIcon', 'appearanceColor'],\n )\n : {}\n return {\n actionLabel: translate('resources.audit.resourceComments.update', 'Update note'),\n resourceKind: 'resources.resource_comment',\n resourceId: before.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.resourceId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n snapshotAfter: afterSnapshot ?? null,\n changes,\n payload: {\n undo: {\n before,\n after: afterSnapshot ?? null,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n let comment = await em.findOne(ResourcesResourceComment, { id: before.id })\n const resource = await requireResource(em, before.resourceId, 'Resource not found')\n\n if (!comment) {\n comment = em.create(ResourcesResourceComment, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n resource,\n body: before.body,\n authorUserId: before.authorUserId,\n appearanceIcon: before.appearanceIcon,\n appearanceColor: before.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n } else {\n comment.resource = resource\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.appearanceIcon = before.appearanceIcon\n comment.appearanceColor = before.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n },\n}\n\nconst deleteCommentCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, { commentId: string }> = {\n id: 'resources.resource-comments.delete',\n async prepare(input, ctx) {\n const id = requireId(input, 'Comment id required')\n const em = (ctx.container.resolve('em') as EntityManager)\n const snapshot = await loadCommentSnapshot(em, id)\n return snapshot ? { before: snapshot } : {}\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Comment id required')\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const comment = await em.findOne(ResourcesResourceComment, { id })\n if (!comment) throw new CrudHttpError(404, { error: 'Comment not found' })\n ensureTenantScope(ctx, comment.tenantId)\n ensureOrganizationScope(ctx, comment.organizationId)\n em.remove(comment)\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n return { commentId: comment.id }\n },\n buildLog: async ({ snapshots }) => {\n const before = snapshots.before as CommentSnapshot | undefined\n if (!before) return null\n const { translate } = await resolveTranslations()\n return {\n actionLabel: translate('resources.audit.resourceComments.delete', 'Delete note'),\n resourceKind: 'resources.resource_comment',\n resourceId: before.id,\n parentResourceKind: 'resources.resource',\n parentResourceId: before.resourceId ?? null,\n tenantId: before.tenantId,\n organizationId: before.organizationId,\n snapshotBefore: before,\n payload: {\n undo: {\n before,\n } satisfies CommentUndoPayload,\n },\n }\n },\n undo: async ({ logEntry, ctx }) => {\n const payload = extractUndoPayload<CommentUndoPayload>(logEntry)\n const before = payload?.before\n if (!before) return\n const em = (ctx.container.resolve('em') as EntityManager).fork()\n const resource = await requireResource(em, before.resourceId, 'Resource not found')\n let comment = await em.findOne(ResourcesResourceComment, { id: before.id })\n if (!comment) {\n comment = em.create(ResourcesResourceComment, {\n id: before.id,\n organizationId: before.organizationId,\n tenantId: before.tenantId,\n resource,\n body: before.body,\n authorUserId: before.authorUserId,\n appearanceIcon: before.appearanceIcon,\n appearanceColor: before.appearanceColor,\n createdAt: new Date(),\n updatedAt: new Date(),\n })\n em.persist(comment)\n } else {\n comment.resource = resource\n comment.body = before.body\n comment.authorUserId = before.authorUserId\n comment.appearanceIcon = before.appearanceIcon\n comment.appearanceColor = before.appearanceColor\n }\n await em.flush()\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'created',\n entity: comment,\n identifiers: {\n id: comment.id,\n organizationId: comment.organizationId,\n tenantId: comment.tenantId,\n },\n indexer: commentCrudIndexer,\n })\n },\n}\n\nregisterCommand(createCommentCommand)\nregisterCommand(updateCommentCommand)\nregisterCommand(deleteCommentCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,163 +1,7 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { NextResponse } from "next/server";
|
|
3
1
|
import { z } from "zod";
|
|
4
|
-
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
|
-
import { runWithCacheTenant } from "@open-mercato/cache";
|
|
6
|
-
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
7
|
-
import { findAndCountWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
8
|
-
import { resolveDateRange } from "@open-mercato/ui/backend/date-range";
|
|
9
2
|
import { SalesOrder } from "../../../../data/entities.js";
|
|
10
3
|
import { extractCustomerName } from "../helpers.js";
|
|
11
|
-
import {
|
|
12
|
-
const WIDGET_CACHE_TTL = 12e4;
|
|
13
|
-
const WIDGET_CACHE_SEGMENT_TTL = 864e5;
|
|
14
|
-
const WIDGET_CACHE_SEGMENT_KEY = "widget-data:__segment__";
|
|
15
|
-
const WIDGET_CACHE_TAGS = ["widget-data", "widget-data:sales:orders"];
|
|
16
|
-
const WIDGET_CACHE_ID = "sales:new-orders";
|
|
17
|
-
const querySchema = z.object({
|
|
18
|
-
limit: z.coerce.number().min(1).max(20).default(5),
|
|
19
|
-
datePeriod: z.enum(["last24h", "last7d", "last30d", "custom"]).default("last24h"),
|
|
20
|
-
customFrom: z.string().optional(),
|
|
21
|
-
customTo: z.string().optional(),
|
|
22
|
-
tenantId: z.string().uuid().optional(),
|
|
23
|
-
organizationId: z.string().uuid().optional()
|
|
24
|
-
});
|
|
25
|
-
const metadata = {
|
|
26
|
-
GET: { requireAuth: true, requireFeatures: ["dashboards.view", "sales.widgets.new-orders"] }
|
|
27
|
-
};
|
|
28
|
-
function normalizeOrganizationIds(organizationIds) {
|
|
29
|
-
if (organizationIds === null) return null;
|
|
30
|
-
const set = new Set(organizationIds);
|
|
31
|
-
return Array.from(set).sort((a, b) => a.localeCompare(b));
|
|
32
|
-
}
|
|
33
|
-
function buildCacheKey(params) {
|
|
34
|
-
const hash = createHash("sha256");
|
|
35
|
-
hash.update(
|
|
36
|
-
JSON.stringify({
|
|
37
|
-
widget: WIDGET_CACHE_ID,
|
|
38
|
-
...params,
|
|
39
|
-
organizationIds: normalizeOrganizationIds(params.organizationIds)
|
|
40
|
-
})
|
|
41
|
-
);
|
|
42
|
-
return `widget-data:${hash.digest("hex").slice(0, 16)}`;
|
|
43
|
-
}
|
|
44
|
-
async function resolveContext(req, translate) {
|
|
45
|
-
const url = new URL(req.url);
|
|
46
|
-
const rawQuery = {};
|
|
47
|
-
for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value;
|
|
48
|
-
const parsed = querySchema.safeParse(rawQuery);
|
|
49
|
-
if (!parsed.success) {
|
|
50
|
-
throw new CrudHttpError(400, { error: translate("sales.errors.invalid_query", "Invalid query parameters") });
|
|
51
|
-
}
|
|
52
|
-
const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {
|
|
53
|
-
tenantId: parsed.data.tenantId ?? null,
|
|
54
|
-
organizationId: parsed.data.organizationId ?? null
|
|
55
|
-
});
|
|
56
|
-
return {
|
|
57
|
-
container,
|
|
58
|
-
em,
|
|
59
|
-
tenantId,
|
|
60
|
-
organizationIds,
|
|
61
|
-
limit: parsed.data.limit,
|
|
62
|
-
datePeriod: parsed.data.datePeriod,
|
|
63
|
-
customFrom: parsed.data.customFrom,
|
|
64
|
-
customTo: parsed.data.customTo
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
async function GET(req) {
|
|
68
|
-
const { translate } = await resolveTranslations();
|
|
69
|
-
try {
|
|
70
|
-
const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(
|
|
71
|
-
req,
|
|
72
|
-
translate
|
|
73
|
-
);
|
|
74
|
-
const range = (() => {
|
|
75
|
-
if (datePeriod === "custom") {
|
|
76
|
-
const from = customFrom ? new Date(customFrom) : /* @__PURE__ */ new Date(0);
|
|
77
|
-
const to = customTo ? new Date(customTo) : /* @__PURE__ */ new Date();
|
|
78
|
-
return { start: from, end: to };
|
|
79
|
-
}
|
|
80
|
-
const preset = datePeriod === "last7d" ? "last_7_days" : datePeriod === "last30d" ? "last_30_days" : "today";
|
|
81
|
-
return resolveDateRange(preset);
|
|
82
|
-
})();
|
|
83
|
-
let cache = null;
|
|
84
|
-
try {
|
|
85
|
-
cache = container.resolve("cache");
|
|
86
|
-
} catch {
|
|
87
|
-
cache = null;
|
|
88
|
-
}
|
|
89
|
-
const cacheKey = buildCacheKey({ tenantId, organizationIds, limit, datePeriod, customFrom, customTo });
|
|
90
|
-
const tenantScope = tenantId ?? null;
|
|
91
|
-
if (cache) {
|
|
92
|
-
try {
|
|
93
|
-
const cached = await runWithCacheTenant(tenantScope, () => cache.get(cacheKey));
|
|
94
|
-
if (cached && typeof cached === "object" && "items" in cached) {
|
|
95
|
-
return NextResponse.json(cached);
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const where = {
|
|
101
|
-
tenantId,
|
|
102
|
-
deletedAt: null,
|
|
103
|
-
createdAt: { $gte: range.start, $lte: range.end }
|
|
104
|
-
};
|
|
105
|
-
if (Array.isArray(organizationIds)) {
|
|
106
|
-
const unique = Array.from(new Set(organizationIds));
|
|
107
|
-
where.organizationId = unique.length === 1 ? unique[0] : { $in: unique };
|
|
108
|
-
}
|
|
109
|
-
const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null;
|
|
110
|
-
const [orders, total] = await findAndCountWithDecryption(
|
|
111
|
-
em,
|
|
112
|
-
SalesOrder,
|
|
113
|
-
where,
|
|
114
|
-
{
|
|
115
|
-
limit,
|
|
116
|
-
orderBy: { createdAt: "desc" }
|
|
117
|
-
},
|
|
118
|
-
{ tenantId, organizationId: organizationIdScope }
|
|
119
|
-
);
|
|
120
|
-
const items = orders.map((order) => ({
|
|
121
|
-
id: order.id,
|
|
122
|
-
orderNumber: order.orderNumber,
|
|
123
|
-
status: order.status ?? null,
|
|
124
|
-
fulfillmentStatus: order.fulfillmentStatus ?? null,
|
|
125
|
-
paymentStatus: order.paymentStatus ?? null,
|
|
126
|
-
customerName: extractCustomerName(order.customerSnapshot) ?? null,
|
|
127
|
-
customerEntityId: order.customerEntityId ?? null,
|
|
128
|
-
netAmount: order.grandTotalNetAmount ?? "0",
|
|
129
|
-
grossAmount: order.grandTotalGrossAmount ?? "0",
|
|
130
|
-
currency: order.currencyCode ?? null,
|
|
131
|
-
createdAt: order.createdAt.toISOString()
|
|
132
|
-
}));
|
|
133
|
-
const response = {
|
|
134
|
-
items,
|
|
135
|
-
total,
|
|
136
|
-
dateRange: { from: range.start.toISOString(), to: range.end.toISOString() }
|
|
137
|
-
};
|
|
138
|
-
if (cache) {
|
|
139
|
-
try {
|
|
140
|
-
await runWithCacheTenant(tenantScope, () => cache.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: WIDGET_CACHE_TAGS }));
|
|
141
|
-
await runWithCacheTenant(tenantScope, () => cache.set(
|
|
142
|
-
WIDGET_CACHE_SEGMENT_KEY,
|
|
143
|
-
{ updatedAt: response.dateRange.to },
|
|
144
|
-
{ ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ["widget-data"] }
|
|
145
|
-
));
|
|
146
|
-
} catch {
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
return NextResponse.json(response);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
if (err instanceof CrudHttpError) {
|
|
152
|
-
return NextResponse.json(err.body, { status: err.status });
|
|
153
|
-
}
|
|
154
|
-
console.error("sales.widgets.newOrders failed", err);
|
|
155
|
-
return NextResponse.json(
|
|
156
|
-
{ error: translate("sales.widgets.newOrders.error", "Failed to load orders") },
|
|
157
|
-
{ status: 500 }
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
4
|
+
import { makeDashboardWidgetRoute } from "../../../../widgets/dashboard/makeDashboardWidgetRoute.js";
|
|
161
5
|
const orderItemSchema = z.object({
|
|
162
6
|
id: z.string().uuid(),
|
|
163
7
|
orderNumber: z.string(),
|
|
@@ -171,33 +15,34 @@ const orderItemSchema = z.object({
|
|
|
171
15
|
currency: z.string().nullable(),
|
|
172
16
|
createdAt: z.string()
|
|
173
17
|
});
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
18
|
+
const { GET, metadata, openApi } = makeDashboardWidgetRoute({
|
|
19
|
+
entity: SalesOrder,
|
|
20
|
+
cacheId: "sales:new-orders",
|
|
21
|
+
cacheTags: ["widget-data:sales:orders"],
|
|
22
|
+
feature: "sales.widgets.new-orders",
|
|
23
|
+
itemSchema: orderItemSchema,
|
|
24
|
+
errorPrefix: "sales.widgets.newOrders",
|
|
25
|
+
openApi: {
|
|
26
|
+
summary: "New orders dashboard widget",
|
|
27
|
+
description: "Fetches recently created sales orders for the dashboard widget with a configurable date period.",
|
|
28
|
+
getSummary: "Fetch recently created sales orders",
|
|
29
|
+
itemDescription: "List of recent orders",
|
|
30
|
+
errorFallback: "Failed to load orders"
|
|
31
|
+
},
|
|
32
|
+
mapItem: (order) => ({
|
|
33
|
+
id: order.id,
|
|
34
|
+
orderNumber: order.orderNumber,
|
|
35
|
+
status: order.status ?? null,
|
|
36
|
+
fulfillmentStatus: order.fulfillmentStatus ?? null,
|
|
37
|
+
paymentStatus: order.paymentStatus ?? null,
|
|
38
|
+
customerName: extractCustomerName(order.customerSnapshot) ?? null,
|
|
39
|
+
customerEntityId: order.customerEntityId ?? null,
|
|
40
|
+
netAmount: order.grandTotalNetAmount ?? "0",
|
|
41
|
+
grossAmount: order.grandTotalGrossAmount ?? "0",
|
|
42
|
+
currency: order.currencyCode ?? null,
|
|
43
|
+
createdAt: order.createdAt ? order.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
180
44
|
})
|
|
181
45
|
});
|
|
182
|
-
const widgetErrorSchema = z.object({ error: z.string() });
|
|
183
|
-
const openApi = {
|
|
184
|
-
tag: "Sales",
|
|
185
|
-
summary: "New orders dashboard widget",
|
|
186
|
-
description: "Fetches recently created sales orders for the dashboard widget with a configurable date period.",
|
|
187
|
-
methods: {
|
|
188
|
-
GET: {
|
|
189
|
-
summary: "Fetch recently created sales orders",
|
|
190
|
-
query: querySchema,
|
|
191
|
-
responses: [{ status: 200, description: "List of recent orders", schema: responseSchema }],
|
|
192
|
-
errors: [
|
|
193
|
-
{ status: 400, description: "Invalid query parameters", schema: widgetErrorSchema },
|
|
194
|
-
{ status: 401, description: "Unauthorized", schema: widgetErrorSchema },
|
|
195
|
-
{ status: 403, description: "Forbidden", schema: widgetErrorSchema },
|
|
196
|
-
{ status: 500, description: "Widget failed to load", schema: widgetErrorSchema }
|
|
197
|
-
]
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
46
|
export {
|
|
202
47
|
GET,
|
|
203
48
|
metadata,
|