@open-mercato/core 0.6.6-develop.6474.1.2c411ad42b → 0.6.6-develop.6480.1.309f47ca1c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/modules/progress/api/active/route.js +1 -1
- package/dist/modules/progress/api/active/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/[id]/route.js +1 -1
- package/dist/modules/progress/api/jobs/[id]/route.js.map +2 -2
- package/dist/modules/progress/api/jobs/route.js +1 -1
- package/dist/modules/progress/api/jobs/route.js.map +2 -2
- package/dist/modules/sales/api/notes/route.js +71 -2
- package/dist/modules/sales/api/notes/route.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +12 -8
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/sales/commands/notes.js +15 -7
- package/dist/modules/sales/commands/notes.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/progress/api/active/route.ts +1 -1
- package/src/modules/progress/api/jobs/[id]/route.ts +1 -1
- package/src/modules/progress/api/jobs/route.ts +1 -1
- package/src/modules/sales/api/notes/route.ts +103 -4
- package/src/modules/sales/commands/documents.ts +16 -8
- package/src/modules/sales/commands/notes.ts +15 -7
|
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
const routeMetadata = {
|
|
5
|
-
GET: { requireAuth: true }
|
|
5
|
+
GET: { requireAuth: true, requireFeatures: ["progress.view"] }
|
|
6
6
|
};
|
|
7
7
|
const metadata = routeMetadata;
|
|
8
8
|
async function GET(req) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/progress/api/active/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ProgressJob } from '../../data/entities'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ active: [], recentlyCompleted: [] }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId }\n\n await progressService.markStaleJobsFailed(auth.tenantId, undefined, auth.orgId)\n\n const [jobs, recentlyCompleted] = await Promise.all([\n progressService.getActiveJobs(ctx),\n progressService.getRecentlyCompletedJobs(ctx),\n ])\n\n return NextResponse.json({\n active: jobs.map(formatJob),\n recentlyCompleted: recentlyCompleted.map(formatJob),\n })\n}\n\nfunction formatJob(job: ProgressJob) {\n return {\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n errorMessage: job.errorMessage,\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get active progress jobs',\n description: 'Returns currently running jobs and recently completed jobs for the progress top bar.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Active and recently completed jobs' },\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { ProgressJob } from '../../data/entities'\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['progress.view'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ active: [], recentlyCompleted: [] }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const ctx = { tenantId: auth.tenantId, organizationId: auth.orgId }\n\n await progressService.markStaleJobsFailed(auth.tenantId, undefined, auth.orgId)\n\n const [jobs, recentlyCompleted] = await Promise.all([\n progressService.getActiveJobs(ctx),\n progressService.getRecentlyCompletedJobs(ctx),\n ])\n\n return NextResponse.json({\n active: jobs.map(formatJob),\n recentlyCompleted: recentlyCompleted.map(formatJob),\n })\n}\n\nfunction formatJob(job: ProgressJob) {\n return {\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta ?? null,\n startedAt: job.startedAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n errorMessage: job.errorMessage,\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get active progress jobs',\n description: 'Returns currently running jobs and recently completed jobs for the progress top bar.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Active and recently completed jobs' },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,eAAe,EAAE;AAC/D;AAEO,MAAM,WAAW;AAExB,eAAsB,IAAI,KAAc;AACtC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,QAAQ,CAAC,KAAK,UAAU;AAC3B,WAAO,aAAa,KAAK,EAAE,QAAQ,CAAC,GAAG,mBAAmB,CAAC,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACjF;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,kBAAkB,UAAU,QAAQ,iBAAiB;AAE3D,QAAM,MAAM,EAAE,UAAU,KAAK,UAAU,gBAAgB,KAAK,MAAM;AAElE,QAAM,gBAAgB,oBAAoB,KAAK,UAAU,QAAW,KAAK,KAAK;AAE9E,QAAM,CAAC,MAAM,iBAAiB,IAAI,MAAM,QAAQ,IAAI;AAAA,IAClD,gBAAgB,cAAc,GAAG;AAAA,IACjC,gBAAgB,yBAAyB,GAAG;AAAA,EAC9C,CAAC;AAED,SAAO,aAAa,KAAK;AAAA,IACvB,QAAQ,KAAK,IAAI,SAAS;AAAA,IAC1B,mBAAmB,kBAAkB,IAAI,SAAS;AAAA,EACpD,CAAC;AACH;AAEA,SAAS,UAAU,KAAkB;AACnC,SAAO;AAAA,IACL,IAAI,IAAI;AAAA,IACR,SAAS,IAAI;AAAA,IACb,MAAM,IAAI;AAAA,IACV,aAAa,IAAI;AAAA,IACjB,QAAQ,IAAI;AAAA,IACZ,iBAAiB,IAAI;AAAA,IACrB,gBAAgB,IAAI;AAAA,IACpB,YAAY,IAAI;AAAA,IAChB,YAAY,IAAI;AAAA,IAChB,aAAa,IAAI;AAAA,IACjB,MAAM,IAAI,QAAQ;AAAA,IAClB,WAAW,IAAI,WAAW,YAAY,KAAK;AAAA,IAC3C,YAAY,IAAI,YAAY,YAAY,KAAK;AAAA,IAC7C,cAAc,IAAI;AAAA,EACpB;AACF;AAEO,MAAM,UAAU;AAAA,EACrB,KAAK;AAAA,IACH,SAAS;AAAA,IACT,aAAa;AAAA,IACb,MAAM,CAAC,UAAU;AAAA,IACjB,WAAW;AAAA,MACT,KAAK,EAAE,aAAa,qCAAqC;AAAA,IAC3D;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
|
4
4
|
import { ProgressJob } from "../../../data/entities.js";
|
|
5
5
|
import { updateProgressSchema } from "../../../data/validators.js";
|
|
6
6
|
const routeMetadata = {
|
|
7
|
-
GET: { requireAuth: true },
|
|
7
|
+
GET: { requireAuth: true, requireFeatures: ["progress.view"] },
|
|
8
8
|
PUT: { requireAuth: true, requireFeatures: ["progress.update"] },
|
|
9
9
|
DELETE: { requireAuth: true, requireFeatures: ["progress.cancel"] }
|
|
10
10
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/progress/api/jobs/%5Bid%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../../../data/entities'\nimport { updateProgressSchema } from '../../../data/validators'\nimport type { ProgressService } from '../../../lib/progressService'\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n PUT: { requireAuth: true, requireFeatures: ['progress.update'] },\n DELETE: { requireAuth: true, requireFeatures: ['progress.cancel'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const job = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n ...(auth.orgId ? { organizationId: auth.orgId } : {}),\n })\n\n if (!job) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta,\n resultSummary: job.resultSummary,\n errorMessage: job.errorMessage,\n startedByUserId: job.startedByUserId,\n startedAt: job.startedAt?.toISOString() ?? null,\n heartbeatAt: job.heartbeatAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n parentJobId: job.parentJobId,\n partitionIndex: job.partitionIndex,\n partitionCount: job.partitionCount,\n createdAt: job.createdAt.toISOString(),\n updatedAt: job.updatedAt.toISOString(),\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = updateProgressSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const existing = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n ...(auth.orgId ? { organizationId: auth.orgId } : {}),\n })\n if (!existing) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n const progressService = container.resolve('progressService') as ProgressService\n\n const job = await progressService.updateProgress(params.id, parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ ok: true, progressPercent: job.progressPercent })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as ProgressService\n\n try {\n await progressService.cancelJob(params.id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n return NextResponse.json({ ok: true })\n } catch {\n return NextResponse.json({ error: 'Cannot cancel this job' }, { status: 400 })\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get progress job details',\n description: 'Returns full details of a specific progress job by ID.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress job details' },\n 404: { description: 'Job not found' },\n },\n },\n PUT: {\n summary: 'Update progress job',\n description: 'Updates progress metrics and heartbeat for a running job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress updated' },\n 400: { description: 'Invalid input' },\n 404: { description: 'Job not found' },\n },\n },\n DELETE: {\n summary: 'Cancel progress job',\n description: 'Requests cancellation of a running or pending job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Cancellation requested' },\n 400: { description: 'Cannot cancel this job' },\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAEvC,SAAS,mBAAmB;AAC5B,SAAS,4BAA4B;AAGrC,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ProgressJob } from '../../../data/entities'\nimport { updateProgressSchema } from '../../../data/validators'\nimport type { ProgressService } from '../../../lib/progressService'\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['progress.view'] },\n PUT: { requireAuth: true, requireFeatures: ['progress.update'] },\n DELETE: { requireAuth: true, requireFeatures: ['progress.cancel'] },\n}\n\nexport const metadata = routeMetadata\n\nexport async function GET(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const job = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n ...(auth.orgId ? { organizationId: auth.orgId } : {}),\n })\n\n if (!job) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n return NextResponse.json({\n id: job.id,\n jobType: job.jobType,\n name: job.name,\n description: job.description,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount,\n etaSeconds: job.etaSeconds,\n cancellable: job.cancellable,\n meta: job.meta,\n resultSummary: job.resultSummary,\n errorMessage: job.errorMessage,\n startedByUserId: job.startedByUserId,\n startedAt: job.startedAt?.toISOString() ?? null,\n heartbeatAt: job.heartbeatAt?.toISOString() ?? null,\n finishedAt: job.finishedAt?.toISOString() ?? null,\n parentJobId: job.parentJobId,\n partitionIndex: job.partitionIndex,\n partitionCount: job.partitionCount,\n createdAt: job.createdAt.toISOString(),\n updatedAt: job.updatedAt.toISOString(),\n tenantId: job.tenantId,\n organizationId: job.organizationId,\n })\n}\n\nexport async function PUT(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = updateProgressSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const existing = await em.findOne(ProgressJob, {\n id: params.id,\n tenantId: auth.tenantId,\n ...(auth.orgId ? { organizationId: auth.orgId } : {}),\n })\n if (!existing) {\n return NextResponse.json({ error: 'Not found' }, { status: 404 })\n }\n\n const progressService = container.resolve('progressService') as ProgressService\n\n const job = await progressService.updateProgress(params.id, parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ ok: true, progressPercent: job.progressPercent })\n}\n\nexport async function DELETE(req: Request, { params }: { params: { id: string } }) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as ProgressService\n\n try {\n await progressService.cancelJob(params.id, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n return NextResponse.json({ ok: true })\n } catch {\n return NextResponse.json({ error: 'Cannot cancel this job' }, { status: 400 })\n }\n}\n\nexport const openApi = {\n GET: {\n summary: 'Get progress job details',\n description: 'Returns full details of a specific progress job by ID.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress job details' },\n 404: { description: 'Job not found' },\n },\n },\n PUT: {\n summary: 'Update progress job',\n description: 'Updates progress metrics and heartbeat for a running job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Progress updated' },\n 400: { description: 'Invalid input' },\n 404: { description: 'Job not found' },\n },\n },\n DELETE: {\n summary: 'Cancel progress job',\n description: 'Requests cancellation of a running or pending job.',\n tags: ['Progress'],\n responses: {\n 200: { description: 'Cancellation requested' },\n 400: { description: 'Cannot cancel this job' },\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
13
13
|
const logger = createLogger("progress").child({ component: "jobs" });
|
|
14
14
|
const routeMetadata = {
|
|
15
|
-
GET: { requireAuth: true },
|
|
15
|
+
GET: { requireAuth: true, requireFeatures: ["progress.view"] },
|
|
16
16
|
POST: { requireAuth: true, requireFeatures: ["progress.create"] }
|
|
17
17
|
};
|
|
18
18
|
const metadata = routeMetadata;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/progress/api/jobs/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { ProgressJob } from '../../data/entities'\nimport { createProgressJobSchema, listProgressJobsSchema } from '../../data/validators'\nimport {\n createProgressCrudOpenApi,\n createPagedListResponseSchema,\n} from '../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('progress').child({ component: 'jobs' })\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true, requireFeatures: ['progress.create'] },\n}\n\nexport const metadata = routeMetadata\n\nconst listQuerySchema = listProgressJobsSchema\n\ntype JobRow = {\n id: string\n jobType: string\n name: string\n description: string | null\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n cancellable: boolean\n startedAt: string | null\n finishedAt: string | null\n errorMessage: string | null\n createdAt: string | null\n tenantId: string\n organizationId: string | null\n}\n\nconst toRow = (job: ProgressJob): JobRow => ({\n id: String(job.id),\n jobType: String(job.jobType),\n name: String(job.name),\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: !!job.cancellable,\n startedAt: job.startedAt ? job.startedAt.toISOString() : null,\n finishedAt: job.finishedAt ? job.finishedAt.toISOString() : null,\n errorMessage: job.errorMessage ?? null,\n createdAt: job.createdAt ? job.createdAt.toISOString() : null,\n tenantId: String(job.tenantId),\n organizationId: job.organizationId ? String(job.organizationId) : null,\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n status: url.searchParams.get('status') ?? undefined,\n jobType: url.searchParams.get('jobType') ?? undefined,\n parentJobId: url.searchParams.get('parentJobId') ?? undefined,\n includeCompleted: url.searchParams.get('includeCompleted') ?? undefined,\n completedSince: url.searchParams.get('completedSince') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { status, jobType, parentJobId, includeCompleted, completedSince, page, pageSize, search, sortField, sortDir } = parsed.data\n const filter: FilterQuery<ProgressJob> = {\n tenantId: auth.tenantId,\n }\n\n if (auth.orgId) {\n filter.organizationId = auth.orgId\n }\n\n if (status) {\n const statusValues = status.split(',')\n filter.status = statusValues.length > 1 ? { $in: statusValues as never } : status as never\n } else if (includeCompleted !== 'true') {\n filter.status = { $in: ['pending', 'running'] }\n }\n\n if (jobType) filter.jobType = jobType\n if (parentJobId) filter.parentJobId = parentJobId\n if (completedSince) filter.finishedAt = { $gte: new Date(completedSince) }\n\n if (search) {\n const escaped = escapeLikePattern(search)\n filter.$or = [\n { name: { $ilike: `%${escaped}%` } },\n { jobType: { $ilike: `%${escaped}%` } },\n ]\n }\n\n const fieldMap: Record<string, string> = {\n createdAt: 'createdAt',\n startedAt: 'startedAt',\n finishedAt: 'finishedAt',\n }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'createdAt'\n orderBy[mapped] = sortDir === 'asc' ? 'ASC' : 'DESC'\n } else {\n orderBy.createdAt = 'DESC'\n }\n\n const [rows, total] = await em.findAndCount(ProgressJob, filter, {\n orderBy,\n limit: pageSize,\n offset: (page - 1) * pageSize,\n })\n const items = rows.map(toRow)\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = createProgressJobSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const job = await progressService.createJob(parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ id: job.id }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n logger.error('Unhandled error creating progress job', { message, stack })\n return NextResponse.json(\n { error: 'Failed to create progress job.' },\n { status: 500 },\n )\n }\n}\n\nconst jobListItemSchema = z.object({\n id: z.string().uuid(),\n jobType: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n status: z.string(),\n progressPercent: z.number(),\n processedCount: z.number(),\n totalCount: z.number().nullable(),\n etaSeconds: z.number().nullable(),\n cancellable: z.boolean(),\n startedAt: z.string().nullable(),\n finishedAt: z.string().nullable(),\n errorMessage: z.string().nullable(),\n createdAt: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createProgressCrudOpenApi({\n resourceName: 'ProgressJob',\n pluralName: 'ProgressJobs',\n querySchema: listQuerySchema,\n listResponseSchema: createPagedListResponseSchema(jobListItemSchema),\n create: {\n schema: createProgressJobSchema,\n description: 'Creates a new progress job for tracking a long-running operation.',\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AAGvC,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB,8BAA8B;AAChE;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,UAAU,EAAE,MAAM,EAAE,WAAW,OAAO,CAAC;AAEnE,MAAM,gBAAgB;AAAA,EACpB,KAAK,EAAE,aAAa,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { FilterQuery } from '@mikro-orm/core'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { ProgressJob } from '../../data/entities'\nimport { createProgressJobSchema, listProgressJobsSchema } from '../../data/validators'\nimport {\n createProgressCrudOpenApi,\n createPagedListResponseSchema,\n} from '../openapi'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('progress').child({ component: 'jobs' })\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['progress.view'] },\n POST: { requireAuth: true, requireFeatures: ['progress.create'] },\n}\n\nexport const metadata = routeMetadata\n\nconst listQuerySchema = listProgressJobsSchema\n\ntype JobRow = {\n id: string\n jobType: string\n name: string\n description: string | null\n status: string\n progressPercent: number\n processedCount: number\n totalCount: number | null\n etaSeconds: number | null\n cancellable: boolean\n startedAt: string | null\n finishedAt: string | null\n errorMessage: string | null\n createdAt: string | null\n tenantId: string\n organizationId: string | null\n}\n\nconst toRow = (job: ProgressJob): JobRow => ({\n id: String(job.id),\n jobType: String(job.jobType),\n name: String(job.name),\n description: job.description ?? null,\n status: job.status,\n progressPercent: job.progressPercent,\n processedCount: job.processedCount,\n totalCount: job.totalCount ?? null,\n etaSeconds: job.etaSeconds ?? null,\n cancellable: !!job.cancellable,\n startedAt: job.startedAt ? job.startedAt.toISOString() : null,\n finishedAt: job.finishedAt ? job.finishedAt.toISOString() : null,\n errorMessage: job.errorMessage ?? null,\n createdAt: job.createdAt ? job.createdAt.toISOString() : null,\n tenantId: String(job.tenantId),\n organizationId: job.organizationId ? String(job.organizationId) : null,\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 401 })\n }\n\n const url = new URL(req.url)\n const parsed = listQuerySchema.safeParse({\n status: url.searchParams.get('status') ?? undefined,\n jobType: url.searchParams.get('jobType') ?? undefined,\n parentJobId: url.searchParams.get('parentJobId') ?? undefined,\n includeCompleted: url.searchParams.get('includeCompleted') ?? undefined,\n completedSince: url.searchParams.get('completedSince') ?? undefined,\n page: url.searchParams.get('page') ?? undefined,\n pageSize: url.searchParams.get('pageSize') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField: url.searchParams.get('sortField') ?? undefined,\n sortDir: url.searchParams.get('sortDir') ?? undefined,\n })\n if (!parsed.success) {\n return NextResponse.json({ items: [], total: 0, page: 1, pageSize: 20, totalPages: 1 }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n\n const { status, jobType, parentJobId, includeCompleted, completedSince, page, pageSize, search, sortField, sortDir } = parsed.data\n const filter: FilterQuery<ProgressJob> = {\n tenantId: auth.tenantId,\n }\n\n if (auth.orgId) {\n filter.organizationId = auth.orgId\n }\n\n if (status) {\n const statusValues = status.split(',')\n filter.status = statusValues.length > 1 ? { $in: statusValues as never } : status as never\n } else if (includeCompleted !== 'true') {\n filter.status = { $in: ['pending', 'running'] }\n }\n\n if (jobType) filter.jobType = jobType\n if (parentJobId) filter.parentJobId = parentJobId\n if (completedSince) filter.finishedAt = { $gte: new Date(completedSince) }\n\n if (search) {\n const escaped = escapeLikePattern(search)\n filter.$or = [\n { name: { $ilike: `%${escaped}%` } },\n { jobType: { $ilike: `%${escaped}%` } },\n ]\n }\n\n const fieldMap: Record<string, string> = {\n createdAt: 'createdAt',\n startedAt: 'startedAt',\n finishedAt: 'finishedAt',\n }\n const orderBy: Record<string, 'ASC' | 'DESC'> = {}\n if (sortField) {\n const mapped = fieldMap[sortField] || 'createdAt'\n orderBy[mapped] = sortDir === 'asc' ? 'ASC' : 'DESC'\n } else {\n orderBy.createdAt = 'DESC'\n }\n\n const [rows, total] = await em.findAndCount(ProgressJob, filter, {\n orderBy,\n limit: pageSize,\n offset: (page - 1) * pageSize,\n })\n const items = rows.map(toRow)\n const totalPages = Math.max(1, Math.ceil(total / pageSize))\n\n return NextResponse.json({ items, total, page, pageSize, totalPages })\n}\n\nexport async function POST(req: Request) {\n try {\n const auth = await getAuthFromRequest(req)\n if (!auth || !auth.tenantId) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const body = await req.json()\n const parsed = createProgressJobSchema.safeParse(body)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid input', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const progressService = container.resolve('progressService') as import('../../lib/progressService').ProgressService\n\n const job = await progressService.createJob(parsed.data, {\n tenantId: auth.tenantId,\n organizationId: auth.orgId,\n userId: auth.sub,\n })\n\n return NextResponse.json({ id: job.id }, { status: 201 })\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n const stack = error instanceof Error ? error.stack : undefined\n logger.error('Unhandled error creating progress job', { message, stack })\n return NextResponse.json(\n { error: 'Failed to create progress job.' },\n { status: 500 },\n )\n }\n}\n\nconst jobListItemSchema = z.object({\n id: z.string().uuid(),\n jobType: z.string(),\n name: z.string(),\n description: z.string().nullable(),\n status: z.string(),\n progressPercent: z.number(),\n processedCount: z.number(),\n totalCount: z.number().nullable(),\n etaSeconds: z.number().nullable(),\n cancellable: z.boolean(),\n startedAt: z.string().nullable(),\n finishedAt: z.string().nullable(),\n errorMessage: z.string().nullable(),\n createdAt: z.string().nullable(),\n tenantId: z.string().uuid(),\n organizationId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createProgressCrudOpenApi({\n resourceName: 'ProgressJob',\n pluralName: 'ProgressJobs',\n querySchema: listQuerySchema,\n listResponseSchema: createPagedListResponseSchema(jobListItemSchema),\n create: {\n schema: createProgressJobSchema,\n description: 'Creates a new progress job for tracking a long-running operation.',\n },\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { makeCrudRoute } from "@open-mercato/shared/lib/crud/factory";
|
|
3
3
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
4
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
5
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
5
6
|
import { SalesNote } from "../../data/entities.js";
|
|
6
7
|
import { noteCreateSchema, noteUpdateSchema } from "../../data/validators.js";
|
|
@@ -22,6 +23,63 @@ const listSchema = z.object({
|
|
|
22
23
|
sortField: z.string().optional(),
|
|
23
24
|
sortDir: z.enum(["asc", "desc"]).optional()
|
|
24
25
|
}).passthrough();
|
|
26
|
+
const noteFeatureByContext = {
|
|
27
|
+
order: { view: "sales.orders.view", manage: "sales.orders.manage" },
|
|
28
|
+
quote: { view: "sales.quotes.view", manage: "sales.quotes.manage" },
|
|
29
|
+
invoice: { view: "sales.invoices.manage", manage: "sales.invoices.manage" },
|
|
30
|
+
credit_memo: { view: "sales.credit_memos.manage", manage: "sales.credit_memos.manage" }
|
|
31
|
+
};
|
|
32
|
+
const allNoteViewFeatures = Array.from(
|
|
33
|
+
new Set(Object.values(noteFeatureByContext).map((features) => features.view))
|
|
34
|
+
);
|
|
35
|
+
const allNoteManageFeatures = Array.from(
|
|
36
|
+
new Set(Object.values(noteFeatureByContext).map((features) => features.manage))
|
|
37
|
+
);
|
|
38
|
+
function resolveNoteListContextType(query) {
|
|
39
|
+
if (query.contextType) return query.contextType;
|
|
40
|
+
if (query.orderId) return "order";
|
|
41
|
+
if (query.quoteId) return "quote";
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function requiredNoteFeatures(action, contextType) {
|
|
45
|
+
if (contextType) return [noteFeatureByContext[contextType][action]];
|
|
46
|
+
return action === "view" ? allNoteViewFeatures : allNoteManageFeatures;
|
|
47
|
+
}
|
|
48
|
+
async function ensureNotePermission(ctx, action, contextType, translate) {
|
|
49
|
+
const auth = ctx.auth;
|
|
50
|
+
if (!auth?.sub) {
|
|
51
|
+
throw new CrudHttpError(401, { error: translate("api.errors.unauthorized", "Unauthorized") });
|
|
52
|
+
}
|
|
53
|
+
const requiredFeatures = requiredNoteFeatures(action, contextType);
|
|
54
|
+
const rbac = ctx.container.resolve("rbacService");
|
|
55
|
+
const ok = await rbac.userHasAllFeatures(auth.sub, requiredFeatures, {
|
|
56
|
+
tenantId: auth.tenantId ?? null,
|
|
57
|
+
organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null
|
|
58
|
+
});
|
|
59
|
+
if (!ok) {
|
|
60
|
+
throw new CrudHttpError(403, {
|
|
61
|
+
error: translate("api.errors.forbidden", "Forbidden"),
|
|
62
|
+
requiredFeatures
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function loadNoteForPermission(ctx, id, translate) {
|
|
67
|
+
const em = ctx.container.resolve("em");
|
|
68
|
+
const note = await findOneWithDecryption(em, SalesNote, { id }, {}, {
|
|
69
|
+
tenantId: ctx.auth?.tenantId ?? void 0,
|
|
70
|
+
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? void 0
|
|
71
|
+
});
|
|
72
|
+
if (!note) {
|
|
73
|
+
throw new CrudHttpError(404, {
|
|
74
|
+
error: translate("sales.documents.detail.error", "Document not found or inaccessible.")
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return note;
|
|
78
|
+
}
|
|
79
|
+
async function ensureExistingNotePermission(ctx, id, action, translate) {
|
|
80
|
+
const note = await loadNoteForPermission(ctx, id, translate);
|
|
81
|
+
await ensureNotePermission(ctx, action, note.contextType, translate);
|
|
82
|
+
}
|
|
25
83
|
const routeMetadata = {
|
|
26
84
|
GET: { requireAuth: true },
|
|
27
85
|
POST: { requireAuth: true },
|
|
@@ -40,6 +98,12 @@ const crud = makeCrudRoute({
|
|
|
40
98
|
indexer: {
|
|
41
99
|
entityType: E.sales.sales_note
|
|
42
100
|
},
|
|
101
|
+
hooks: {
|
|
102
|
+
beforeList: async (query, ctx) => {
|
|
103
|
+
const { translate } = await resolveTranslations();
|
|
104
|
+
await ensureNotePermission(ctx, "view", resolveNoteListContextType(query), translate);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
43
107
|
list: {
|
|
44
108
|
schema: listSchema,
|
|
45
109
|
entityId: E.sales.sales_note,
|
|
@@ -77,7 +141,9 @@ const crud = makeCrudRoute({
|
|
|
77
141
|
schema: rawBodySchema,
|
|
78
142
|
mapInput: async ({ raw, ctx }) => {
|
|
79
143
|
const { translate } = await resolveTranslations();
|
|
80
|
-
|
|
144
|
+
const input = noteCreateSchema.parse(withScopedPayload(raw ?? {}, ctx, translate));
|
|
145
|
+
await ensureNotePermission(ctx, "manage", input.contextType, translate);
|
|
146
|
+
return input;
|
|
81
147
|
},
|
|
82
148
|
response: ({ result }) => ({
|
|
83
149
|
id: result?.noteId ?? result?.id ?? null,
|
|
@@ -90,7 +156,9 @@ const crud = makeCrudRoute({
|
|
|
90
156
|
schema: rawBodySchema,
|
|
91
157
|
mapInput: async ({ raw, ctx }) => {
|
|
92
158
|
const { translate } = await resolveTranslations();
|
|
93
|
-
|
|
159
|
+
const input = noteUpdateSchema.parse(withScopedPayload(raw ?? {}, ctx, translate));
|
|
160
|
+
await ensureExistingNotePermission(ctx, input.id, "manage", translate);
|
|
161
|
+
return input;
|
|
94
162
|
},
|
|
95
163
|
response: () => ({ ok: true })
|
|
96
164
|
},
|
|
@@ -103,6 +171,7 @@ const crud = makeCrudRoute({
|
|
|
103
171
|
if (!id) {
|
|
104
172
|
throw new CrudHttpError(400, { error: translate("sales.documents.detail.error", "Document not found or inaccessible.") });
|
|
105
173
|
}
|
|
174
|
+
await ensureExistingNotePermission(ctx, id, "manage", translate);
|
|
106
175
|
return { id };
|
|
107
176
|
},
|
|
108
177
|
response: () => ({ ok: true })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/sales/api/notes/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { SalesNote } from '../../data/entities'\nimport { noteCreateSchema, noteUpdateSchema } from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultOkResponseSchema,\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n contextType: z.enum(['order', 'quote', 'invoice', 'credit_memo']).optional(),\n contextId: z.string().uuid().optional(),\n orderId: z.string().uuid().optional(),\n quoteId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true },\n PUT: { requireAuth: true },\n DELETE: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesNote,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n },\n indexer: {\n entityType: E.sales.sales_note,\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_note,\n fields: [\n 'id',\n 'context_type',\n 'context_id',\n 'order_id',\n 'quote_id',\n 'body',\n 'author_user_id',\n 'appearance_icon',\n 'appearance_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.contextId) filters.context_id = { $eq: query.contextId }\n if (query.contextType) filters.context_type = { $eq: query.contextType }\n if (query.orderId) filters.order_id = { $eq: query.orderId }\n if (query.quoteId) filters.quote_id = { $eq: query.quoteId }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.notes.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { makeCrudRoute, type CrudCtx } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { SalesNote, type SalesDocumentKind } from '../../data/entities'\nimport { noteCreateSchema, noteUpdateSchema } from '../../data/validators'\nimport { withScopedPayload } from '../utils'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n createPagedListResponseSchema,\n createSalesCrudOpenApi,\n defaultOkResponseSchema,\n} from '../openapi'\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst listSchema = z\n .object({\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n contextType: z.enum(['order', 'quote', 'invoice', 'credit_memo']).optional(),\n contextId: z.string().uuid().optional(),\n orderId: z.string().uuid().optional(),\n quoteId: z.string().uuid().optional(),\n sortField: z.string().optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n })\n .passthrough()\n\ntype NoteListQuery = z.infer<typeof listSchema>\ntype NoteAction = 'view' | 'manage'\n\nconst noteFeatureByContext: Record<SalesDocumentKind, Record<NoteAction, string>> = {\n order: { view: 'sales.orders.view', manage: 'sales.orders.manage' },\n quote: { view: 'sales.quotes.view', manage: 'sales.quotes.manage' },\n invoice: { view: 'sales.invoices.manage', manage: 'sales.invoices.manage' },\n credit_memo: { view: 'sales.credit_memos.manage', manage: 'sales.credit_memos.manage' },\n}\n\nconst allNoteViewFeatures = Array.from(\n new Set(Object.values(noteFeatureByContext).map((features) => features.view)),\n)\nconst allNoteManageFeatures = Array.from(\n new Set(Object.values(noteFeatureByContext).map((features) => features.manage)),\n)\n\nfunction resolveNoteListContextType(query: NoteListQuery): SalesDocumentKind | null {\n if (query.contextType) return query.contextType\n if (query.orderId) return 'order'\n if (query.quoteId) return 'quote'\n return null\n}\n\nfunction requiredNoteFeatures(action: NoteAction, contextType: SalesDocumentKind | null): string[] {\n if (contextType) return [noteFeatureByContext[contextType][action]]\n return action === 'view' ? allNoteViewFeatures : allNoteManageFeatures\n}\n\nasync function ensureNotePermission(\n ctx: CrudCtx,\n action: NoteAction,\n contextType: SalesDocumentKind | null,\n translate: (key: string, fallback?: string) => string\n) {\n const auth = ctx.auth\n if (!auth?.sub) {\n throw new CrudHttpError(401, { error: translate('api.errors.unauthorized', 'Unauthorized') })\n }\n\n const requiredFeatures = requiredNoteFeatures(action, contextType)\n const rbac = ctx.container.resolve<RbacService>('rbacService')\n const ok = await rbac.userHasAllFeatures(auth.sub, requiredFeatures, {\n tenantId: auth.tenantId ?? null,\n organizationId: ctx.selectedOrganizationId ?? auth.orgId ?? null,\n })\n\n if (!ok) {\n throw new CrudHttpError(403, {\n error: translate('api.errors.forbidden', 'Forbidden'),\n requiredFeatures,\n })\n }\n}\n\nasync function loadNoteForPermission(\n ctx: CrudCtx,\n id: string,\n translate: (key: string, fallback?: string) => string\n): Promise<SalesNote> {\n const em = ctx.container.resolve<EntityManager>('em')\n const note = await findOneWithDecryption(em, SalesNote, { id }, {}, {\n tenantId: ctx.auth?.tenantId ?? undefined,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? undefined,\n })\n\n if (!note) {\n throw new CrudHttpError(404, {\n error: translate('sales.documents.detail.error', 'Document not found or inaccessible.'),\n })\n }\n\n return note\n}\n\nasync function ensureExistingNotePermission(\n ctx: CrudCtx,\n id: string,\n action: NoteAction,\n translate: (key: string, fallback?: string) => string\n) {\n const note = await loadNoteForPermission(ctx, id, translate)\n await ensureNotePermission(ctx, action, note.contextType, translate)\n}\n\nconst routeMetadata = {\n GET: { requireAuth: true },\n POST: { requireAuth: true },\n PUT: { requireAuth: true },\n DELETE: { requireAuth: true },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute({\n metadata: routeMetadata,\n orm: {\n entity: SalesNote,\n idField: 'id',\n orgField: 'organizationId',\n tenantField: 'tenantId',\n },\n indexer: {\n entityType: E.sales.sales_note,\n },\n hooks: {\n beforeList: async (query, ctx) => {\n const { translate } = await resolveTranslations()\n await ensureNotePermission(ctx, 'view', resolveNoteListContextType(query as NoteListQuery), translate)\n },\n },\n list: {\n schema: listSchema,\n entityId: E.sales.sales_note,\n fields: [\n 'id',\n 'context_type',\n 'context_id',\n 'order_id',\n 'quote_id',\n 'body',\n 'author_user_id',\n 'appearance_icon',\n 'appearance_color',\n 'organization_id',\n 'tenant_id',\n 'created_at',\n 'updated_at',\n ],\n sortFieldMap: {\n createdAt: 'created_at',\n updatedAt: 'updated_at',\n },\n buildFilters: async (query: any) => {\n const filters: Record<string, any> = {}\n if (query.contextId) filters.context_id = { $eq: query.contextId }\n if (query.contextType) filters.context_type = { $eq: query.contextType }\n if (query.orderId) filters.order_id = { $eq: query.orderId }\n if (query.quoteId) filters.quote_id = { $eq: query.quoteId }\n return filters\n },\n },\n actions: {\n create: {\n commandId: 'sales.notes.create',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const input = noteCreateSchema.parse(withScopedPayload(raw ?? {}, ctx, translate))\n await ensureNotePermission(ctx, 'manage', input.contextType, translate)\n return input\n },\n response: ({ result }) => ({\n id: result?.noteId ?? result?.id ?? null,\n authorUserId: result?.authorUserId ?? null,\n }),\n status: 201,\n },\n update: {\n commandId: 'sales.notes.update',\n schema: rawBodySchema,\n mapInput: async ({ raw, ctx }) => {\n const { translate } = await resolveTranslations()\n const input = noteUpdateSchema.parse(withScopedPayload(raw ?? {}, ctx, translate))\n await ensureExistingNotePermission(ctx, input.id, 'manage', translate)\n return input\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'sales.notes.delete',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n const { translate } = await resolveTranslations()\n const id =\n parsed?.body?.id ??\n parsed?.id ??\n parsed?.query?.id ??\n (ctx.request ? new URL(ctx.request.url).searchParams.get('id') : null)\n if (!id) {\n throw new CrudHttpError(400, { error: translate('sales.documents.detail.error', 'Document not found or inaccessible.') })\n }\n await ensureExistingNotePermission(ctx, id, 'manage', translate)\n return { id }\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport const GET = crud.GET\nexport const POST = crud.POST\nexport const PUT = crud.PUT\nexport const DELETE = crud.DELETE\n\nconst noteListItemSchema = z\n .object({\n id: z.string().uuid(),\n context_type: z.enum(['order', 'quote', 'invoice', 'credit_memo']),\n context_id: z.string().uuid(),\n order_id: z.string().uuid().nullable().optional(),\n quote_id: z.string().uuid().nullable().optional(),\n body: z.string().nullable(),\n author_user_id: z.string().uuid().nullable().optional(),\n appearance_icon: z.string().nullable().optional(),\n appearance_color: z.string().nullable().optional(),\n organization_id: z.string().uuid().nullable().optional(),\n tenant_id: z.string().uuid().nullable().optional(),\n created_at: z.string().nullable(),\n updated_at: z.string().nullable().optional(),\n })\n .passthrough()\n\nconst noteCreateResponseSchema = z.object({\n id: z.string().uuid().nullable(),\n authorUserId: z.string().uuid().nullable(),\n})\n\nexport const openApi = createSalesCrudOpenApi({\n resourceName: 'Sales note',\n querySchema: listSchema,\n listResponseSchema: createPagedListResponseSchema(noteListItemSchema),\n create: {\n schema: noteCreateSchema,\n responseSchema: noteCreateResponseSchema,\n description: 'Creates a note attached to a sales document.',\n },\n update: {\n schema: noteUpdateSchema,\n responseSchema: defaultOkResponseSchema,\n description: 'Updates a sales note.',\n },\n del: {\n schema: z.object({ id: z.string().uuid() }),\n responseSchema: defaultOkResponseSchema,\n description: 'Deletes a sales note.',\n },\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3318,10 +3318,12 @@ const createQuoteCommand = {
|
|
|
3318
3318
|
sourceEntityId: quote.id,
|
|
3319
3319
|
linkHref: `/backend/sales/quotes/${quote.id}`
|
|
3320
3320
|
});
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3321
|
+
if (!ctx.bulkImport?.skipNotifications) {
|
|
3322
|
+
await notificationService.createForFeature(notificationInput, {
|
|
3323
|
+
tenantId: quote.tenantId,
|
|
3324
|
+
organizationId: quote.organizationId ?? null
|
|
3325
|
+
});
|
|
3326
|
+
}
|
|
3325
3327
|
}
|
|
3326
3328
|
} catch (err) {
|
|
3327
3329
|
logger.error("sales.quotes.create failed to create notification", { err });
|
|
@@ -4201,10 +4203,12 @@ const createOrderCommand = {
|
|
|
4201
4203
|
sourceEntityId: order.id,
|
|
4202
4204
|
linkHref: `/backend/sales/orders/${order.id}`
|
|
4203
4205
|
});
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4206
|
+
if (!ctx.bulkImport?.skipNotifications) {
|
|
4207
|
+
await notificationService.createForFeature(notificationInput, {
|
|
4208
|
+
tenantId: order.tenantId,
|
|
4209
|
+
organizationId: order.organizationId ?? null
|
|
4210
|
+
});
|
|
4211
|
+
}
|
|
4208
4212
|
}
|
|
4209
4213
|
} catch (err) {
|
|
4210
4214
|
logger.error("sales.orders.create failed to create notification", { err });
|