Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/api/src/isms/dto/export-isms-document.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
23 changes: 23 additions & 0 deletions apps/api/src/isms/isms.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)', () => {
Expand Down
10 changes: 7 additions & 3 deletions apps/api/src/isms/isms.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
})),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ export function IsmsApprovalSection({
<Text as="span" size="sm" weight="medium">
v{publishedVersion}
</Text>{' '}
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.
</AlertDescription>
</Alert>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<ScopeForm
key={document.currentVersionId ?? 'draft'}
key={formKey}
narrative={narrative}
canEdit={canManage}
onSave={handleSaveNarrative}
Expand Down
Loading