@open-mercato/core 0.6.6-develop.6471.1.9ab5bdd79a → 0.6.6-develop.6474.1.2c411ad42b
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.
|
@@ -53,6 +53,15 @@ async function GET(req) {
|
|
|
53
53
|
error: translate("sales.documents.errors.organization_required", "Organization context is required")
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
|
+
const requiredFeature = query.kind === "order" ? "sales.orders.view" : "sales.quotes.view";
|
|
57
|
+
const rbac = container.resolve("rbacService");
|
|
58
|
+
const hasAccess = await rbac.userHasAllFeatures(auth.sub, [requiredFeature], {
|
|
59
|
+
tenantId: auth.tenantId,
|
|
60
|
+
organizationId
|
|
61
|
+
});
|
|
62
|
+
if (!hasAccess) {
|
|
63
|
+
throw new CrudHttpError(403, { error: translate("api.errors.forbidden", "Forbidden") });
|
|
64
|
+
}
|
|
56
65
|
const resourceKind = query.kind === "order" ? "sales.order" : "sales.quote";
|
|
57
66
|
const actionLogService = container.resolve("actionLogService");
|
|
58
67
|
const em = container.resolve("em").fork();
|
|
@@ -147,6 +156,7 @@ const openApi = {
|
|
|
147
156
|
{ status: 200, description: "History entries", schema: documentHistoryResponseSchema },
|
|
148
157
|
{ status: 400, description: "Invalid query", schema: z.object({ error: z.string() }) },
|
|
149
158
|
{ status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
|
|
159
|
+
{ status: 403, description: "Forbidden", schema: z.object({ error: z.string() }) },
|
|
150
160
|
{ status: 404, description: "Document not found", schema: z.object({ error: z.string() }) }
|
|
151
161
|
]
|
|
152
162
|
}
|
|
@@ -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 { 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 { 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 const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.documents.errors.organization_required', 'Organization context is required'),\n })\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,\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 organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId },\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: 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,2BAA2B;AACpC,SAAS,eAAe,uBAAuB;AAC/C,SAAS,oBAAoB;
|
|
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 { 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 const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!organizationId) {\n throw new CrudHttpError(400, {\n error: translate('sales.documents.errors.organization_required', 'Organization context is required'),\n })\n }\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,\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 organizationId,\n deletedAt: null,\n },\n { orderBy: { createdAt: 'DESC' } },\n { tenantId: auth.tenantId, organizationId },\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": "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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.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6474.1.2c411ad42b",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -253,16 +253,16 @@
|
|
|
253
253
|
"zod": "^4.4.3"
|
|
254
254
|
},
|
|
255
255
|
"peerDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6474.1.2c411ad42b",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6474.1.2c411ad42b",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6474.1.2c411ad42b",
|
|
259
259
|
"react": "^19.0.0",
|
|
260
260
|
"react-dom": "^19.0.0"
|
|
261
261
|
},
|
|
262
262
|
"devDependencies": {
|
|
263
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
264
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
265
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
263
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6474.1.2c411ad42b",
|
|
264
|
+
"@open-mercato/shared": "0.6.6-develop.6474.1.2c411ad42b",
|
|
265
|
+
"@open-mercato/ui": "0.6.6-develop.6474.1.2c411ad42b",
|
|
266
266
|
"@testing-library/dom": "^10.4.1",
|
|
267
267
|
"@testing-library/jest-dom": "^6.9.1",
|
|
268
268
|
"@testing-library/react": "^16.3.1",
|
|
@@ -9,6 +9,7 @@ import { CrudHttpError, isCrudHttpError } from '@open-mercato/shared/lib/crud/er
|
|
|
9
9
|
import { NextResponse } from 'next/server'
|
|
10
10
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
11
11
|
import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'
|
|
12
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
12
13
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
13
14
|
import { buildHistoryEntries } from '../../lib/historyHelpers'
|
|
14
15
|
import { SalesNote } from '../../data/entities'
|
|
@@ -73,6 +74,16 @@ export async function GET(req: Request) {
|
|
|
73
74
|
})
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
const requiredFeature = query.kind === 'order' ? 'sales.orders.view' : 'sales.quotes.view'
|
|
78
|
+
const rbac = container.resolve('rbacService') as RbacService
|
|
79
|
+
const hasAccess = await rbac.userHasAllFeatures(auth.sub, [requiredFeature], {
|
|
80
|
+
tenantId: auth.tenantId,
|
|
81
|
+
organizationId,
|
|
82
|
+
})
|
|
83
|
+
if (!hasAccess) {
|
|
84
|
+
throw new CrudHttpError(403, { error: translate('api.errors.forbidden', 'Forbidden') })
|
|
85
|
+
}
|
|
86
|
+
|
|
76
87
|
const resourceKind = query.kind === 'order' ? 'sales.order' : 'sales.quote'
|
|
77
88
|
|
|
78
89
|
const actionLogService = container.resolve('actionLogService') as ActionLogService
|
|
@@ -178,6 +189,7 @@ export const openApi: OpenApiRouteDoc = {
|
|
|
178
189
|
{ status: 200, description: 'History entries', schema: documentHistoryResponseSchema },
|
|
179
190
|
{ status: 400, description: 'Invalid query', schema: z.object({ error: z.string() }) },
|
|
180
191
|
{ status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },
|
|
192
|
+
{ status: 403, description: 'Forbidden', schema: z.object({ error: z.string() }) },
|
|
181
193
|
{ status: 404, description: 'Document not found', schema: z.object({ error: z.string() }) },
|
|
182
194
|
],
|
|
183
195
|
},
|