diff --git a/apps/api/src/isms/dto/export-isms-document.dto.ts b/apps/api/src/isms/dto/export-isms-document.dto.ts index 632b1a292..b91f83c87 100644 --- a/apps/api/src/isms/dto/export-isms-document.dto.ts +++ b/apps/api/src/isms/dto/export-isms-document.dto.ts @@ -12,7 +12,8 @@ export class ExportIsmsDocumentDto { @ApiPropertyOptional({ description: - 'Published version to export. Omit to export the current working draft; ' + + "Published version to export. Omit to export the document's current " + + 'published version (or the working draft if it has never been published); ' + 'provide a version id to download exactly what was approved at that version.', }) @IsOptional() diff --git a/apps/api/src/isms/isms.service.spec.ts b/apps/api/src/isms/isms.service.spec.ts index af4c4e5d0..7d801200f 100644 --- a/apps/api/src/isms/isms.service.spec.ts +++ b/apps/api/src/isms/isms.service.spec.ts @@ -89,6 +89,29 @@ describe('IsmsService ensureSetup', () => { expect(mockDb.ismsDocument.findMany).toHaveBeenCalledTimes(1); expect(result.documents).toHaveLength(1); }); + + it('reports hasApprovedVersion from a published version OR an approved status', async () => { + (mockDb.frameworkEditorFramework.findUnique as jest.Mock).mockResolvedValue({ + id: 'fw_1', + requirements: [], + }); + (mockDb.ismsDocument.findMany as jest.Mock).mockResolvedValueOnce([ + // Published version exists. + { id: 'd1', type: 'isms_scope', status: 'draft', requirementId: null, currentVersionId: 'isms_ver_1' }, + // Approved before versioning existed: no version row, but still approved. + { id: 'd2', type: 'leadership_commitment', status: 'approved', requirementId: null, currentVersionId: null }, + // Never approved. + { id: 'd3', type: 'objectives_plan', status: 'draft', requirementId: null, currentVersionId: null }, + ]); + + const result = await service.ensureSetup({ ...dto, canWrite: false }); + + expect(result.documents.map((d) => d.hasApprovedVersion)).toEqual([ + true, + true, + false, + ]); + }); }); describe('template-driven (templates seeded)', () => { diff --git a/apps/api/src/isms/isms.service.ts b/apps/api/src/isms/isms.service.ts index 15dbc1974..e0a73497f 100644 --- a/apps/api/src/isms/isms.service.ts +++ b/apps/api/src/isms/isms.service.ts @@ -65,9 +65,13 @@ export class IsmsService { type: doc.type, status: doc.status, requirementId: doc.requirementId, - // A published version exists (the document has been approved at least - // once) — independent of whether the draft has since been edited. - hasApprovedVersion: doc.currentVersionId != null, + // The document has an approved artifact: either a published version row + // (approved under the versioning model) or `status === 'approved'` — the + // latter covers documents approved before versioning existed, whose legacy + // version rows were dropped by the migration and which capture a real + // versioned artifact on their next approval. + hasApprovedVersion: + doc.currentVersionId != null || doc.status === 'approved', })), }; } diff --git a/apps/app/src/app/(app)/[orgId]/documents/isms/components/IsmsApprovalSection.tsx b/apps/app/src/app/(app)/[orgId]/documents/isms/components/IsmsApprovalSection.tsx index d5bc90fd5..37ba6d724 100644 --- a/apps/app/src/app/(app)/[orgId]/documents/isms/components/IsmsApprovalSection.tsx +++ b/apps/app/src/app/(app)/[orgId]/documents/isms/components/IsmsApprovalSection.tsx @@ -128,8 +128,8 @@ export function IsmsApprovalSection({ v{publishedVersion} {' '} - stays live and exportable. Your changes are an in-progress draft (v - {nextDraftVersion}) — submit it for approval to publish. + stays live and exportable. Your changes are an in-progress draft{' '} + {`(v${nextDraftVersion})`} — submit it for approval to publish. )} diff --git a/apps/app/src/app/(app)/[orgId]/documents/isms/components/ScopeClient.tsx b/apps/app/src/app/(app)/[orgId]/documents/isms/components/ScopeClient.tsx index f3b006621..391391dd1 100644 --- a/apps/app/src/app/(app)/[orgId]/documents/isms/components/ScopeClient.tsx +++ b/apps/app/src/app/(app)/[orgId]/documents/isms/components/ScopeClient.tsx @@ -64,10 +64,17 @@ export function ScopeClient(props: ScopeClientProps) { }; const narrative = toScopeNarrative(document.draftNarrative); + // Re-seed the form (via remount) whenever the persisted draft content + // changes — e.g. after "Generate from platform data" or a reload — not + // just when the published version changes. ScopeForm reads its defaults + // at mount, so the key must reflect the draft, not currentVersionId alone. + const formKey = `${document.currentVersionId ?? 'draft'}:${JSON.stringify( + document.draftNarrative ?? {}, + )}`; return (