690329:2209 Fixing refactor Correspondence GPT-5.3-Codex #04
This commit is contained in:
BIN
Binary file not shown.
+612
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+898
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+620
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+612
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+620
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+898
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
+1
-1
@@ -1 +1 @@
|
|||||||
{"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\due-date-reminder.service.spec.ts":[1,1598],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.spec.ts":[1,7578],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.service.spec.ts":[1,1526],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.service.spec.ts":[1,1474],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.spec.ts":[1,921],"E:\\np-dms\\lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.spec.ts":[1,1166],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.spec.ts":[1,1175],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.spec.ts":[1,4448],"E:\\np-dms\\lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.spec.ts":[1,369],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\user\\user.service.spec.ts":[1,1074],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.spec.ts":[1,1277],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.spec.ts":[1,8589],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.service.spec.ts":[1,1251],"E:\\np-dms\\lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.spec.ts":[1,460],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.service.spec.ts":[1,1043],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.controller.spec.ts":[1,2047],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.spec.ts":[1,936],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.controller.spec.ts":[1,1666],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.controller.spec.ts":[1,6122],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.spec.ts":[1,1859],"E:\\np-dms\\lcbp3\\backend\\src\\app.controller.spec.ts":[1,564],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.spec.ts":[1,3140]}
|
{"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\due-date-reminder.service.spec.ts":[1,1598],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.service.spec.ts":[1,5627],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.service.spec.ts":[1,1526],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\document-numbering.service.spec.ts":[1,1474],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\casl\\ability.factory.spec.ts":[1,921],"E:\\np-dms\\lcbp3\\backend\\src\\common\\services\\uuid-resolver.service.spec.ts":[1,1166],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\workflow-engine.service.spec.ts":[1,1175],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\workflow-engine\\dsl\\parser.service.spec.ts":[1,4448],"E:\\np-dms\\lcbp3\\backend\\src\\common\\pipes\\parse-uuid.pipe.spec.ts":[1,369],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\user\\user.service.spec.ts":[1,1074],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.service.spec.ts":[1,1277],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\correspondence\\correspondence.controller.spec.ts":[1,7855],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.service.spec.ts":[1,1251],"E:\\np-dms\\lcbp3\\backend\\src\\common\\entities\\uuid-base.entity.spec.ts":[1,460],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.service.spec.ts":[1,1043],"E:\\np-dms\\lcbp3\\backend\\src\\common\\auth\\auth.controller.spec.ts":[1,2047],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\document-numbering\\services\\manual-override.service.spec.ts":[1,936],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\project\\project.controller.spec.ts":[1,1666],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\migration\\migration.controller.spec.ts":[1,6122],"E:\\np-dms\\lcbp3\\backend\\src\\common\\file-storage\\file-storage.controller.spec.ts":[1,1859],"E:\\np-dms\\lcbp3\\backend\\src\\app.controller.spec.ts":[1,564],"E:\\np-dms\\lcbp3\\backend\\src\\modules\\json-schema\\json-schema.controller.spec.ts":[1,3140]}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { DataSource, Repository } from 'typeorm';
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { ForbiddenException } from '@nestjs/common';
|
||||||
import { CorrespondenceService } from './correspondence.service';
|
import { CorrespondenceService } from './correspondence.service';
|
||||||
import { Correspondence } from './entities/correspondence.entity';
|
import { Correspondence } from './entities/correspondence.entity';
|
||||||
import { CorrespondenceRevision } from './entities/correspondence-revision.entity';
|
import { CorrespondenceRevision } from './entities/correspondence-revision.entity';
|
||||||
@@ -173,6 +174,83 @@ describe('CorrespondenceService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
|
it('should allow non-draft update for org-admin+ permissions', async () => {
|
||||||
|
const mockUser = {
|
||||||
|
user_id: 1,
|
||||||
|
primaryOrganizationId: 10,
|
||||||
|
} as unknown as User;
|
||||||
|
const mockRevision = {
|
||||||
|
id: 100,
|
||||||
|
correspondenceId: 1,
|
||||||
|
isCurrent: true,
|
||||||
|
statusId: 23,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(revisionRepo, 'findOne')
|
||||||
|
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||||
|
|
||||||
|
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
|
||||||
|
getRepositoryToken(CorrespondenceStatus)
|
||||||
|
);
|
||||||
|
(statusRepo.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
id: 23,
|
||||||
|
statusCode: 'SUBOWN',
|
||||||
|
});
|
||||||
|
|
||||||
|
const userService = testingModule.get<UserService>(UserService);
|
||||||
|
(userService.getUserPermissions as jest.Mock).mockResolvedValue([
|
||||||
|
'correspondence.cancel',
|
||||||
|
]);
|
||||||
|
|
||||||
|
jest.spyOn(correspondenceRepo, 'findOne').mockResolvedValue({
|
||||||
|
id: 1,
|
||||||
|
publicId: 'corr-uuid-1',
|
||||||
|
correspondenceNumber: 'CORR-001',
|
||||||
|
projectId: 1,
|
||||||
|
createdAt: new Date(),
|
||||||
|
revisions: [],
|
||||||
|
} as unknown as Correspondence);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.update(1, { subject: 'Updated Subject' }, mockUser)
|
||||||
|
).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject non-draft update for non-admin permissions', async () => {
|
||||||
|
const mockUser = {
|
||||||
|
user_id: 2,
|
||||||
|
primaryOrganizationId: 10,
|
||||||
|
} as unknown as User;
|
||||||
|
const mockRevision = {
|
||||||
|
id: 101,
|
||||||
|
correspondenceId: 2,
|
||||||
|
isCurrent: true,
|
||||||
|
statusId: 23,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(revisionRepo, 'findOne')
|
||||||
|
.mockResolvedValue(mockRevision as unknown as CorrespondenceRevision);
|
||||||
|
|
||||||
|
const statusRepo = testingModule.get<Repository<CorrespondenceStatus>>(
|
||||||
|
getRepositoryToken(CorrespondenceStatus)
|
||||||
|
);
|
||||||
|
(statusRepo.findOne as jest.Mock).mockResolvedValue({
|
||||||
|
id: 23,
|
||||||
|
statusCode: 'SUBOWN',
|
||||||
|
});
|
||||||
|
|
||||||
|
const userService = testingModule.get<UserService>(UserService);
|
||||||
|
(userService.getUserPermissions as jest.Mock).mockResolvedValue([
|
||||||
|
'correspondence.edit',
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.update(2, { subject: 'Should Fail' }, mockUser)
|
||||||
|
).rejects.toThrow(ForbiddenException);
|
||||||
|
});
|
||||||
|
|
||||||
it('should NOT regenerate number if critical fields unchanged', async () => {
|
it('should NOT regenerate number if critical fields unchanged', async () => {
|
||||||
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
const mockUser = { id: 1, primaryOrganizationId: 10 } as unknown as User;
|
||||||
const mockRevision = {
|
const mockRevision = {
|
||||||
|
|||||||
@@ -631,8 +631,20 @@ export class CorrespondenceService {
|
|||||||
const status = await this.statusRepo.findOne({
|
const status = await this.statusRepo.findOne({
|
||||||
where: { id: revision.statusId },
|
where: { id: revision.statusId },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (status && status.statusCode !== 'DRAFT') {
|
if (status && status.statusCode !== 'DRAFT') {
|
||||||
throw new BadRequestException('Only DRAFT documents can be updated');
|
const permissions = await this.userService.getUserPermissions(
|
||||||
|
user.user_id
|
||||||
|
);
|
||||||
|
const canEditSubmittedOrLater =
|
||||||
|
permissions.includes('correspondence.cancel') ||
|
||||||
|
permissions.includes('system.manage_all');
|
||||||
|
|
||||||
|
if (!canEditSubmittedOrLater) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
'Only Org Admin or Superadmin can edit non-draft correspondences'
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
import { CorrespondenceForm } from '@/components/correspondences/form';
|
import { CorrespondenceForm } from '@/components/correspondences/form';
|
||||||
import { useCorrespondence } from '@/hooks/use-correspondence';
|
import { useCorrespondence } from '@/hooks/use-correspondence';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
export default function EditCorrespondencePage() {
|
export default function EditCorrespondencePage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const uuid = (params?.uuid as string) ?? '';
|
const uuid = (params?.uuid as string) ?? '';
|
||||||
|
const selectedRevisionId = searchParams.get('revId') ?? undefined;
|
||||||
|
|
||||||
const { data: correspondence, isLoading, isError } = useCorrespondence(uuid);
|
const { data: correspondence, isLoading, isError } = useCorrespondence(uuid);
|
||||||
|
|
||||||
@@ -46,7 +48,7 @@ export default function EditCorrespondencePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-card border rounded-lg p-6 shadow-sm">
|
<div className="bg-card border rounded-lg p-6 shadow-sm">
|
||||||
<CorrespondenceForm initialData={correspondence} uuid={uuid} />
|
<CorrespondenceForm initialData={correspondence} uuid={uuid} selectedRevisionId={selectedRevisionId} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,11 +3,13 @@
|
|||||||
import { CorrespondenceDetail } from '@/components/correspondences/detail';
|
import { CorrespondenceDetail } from '@/components/correspondences/detail';
|
||||||
import { useCorrespondence } from '@/hooks/use-correspondence';
|
import { useCorrespondence } from '@/hooks/use-correspondence';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
import { useParams } from 'next/navigation';
|
import { useParams, useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
export default function CorrespondenceDetailPage() {
|
export default function CorrespondenceDetailPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const uuid = (params?.uuid as string) ?? '';
|
const uuid = (params?.uuid as string) ?? '';
|
||||||
|
const selectedRevisionId = searchParams.get('revId') ?? undefined;
|
||||||
|
|
||||||
const { data: correspondence, isLoading, isError } = useCorrespondence(uuid);
|
const { data: correspondence, isLoading, isError } = useCorrespondence(uuid);
|
||||||
|
|
||||||
@@ -36,5 +38,5 @@ export default function CorrespondenceDetailPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CorrespondenceDetail data={correspondence} />;
|
return <CorrespondenceDetail data={correspondence} selectedRevisionId={selectedRevisionId} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,26 +22,39 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
|
|
||||||
interface CorrespondenceDetailProps {
|
interface CorrespondenceDetailProps {
|
||||||
data: Correspondence;
|
data: Correspondence;
|
||||||
|
selectedRevisionId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CorrespondenceDetail({ data }: CorrespondenceDetailProps) {
|
export function CorrespondenceDetail({ data, selectedRevisionId }: CorrespondenceDetailProps) {
|
||||||
const submitMutation = useSubmitCorrespondence();
|
const submitMutation = useSubmitCorrespondence();
|
||||||
const processMutation = useProcessWorkflow();
|
const processMutation = useProcessWorkflow();
|
||||||
const cancelMutation = useCancelCorrespondence();
|
const cancelMutation = useCancelCorrespondence();
|
||||||
const { hasPermission } = useAuthStore();
|
const { user, hasPermission } = useAuthStore();
|
||||||
const [actionState, setActionState] = useState<'approve' | 'reject' | 'cancel' | null>(null);
|
const [actionState, setActionState] = useState<'approve' | 'reject' | 'cancel' | null>(null);
|
||||||
const [comments, setComments] = useState('');
|
const [comments, setComments] = useState('');
|
||||||
const [cancelReason, setCancelReason] = useState('');
|
const [cancelReason, setCancelReason] = useState('');
|
||||||
|
|
||||||
if (!data) return <div>No data found</div>;
|
if (!data) return <div>No data found</div>;
|
||||||
|
|
||||||
const currentRevision = data.revisions?.find((r) => r.isCurrent) || data.revisions?.[0];
|
const selectedRevision = selectedRevisionId
|
||||||
|
? data.revisions?.find((r) => r.publicId === selectedRevisionId)
|
||||||
|
: undefined;
|
||||||
|
const currentRevision = selectedRevision || data.revisions?.find((r) => r.isCurrent) || data.revisions?.[0];
|
||||||
const subject = currentRevision?.subject || '-';
|
const subject = currentRevision?.subject || '-';
|
||||||
const description = currentRevision?.description || '-';
|
const description = currentRevision?.description || '-';
|
||||||
const status = currentRevision?.status?.statusCode || 'UNKNOWN';
|
const status = currentRevision?.status?.statusCode || 'UNKNOWN';
|
||||||
const attachments = currentRevision?.attachments || [];
|
const attachments = currentRevision?.attachments || [];
|
||||||
const importance = (currentRevision?.details?.importance as string) || 'NORMAL';
|
const importance = (currentRevision?.details?.importance as string) || 'NORMAL';
|
||||||
const canEditMetadata = hasPermission('correspondence.edit');
|
const canEditMetadata = hasPermission('correspondence.edit');
|
||||||
|
const privilegedEditableStatuses = ['SUBCSC', 'SUBOWN', 'IN_REVIEW_CSC'];
|
||||||
|
const normalizedRole = (user?.role || '').toUpperCase().replace(/\s+/g, '_');
|
||||||
|
const isPrivilegedEditRole = ['SUPERADMIN', 'SUPER_ADMIN', 'ADMIN', 'DC', 'DOCUMENT_CONTROL'].includes(
|
||||||
|
normalizedRole
|
||||||
|
);
|
||||||
|
const canEditInStatus =
|
||||||
|
status === 'DRAFT' ||
|
||||||
|
(privilegedEditableStatuses.includes(status) && isPrivilegedEditRole);
|
||||||
|
const canEditDocument = canEditInStatus && (hasPermission('correspondence.edit') || isPrivilegedEditRole);
|
||||||
|
|
||||||
const toRecipients = data.recipients?.filter((r) => r.recipientType === 'TO') || [];
|
const toRecipients = data.recipients?.filter((r) => r.recipientType === 'TO') || [];
|
||||||
const ccRecipients = data.recipients?.filter((r) => r.recipientType === 'CC') || [];
|
const ccRecipients = data.recipients?.filter((r) => r.recipientType === 'CC') || [];
|
||||||
@@ -100,15 +113,13 @@ export function CorrespondenceDetail({ data }: CorrespondenceDetailProps) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
{status === 'DRAFT' && (
|
{canEditDocument && (
|
||||||
<Can permission="correspondence.edit">
|
<Link href={`/correspondences/${data.publicId}/edit${selectedRevisionId ? `?revId=${selectedRevisionId}` : ''}`}>
|
||||||
<Link href={`/correspondences/${data.publicId}/edit`}>
|
<Button variant="outline">
|
||||||
<Button variant="outline">
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
Edit
|
||||||
Edit
|
</Button>
|
||||||
</Button>
|
</Link>
|
||||||
</Link>
|
|
||||||
</Can>
|
|
||||||
)}
|
)}
|
||||||
{status === 'DRAFT' && (
|
{status === 'DRAFT' && (
|
||||||
<Can permission="correspondence.submit">
|
<Can permission="correspondence.submit">
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ interface InitialCorrespondenceData {
|
|||||||
correspondenceTypeId?: number;
|
correspondenceTypeId?: number;
|
||||||
disciplineId?: number;
|
disciplineId?: number;
|
||||||
revisions?: Array<{
|
revisions?: Array<{
|
||||||
|
publicId?: string;
|
||||||
isCurrent?: boolean;
|
isCurrent?: boolean;
|
||||||
subject?: string;
|
subject?: string;
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -124,7 +125,15 @@ const normalizePublicId = (value: unknown): string | undefined => {
|
|||||||
return trimmed.length > 0 ? trimmed : undefined;
|
return trimmed.length > 0 ? trimmed : undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CorrespondenceForm({ initialData, uuid }: { initialData?: InitialCorrespondenceData; uuid?: string }) {
|
export function CorrespondenceForm({
|
||||||
|
initialData,
|
||||||
|
uuid,
|
||||||
|
selectedRevisionId,
|
||||||
|
}: {
|
||||||
|
initialData?: InitialCorrespondenceData;
|
||||||
|
uuid?: string;
|
||||||
|
selectedRevisionId?: string;
|
||||||
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const createMutation = useCreateCorrespondence();
|
const createMutation = useCreateCorrespondence();
|
||||||
const updateMutation = useUpdateCorrespondence();
|
const updateMutation = useUpdateCorrespondence();
|
||||||
@@ -138,7 +147,10 @@ export function CorrespondenceForm({ initialData, uuid }: { initialData?: Initia
|
|||||||
const correspondenceTypes = extractArrayData<CorrespondenceTypeOption>(correspondenceTypesData);
|
const correspondenceTypes = extractArrayData<CorrespondenceTypeOption>(correspondenceTypesData);
|
||||||
|
|
||||||
// Extract initial values if editing
|
// Extract initial values if editing
|
||||||
const currentRev = initialData?.revisions?.find((r) => r.isCurrent) || initialData?.revisions?.[0];
|
const selectedRevision = selectedRevisionId
|
||||||
|
? initialData?.revisions?.find((r) => normalizePublicId(r.publicId) === selectedRevisionId)
|
||||||
|
: undefined;
|
||||||
|
const currentRev = selectedRevision || initialData?.revisions?.find((r) => r.isCurrent) || initialData?.revisions?.[0];
|
||||||
const initialToRecipient = initialData?.recipients?.find((r) => r.recipientType === 'TO');
|
const initialToRecipient = initialData?.recipients?.find((r) => r.recipientType === 'TO');
|
||||||
const initialCcRecipientIds =
|
const initialCcRecipientIds =
|
||||||
initialData?.recipients
|
initialData?.recipients
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ import { Button } from '@/components/ui/button';
|
|||||||
import { Eye, Edit } from 'lucide-react';
|
import { Eye, Edit } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
|
import { useAuthStore } from '@/lib/stores/auth-store';
|
||||||
|
|
||||||
interface CorrespondenceListProps {
|
interface CorrespondenceListProps {
|
||||||
data: CorrespondenceRevision[];
|
data: CorrespondenceRevision[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CorrespondenceList({ data }: CorrespondenceListProps) {
|
export function CorrespondenceList({ data }: CorrespondenceListProps) {
|
||||||
|
const { user, hasPermission } = useAuthStore();
|
||||||
|
const privilegedEditableStatuses = ['SUBCSC', 'SUBOWN', 'IN_REVIEW_CSC'];
|
||||||
|
|
||||||
const columns: ColumnDef<CorrespondenceRevision>[] = [
|
const columns: ColumnDef<CorrespondenceRevision>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: 'correspondence.correspondenceNumber',
|
accessorKey: 'correspondence.correspondenceNumber',
|
||||||
@@ -90,18 +94,33 @@ export function CorrespondenceList({ data }: CorrespondenceListProps) {
|
|||||||
// Edit/View link goes to the DOCUMENT detail (correspondence.publicId)
|
// Edit/View link goes to the DOCUMENT detail (correspondence.publicId)
|
||||||
// Ideally we might pass ?revId=item.publicId to view specific revision, but detail page defaults to latest.
|
// Ideally we might pass ?revId=item.publicId to view specific revision, but detail page defaults to latest.
|
||||||
// For editing, we edit the document.
|
// For editing, we edit the document.
|
||||||
const docUuid = item.correspondence.publicId;
|
const docUuid = item.correspondence?.publicId;
|
||||||
|
const revId = item.publicId;
|
||||||
const statusCode = item.status?.statusCode;
|
const statusCode = item.status?.statusCode;
|
||||||
|
const normalizedRole = (user?.role || '').toUpperCase().replace(/\s+/g, '_');
|
||||||
|
const isPrivilegedEditRole = ['SUPERADMIN', 'SUPER_ADMIN', 'ADMIN', 'DC', 'DOCUMENT_CONTROL'].includes(
|
||||||
|
normalizedRole
|
||||||
|
);
|
||||||
|
const canEditInStatus =
|
||||||
|
statusCode === 'DRAFT' ||
|
||||||
|
(typeof statusCode === 'string' &&
|
||||||
|
privilegedEditableStatuses.includes(statusCode) &&
|
||||||
|
isPrivilegedEditRole);
|
||||||
|
const canEdit = canEditInStatus && (hasPermission('correspondence.edit') || isPrivilegedEditRole);
|
||||||
|
|
||||||
|
if (!docUuid) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Link href={`/correspondences/${docUuid}`}>
|
<Link href={`/correspondences/${docUuid}?revId=${revId}`}>
|
||||||
<Button variant="ghost" size="icon" title="View Details">
|
<Button variant="ghost" size="icon" title="View Details">
|
||||||
<Eye className="h-4 w-4" />
|
<Eye className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
{statusCode === 'DRAFT' && (
|
{canEdit && (
|
||||||
<Link href={`/correspondences/${docUuid}/edit`}>
|
<Link href={`/correspondences/${docUuid}/edit?revId=${revId}`}>
|
||||||
<Button variant="ghost" size="icon" title="Edit">
|
<Button variant="ghost" size="icon" title="Edit">
|
||||||
<Edit className="h-4 w-4" />
|
<Edit className="h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user