@open-mercato/core 0.6.7-develop.6751.1.ac823a3d26 → 0.6.7-develop.6758.1.697eade236

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/attachments/api/route.js +16 -2
  3. package/dist/modules/attachments/api/route.js.map +2 -2
  4. package/dist/modules/attachments/lib/upload-limits.js +33 -0
  5. package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
  6. package/dist/modules/communication_channels/api/post/test-seed/route.js +52 -2
  7. package/dist/modules/communication_channels/api/post/test-seed/route.js.map +2 -2
  8. package/dist/modules/currencies/di.js +4 -0
  9. package/dist/modules/currencies/di.js.map +2 -2
  10. package/dist/modules/currencies/services/baseCurrencyService.js +42 -0
  11. package/dist/modules/currencies/services/baseCurrencyService.js.map +7 -0
  12. package/dist/modules/customers/analytics.js +3 -1
  13. package/dist/modules/customers/analytics.js.map +2 -2
  14. package/dist/modules/customers/api/deals/aggregate/route.js +5 -4
  15. package/dist/modules/customers/api/deals/aggregate/route.js.map +2 -2
  16. package/dist/modules/customers/api/deals/summary/route.js +5 -4
  17. package/dist/modules/customers/api/deals/summary/route.js.map +2 -2
  18. package/dist/modules/customers/lib/optionalBaseCurrency.js +19 -0
  19. package/dist/modules/customers/lib/optionalBaseCurrency.js.map +7 -0
  20. package/dist/modules/dashboards/api/widgets/data/batch/route.js +8 -1
  21. package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +2 -2
  22. package/dist/modules/dashboards/api/widgets/data/route.js +8 -1
  23. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  24. package/dist/modules/dashboards/api/widgets/data/schema.js +2 -1
  25. package/dist/modules/dashboards/api/widgets/data/schema.js.map +2 -2
  26. package/dist/modules/dashboards/components/UnlabelledAmountNotice.js +33 -0
  27. package/dist/modules/dashboards/components/UnlabelledAmountNotice.js.map +7 -0
  28. package/dist/modules/dashboards/lib/aggregations.js +17 -0
  29. package/dist/modules/dashboards/lib/aggregations.js.map +2 -2
  30. package/dist/modules/dashboards/lib/formatters.js +84 -11
  31. package/dist/modules/dashboards/lib/formatters.js.map +2 -2
  32. package/dist/modules/dashboards/lib/optionalBaseCurrency.js +11 -0
  33. package/dist/modules/dashboards/lib/optionalBaseCurrency.js.map +7 -0
  34. package/dist/modules/dashboards/services/widgetDataService.js +86 -6
  35. package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
  36. package/dist/modules/dashboards/subscribers/invalidateWidgetDataCache.js +26 -0
  37. package/dist/modules/dashboards/subscribers/invalidateWidgetDataCache.js.map +7 -0
  38. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +9 -3
  39. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +2 -2
  40. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +10 -4
  41. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +2 -2
  42. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +9 -3
  43. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +2 -2
  44. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +8 -3
  45. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
  46. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +26 -18
  47. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
  48. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +22 -14
  49. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
  50. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +10 -4
  51. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
  52. package/dist/modules/notifications/api/route.js +62 -3
  53. package/dist/modules/notifications/api/route.js.map +2 -2
  54. package/dist/modules/sales/analytics.js +7 -3
  55. package/dist/modules/sales/analytics.js.map +2 -2
  56. package/dist/modules/sales/api/document-history/route.js +2 -1
  57. package/dist/modules/sales/api/document-history/route.js.map +2 -2
  58. package/dist/modules/sales/lib/historyHelpers.js +69 -1
  59. package/dist/modules/sales/lib/historyHelpers.js.map +2 -2
  60. package/dist/modules/sales/widgets/injection/document-history/widget.client.js +51 -2
  61. package/dist/modules/sales/widgets/injection/document-history/widget.client.js.map +2 -2
  62. package/package.json +7 -7
  63. package/src/modules/attachments/api/route.ts +16 -2
  64. package/src/modules/attachments/lib/upload-limits.ts +35 -0
  65. package/src/modules/communication_channels/api/post/test-seed/route.ts +69 -2
  66. package/src/modules/currencies/di.ts +4 -1
  67. package/src/modules/currencies/services/baseCurrencyService.ts +59 -0
  68. package/src/modules/customers/analytics.ts +2 -0
  69. package/src/modules/customers/api/deals/aggregate/route.ts +5 -4
  70. package/src/modules/customers/api/deals/summary/route.ts +5 -4
  71. package/src/modules/customers/lib/optionalBaseCurrency.ts +34 -0
  72. package/src/modules/dashboards/api/widgets/data/batch/route.ts +8 -1
  73. package/src/modules/dashboards/api/widgets/data/route.ts +8 -1
  74. package/src/modules/dashboards/api/widgets/data/schema.ts +1 -0
  75. package/src/modules/dashboards/components/UnlabelledAmountNotice.tsx +43 -0
  76. package/src/modules/dashboards/i18n/de.json +2 -0
  77. package/src/modules/dashboards/i18n/en.json +2 -0
  78. package/src/modules/dashboards/i18n/es.json +2 -0
  79. package/src/modules/dashboards/i18n/pl.json +2 -0
  80. package/src/modules/dashboards/lib/aggregations.ts +35 -0
  81. package/src/modules/dashboards/lib/formatters.ts +137 -12
  82. package/src/modules/dashboards/lib/optionalBaseCurrency.ts +22 -0
  83. package/src/modules/dashboards/services/widgetDataService.ts +110 -4
  84. package/src/modules/dashboards/subscribers/invalidateWidgetDataCache.ts +32 -0
  85. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +9 -3
  86. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +9 -3
  87. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +9 -3
  88. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +7 -2
  89. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +25 -15
  90. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +21 -11
  91. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +9 -3
  92. package/src/modules/notifications/api/route.ts +88 -3
  93. package/src/modules/sales/analytics.ts +4 -0
  94. package/src/modules/sales/api/document-history/route.ts +1 -0
  95. package/src/modules/sales/i18n/de.json +19 -0
  96. package/src/modules/sales/i18n/en.json +19 -0
  97. package/src/modules/sales/i18n/es.json +19 -0
  98. package/src/modules/sales/i18n/pl.json +19 -0
  99. package/src/modules/sales/lib/historyHelpers.ts +88 -1
  100. package/src/modules/sales/widgets/injection/document-history/widget.client.tsx +64 -3
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/notifications/api/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport { buildNotificationReadScopeWhere } from '../lib/notificationScope'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveNotificationContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n\n const filters: Record<string, unknown> = {\n recipientUserId: scope.userId,\n tenantId: scope.tenantId,\n ...buildNotificationReadScopeWhere(scope),\n }\n\n if (input.status) {\n filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status\n } else {\n filters.status = { $ne: 'dismissed' }\n }\n if (input.type) {\n filters.type = input.type\n }\n if (input.severity) {\n filters.severity = input.severity\n }\n if (input.sourceEntityType) {\n filters.sourceEntityType = input.sourceEntityType\n }\n if (input.sourceEntityId) {\n filters.sourceEntityId = input.sourceEntityId\n }\n if (input.since) {\n filters.createdAt = { $gt: new Date(input.since) }\n }\n\n const [notifications, total] = await Promise.all([\n em.find(Notification, filters, {\n orderBy: { createdAt: 'desc' },\n limit: input.pageSize,\n offset: (input.page - 1) * input.pageSize,\n }),\n em.count(Notification, filters),\n ])\n\n const items = notifications.map(toNotificationDto)\n\n return Response.json({\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n totalPages: Math.ceil(total / input.pageSize),\n })\n}\n\nexport async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = createNotificationSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service.create(parsed.data, scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ id: guarded.result.id }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n}\n\nexport const openApi = buildNotificationsCrudOpenApi({\n resourceName: 'Notification',\n querySchema: listNotificationsSchema,\n listResponseSchema: createPagedListResponseSchema(notificationItemSchema),\n create: {\n schema: createNotificationSchema,\n responseSchema: z.object({ id: z.string().uuid() }),\n description: 'Creates a notification for a user.',\n },\n})\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/core'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport {\n buildCollectionTags,\n debugCrudCache,\n isCrudCacheEnabled,\n resolveCrudCache,\n} from '@open-mercato/shared/lib/crud/cache'\nimport { Notification } from '../data/entities'\nimport { listNotificationsSchema, createNotificationSchema } from '../data/validators'\nimport { toNotificationDto } from '../lib/notificationMapper'\nimport { buildNotificationReadScopeWhere } from '../lib/notificationScope'\nimport {\n NOTIFICATION_RESOURCE_KIND,\n notificationCrudErrorResponse,\n notificationValidationErrorResponse,\n resolveNotificationContext,\n runGuardedNotificationWrite,\n} from '../lib/routeHelpers'\nimport {\n buildNotificationsCrudOpenApi,\n createPagedListResponseSchema,\n notificationItemSchema,\n} from './openapi'\n\nexport const metadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['notifications.create'] },\n}\n\nconst NOTIFICATIONS_LIST_TTL_MS = 10_000\nconst NOTIFICATIONS_LIST_CACHE_VERSION = 1\nconst NOTIFICATIONS_LIST_RESOURCE = 'notifications.notification'\n\ntype NotificationsListPayload = {\n items: ReturnType<typeof toNotificationDto>[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n}\n\nfunction buildNotificationsListCacheKey(\n userId: string,\n input: z.infer<typeof listNotificationsSchema>,\n): string {\n const filterSignature = JSON.stringify({\n status: Array.isArray(input.status)\n ? [...input.status].sort((left, right) => left.localeCompare(right))\n : input.status ?? null,\n type: input.type ?? null,\n severity: input.severity ?? null,\n sourceEntityType: input.sourceEntityType ?? null,\n sourceEntityId: input.sourceEntityId ?? null,\n since: input.since ?? null,\n page: input.page,\n pageSize: input.pageSize,\n })\n return `notifications:list:v${NOTIFICATIONS_LIST_CACHE_VERSION}:u=${userId}:filters=${filterSignature}`\n}\n\nfunction isNotificationsListPayload(value: unknown): value is NotificationsListPayload {\n if (!value || typeof value !== 'object') return false\n const payload = value as Partial<NotificationsListPayload>\n return (\n Array.isArray(payload.items)\n && typeof payload.total === 'number'\n && typeof payload.page === 'number'\n && typeof payload.pageSize === 'number'\n && typeof payload.totalPages === 'number'\n )\n}\n\nexport async function GET(req: Request) {\n const { ctx, scope } = await resolveNotificationContext(req)\n const em = ctx.container.resolve('em') as EntityManager\n\n const url = new URL(req.url)\n const queryParams = Object.fromEntries(url.searchParams.entries())\n const input = listNotificationsSchema.parse(queryParams)\n const userId = scope.userId\n const cache = userId && isCrudCacheEnabled()\n ? resolveCrudCache(ctx.container)\n : null\n const cacheKey = cache && userId ? buildNotificationsListCacheKey(userId, input) : null\n\n if (cache && cacheKey) {\n try {\n const cached = await runWithCacheTenant(scope.tenantId, () => cache.get(cacheKey))\n if (isNotificationsListPayload(cached)) {\n return Response.json(cached)\n }\n } catch (error) {\n debugCrudCache('notifications-list-cache-read-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n const filters: Record<string, unknown> = {\n recipientUserId: userId,\n tenantId: scope.tenantId,\n ...buildNotificationReadScopeWhere(scope),\n }\n\n if (input.status) {\n filters.status = Array.isArray(input.status) ? { $in: input.status } : input.status\n } else {\n filters.status = { $ne: 'dismissed' }\n }\n if (input.type) {\n filters.type = input.type\n }\n if (input.severity) {\n filters.severity = input.severity\n }\n if (input.sourceEntityType) {\n filters.sourceEntityType = input.sourceEntityType\n }\n if (input.sourceEntityId) {\n filters.sourceEntityId = input.sourceEntityId\n }\n if (input.since) {\n filters.createdAt = { $gt: new Date(input.since) }\n }\n\n const [notifications, total] = await Promise.all([\n em.find(Notification, filters, {\n orderBy: { createdAt: 'desc' },\n limit: input.pageSize,\n offset: (input.page - 1) * input.pageSize,\n }),\n em.count(Notification, filters),\n ])\n\n const items = notifications.map(toNotificationDto)\n\n const payload: NotificationsListPayload = {\n items,\n total,\n page: input.page,\n pageSize: input.pageSize,\n totalPages: Math.ceil(total / input.pageSize),\n }\n\n if (cache && cacheKey) {\n try {\n await runWithCacheTenant(scope.tenantId, () =>\n cache.set(cacheKey, payload, {\n ttl: NOTIFICATIONS_LIST_TTL_MS,\n tags: buildCollectionTags(NOTIFICATIONS_LIST_RESOURCE, scope.tenantId, [null]),\n }),\n )\n } catch (error) {\n debugCrudCache('notifications-list-cache-write-failed', {\n error: error instanceof Error ? error.message : String(error),\n })\n }\n }\n\n return Response.json(payload)\n}\n\nexport async function POST(req: Request) {\n const { service, scope, ctx } = await resolveNotificationContext(req)\n\n const body = await req.json().catch(() => ({}))\n const parsed = createNotificationSchema.safeParse(body)\n if (!parsed.success) {\n return notificationValidationErrorResponse(parsed.error)\n }\n\n try {\n const guarded = await runGuardedNotificationWrite(\n ctx.container,\n scope,\n req,\n {\n resourceKind: NOTIFICATION_RESOURCE_KIND,\n operation: 'create',\n payload: parsed.data as Record<string, unknown>,\n },\n () => service.create(parsed.data, scope),\n )\n if (!guarded.ok) return guarded.response\n\n return Response.json({ id: guarded.result.id }, { status: 201 })\n } catch (error) {\n const errorResponse = notificationCrudErrorResponse(error)\n if (errorResponse) return errorResponse\n throw error\n }\n}\n\nexport const openApi = buildNotificationsCrudOpenApi({\n resourceName: 'Notification',\n querySchema: listNotificationsSchema,\n listResponseSchema: createPagedListResponseSchema(notificationItemSchema),\n create: {\n schema: createNotificationSchema,\n responseSchema: z.object({ id: z.string().uuid() }),\n description: 'Creates a notification for a user.',\n },\n})\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,8 @@ const analyticsConfig = {
6
6
  entityConfig: {
7
7
  tableName: "sales_orders",
8
8
  dateField: "placed_at",
9
- defaultScopeFields: ["tenant_id", "organization_id"]
9
+ defaultScopeFields: ["tenant_id", "organization_id"],
10
+ currencyField: "currencyCode"
10
11
  },
11
12
  fieldMappings: {
12
13
  id: { dbColumn: "id", type: "uuid" },
@@ -37,10 +38,12 @@ const analyticsConfig = {
37
38
  entityConfig: {
38
39
  tableName: "sales_order_lines",
39
40
  dateField: "created_at",
40
- defaultScopeFields: ["tenant_id", "organization_id"]
41
+ defaultScopeFields: ["tenant_id", "organization_id"],
42
+ currencyField: "currencyCode"
41
43
  },
42
44
  fieldMappings: {
43
45
  id: { dbColumn: "id", type: "uuid" },
46
+ currencyCode: { dbColumn: "currency_code", type: "text" },
44
47
  totalGrossAmount: { dbColumn: "total_gross_amount", type: "numeric" },
45
48
  totalNetAmount: { dbColumn: "total_net_amount", type: "numeric" },
46
49
  unitGrossPrice: { dbColumn: "unit_gross_price", type: "numeric" },
@@ -61,7 +64,8 @@ const analyticsConfig = {
61
64
  entityConfig: {
62
65
  tableName: "sales_quotes",
63
66
  dateField: "created_at",
64
- defaultScopeFields: ["tenant_id", "organization_id"]
67
+ defaultScopeFields: ["tenant_id", "organization_id"],
68
+ currencyField: "currencyCode"
65
69
  },
66
70
  fieldMappings: {
67
71
  id: { dbColumn: "id", type: "uuid" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/sales/analytics.ts"],
4
- "sourcesContent": ["import type { AnalyticsModuleConfig } from '@open-mercato/shared/modules/analytics'\n\nexport const analyticsConfig: AnalyticsModuleConfig = {\n entities: [\n {\n entityId: 'sales:orders',\n requiredFeatures: ['sales.orders.view'],\n entityConfig: {\n tableName: 'sales_orders',\n dateField: 'placed_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n grandTotalGrossAmount: { dbColumn: 'grand_total_gross_amount', type: 'numeric' },\n grandTotalNetAmount: { dbColumn: 'grand_total_net_amount', type: 'numeric' },\n subtotalGrossAmount: { dbColumn: 'subtotal_gross_amount', type: 'numeric' },\n subtotalNetAmount: { dbColumn: 'subtotal_net_amount', type: 'numeric' },\n discountTotalAmount: { dbColumn: 'discount_total_amount', type: 'numeric' },\n taxTotalAmount: { dbColumn: 'tax_total_amount', type: 'numeric' },\n lineItemCount: { dbColumn: 'line_item_count', type: 'numeric' },\n status: { dbColumn: 'status', type: 'text' },\n fulfillmentStatus: { dbColumn: 'fulfillment_status', type: 'text' },\n paymentStatus: { dbColumn: 'payment_status', type: 'text' },\n customerEntityId: { dbColumn: 'customer_entity_id', type: 'uuid' },\n channelId: { dbColumn: 'channel_id', type: 'uuid' },\n placedAt: { dbColumn: 'placed_at', type: 'timestamp' },\n currencyCode: { dbColumn: 'currency_code', type: 'text' },\n shippingAddressSnapshot: { dbColumn: 'shipping_address_snapshot', type: 'jsonb' },\n },\n labelResolvers: {\n customerEntityId: { table: 'customer_entities', idColumn: 'id', labelColumn: 'display_name' },\n channelId: { table: 'sales_channels', idColumn: 'id', labelColumn: 'name' },\n },\n },\n {\n entityId: 'sales:order_lines',\n requiredFeatures: ['sales.orders.view'],\n entityConfig: {\n tableName: 'sales_order_lines',\n dateField: 'created_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n totalGrossAmount: { dbColumn: 'total_gross_amount', type: 'numeric' },\n totalNetAmount: { dbColumn: 'total_net_amount', type: 'numeric' },\n unitGrossPrice: { dbColumn: 'unit_gross_price', type: 'numeric' },\n quantity: { dbColumn: 'quantity', type: 'numeric' },\n productId: { dbColumn: 'product_id', type: 'uuid' },\n productVariantId: { dbColumn: 'product_variant_id', type: 'uuid' },\n status: { dbColumn: 'status', type: 'text' },\n createdAt: { dbColumn: 'created_at', type: 'timestamp' },\n },\n labelResolvers: {\n productId: { table: 'catalog_products', idColumn: 'id', labelColumn: 'title' },\n productVariantId: { table: 'catalog_product_variants', idColumn: 'id', labelColumn: 'name' },\n },\n },\n {\n entityId: 'sales:quotes',\n requiredFeatures: ['sales.quotes.view'],\n entityConfig: {\n tableName: 'sales_quotes',\n dateField: 'created_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n quoteNumber: { dbColumn: 'quote_number', type: 'text' },\n status: { dbColumn: 'status', type: 'text' },\n customerEntityId: { dbColumn: 'customer_entity_id', type: 'uuid' },\n channelId: { dbColumn: 'channel_id', type: 'uuid' },\n validFrom: { dbColumn: 'valid_from', type: 'timestamp' },\n validUntil: { dbColumn: 'valid_until', type: 'timestamp' },\n placedAt: { dbColumn: 'placed_at', type: 'timestamp' },\n createdAt: { dbColumn: 'created_at', type: 'timestamp' },\n currencyCode: { dbColumn: 'currency_code', type: 'text' },\n subtotalGrossAmount: { dbColumn: 'subtotal_gross_amount', type: 'numeric' },\n subtotalNetAmount: { dbColumn: 'subtotal_net_amount', type: 'numeric' },\n discountTotalAmount: { dbColumn: 'discount_total_amount', type: 'numeric' },\n taxTotalAmount: { dbColumn: 'tax_total_amount', type: 'numeric' },\n grandTotalGrossAmount: { dbColumn: 'grand_total_gross_amount', type: 'numeric' },\n grandTotalNetAmount: { dbColumn: 'grand_total_net_amount', type: 'numeric' },\n lineItemCount: { dbColumn: 'line_item_count', type: 'numeric' },\n shippingAddressSnapshot: { dbColumn: 'shipping_address_snapshot', type: 'jsonb' },\n customerSnapshot: { dbColumn: 'customer_snapshot', type: 'jsonb' },\n },\n labelResolvers: {\n customerEntityId: { table: 'customer_entities', idColumn: 'id', labelColumn: 'display_name' },\n channelId: { table: 'sales_channels', idColumn: 'id', labelColumn: 'name' },\n },\n },\n ],\n}\n\nexport default analyticsConfig\nexport const config = analyticsConfig\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { AnalyticsModuleConfig } from '@open-mercato/shared/modules/analytics'\n\nexport const analyticsConfig: AnalyticsModuleConfig = {\n entities: [\n {\n entityId: 'sales:orders',\n requiredFeatures: ['sales.orders.view'],\n entityConfig: {\n tableName: 'sales_orders',\n dateField: 'placed_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n currencyField: 'currencyCode',\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n grandTotalGrossAmount: { dbColumn: 'grand_total_gross_amount', type: 'numeric' },\n grandTotalNetAmount: { dbColumn: 'grand_total_net_amount', type: 'numeric' },\n subtotalGrossAmount: { dbColumn: 'subtotal_gross_amount', type: 'numeric' },\n subtotalNetAmount: { dbColumn: 'subtotal_net_amount', type: 'numeric' },\n discountTotalAmount: { dbColumn: 'discount_total_amount', type: 'numeric' },\n taxTotalAmount: { dbColumn: 'tax_total_amount', type: 'numeric' },\n lineItemCount: { dbColumn: 'line_item_count', type: 'numeric' },\n status: { dbColumn: 'status', type: 'text' },\n fulfillmentStatus: { dbColumn: 'fulfillment_status', type: 'text' },\n paymentStatus: { dbColumn: 'payment_status', type: 'text' },\n customerEntityId: { dbColumn: 'customer_entity_id', type: 'uuid' },\n channelId: { dbColumn: 'channel_id', type: 'uuid' },\n placedAt: { dbColumn: 'placed_at', type: 'timestamp' },\n currencyCode: { dbColumn: 'currency_code', type: 'text' },\n shippingAddressSnapshot: { dbColumn: 'shipping_address_snapshot', type: 'jsonb' },\n },\n labelResolvers: {\n customerEntityId: { table: 'customer_entities', idColumn: 'id', labelColumn: 'display_name' },\n channelId: { table: 'sales_channels', idColumn: 'id', labelColumn: 'name' },\n },\n },\n {\n entityId: 'sales:order_lines',\n requiredFeatures: ['sales.orders.view'],\n entityConfig: {\n tableName: 'sales_order_lines',\n dateField: 'created_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n currencyField: 'currencyCode',\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n currencyCode: { dbColumn: 'currency_code', type: 'text' },\n totalGrossAmount: { dbColumn: 'total_gross_amount', type: 'numeric' },\n totalNetAmount: { dbColumn: 'total_net_amount', type: 'numeric' },\n unitGrossPrice: { dbColumn: 'unit_gross_price', type: 'numeric' },\n quantity: { dbColumn: 'quantity', type: 'numeric' },\n productId: { dbColumn: 'product_id', type: 'uuid' },\n productVariantId: { dbColumn: 'product_variant_id', type: 'uuid' },\n status: { dbColumn: 'status', type: 'text' },\n createdAt: { dbColumn: 'created_at', type: 'timestamp' },\n },\n labelResolvers: {\n productId: { table: 'catalog_products', idColumn: 'id', labelColumn: 'title' },\n productVariantId: { table: 'catalog_product_variants', idColumn: 'id', labelColumn: 'name' },\n },\n },\n {\n entityId: 'sales:quotes',\n requiredFeatures: ['sales.quotes.view'],\n entityConfig: {\n tableName: 'sales_quotes',\n dateField: 'created_at',\n defaultScopeFields: ['tenant_id', 'organization_id'],\n currencyField: 'currencyCode',\n },\n fieldMappings: {\n id: { dbColumn: 'id', type: 'uuid' },\n quoteNumber: { dbColumn: 'quote_number', type: 'text' },\n status: { dbColumn: 'status', type: 'text' },\n customerEntityId: { dbColumn: 'customer_entity_id', type: 'uuid' },\n channelId: { dbColumn: 'channel_id', type: 'uuid' },\n validFrom: { dbColumn: 'valid_from', type: 'timestamp' },\n validUntil: { dbColumn: 'valid_until', type: 'timestamp' },\n placedAt: { dbColumn: 'placed_at', type: 'timestamp' },\n createdAt: { dbColumn: 'created_at', type: 'timestamp' },\n currencyCode: { dbColumn: 'currency_code', type: 'text' },\n subtotalGrossAmount: { dbColumn: 'subtotal_gross_amount', type: 'numeric' },\n subtotalNetAmount: { dbColumn: 'subtotal_net_amount', type: 'numeric' },\n discountTotalAmount: { dbColumn: 'discount_total_amount', type: 'numeric' },\n taxTotalAmount: { dbColumn: 'tax_total_amount', type: 'numeric' },\n grandTotalGrossAmount: { dbColumn: 'grand_total_gross_amount', type: 'numeric' },\n grandTotalNetAmount: { dbColumn: 'grand_total_net_amount', type: 'numeric' },\n lineItemCount: { dbColumn: 'line_item_count', type: 'numeric' },\n shippingAddressSnapshot: { dbColumn: 'shipping_address_snapshot', type: 'jsonb' },\n customerSnapshot: { dbColumn: 'customer_snapshot', type: 'jsonb' },\n },\n labelResolvers: {\n customerEntityId: { table: 'customer_entities', idColumn: 'id', labelColumn: 'display_name' },\n channelId: { table: 'sales_channels', idColumn: 'id', labelColumn: 'name' },\n },\n },\n ],\n}\n\nexport default analyticsConfig\nexport const config = analyticsConfig\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -135,7 +135,8 @@ const historyEntrySchema = z.object({
135
135
  statusFrom: z.string().nullable().optional(),
136
136
  statusTo: z.string().nullable().optional(),
137
137
  documentKind: z.enum(["order", "quote"]).optional(),
138
- commandId: z.string().optional()
138
+ commandId: z.string().optional(),
139
+ changedFields: z.array(z.string()).optional()
139
140
  }).optional()
140
141
  });
141
142
  const documentHistoryResponseSchema = z.object({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/sales/api/document-history/route.ts"],
4
- "sourcesContent": ["import { z } from 'zod'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadAuditLogDisplayMaps } from '@open-mercato/core/modules/audit_logs/api/audit-logs/display'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildHistoryEntries } from '../../lib/historyHelpers'\nimport { SalesNote } from '../../data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\n// Spec: SPEC-006-2026-01-23-order-status-history\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst querySchema = z.object({\n kind: z.enum(['order', 'quote']),\n id: z.string().uuid(),\n limit: z.coerce.number().min(1).max(100).default(50),\n before: z.string().optional(),\n after: z.string().optional(),\n types: z.string().optional(), // comma-separated: status,action,comment\n})\n\nexport type DocumentHistoryEntryKind = 'status' | 'action' | 'comment'\n\nconst HISTORY_TYPES: ReadonlySet<DocumentHistoryEntryKind> = new Set(['status', 'action', 'comment'])\n\nexport function parseDocumentHistoryTypes(input: string | null | undefined): Set<DocumentHistoryEntryKind> {\n const raw = typeof input === 'string' ? input.trim() : ''\n if (!raw) return new Set()\n const values = raw\n .split(',')\n .map((part) => part.trim().toLowerCase())\n .filter((value) => value.length > 0)\n const result = new Set<DocumentHistoryEntryKind>()\n values.forEach((value) => {\n if (HISTORY_TYPES.has(value as DocumentHistoryEntryKind)) {\n result.add(value as DocumentHistoryEntryKind)\n }\n })\n return result\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.documents.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n // Resolve the organization scope the same way every other scoped read does.\n // Under \"All organizations\" (super-admin) `rbacOrganizationId` is null and\n // `where` is empty, so the read scopes by tenant + resource only instead of\n // failing \u2014 the document's `id` is a tenant-unique UUID, so no cross-org leak.\n const orgFilter = resolveOrganizationScopeFilter(scope, auth)\n const organizationId = orgFilter.rbacOrganizationId\n\n const requiredFeature = query.kind === 'order' ? 'sales.orders.view' : 'sales.quotes.view'\n const rbac = container.resolve('rbacService') as RbacService\n const hasAccess = await rbac.userHasAllFeatures(auth.sub, [requiredFeature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasAccess) {\n throw new CrudHttpError(403, { error: translate('api.errors.forbidden', 'Forbidden') })\n }\n\n const resourceKind = query.kind === 'order' ? 'sales.order' : 'sales.quote'\n\n const actionLogService = container.resolve('actionLogService') as ActionLogService\n const em = (container.resolve('em') as EntityManager).fork()\n\n const [actionLogList, notes] = await Promise.all([\n actionLogService.list({\n tenantId: auth.tenantId,\n organizationId: organizationId ?? undefined,\n resourceKind,\n resourceId: query.id,\n includeRelated: true,\n limit: query.limit,\n before: query.before ? new Date(query.before) : undefined,\n after: query.after ? new Date(query.after) : undefined,\n }),\n findWithDecryption(\n em,\n SalesNote,\n {\n contextType: query.kind,\n contextId: query.id,\n tenantId: auth.tenantId,\n ...orgFilter.where,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId: organizationId ?? undefined },\n ),\n ])\n const logs = actionLogList.items as ActionLog[]\n\n const allUserIds = [\n ...logs.map((l) => l.actorUserId).filter((v): v is string => !!v),\n ...notes.map((n) => n.authorUserId).filter((v): v is string => !!v),\n ]\n\n const displayMaps = await loadAuditLogDisplayMaps(em, {\n userIds: allUserIds,\n tenantIds: [],\n organizationIds: [],\n })\n\n let items = buildHistoryEntries({ actionLogs: logs, notes, kind: query.kind, displayUsers: displayMaps.users })\n const typesFilter = parseDocumentHistoryTypes(query.types)\n if (typesFilter.size > 0) {\n items = items.filter((entry) => typesFilter.has(entry.kind as DocumentHistoryEntryKind))\n }\n\n let nextCursor: string | undefined = undefined\n if (logs.length === query.limit && items.length > 0) {\n const last = items[items.length - 1]\n nextCursor = Buffer.from(`${last.occurredAt}|${last.id}`).toString('base64')\n }\n\n return NextResponse.json({ items, nextCursor })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('sales.document-history.get failed', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { error: translate('sales.documents.history.error', 'Failed to load history.') },\n { status: 400 }\n )\n }\n}\n\nconst historyActorSchema = z.object({\n id: z.string().uuid().nullable(),\n label: z.string(),\n})\n\nconst historyEntrySchema = z.object({\n id: z.string(),\n occurredAt: z.string().datetime(),\n kind: z.enum(['status', 'action', 'comment']),\n action: z.string(),\n actor: historyActorSchema,\n source: z.enum(['action_log', 'note']),\n metadata: z.object({\n statusFrom: z.string().nullable().optional(),\n statusTo: z.string().nullable().optional(),\n documentKind: z.enum(['order', 'quote']).optional(),\n commandId: z.string().optional(),\n }).optional(),\n})\n\nconst documentHistoryResponseSchema = z.object({\n items: z.array(historyEntrySchema),\n nextCursor: z.string().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Get document change history',\n methods: {\n GET: {\n summary: 'List history entries for an order or quote',\n query: querySchema,\n responses: [\n { status: 200, description: 'History entries', schema: documentHistoryResponseSchema },\n { status: 400, description: 'Invalid query', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 404, description: 'Document not found', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
- "mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,sCAAsC;AAC/C,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAK7B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,OAAO;AAI5B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,KAAK,CAAC,SAAS,OAAO,CAAC;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACnD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA;AAC7B,CAAC;AAID,MAAM,gBAAuD,oBAAI,IAAI,CAAC,UAAU,UAAU,SAAS,CAAC;AAE7F,SAAS,0BAA0B,OAAiE;AACzG,QAAM,MAAM,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;AACvD,MAAI,CAAC,IAAK,QAAO,oBAAI,IAAI;AACzB,QAAM,SAAS,IACZ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,YAAY,CAAC,EACvC,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACrC,QAAM,SAAS,oBAAI,IAA8B;AACjD,SAAO,QAAQ,CAAC,UAAU;AACxB,QAAI,cAAc,IAAI,KAAiC,GAAG;AACxD,aAAO,IAAI,KAAiC;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,YAAY,MAAM,OAAO,YAAY,IAAI,YAAY,CAAC;AAEpE,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAEhD,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,IAC1G;AAEA,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AAKxF,UAAM,YAAY,+BAA+B,OAAO,IAAI;AAC5D,UAAM,iBAAiB,UAAU;AAEjC,UAAM,kBAAkB,MAAM,SAAS,UAAU,sBAAsB;AACvE,UAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,UAAM,YAAY,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,eAAe,GAAG;AAAA,MAC3E,UAAU,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,wBAAwB,WAAW,EAAE,CAAC;AAAA,IACxF;AAEA,UAAM,eAAe,MAAM,SAAS,UAAU,gBAAgB;AAE9D,UAAM,mBAAmB,UAAU,QAAQ,kBAAkB;AAC7D,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAE3D,UAAM,CAAC,eAAe,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC/C,iBAAiB,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,gBAAgB,kBAAkB;AAAA,QAClC;AAAA,QACA,YAAY,MAAM;AAAA,QAClB,gBAAgB;AAAA,QAChB,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM,SAAS,IAAI,KAAK,MAAM,MAAM,IAAI;AAAA,QAChD,OAAO,MAAM,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI;AAAA,MAC/C,CAAC;AAAA,MACD;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,UACE,aAAa,MAAM;AAAA,UACnB,WAAW,MAAM;AAAA,UACjB,UAAU,KAAK;AAAA,UACf,GAAG,UAAU;AAAA,UACb,WAAW;AAAA,QACb;AAAA,QACA,EAAE,SAAS,EAAE,WAAW,OAAO,EAAE;AAAA,QACjC,EAAE,UAAU,KAAK,UAAU,gBAAgB,kBAAkB,OAAU;AAAA,MACzE;AAAA,IACF,CAAC;AACD,UAAM,OAAO,cAAc;AAE3B,UAAM,aAAa;AAAA,MACjB,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAChE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,IACpE;AAEA,UAAM,cAAc,MAAM,wBAAwB,IAAI;AAAA,MACpD,SAAS;AAAA,MACT,WAAW,CAAC;AAAA,MACZ,iBAAiB,CAAC;AAAA,IACpB,CAAC;AAED,QAAI,QAAQ,oBAAoB,EAAE,YAAY,MAAM,OAAO,MAAM,MAAM,MAAM,cAAc,YAAY,MAAM,CAAC;AAC9G,UAAM,cAAc,0BAA0B,MAAM,KAAK;AACzD,QAAI,YAAY,OAAO,GAAG;AACxB,cAAQ,MAAM,OAAO,CAAC,UAAU,YAAY,IAAI,MAAM,IAAgC,CAAC;AAAA,IACzF;AAEA,QAAI,aAAiC;AACrC,QAAI,KAAK,WAAW,MAAM,SAAS,MAAM,SAAS,GAAG;AACnD,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,mBAAa,OAAO,KAAK,GAAG,KAAK,UAAU,IAAI,KAAK,EAAE,EAAE,EAAE,SAAS,QAAQ;AAAA,IAC7E;AAEA,WAAO,aAAa,KAAK,EAAE,OAAO,WAAW,CAAC;AAAA,EAChD,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,WAAO,MAAM,qCAAqC,EAAE,IAAI,CAAC;AACzD,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,UAAU,iCAAiC,yBAAyB,EAAE;AAAA,MAC/E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAEA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,OAAO;AAAA,EACb,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,MAAM,EAAE,KAAK,CAAC,UAAU,UAAU,SAAS,CAAC;AAAA,EAC5C,QAAQ,EAAE,OAAO;AAAA,EACjB,OAAO;AAAA,EACP,QAAQ,EAAE,KAAK,CAAC,cAAc,MAAM,CAAC;AAAA,EACrC,UAAU,EAAE,OAAO;AAAA,IACjB,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,cAAc,EAAE,KAAK,CAAC,SAAS,OAAO,CAAC,EAAE,SAAS;AAAA,IAClD,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,CAAC,EAAE,SAAS;AACd,CAAC;AAED,MAAM,gCAAgC,EAAE,OAAO;AAAA,EAC7C,OAAO,EAAE,MAAM,kBAAkB;AAAA,EACjC,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,8BAA8B;AAAA,QACrF,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACrF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACjF,EAAE,QAAQ,KAAK,aAAa,sBAAsB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { z } from 'zod'\nimport { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport { loadAuditLogDisplayMaps } from '@open-mercato/core/modules/audit_logs/api/audit-logs/display'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { resolveOrganizationScopeFilter } from '@open-mercato/core/modules/directory/utils/organizationScopeFilter'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { buildHistoryEntries } from '../../lib/historyHelpers'\nimport { SalesNote } from '../../data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('sales')\n\n// Spec: SPEC-006-2026-01-23-order-status-history\n\nexport const metadata = {\n GET: { requireAuth: true },\n}\n\nconst querySchema = z.object({\n kind: z.enum(['order', 'quote']),\n id: z.string().uuid(),\n limit: z.coerce.number().min(1).max(100).default(50),\n before: z.string().optional(),\n after: z.string().optional(),\n types: z.string().optional(), // comma-separated: status,action,comment\n})\n\nexport type DocumentHistoryEntryKind = 'status' | 'action' | 'comment'\n\nconst HISTORY_TYPES: ReadonlySet<DocumentHistoryEntryKind> = new Set(['status', 'action', 'comment'])\n\nexport function parseDocumentHistoryTypes(input: string | null | undefined): Set<DocumentHistoryEntryKind> {\n const raw = typeof input === 'string' ? input.trim() : ''\n if (!raw) return new Set()\n const values = raw\n .split(',')\n .map((part) => part.trim().toLowerCase())\n .filter((value) => value.length > 0)\n const result = new Set<DocumentHistoryEntryKind>()\n values.forEach((value) => {\n if (HISTORY_TYPES.has(value as DocumentHistoryEntryKind)) {\n result.add(value as DocumentHistoryEntryKind)\n }\n })\n return result\n}\n\nexport async function GET(req: Request) {\n try {\n const url = new URL(req.url)\n const query = querySchema.parse(Object.fromEntries(url.searchParams))\n\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth || !auth.tenantId) {\n throw new CrudHttpError(401, { error: translate('sales.documents.errors.unauthorized', 'Unauthorized') })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n // Resolve the organization scope the same way every other scoped read does.\n // Under \"All organizations\" (super-admin) `rbacOrganizationId` is null and\n // `where` is empty, so the read scopes by tenant + resource only instead of\n // failing \u2014 the document's `id` is a tenant-unique UUID, so no cross-org leak.\n const orgFilter = resolveOrganizationScopeFilter(scope, auth)\n const organizationId = orgFilter.rbacOrganizationId\n\n const requiredFeature = query.kind === 'order' ? 'sales.orders.view' : 'sales.quotes.view'\n const rbac = container.resolve('rbacService') as RbacService\n const hasAccess = await rbac.userHasAllFeatures(auth.sub, [requiredFeature], {\n tenantId: auth.tenantId,\n organizationId,\n })\n if (!hasAccess) {\n throw new CrudHttpError(403, { error: translate('api.errors.forbidden', 'Forbidden') })\n }\n\n const resourceKind = query.kind === 'order' ? 'sales.order' : 'sales.quote'\n\n const actionLogService = container.resolve('actionLogService') as ActionLogService\n const em = (container.resolve('em') as EntityManager).fork()\n\n const [actionLogList, notes] = await Promise.all([\n actionLogService.list({\n tenantId: auth.tenantId,\n organizationId: organizationId ?? undefined,\n resourceKind,\n resourceId: query.id,\n includeRelated: true,\n limit: query.limit,\n before: query.before ? new Date(query.before) : undefined,\n after: query.after ? new Date(query.after) : undefined,\n }),\n findWithDecryption(\n em,\n SalesNote,\n {\n contextType: query.kind,\n contextId: query.id,\n tenantId: auth.tenantId,\n ...orgFilter.where,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId: organizationId ?? undefined },\n ),\n ])\n const logs = actionLogList.items as ActionLog[]\n\n const allUserIds = [\n ...logs.map((l) => l.actorUserId).filter((v): v is string => !!v),\n ...notes.map((n) => n.authorUserId).filter((v): v is string => !!v),\n ]\n\n const displayMaps = await loadAuditLogDisplayMaps(em, {\n userIds: allUserIds,\n tenantIds: [],\n organizationIds: [],\n })\n\n let items = buildHistoryEntries({ actionLogs: logs, notes, kind: query.kind, displayUsers: displayMaps.users })\n const typesFilter = parseDocumentHistoryTypes(query.types)\n if (typesFilter.size > 0) {\n items = items.filter((entry) => typesFilter.has(entry.kind as DocumentHistoryEntryKind))\n }\n\n let nextCursor: string | undefined = undefined\n if (logs.length === query.limit && items.length > 0) {\n const last = items[items.length - 1]\n nextCursor = Buffer.from(`${last.occurredAt}|${last.id}`).toString('base64')\n }\n\n return NextResponse.json({ items, nextCursor })\n } catch (err) {\n if (isCrudHttpError(err)) {\n return NextResponse.json(err.body, { status: err.status })\n }\n logger.error('sales.document-history.get failed', { err })\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { error: translate('sales.documents.history.error', 'Failed to load history.') },\n { status: 400 }\n )\n }\n}\n\nconst historyActorSchema = z.object({\n id: z.string().uuid().nullable(),\n label: z.string(),\n})\n\nconst historyEntrySchema = z.object({\n id: z.string(),\n occurredAt: z.string().datetime(),\n kind: z.enum(['status', 'action', 'comment']),\n action: z.string(),\n actor: historyActorSchema,\n source: z.enum(['action_log', 'note']),\n metadata: z.object({\n statusFrom: z.string().nullable().optional(),\n statusTo: z.string().nullable().optional(),\n documentKind: z.enum(['order', 'quote']).optional(),\n commandId: z.string().optional(),\n changedFields: z.array(z.string()).optional(),\n }).optional(),\n})\n\nconst documentHistoryResponseSchema = z.object({\n items: z.array(historyEntrySchema),\n nextCursor: z.string().optional(),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: 'Get document change history',\n methods: {\n GET: {\n summary: 'List history entries for an order or quote',\n query: querySchema,\n responses: [\n { status: 200, description: 'History entries', schema: documentHistoryResponseSchema },\n { status: 400, description: 'Invalid query', schema: z.object({ error: z.string() }) },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },\n { status: 404, description: 'Document not found', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAElB,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AACvC,SAAS,0BAA0B;AACnC,SAAS,0CAA0C;AACnD,SAAS,sCAAsC;AAC/C,SAAS,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;AAK7B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,0BAA0B;AACnC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,OAAO;AAI5B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,KAAK;AAC3B;AAEA,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,MAAM,EAAE,KAAK,CAAC,SAAS,OAAO,CAAC;AAAA,EAC/B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,OAAO,EAAE,OAAO,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE;AAAA,EACnD,QAAQ,EAAE,OAAO,EAAE,SAAS;AAAA,EAC5B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA;AAC7B,CAAC;AAID,MAAM,gBAAuD,oBAAI,IAAI,CAAC,UAAU,UAAU,SAAS,CAAC;AAE7F,SAAS,0BAA0B,OAAiE;AACzG,QAAM,MAAM,OAAO,UAAU,WAAW,MAAM,KAAK,IAAI;AACvD,MAAI,CAAC,IAAK,QAAO,oBAAI,IAAI;AACzB,QAAM,SAAS,IACZ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,EAAE,YAAY,CAAC,EACvC,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACrC,QAAM,SAAS,oBAAI,IAA8B;AACjD,SAAO,QAAQ,CAAC,UAAU;AACxB,QAAI,cAAc,IAAI,KAAiC,GAAG;AACxD,aAAO,IAAI,KAAiC;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,SAAO;AACT;AAEA,eAAsB,IAAI,KAAc;AACtC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,UAAM,QAAQ,YAAY,MAAM,OAAO,YAAY,IAAI,YAAY,CAAC;AAEpE,UAAM,YAAY,MAAM,uBAAuB;AAC/C,UAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAEhD,QAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,uCAAuC,cAAc,EAAE,CAAC;AAAA,IAC1G;AAEA,UAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AAKxF,UAAM,YAAY,+BAA+B,OAAO,IAAI;AAC5D,UAAM,iBAAiB,UAAU;AAEjC,UAAM,kBAAkB,MAAM,SAAS,UAAU,sBAAsB;AACvE,UAAM,OAAO,UAAU,QAAQ,aAAa;AAC5C,UAAM,YAAY,MAAM,KAAK,mBAAmB,KAAK,KAAK,CAAC,eAAe,GAAG;AAAA,MAC3E,UAAU,KAAK;AAAA,MACf;AAAA,IACF,CAAC;AACD,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,UAAU,wBAAwB,WAAW,EAAE,CAAC;AAAA,IACxF;AAEA,UAAM,eAAe,MAAM,SAAS,UAAU,gBAAgB;AAE9D,UAAM,mBAAmB,UAAU,QAAQ,kBAAkB;AAC7D,UAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAE3D,UAAM,CAAC,eAAe,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,MAC/C,iBAAiB,KAAK;AAAA,QACpB,UAAU,KAAK;AAAA,QACf,gBAAgB,kBAAkB;AAAA,QAClC;AAAA,QACA,YAAY,MAAM;AAAA,QAClB,gBAAgB;AAAA,QAChB,OAAO,MAAM;AAAA,QACb,QAAQ,MAAM,SAAS,IAAI,KAAK,MAAM,MAAM,IAAI;AAAA,QAChD,OAAO,MAAM,QAAQ,IAAI,KAAK,MAAM,KAAK,IAAI;AAAA,MAC/C,CAAC;AAAA,MACD;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,UACE,aAAa,MAAM;AAAA,UACnB,WAAW,MAAM;AAAA,UACjB,UAAU,KAAK;AAAA,UACf,GAAG,UAAU;AAAA,UACb,WAAW;AAAA,QACb;AAAA,QACA,EAAE,SAAS,EAAE,WAAW,OAAO,EAAE;AAAA,QACjC,EAAE,UAAU,KAAK,UAAU,gBAAgB,kBAAkB,OAAU;AAAA,MACzE;AAAA,IACF,CAAC;AACD,UAAM,OAAO,cAAc;AAE3B,UAAM,aAAa;AAAA,MACjB,GAAG,KAAK,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,MAChE,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,MAAmB,CAAC,CAAC,CAAC;AAAA,IACpE;AAEA,UAAM,cAAc,MAAM,wBAAwB,IAAI;AAAA,MACpD,SAAS;AAAA,MACT,WAAW,CAAC;AAAA,MACZ,iBAAiB,CAAC;AAAA,IACpB,CAAC;AAED,QAAI,QAAQ,oBAAoB,EAAE,YAAY,MAAM,OAAO,MAAM,MAAM,MAAM,cAAc,YAAY,MAAM,CAAC;AAC9G,UAAM,cAAc,0BAA0B,MAAM,KAAK;AACzD,QAAI,YAAY,OAAO,GAAG;AACxB,cAAQ,MAAM,OAAO,CAAC,UAAU,YAAY,IAAI,MAAM,IAAgC,CAAC;AAAA,IACzF;AAEA,QAAI,aAAiC;AACrC,QAAI,KAAK,WAAW,MAAM,SAAS,MAAM,SAAS,GAAG;AACnD,YAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,mBAAa,OAAO,KAAK,GAAG,KAAK,UAAU,IAAI,KAAK,EAAE,EAAE,EAAE,SAAS,QAAQ;AAAA,IAC7E;AAEA,WAAO,aAAa,KAAK,EAAE,OAAO,WAAW,CAAC;AAAA,EAChD,SAAS,KAAK;AACZ,QAAI,gBAAgB,GAAG,GAAG;AACxB,aAAO,aAAa,KAAK,IAAI,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IAC3D;AACA,WAAO,MAAM,qCAAqC,EAAE,IAAI,CAAC;AACzD,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,UAAU,iCAAiC,yBAAyB,EAAE;AAAA,MAC/E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AACF;AAEA,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AAAA,EAC/B,OAAO,EAAE,OAAO;AAClB,CAAC;AAED,MAAM,qBAAqB,EAAE,OAAO;AAAA,EAClC,IAAI,EAAE,OAAO;AAAA,EACb,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,MAAM,EAAE,KAAK,CAAC,UAAU,UAAU,SAAS,CAAC;AAAA,EAC5C,QAAQ,EAAE,OAAO;AAAA,EACjB,OAAO;AAAA,EACP,QAAQ,EAAE,KAAK,CAAC,cAAc,MAAM,CAAC;AAAA,EACrC,UAAU,EAAE,OAAO;AAAA,IACjB,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACzC,cAAc,EAAE,KAAK,CAAC,SAAS,OAAO,CAAC,EAAE,SAAS;AAAA,IAClD,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,IAC/B,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAC9C,CAAC,EAAE,SAAS;AACd,CAAC;AAED,MAAM,gCAAgC,EAAE,OAAO;AAAA,EAC7C,OAAO,EAAE,MAAM,kBAAkB;AAAA,EACjC,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,mBAAmB,QAAQ,8BAA8B;AAAA,QACrF,EAAE,QAAQ,KAAK,aAAa,iBAAiB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACrF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACpF,EAAE,QAAQ,KAAK,aAAa,aAAa,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,QACjF,EAAE,QAAQ,KAAK,aAAa,sBAAsB,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE;AAAA,MAC5F;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,65 @@
1
+ import { isRecord } from "@open-mercato/shared/lib/utils";
2
+ const DOCUMENT_LINE_UPSERT_COMMANDS = /* @__PURE__ */ new Set([
3
+ "sales.orders.lines.upsert",
4
+ "sales.quotes.lines.upsert"
5
+ ]);
6
+ const USER_EDITABLE_LINE_FIELDS = [
7
+ "productId",
8
+ "productVariantId",
9
+ "name",
10
+ "description",
11
+ "comment",
12
+ "quantity",
13
+ "quantityUnit",
14
+ "currencyCode",
15
+ "unitPriceNet",
16
+ "unitPriceGross",
17
+ "discountAmount",
18
+ "discountPercent",
19
+ "taxRate",
20
+ "configuration",
21
+ "promotionCode",
22
+ "customFields",
23
+ "statusEntryId"
24
+ ];
25
+ function readSnapshotLines(snapshot) {
26
+ if (!isRecord(snapshot) || !Array.isArray(snapshot.lines)) return [];
27
+ return snapshot.lines.filter(isRecord);
28
+ }
29
+ function historyValuesEqual(left, right) {
30
+ if (Object.is(left, right)) return true;
31
+ try {
32
+ return JSON.stringify(left) === JSON.stringify(right);
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+ function deriveDocumentLineChangedFields(log) {
38
+ if (!DOCUMENT_LINE_UPSERT_COMMANDS.has(log.commandId)) return [];
39
+ const beforeById = new Map(
40
+ readSnapshotLines(log.snapshotBefore).filter((line) => typeof line.id === "string").map((line) => [line.id, line])
41
+ );
42
+ const afterLines = readSnapshotLines(log.snapshotAfter);
43
+ const changedFields = /* @__PURE__ */ new Set();
44
+ for (const afterLine of afterLines) {
45
+ if (typeof afterLine.id !== "string") continue;
46
+ const beforeLine = beforeById.get(afterLine.id);
47
+ if (!beforeLine) continue;
48
+ for (const field of USER_EDITABLE_LINE_FIELDS) {
49
+ if (!historyValuesEqual(beforeLine[field], afterLine[field])) {
50
+ changedFields.add(field);
51
+ }
52
+ }
53
+ }
54
+ return USER_EDITABLE_LINE_FIELDS.filter((field) => changedFields.has(field));
55
+ }
56
+ function deriveHistoryChangedFields(log) {
57
+ if (DOCUMENT_LINE_UPSERT_COMMANDS.has(log.commandId)) {
58
+ return deriveDocumentLineChangedFields(log);
59
+ }
60
+ const persisted = Array.isArray(log.changedFields) ? log.changedFields.filter((field) => typeof field === "string" && field.trim().length > 0) : [];
61
+ return Array.from(new Set(persisted));
62
+ }
1
63
  function extractStatusFromSnapshot(snapshot) {
2
64
  if (!snapshot || typeof snapshot !== "object") return null;
3
65
  const s = snapshot;
@@ -28,9 +90,14 @@ function detectStatusChange(log) {
28
90
  }
29
91
  function normalizeActionLogToHistoryEntry(log, kind, displayUsers) {
30
92
  const statusChange = detectStatusChange(log);
93
+ const changedFields = deriveHistoryChangedFields(log);
31
94
  let entryKind = "action";
32
95
  let action = log.actionLabel || log.commandId;
33
- let metadata = { documentKind: kind, commandId: log.commandId };
96
+ let metadata = {
97
+ documentKind: kind,
98
+ commandId: log.commandId,
99
+ ...changedFields.length > 0 ? { changedFields } : {}
100
+ };
34
101
  if (statusChange) {
35
102
  const hasStatusValues = statusChange.statusFrom != null || statusChange.statusTo != null;
36
103
  if (hasStatusValues) {
@@ -75,6 +142,7 @@ function buildHistoryEntries(input) {
75
142
  }
76
143
  export {
77
144
  buildHistoryEntries,
145
+ deriveHistoryChangedFields,
78
146
  detectStatusChange,
79
147
  normalizeActionLogToHistoryEntry,
80
148
  normalizeNoteToHistoryEntry
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/sales/lib/historyHelpers.ts"],
4
- "sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { SalesNote } from '../data/entities'\n\nexport type HistoryEntry = {\n id: string\n occurredAt: string\n kind: 'status' | 'action' | 'comment'\n action: string\n actor: { id: string | null; label: string }\n source: 'action_log' | 'note'\n metadata?: {\n statusFrom?: string | null\n statusTo?: string | null\n documentKind?: 'order' | 'quote'\n commandId?: string\n }\n}\n\nfunction extractStatusFromSnapshot(snapshot: unknown): string | null {\n if (!snapshot || typeof snapshot !== 'object') return null\n const s = snapshot as Record<string, unknown>\n // Dedicated logStatusChange entries: { status: \"value\" }\n if (typeof s.status === 'string') return s.status\n // Regular update snapshots: { order: { status, fulfillment_status, payment_status, ... } }\n if (s.order && typeof s.order === 'object') {\n const order = s.order as Record<string, unknown>\n const orderStatus = typeof order.status === 'string' ? order.status : null\n const fulfillmentStatus = typeof order.fulfillment_status === 'string' ? order.fulfillment_status : null\n const paymentStatus = typeof order.payment_status === 'string' ? order.payment_status : null\n return orderStatus ?? fulfillmentStatus ?? paymentStatus ?? null\n }\n // Regular update snapshots: { quote: { status: \"value\", ... } }\n if (s.quote && typeof s.quote === 'object') {\n const quote = s.quote as Record<string, unknown>\n if (typeof quote.status === 'string') return quote.status\n }\n return null\n}\n\nexport function detectStatusChange(log: ActionLog): {\n statusFrom: string | null\n statusTo: string | null\n} | null {\n const after = extractStatusFromSnapshot(log.snapshotAfter)\n const before = log.snapshotBefore\n ? extractStatusFromSnapshot(log.snapshotBefore)\n : null\n // Creation (no snapshotBefore): always one status entry (initial value or \"created\")\n if (!log.snapshotBefore) {\n return { statusFrom: null, statusTo: after ?? null }\n }\n if (before !== after && (before !== null || after !== null)) {\n return { statusFrom: before, statusTo: after ?? null }\n }\n return null\n}\n\n/**\n * Normalizes ActionLog to HistoryEntry (status/action/comment).\n */\nexport function normalizeActionLogToHistoryEntry(\n log: ActionLog,\n kind: 'order' | 'quote',\n displayUsers?: Record<string, string>,\n): HistoryEntry {\n const statusChange = detectStatusChange(log)\n let entryKind: 'status' | 'action' = 'action'\n let action = log.actionLabel || log.commandId\n let metadata: HistoryEntry['metadata'] = { documentKind: kind, commandId: log.commandId }\n if (statusChange) {\n const hasStatusValues = statusChange.statusFrom != null || statusChange.statusTo != null\n if (hasStatusValues) {\n entryKind = 'status'\n action = statusChange.statusTo ?? 'unknown'\n metadata = { ...metadata, statusFrom: statusChange.statusFrom, statusTo: statusChange.statusTo }\n }\n // When both are null (e.g. Create return/shipment/payment with non-document snapshot), keep as action and use actionLabel\n }\n const actorLabel = log.actorUserId\n ? (displayUsers?.[log.actorUserId] ?? log.actorUserId)\n : 'system'\n return {\n id: log.id,\n occurredAt: log.createdAt.toISOString(),\n kind: entryKind,\n action,\n actor: { id: log.actorUserId, label: actorLabel },\n source: 'action_log',\n metadata,\n }\n}\n\nexport function normalizeNoteToHistoryEntry(\n note: SalesNote,\n kind: 'order' | 'quote',\n displayUsers?: Record<string, string>,\n): HistoryEntry {\n const actorLabel = note.authorUserId\n ? (displayUsers?.[note.authorUserId] ?? note.authorUserId)\n : 'system'\n return {\n id: note.id,\n occurredAt: note.createdAt.toISOString(),\n kind: 'comment',\n action: note.body,\n actor: { id: note.authorUserId ?? null, label: actorLabel },\n source: 'note',\n metadata: { documentKind: kind },\n }\n}\n\nexport type HistoryBuilderInput = {\n actionLogs: ActionLog[]\n notes?: SalesNote[]\n kind: 'order' | 'quote'\n displayUsers?: Record<string, string>\n}\n\nexport function buildHistoryEntries(input: HistoryBuilderInput): HistoryEntry[] {\n const logEntries = input.actionLogs.map((log) =>\n normalizeActionLogToHistoryEntry(log, input.kind, input.displayUsers)\n )\n const noteEntries = (input.notes ?? []).map((note) =>\n normalizeNoteToHistoryEntry(note, input.kind, input.displayUsers)\n )\n return [...logEntries, ...noteEntries].sort(\n (a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime()\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { isRecord } from '@open-mercato/shared/lib/utils'\nimport type { SalesNote } from '../data/entities'\n\nexport type HistoryEntry = {\n id: string\n occurredAt: string\n kind: 'status' | 'action' | 'comment'\n action: string\n actor: { id: string | null; label: string }\n source: 'action_log' | 'note'\n metadata?: {\n statusFrom?: string | null\n statusTo?: string | null\n documentKind?: 'order' | 'quote'\n commandId?: string\n changedFields?: string[]\n }\n}\n\nconst DOCUMENT_LINE_UPSERT_COMMANDS = new Set([\n 'sales.orders.lines.upsert',\n 'sales.quotes.lines.upsert',\n])\n\nconst USER_EDITABLE_LINE_FIELDS = [\n 'productId',\n 'productVariantId',\n 'name',\n 'description',\n 'comment',\n 'quantity',\n 'quantityUnit',\n 'currencyCode',\n 'unitPriceNet',\n 'unitPriceGross',\n 'discountAmount',\n 'discountPercent',\n 'taxRate',\n 'configuration',\n 'promotionCode',\n 'customFields',\n 'statusEntryId',\n] as const\n\nfunction readSnapshotLines(snapshot: unknown): Array<Record<string, unknown>> {\n if (!isRecord(snapshot) || !Array.isArray(snapshot.lines)) return []\n return snapshot.lines.filter(isRecord)\n}\n\nfunction historyValuesEqual(left: unknown, right: unknown): boolean {\n if (Object.is(left, right)) return true\n try {\n return JSON.stringify(left) === JSON.stringify(right)\n } catch {\n return false\n }\n}\n\n/**\n * Derives a concise, user-editable field list for document line updates.\n * Calculated totals and normalized UoM fields are intentionally excluded so\n * the timeline describes the user's edit rather than downstream recalculation.\n */\nfunction deriveDocumentLineChangedFields(log: ActionLog): string[] {\n if (!DOCUMENT_LINE_UPSERT_COMMANDS.has(log.commandId)) return []\n\n const beforeById = new Map(\n readSnapshotLines(log.snapshotBefore)\n .filter((line) => typeof line.id === 'string')\n .map((line) => [line.id as string, line]),\n )\n const afterLines = readSnapshotLines(log.snapshotAfter)\n\n const changedFields = new Set<string>()\n for (const afterLine of afterLines) {\n if (typeof afterLine.id !== 'string') continue\n const beforeLine = beforeById.get(afterLine.id)\n if (!beforeLine) continue\n\n for (const field of USER_EDITABLE_LINE_FIELDS) {\n if (!historyValuesEqual(beforeLine[field], afterLine[field])) {\n changedFields.add(field)\n }\n }\n }\n\n return USER_EDITABLE_LINE_FIELDS.filter((field) => changedFields.has(field))\n}\n\nexport function deriveHistoryChangedFields(log: ActionLog): string[] {\n if (DOCUMENT_LINE_UPSERT_COMMANDS.has(log.commandId)) {\n return deriveDocumentLineChangedFields(log)\n }\n const persisted = Array.isArray(log.changedFields)\n ? log.changedFields.filter((field): field is string => typeof field === 'string' && field.trim().length > 0)\n : []\n return Array.from(new Set(persisted))\n}\n\nfunction extractStatusFromSnapshot(snapshot: unknown): string | null {\n if (!snapshot || typeof snapshot !== 'object') return null\n const s = snapshot as Record<string, unknown>\n // Dedicated logStatusChange entries: { status: \"value\" }\n if (typeof s.status === 'string') return s.status\n // Regular update snapshots: { order: { status, fulfillment_status, payment_status, ... } }\n if (s.order && typeof s.order === 'object') {\n const order = s.order as Record<string, unknown>\n const orderStatus = typeof order.status === 'string' ? order.status : null\n const fulfillmentStatus = typeof order.fulfillment_status === 'string' ? order.fulfillment_status : null\n const paymentStatus = typeof order.payment_status === 'string' ? order.payment_status : null\n return orderStatus ?? fulfillmentStatus ?? paymentStatus ?? null\n }\n // Regular update snapshots: { quote: { status: \"value\", ... } }\n if (s.quote && typeof s.quote === 'object') {\n const quote = s.quote as Record<string, unknown>\n if (typeof quote.status === 'string') return quote.status\n }\n return null\n}\n\nexport function detectStatusChange(log: ActionLog): {\n statusFrom: string | null\n statusTo: string | null\n} | null {\n const after = extractStatusFromSnapshot(log.snapshotAfter)\n const before = log.snapshotBefore\n ? extractStatusFromSnapshot(log.snapshotBefore)\n : null\n // Creation (no snapshotBefore): always one status entry (initial value or \"created\")\n if (!log.snapshotBefore) {\n return { statusFrom: null, statusTo: after ?? null }\n }\n if (before !== after && (before !== null || after !== null)) {\n return { statusFrom: before, statusTo: after ?? null }\n }\n return null\n}\n\n/**\n * Normalizes ActionLog to HistoryEntry (status/action/comment).\n */\nexport function normalizeActionLogToHistoryEntry(\n log: ActionLog,\n kind: 'order' | 'quote',\n displayUsers?: Record<string, string>,\n): HistoryEntry {\n const statusChange = detectStatusChange(log)\n const changedFields = deriveHistoryChangedFields(log)\n let entryKind: 'status' | 'action' = 'action'\n let action = log.actionLabel || log.commandId\n let metadata: HistoryEntry['metadata'] = {\n documentKind: kind,\n commandId: log.commandId,\n ...(changedFields.length > 0 ? { changedFields } : {}),\n }\n if (statusChange) {\n const hasStatusValues = statusChange.statusFrom != null || statusChange.statusTo != null\n if (hasStatusValues) {\n entryKind = 'status'\n action = statusChange.statusTo ?? 'unknown'\n metadata = { ...metadata, statusFrom: statusChange.statusFrom, statusTo: statusChange.statusTo }\n }\n // When both are null (e.g. Create return/shipment/payment with non-document snapshot), keep as action and use actionLabel\n }\n const actorLabel = log.actorUserId\n ? (displayUsers?.[log.actorUserId] ?? log.actorUserId)\n : 'system'\n return {\n id: log.id,\n occurredAt: log.createdAt.toISOString(),\n kind: entryKind,\n action,\n actor: { id: log.actorUserId, label: actorLabel },\n source: 'action_log',\n metadata,\n }\n}\n\nexport function normalizeNoteToHistoryEntry(\n note: SalesNote,\n kind: 'order' | 'quote',\n displayUsers?: Record<string, string>,\n): HistoryEntry {\n const actorLabel = note.authorUserId\n ? (displayUsers?.[note.authorUserId] ?? note.authorUserId)\n : 'system'\n return {\n id: note.id,\n occurredAt: note.createdAt.toISOString(),\n kind: 'comment',\n action: note.body,\n actor: { id: note.authorUserId ?? null, label: actorLabel },\n source: 'note',\n metadata: { documentKind: kind },\n }\n}\n\nexport type HistoryBuilderInput = {\n actionLogs: ActionLog[]\n notes?: SalesNote[]\n kind: 'order' | 'quote'\n displayUsers?: Record<string, string>\n}\n\nexport function buildHistoryEntries(input: HistoryBuilderInput): HistoryEntry[] {\n const logEntries = input.actionLogs.map((log) =>\n normalizeActionLogToHistoryEntry(log, input.kind, input.displayUsers)\n )\n const noteEntries = (input.notes ?? []).map((note) =>\n normalizeNoteToHistoryEntry(note, input.kind, input.displayUsers)\n )\n return [...logEntries, ...noteEntries].sort(\n (a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime()\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -23,6 +23,44 @@ const KIND_BG_COLORS = {
23
23
  action: "bg-muted",
24
24
  comment: "bg-muted"
25
25
  };
26
+ const CHANGED_FIELD_LABELS = {
27
+ productId: { key: "sales.documents.history.fields.product", fallback: "Product" },
28
+ productVariantId: { key: "sales.documents.history.fields.variant", fallback: "Variant" },
29
+ name: { key: "sales.documents.history.fields.name", fallback: "Name" },
30
+ description: { key: "sales.documents.history.fields.description", fallback: "Description" },
31
+ comment: { key: "sales.documents.history.fields.comment", fallback: "Comment" },
32
+ quantity: { key: "sales.documents.history.fields.quantity", fallback: "Quantity" },
33
+ quantityUnit: { key: "sales.documents.history.fields.quantityUnit", fallback: "Quantity unit" },
34
+ currencyCode: { key: "sales.documents.history.fields.currency", fallback: "Currency" },
35
+ unitPriceNet: { key: "sales.documents.history.fields.unitPriceNet", fallback: "Net unit price" },
36
+ unitPriceGross: { key: "sales.documents.history.fields.unitPriceGross", fallback: "Gross unit price" },
37
+ discountAmount: { key: "sales.documents.history.fields.discountAmount", fallback: "Discount amount" },
38
+ discountPercent: { key: "sales.documents.history.fields.discountPercent", fallback: "Discount percent" },
39
+ taxRate: { key: "sales.documents.history.fields.taxRate", fallback: "Tax class" },
40
+ configuration: { key: "sales.documents.history.fields.configuration", fallback: "Configuration" },
41
+ promotionCode: { key: "sales.documents.history.fields.promotionCode", fallback: "Promotion code" },
42
+ customFields: { key: "sales.documents.history.fields.customFields", fallback: "Custom fields" },
43
+ statusEntryId: { key: "sales.documents.history.fields.status", fallback: "Status" }
44
+ };
45
+ function normalizeChangedField(field) {
46
+ const segment = field.split(".").pop() ?? field;
47
+ return segment.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
48
+ }
49
+ function humanizeChangedField(field) {
50
+ const normalized = normalizeChangedField(field);
51
+ const spaced = normalized.replace(/([a-z0-9])([A-Z])/g, "$1 $2").trim();
52
+ return spaced.length > 0 ? `${spaced[0].toUpperCase()}${spaced.slice(1)}` : field;
53
+ }
54
+ function translateChangedField(t, field) {
55
+ const normalized = normalizeChangedField(field);
56
+ const translation = CHANGED_FIELD_LABELS[normalized];
57
+ return translation ? t(translation.key, translation.fallback) : humanizeChangedField(field);
58
+ }
59
+ function translateAction(t, entry) {
60
+ const commandId = entry.metadata?.commandId;
61
+ if (!commandId?.startsWith("sales.")) return entry.action;
62
+ return t(`sales.audit.${commandId.slice("sales.".length)}`, entry.action);
63
+ }
26
64
  function StatusDot({ color, className }) {
27
65
  if (!color) return /* @__PURE__ */ jsx("span", { className: cn("h-2.5 w-2.5 rounded-full bg-muted-foreground/40 border border-border inline-flex", className) });
28
66
  return /* @__PURE__ */ jsx(
@@ -58,10 +96,16 @@ function TimelineItem({
58
96
  statusMap,
59
97
  isLast
60
98
  }) {
99
+ const t = useT();
61
100
  const KindIcon = KIND_ICONS[entry.kind];
62
101
  const relativeTime = formatRelativeTime(entry.occurredAt);
63
102
  const absoluteTime = formatDateTime(entry.occurredAt);
64
103
  const isStatusChange = entry.kind === "status" && entry.metadata?.statusTo;
104
+ const action = translateAction(t, entry);
105
+ const actorLabel = entry.actor.id ? entry.actor.label : t("sales.documents.history.actor.system", "System");
106
+ const changedFieldLabels = Array.from(
107
+ new Set((entry.metadata?.changedFields ?? []).map((field) => translateChangedField(t, field)))
108
+ );
65
109
  return /* @__PURE__ */ jsxs("div", { "data-testid": "timeline-entry", className: "relative flex gap-3", children: [
66
110
  !isLast && /* @__PURE__ */ jsx("div", { className: "absolute left-[11px] top-6 bottom-0 w-px bg-border", "aria-hidden": true }),
67
111
  /* @__PURE__ */ jsx(
@@ -78,7 +122,7 @@ function TimelineItem({
78
122
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [
79
123
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1.5 text-xs font-medium text-foreground", children: [
80
124
  /* @__PURE__ */ jsx(User, { className: "h-3 w-3 text-muted-foreground", "aria-hidden": true }),
81
- entry.actor.label
125
+ actorLabel
82
126
  ] }),
83
127
  /* @__PURE__ */ jsx(
84
128
  "span",
@@ -96,7 +140,12 @@ function TimelineItem({
96
140
  statusTo: entry.metadata?.statusTo,
97
141
  statusMap
98
142
  }
99
- ) : /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children: entry.action })
143
+ ) : /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
144
+ /* @__PURE__ */ jsx("div", { className: "text-sm text-foreground", children: action }),
145
+ entry.kind === "action" && changedFieldLabels.length > 0 ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("sales.documents.history.changedFields", "Changed fields: {fields}", {
146
+ fields: changedFieldLabels.join(", ")
147
+ }) }) : null
148
+ ] })
100
149
  ] }) })
101
150
  ] });
102
151
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/sales/widgets/injection/document-history/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from \"react\"\nimport { Spinner } from \"@open-mercato/ui/primitives/spinner\"\nimport { useT } from \"@open-mercato/shared/lib/i18n/context\"\nimport { apiCall } from \"@open-mercato/ui/backend/utils/apiCall\"\nimport { formatRelativeTime, formatDateTime } from \"@open-mercato/shared/lib/time\"\nimport { cn } from \"@open-mercato/shared/lib/utils\"\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { ArrowRightLeft, Zap, MessageSquare, User, Filter, ChevronDown, Check } from 'lucide-react'\n\nexport type TimelineEntry = {\n id: string\n occurredAt: string\n kind: \"status\" | \"action\" | \"comment\"\n action: string\n actor: { id: string | null; label: string }\n source: \"action_log\" | \"note\"\n metadata?: {\n statusFrom?: string | null\n statusTo?: string | null\n documentKind?: \"order\" | \"quote\"\n commandId?: string\n }\n}\n\ntype StatusOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\ntype TimelineContext = {\n kind: \"order\" | \"quote\"\n record: { id: string }\n}\n\nconst isValidContext = (ctx: unknown): ctx is TimelineContext =>\n ctx !== null &&\n typeof ctx === 'object' &&\n 'kind' in ctx &&\n 'record' in ctx &&\n ((ctx as TimelineContext).kind === 'order' || (ctx as TimelineContext).kind === 'quote') &&\n typeof (ctx as TimelineContext).record === 'object' &&\n (ctx as TimelineContext).record !== null &&\n 'id' in (ctx as TimelineContext).record &&\n typeof (ctx as TimelineContext).record.id === 'string'\n\nconst KIND_ICONS = {\n status: ArrowRightLeft,\n action: Zap,\n comment: MessageSquare,\n}\n\nconst KIND_ICON_COLORS = {\n status: 'text-foreground',\n action: 'text-foreground',\n comment: 'text-foreground',\n}\n\nconst KIND_BG_COLORS = {\n status: 'bg-muted',\n action: 'bg-muted',\n comment: 'bg-muted',\n}\n\nfunction StatusDot({ color, className }: { color: string | null | undefined; className?: string }) {\n if (!color) return <span className={cn('h-2.5 w-2.5 rounded-full bg-muted-foreground/40 border border-border inline-flex', className)} />\n return (\n <span\n className={cn('h-2.5 w-2.5 rounded-full border border-border/70 inline-flex', className)}\n style={{ backgroundColor: color }}\n aria-hidden\n />\n )\n}\n\nfunction StatusTransition({\n statusFrom,\n statusTo,\n statusMap,\n}: {\n statusFrom: string | null | undefined\n statusTo: string | null | undefined\n statusMap: Record<string, StatusOption>\n}) {\n const from = statusFrom ? (statusMap[statusFrom] ?? { value: statusFrom, label: statusFrom, color: null, icon: null }) : null\n const to = statusTo ? (statusMap[statusTo] ?? { value: statusTo, label: statusTo, color: null, icon: null }) : null\n\n return (\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {from ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <StatusDot color={from.color} />\n <span>{from.label}</span>\n </span>\n ) : null}\n {from && to ? (\n <ArrowRightLeft className=\"h-3 w-3 text-muted-foreground/60 shrink-0\" />\n ) : null}\n {to ? (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-foreground\">\n <StatusDot color={to.color} />\n <span>{to.label}</span>\n </span>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineItem({\n entry,\n statusMap,\n isLast,\n}: {\n entry: TimelineEntry\n statusMap: Record<string, StatusOption>\n isLast: boolean\n}) {\n const KindIcon = KIND_ICONS[entry.kind]\n const relativeTime = formatRelativeTime(entry.occurredAt)\n const absoluteTime = formatDateTime(entry.occurredAt)\n\n const isStatusChange = entry.kind === 'status' && entry.metadata?.statusTo\n\n return (\n <div data-testid=\"timeline-entry\" className=\"relative flex gap-3\">\n {/* Vertical connector line */}\n {!isLast && (\n <div className=\"absolute left-[11px] top-6 bottom-0 w-px bg-border\" aria-hidden />\n )}\n\n {/* Icon circle */}\n <div\n className={cn(\n 'relative z-10 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border',\n KIND_BG_COLORS[entry.kind],\n )}\n >\n <KindIcon className={cn('h-3 w-3', KIND_ICON_COLORS[entry.kind])} aria-hidden />\n </div>\n\n {/* Content card */}\n <div className=\"flex-1 pb-4\">\n <div className=\"group rounded-lg border bg-card p-3 space-y-1.5\">\n {/* Header: actor + time */}\n <div className=\"flex items-center justify-between gap-2 flex-wrap\">\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-foreground\">\n <User className=\"h-3 w-3 text-muted-foreground\" aria-hidden />\n {entry.actor.label}\n </span>\n <span\n className=\"text-xs text-muted-foreground\"\n title={absoluteTime ?? undefined}\n >\n {relativeTime ?? absoluteTime}\n </span>\n </div>\n\n {/* Body */}\n {isStatusChange ? (\n <StatusTransition\n statusFrom={entry.metadata?.statusFrom}\n statusTo={entry.metadata?.statusTo}\n statusMap={statusMap}\n />\n ) : (\n <div className=\"text-sm text-foreground\">{entry.action}</div>\n )}\n </div>\n </div>\n </div>\n )\n}\n\ntype FilterKind = 'all' | 'status' | 'action' | 'comment'\n\ntype FilterOption = { value: FilterKind; label: string }\n\nfunction FilterDropdown({ filter, onChange }: { filter: FilterKind; onChange: (kind: FilterKind) => void }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!open) return\n function onDocClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false)\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n }\n }, [open])\n\n const options: FilterOption[] = [\n { value: 'all', label: t('sales.documents.history.filter.all', 'All') },\n { value: 'status', label: t('sales.documents.history.filter.status', 'Status changes') },\n { value: 'action', label: t('sales.documents.history.filter.actions', 'Actions') },\n { value: 'comment', label: t('sales.documents.history.filter.comments', 'Comments') },\n ]\n\n const activeLabel = options.find(o => o.value === filter)?.label\n\n return (\n <div className=\"relative\" ref={ref}>\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n onClick={() => setOpen(prev => !prev)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium shadow-sm hover:bg-accent hover:text-accent-foreground transition-colors select-none\"\n >\n <Filter className=\"h-3 w-3\" aria-hidden />\n {t('sales.documents.history.filter.label', 'Filters')}\n {filter !== 'all' && (\n <span className=\"text-muted-foreground\">: {activeLabel}</span>\n )}\n <ChevronDown className={cn('h-3 w-3 transition-transform duration-150', open && 'rotate-180')} aria-hidden />\n </button>\n {open && (\n <div\n role=\"listbox\"\n aria-label={t('sales.documents.history.filter.label', 'Filters')}\n className=\"absolute left-0 top-full mt-1 z-dropdown w-48 rounded-md border bg-background p-1 shadow-md\"\n >\n {options.map(opt => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={filter === opt.value}\n onClick={() => { onChange(opt.value); setOpen(false) }}\n className=\"flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-accent\"\n >\n <Check className={cn('h-3.5 w-3.5 shrink-0', filter === opt.value ? 'opacity-100' : 'opacity-0')} aria-hidden />\n {opt.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport const DocumentHistoryWidget: React.FC<InjectionWidgetComponentProps<unknown, unknown>> = ({ context }) => {\n const t = useT()\n const [entries, setEntries] = React.useState<TimelineEntry[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [statusMap, setStatusMap] = React.useState<Record<string, StatusOption>>({})\n const [filter, setFilter] = React.useState<FilterKind>('all')\n\n React.useEffect(() => {\n const urls = [\n '/api/sales/order-statuses?pageSize=100',\n '/api/sales/shipment-statuses?pageSize=100',\n '/api/sales/payment-statuses?pageSize=100',\n ]\n const map: Record<string, StatusOption> = {}\n const merge = (items: unknown[]) => {\n if (!Array.isArray(items)) return\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const d = item as Record<string, unknown>\n const value = typeof d.value === 'string' ? d.value : null\n if (!value) continue\n map[value] = {\n value,\n label: typeof d.label === 'string' && d.label.length ? d.label : value,\n color: typeof d.color === 'string' && d.color.length ? d.color : null,\n icon: typeof d.icon === 'string' && d.icon.length ? d.icon : null,\n }\n }\n }\n Promise.all(urls.map((url) => apiCall<{ items?: unknown[] }>(url)))\n .then((responses) => {\n for (const res of responses) {\n if (res.ok && Array.isArray(res.result?.items)) merge(res.result.items)\n }\n setStatusMap(map)\n })\n .catch(() => {})\n }, [])\n\n React.useEffect(() => {\n if (!isValidContext(context)) {\n setLoading(false)\n setError(t(\"sales.documents.history.error\", \"Failed to load history.\"))\n return\n }\n\n setLoading(true)\n setError(null)\n apiCall<{ items: TimelineEntry[] }>(\n `/api/sales/document-history?kind=${context.kind}&id=${context.record.id}`\n )\n .then((res) => {\n if (res.ok && Array.isArray(res.result?.items)) {\n setEntries(res.result.items)\n } else {\n setError(t(\"sales.documents.history.error\", \"Failed to load history.\"))\n }\n })\n .catch(() => setError(t(\"sales.documents.history.error\", \"Failed to load history.\")))\n .finally(() => setLoading(false))\n }, [context, t])\n\n const filtered = React.useMemo(\n () => filter === 'all' ? entries : entries.filter(e => e.kind === filter),\n [entries, filter]\n )\n\n return (\n <div className=\"space-y-4\">\n {/* Filter dropdown */}\n <div>\n <FilterDropdown filter={filter} onChange={setFilter} />\n </div>\n\n {/* Content */}\n {loading ? (\n <div className=\"flex items-center justify-center h-24\">\n <Spinner />\n </div>\n ) : error ? (\n <div className=\"text-destructive text-sm\">{error}</div>\n ) : !filtered.length ? (\n <div className=\"text-muted-foreground text-sm py-6 text-center\">\n {t(\"sales.documents.history.empty\", \"No history entries yet.\")}\n </div>\n ) : (\n <div className=\"relative\">\n {filtered.map((entry, index) => (\n <TimelineItem\n key={entry.id}\n entry={entry}\n statusMap={statusMap}\n isLast={index === filtered.length - 1}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport default DocumentHistoryWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from \"react\"\nimport { Spinner } from \"@open-mercato/ui/primitives/spinner\"\nimport { useT, type TranslateFn } from \"@open-mercato/shared/lib/i18n/context\"\nimport { apiCall } from \"@open-mercato/ui/backend/utils/apiCall\"\nimport { formatRelativeTime, formatDateTime } from \"@open-mercato/shared/lib/time\"\nimport { cn } from \"@open-mercato/shared/lib/utils\"\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { ArrowRightLeft, Zap, MessageSquare, User, Filter, ChevronDown, Check } from 'lucide-react'\n\nexport type TimelineEntry = {\n id: string\n occurredAt: string\n kind: \"status\" | \"action\" | \"comment\"\n action: string\n actor: { id: string | null; label: string }\n source: \"action_log\" | \"note\"\n metadata?: {\n statusFrom?: string | null\n statusTo?: string | null\n documentKind?: \"order\" | \"quote\"\n commandId?: string\n changedFields?: string[]\n }\n}\n\ntype StatusOption = {\n value: string\n label: string\n color: string | null\n icon: string | null\n}\n\ntype TimelineContext = {\n kind: \"order\" | \"quote\"\n record: { id: string }\n}\n\nconst isValidContext = (ctx: unknown): ctx is TimelineContext =>\n ctx !== null &&\n typeof ctx === 'object' &&\n 'kind' in ctx &&\n 'record' in ctx &&\n ((ctx as TimelineContext).kind === 'order' || (ctx as TimelineContext).kind === 'quote') &&\n typeof (ctx as TimelineContext).record === 'object' &&\n (ctx as TimelineContext).record !== null &&\n 'id' in (ctx as TimelineContext).record &&\n typeof (ctx as TimelineContext).record.id === 'string'\n\nconst KIND_ICONS = {\n status: ArrowRightLeft,\n action: Zap,\n comment: MessageSquare,\n}\n\nconst KIND_ICON_COLORS = {\n status: 'text-foreground',\n action: 'text-foreground',\n comment: 'text-foreground',\n}\n\nconst KIND_BG_COLORS = {\n status: 'bg-muted',\n action: 'bg-muted',\n comment: 'bg-muted',\n}\n\nconst CHANGED_FIELD_LABELS: Record<string, { key: string; fallback: string }> = {\n productId: { key: 'sales.documents.history.fields.product', fallback: 'Product' },\n productVariantId: { key: 'sales.documents.history.fields.variant', fallback: 'Variant' },\n name: { key: 'sales.documents.history.fields.name', fallback: 'Name' },\n description: { key: 'sales.documents.history.fields.description', fallback: 'Description' },\n comment: { key: 'sales.documents.history.fields.comment', fallback: 'Comment' },\n quantity: { key: 'sales.documents.history.fields.quantity', fallback: 'Quantity' },\n quantityUnit: { key: 'sales.documents.history.fields.quantityUnit', fallback: 'Quantity unit' },\n currencyCode: { key: 'sales.documents.history.fields.currency', fallback: 'Currency' },\n unitPriceNet: { key: 'sales.documents.history.fields.unitPriceNet', fallback: 'Net unit price' },\n unitPriceGross: { key: 'sales.documents.history.fields.unitPriceGross', fallback: 'Gross unit price' },\n discountAmount: { key: 'sales.documents.history.fields.discountAmount', fallback: 'Discount amount' },\n discountPercent: { key: 'sales.documents.history.fields.discountPercent', fallback: 'Discount percent' },\n taxRate: { key: 'sales.documents.history.fields.taxRate', fallback: 'Tax class' },\n configuration: { key: 'sales.documents.history.fields.configuration', fallback: 'Configuration' },\n promotionCode: { key: 'sales.documents.history.fields.promotionCode', fallback: 'Promotion code' },\n customFields: { key: 'sales.documents.history.fields.customFields', fallback: 'Custom fields' },\n statusEntryId: { key: 'sales.documents.history.fields.status', fallback: 'Status' },\n}\n\nfunction normalizeChangedField(field: string): string {\n const segment = field.split('.').pop() ?? field\n return segment.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase())\n}\n\nfunction humanizeChangedField(field: string): string {\n const normalized = normalizeChangedField(field)\n const spaced = normalized.replace(/([a-z0-9])([A-Z])/g, '$1 $2').trim()\n return spaced.length > 0 ? `${spaced[0].toUpperCase()}${spaced.slice(1)}` : field\n}\n\nfunction translateChangedField(t: TranslateFn, field: string): string {\n const normalized = normalizeChangedField(field)\n const translation = CHANGED_FIELD_LABELS[normalized]\n return translation ? t(translation.key, translation.fallback) : humanizeChangedField(field)\n}\n\nfunction translateAction(t: TranslateFn, entry: TimelineEntry): string {\n const commandId = entry.metadata?.commandId\n if (!commandId?.startsWith('sales.')) return entry.action\n return t(`sales.audit.${commandId.slice('sales.'.length)}`, entry.action)\n}\n\nfunction StatusDot({ color, className }: { color: string | null | undefined; className?: string }) {\n if (!color) return <span className={cn('h-2.5 w-2.5 rounded-full bg-muted-foreground/40 border border-border inline-flex', className)} />\n return (\n <span\n className={cn('h-2.5 w-2.5 rounded-full border border-border/70 inline-flex', className)}\n style={{ backgroundColor: color }}\n aria-hidden\n />\n )\n}\n\nfunction StatusTransition({\n statusFrom,\n statusTo,\n statusMap,\n}: {\n statusFrom: string | null | undefined\n statusTo: string | null | undefined\n statusMap: Record<string, StatusOption>\n}) {\n const from = statusFrom ? (statusMap[statusFrom] ?? { value: statusFrom, label: statusFrom, color: null, icon: null }) : null\n const to = statusTo ? (statusMap[statusTo] ?? { value: statusTo, label: statusTo, color: null, icon: null }) : null\n\n return (\n <div className=\"flex items-center gap-1.5 flex-wrap\">\n {from ? (\n <span className=\"inline-flex items-center gap-1 text-xs text-muted-foreground\">\n <StatusDot color={from.color} />\n <span>{from.label}</span>\n </span>\n ) : null}\n {from && to ? (\n <ArrowRightLeft className=\"h-3 w-3 text-muted-foreground/60 shrink-0\" />\n ) : null}\n {to ? (\n <span className=\"inline-flex items-center gap-1 text-xs font-medium text-foreground\">\n <StatusDot color={to.color} />\n <span>{to.label}</span>\n </span>\n ) : null}\n </div>\n )\n}\n\nfunction TimelineItem({\n entry,\n statusMap,\n isLast,\n}: {\n entry: TimelineEntry\n statusMap: Record<string, StatusOption>\n isLast: boolean\n}) {\n const t = useT()\n const KindIcon = KIND_ICONS[entry.kind]\n const relativeTime = formatRelativeTime(entry.occurredAt)\n const absoluteTime = formatDateTime(entry.occurredAt)\n\n const isStatusChange = entry.kind === 'status' && entry.metadata?.statusTo\n const action = translateAction(t, entry)\n const actorLabel = entry.actor.id\n ? entry.actor.label\n : t('sales.documents.history.actor.system', 'System')\n const changedFieldLabels = Array.from(\n new Set((entry.metadata?.changedFields ?? []).map((field) => translateChangedField(t, field))),\n )\n\n return (\n <div data-testid=\"timeline-entry\" className=\"relative flex gap-3\">\n {/* Vertical connector line */}\n {!isLast && (\n <div className=\"absolute left-[11px] top-6 bottom-0 w-px bg-border\" aria-hidden />\n )}\n\n {/* Icon circle */}\n <div\n className={cn(\n 'relative z-10 flex h-6 w-6 shrink-0 items-center justify-center rounded-full border border-border',\n KIND_BG_COLORS[entry.kind],\n )}\n >\n <KindIcon className={cn('h-3 w-3', KIND_ICON_COLORS[entry.kind])} aria-hidden />\n </div>\n\n {/* Content card */}\n <div className=\"flex-1 pb-4\">\n <div className=\"group rounded-lg border bg-card p-3 space-y-1.5\">\n {/* Header: actor + time */}\n <div className=\"flex items-center justify-between gap-2 flex-wrap\">\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-foreground\">\n <User className=\"h-3 w-3 text-muted-foreground\" aria-hidden />\n {actorLabel}\n </span>\n <span\n className=\"text-xs text-muted-foreground\"\n title={absoluteTime ?? undefined}\n >\n {relativeTime ?? absoluteTime}\n </span>\n </div>\n\n {/* Body */}\n {isStatusChange ? (\n <StatusTransition\n statusFrom={entry.metadata?.statusFrom}\n statusTo={entry.metadata?.statusTo}\n statusMap={statusMap}\n />\n ) : (\n <div className=\"space-y-0.5\">\n <div className=\"text-sm text-foreground\">{action}</div>\n {entry.kind === 'action' && changedFieldLabels.length > 0 ? (\n <div className=\"text-xs text-muted-foreground\">\n {t('sales.documents.history.changedFields', 'Changed fields: {fields}', {\n fields: changedFieldLabels.join(', '),\n })}\n </div>\n ) : null}\n </div>\n )}\n </div>\n </div>\n </div>\n )\n}\n\ntype FilterKind = 'all' | 'status' | 'action' | 'comment'\n\ntype FilterOption = { value: FilterKind; label: string }\n\nfunction FilterDropdown({ filter, onChange }: { filter: FilterKind; onChange: (kind: FilterKind) => void }) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const ref = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!open) return\n function onDocClick(e: MouseEvent) {\n if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)\n }\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') setOpen(false)\n }\n document.addEventListener('mousedown', onDocClick)\n document.addEventListener('keydown', onKey)\n return () => {\n document.removeEventListener('mousedown', onDocClick)\n document.removeEventListener('keydown', onKey)\n }\n }, [open])\n\n const options: FilterOption[] = [\n { value: 'all', label: t('sales.documents.history.filter.all', 'All') },\n { value: 'status', label: t('sales.documents.history.filter.status', 'Status changes') },\n { value: 'action', label: t('sales.documents.history.filter.actions', 'Actions') },\n { value: 'comment', label: t('sales.documents.history.filter.comments', 'Comments') },\n ]\n\n const activeLabel = options.find(o => o.value === filter)?.label\n\n return (\n <div className=\"relative\" ref={ref}>\n <button\n type=\"button\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n onClick={() => setOpen(prev => !prev)}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-input bg-background px-2.5 py-1 text-xs font-medium shadow-sm hover:bg-accent hover:text-accent-foreground transition-colors select-none\"\n >\n <Filter className=\"h-3 w-3\" aria-hidden />\n {t('sales.documents.history.filter.label', 'Filters')}\n {filter !== 'all' && (\n <span className=\"text-muted-foreground\">: {activeLabel}</span>\n )}\n <ChevronDown className={cn('h-3 w-3 transition-transform duration-150', open && 'rotate-180')} aria-hidden />\n </button>\n {open && (\n <div\n role=\"listbox\"\n aria-label={t('sales.documents.history.filter.label', 'Filters')}\n className=\"absolute left-0 top-full mt-1 z-dropdown w-48 rounded-md border bg-background p-1 shadow-md\"\n >\n {options.map(opt => (\n <button\n key={opt.value}\n type=\"button\"\n role=\"option\"\n aria-selected={filter === opt.value}\n onClick={() => { onChange(opt.value); setOpen(false) }}\n className=\"flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-accent\"\n >\n <Check className={cn('h-3.5 w-3.5 shrink-0', filter === opt.value ? 'opacity-100' : 'opacity-0')} aria-hidden />\n {opt.label}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport const DocumentHistoryWidget: React.FC<InjectionWidgetComponentProps<unknown, unknown>> = ({ context }) => {\n const t = useT()\n const [entries, setEntries] = React.useState<TimelineEntry[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n const [statusMap, setStatusMap] = React.useState<Record<string, StatusOption>>({})\n const [filter, setFilter] = React.useState<FilterKind>('all')\n\n React.useEffect(() => {\n const urls = [\n '/api/sales/order-statuses?pageSize=100',\n '/api/sales/shipment-statuses?pageSize=100',\n '/api/sales/payment-statuses?pageSize=100',\n ]\n const map: Record<string, StatusOption> = {}\n const merge = (items: unknown[]) => {\n if (!Array.isArray(items)) return\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const d = item as Record<string, unknown>\n const value = typeof d.value === 'string' ? d.value : null\n if (!value) continue\n map[value] = {\n value,\n label: typeof d.label === 'string' && d.label.length ? d.label : value,\n color: typeof d.color === 'string' && d.color.length ? d.color : null,\n icon: typeof d.icon === 'string' && d.icon.length ? d.icon : null,\n }\n }\n }\n Promise.all(urls.map((url) => apiCall<{ items?: unknown[] }>(url)))\n .then((responses) => {\n for (const res of responses) {\n if (res.ok && Array.isArray(res.result?.items)) merge(res.result.items)\n }\n setStatusMap(map)\n })\n .catch(() => {})\n }, [])\n\n React.useEffect(() => {\n if (!isValidContext(context)) {\n setLoading(false)\n setError(t(\"sales.documents.history.error\", \"Failed to load history.\"))\n return\n }\n\n setLoading(true)\n setError(null)\n apiCall<{ items: TimelineEntry[] }>(\n `/api/sales/document-history?kind=${context.kind}&id=${context.record.id}`\n )\n .then((res) => {\n if (res.ok && Array.isArray(res.result?.items)) {\n setEntries(res.result.items)\n } else {\n setError(t(\"sales.documents.history.error\", \"Failed to load history.\"))\n }\n })\n .catch(() => setError(t(\"sales.documents.history.error\", \"Failed to load history.\")))\n .finally(() => setLoading(false))\n }, [context, t])\n\n const filtered = React.useMemo(\n () => filter === 'all' ? entries : entries.filter(e => e.kind === filter),\n [entries, filter]\n )\n\n return (\n <div className=\"space-y-4\">\n {/* Filter dropdown */}\n <div>\n <FilterDropdown filter={filter} onChange={setFilter} />\n </div>\n\n {/* Content */}\n {loading ? (\n <div className=\"flex items-center justify-center h-24\">\n <Spinner />\n </div>\n ) : error ? (\n <div className=\"text-destructive text-sm\">{error}</div>\n ) : !filtered.length ? (\n <div className=\"text-muted-foreground text-sm py-6 text-center\">\n {t(\"sales.documents.history.empty\", \"No history entries yet.\")}\n </div>\n ) : (\n <div className=\"relative\">\n {filtered.map((entry, index) => (\n <TimelineItem\n key={entry.id}\n entry={entry}\n statusMap={statusMap}\n isLast={index === filtered.length - 1}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport default DocumentHistoryWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.7-develop.6751.1.ac823a3d26",
3
+ "version": "0.6.7-develop.6758.1.697eade236",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,16 +254,16 @@
254
254
  "zod": "^4.4.3"
255
255
  },
256
256
  "peerDependencies": {
257
- "@open-mercato/ai-assistant": "0.6.7-develop.6751.1.ac823a3d26",
258
- "@open-mercato/shared": "0.6.7-develop.6751.1.ac823a3d26",
259
- "@open-mercato/ui": "0.6.7-develop.6751.1.ac823a3d26",
257
+ "@open-mercato/ai-assistant": "0.6.7-develop.6758.1.697eade236",
258
+ "@open-mercato/shared": "0.6.7-develop.6758.1.697eade236",
259
+ "@open-mercato/ui": "0.6.7-develop.6758.1.697eade236",
260
260
  "react": "^19.0.0",
261
261
  "react-dom": "^19.0.0"
262
262
  },
263
263
  "devDependencies": {
264
- "@open-mercato/ai-assistant": "0.6.7-develop.6751.1.ac823a3d26",
265
- "@open-mercato/shared": "0.6.7-develop.6751.1.ac823a3d26",
266
- "@open-mercato/ui": "0.6.7-develop.6751.1.ac823a3d26",
264
+ "@open-mercato/ai-assistant": "0.6.7-develop.6758.1.697eade236",
265
+ "@open-mercato/shared": "0.6.7-develop.6758.1.697eade236",
266
+ "@open-mercato/ui": "0.6.7-develop.6758.1.697eade236",
267
267
  "@testing-library/dom": "^10.4.1",
268
268
  "@testing-library/jest-dom": "^6.9.1",
269
269
  "@testing-library/react": "^16.3.1",