diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/models.test.ts b/source/frontend/src/features/mapSideBar/acquisition/add/models.test.ts index 4782e47c17..33f2d6ab10 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/models.test.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/add/models.test.ts @@ -201,5 +201,43 @@ describe('Create acquisition model tests', () => { } as ApiGen_Concepts_AcquisitionFileTeam), ); }); + + it('omits notice of claim when date and comment are empty strings', () => { + const model = new AcquisitionForm(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: '', + receivedDate: '', + rowVersion: null, + }; + + const apiAcquisitionFile = model.toApi(); + + expect(apiAcquisitionFile.noticeOfClaim).toEqual([]); + }); + + it('normalizes notice of claim values before sending to api', () => { + const model = new AcquisitionForm(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: ' Test comment ', + receivedDate: '', + rowVersion: null, + }; + + const apiAcquisitionFile = model.toApi(); + + expect(apiAcquisitionFile.noticeOfClaim).toHaveLength(1); + expect(apiAcquisitionFile.noticeOfClaim?.[0]).toEqual( + expect.objectContaining({ + comment: 'Test comment', + receivedDate: null, + }), + ); + }); }); }); diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts index 79de49e13d..578c87734b 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts @@ -14,7 +14,7 @@ import { stringToNumberOrNull, toTypeCodeNullable, } from '@/utils/formUtils'; -import { exists, isValidId, isValidIsoDateTime } from '@/utils/utils'; +import { exists, isValidId, isValidIsoDateTime, isValidString } from '@/utils/utils'; import { PropertyForm } from '../../shared/models'; import { ChecklistItemFormModel } from '../../shared/tabs/checklist/update/models'; @@ -81,6 +81,19 @@ export class AcquisitionForm implements WithAcquisitionTeam, WithAcquisitionOwne toApi(): ApiGen_Concepts_AcquisitionFile { const fileProperties = this.properties.map(x => this.toPropertyApi(x)); const sortedProperties = applyDisplayOrder(fileProperties); + const noticeOfClaim: ApiGen_Concepts_NoticeOfClaim | null = this.noticeOfClaim + ? { + ...this.noticeOfClaim, + receivedDate: isValidString(this.noticeOfClaim?.receivedDate) + ? this.noticeOfClaim.receivedDate + : null, + comment: isValidString(this.noticeOfClaim?.comment?.trim()) + ? this.noticeOfClaim.comment.trim() + : null, + } + : null; + const hasNoticeOfClaim = + isValidString(noticeOfClaim?.receivedDate) || isValidString(noticeOfClaim?.comment); return { id: this.id ?? 0, @@ -136,7 +149,7 @@ export class AcquisitionForm implements WithAcquisitionTeam, WithAcquisitionOwne legacyStakeholders: null, product: null, project: null, - noticeOfClaim: exists(this.noticeOfClaim) ? [this.noticeOfClaim] : [], + noticeOfClaim: hasNoticeOfClaim && noticeOfClaim ? [noticeOfClaim] : [], ...getEmptyBaseAudit(this.rowVersion), }; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionFileYupSchema.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionFileYupSchema.ts index 7ef81468ef..344a2aff86 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionFileYupSchema.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionFileYupSchema.ts @@ -42,7 +42,10 @@ export const UpdateAcquisitionFileYupSchema = yup .nullable() .max(2000, 'Physical file details must be at most ${max} characters'), noticeOfClaim: yup.object().shape({ - comment: yup.string().max(4000, 'Notice of claim comment must be at most ${max} characters'), + comment: yup + .string() + .nullable() + .max(4000, 'Notice of claim comment must be at most ${max} characters'), }), }) .concat(UpdateAcquisitionTeamYupSchema) diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.test.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.test.ts new file mode 100644 index 0000000000..7ccaa99da7 --- /dev/null +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.test.ts @@ -0,0 +1,43 @@ +import { UpdateAcquisitionSummaryFormModel } from './models'; + +describe('UpdateAcquisitionSummaryFormModel', () => { + describe('toApi noticeOfClaim handling', () => { + it('omits noticeOfClaim when date and comment are empty', () => { + const model = new UpdateAcquisitionSummaryFormModel(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: '', + receivedDate: '', + rowVersion: null, + }; + + const apiModel = model.toApi(); + + expect(apiModel.noticeOfClaim).toEqual([]); + }); + + it('normalizes empty date to null and trims comment', () => { + const model = new UpdateAcquisitionSummaryFormModel(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: ' file update comment ', + receivedDate: '', + rowVersion: null, + }; + + const apiModel = model.toApi(); + + expect(apiModel.noticeOfClaim).toHaveLength(1); + expect(apiModel.noticeOfClaim?.[0]).toEqual( + expect.objectContaining({ + comment: 'file update comment', + receivedDate: null, + }), + ); + }); + }); +}); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.ts index a5039bd80c..e80f43b15b 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/models.ts @@ -9,7 +9,7 @@ import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Co import { ApiGen_Concepts_NoticeOfClaim } from '@/models/api/generated/ApiGen_Concepts_NoticeOfClaim'; import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { fromTypeCode, fromTypeCodeNullable, toTypeCodeNullable } from '@/utils/formUtils'; -import { exists, isValidId, isValidIsoDateTime } from '@/utils/utils'; +import { exists, isValidId, isValidIsoDateTime, isValidString } from '@/utils/utils'; import { AcquisitionOwnerFormModel, @@ -79,6 +79,20 @@ export class UpdateAcquisitionSummaryFormModel noticeOfClaim: ApiGen_Concepts_NoticeOfClaim; toApi(): ApiGen_Concepts_AcquisitionFile { + const noticeOfClaim: ApiGen_Concepts_NoticeOfClaim | null = this.noticeOfClaim + ? { + ...this.noticeOfClaim, + receivedDate: isValidString(this.noticeOfClaim?.receivedDate) + ? this.noticeOfClaim.receivedDate + : null, + comment: isValidString(this.noticeOfClaim?.comment?.trim()) + ? this.noticeOfClaim.comment.trim() + : null, + } + : null; + const hasNoticeOfClaim = + isValidString(noticeOfClaim?.receivedDate) || isValidString(noticeOfClaim?.comment); + return { id: this.id || 0, parentAcquisitionFileId: isValidId(this.parentAcquisitionFileId) @@ -143,7 +157,7 @@ export class UpdateAcquisitionSummaryFormModel product: null, project: null, totalAllowableCompensation: null, - noticeOfClaim: exists(this.noticeOfClaim) ? [this.noticeOfClaim] : [], + noticeOfClaim: hasNoticeOfClaim && noticeOfClaim ? [noticeOfClaim] : [], ...getEmptyBaseAudit(this.rowVersion), }; } diff --git a/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.test.ts b/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.test.ts new file mode 100644 index 0000000000..9428048fc8 --- /dev/null +++ b/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.test.ts @@ -0,0 +1,65 @@ +import { ManagementFormModel } from './ManagementFormModel'; + +describe('ManagementFormModel', () => { + describe('toApi noticeOfClaim handling', () => { + it('omits noticeOfClaim when date and comment are empty', () => { + const model = new ManagementFormModel(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: '', + receivedDate: '', + rowVersion: null, + }; + + const apiModel = model.toApi(); + + expect(apiModel.noticeOfClaim).toEqual([]); + }); + + it('normalizes empty date to null and trims comment', () => { + const model = new ManagementFormModel(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: ' comment from formik ', + receivedDate: '', + rowVersion: null, + }; + + const apiModel = model.toApi(); + + expect(apiModel.noticeOfClaim).toHaveLength(1); + expect(apiModel.noticeOfClaim?.[0]).toEqual( + expect.objectContaining({ + comment: 'comment from formik', + receivedDate: null, + }), + ); + }); + + it('keeps receivedDate when provided and normalizes empty comment', () => { + const model = new ManagementFormModel(); + model.noticeOfClaim = { + id: null, + acquisitionFileId: null, + managementFileId: null, + comment: ' ', + receivedDate: '2026-04-17', + rowVersion: null, + }; + + const apiModel = model.toApi(); + + expect(apiModel.noticeOfClaim).toHaveLength(1); + expect(apiModel.noticeOfClaim?.[0]).toEqual( + expect.objectContaining({ + comment: null, + receivedDate: '2026-04-17', + }), + ); + }); + }); +}); diff --git a/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.ts b/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.ts index 2bfc9e0c79..6bbe11348d 100644 --- a/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.ts +++ b/source/frontend/src/features/mapSideBar/management/models/ManagementFormModel.ts @@ -12,7 +12,7 @@ import { ApiGen_Concepts_NoticeOfClaim } from '@/models/api/generated/ApiGen_Con import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { applyDisplayOrder } from '@/utils'; import { fromTypeCode, toNullableId, toTypeCodeNullable } from '@/utils/formUtils'; -import { exists, isValidId } from '@/utils/utils'; +import { exists, isValidId, isValidString } from '@/utils/utils'; import { PropertyForm } from '../../shared/models'; import { ManagementTeamSubFormModel, WithManagementTeam } from './ManagementTeamSubFormModel'; @@ -50,6 +50,19 @@ export class ManagementFormModel implements WithManagementTeam { const sortedProperties = applyDisplayOrder(fileProperties); const personId = this.responsiblePayer?.personId ?? null; const organizationId = !personId ? this.responsiblePayer?.organizationId ?? null : null; + const noticeOfClaim: ApiGen_Concepts_NoticeOfClaim | null = this.noticeOfClaim + ? { + ...this.noticeOfClaim, + receivedDate: isValidString(this.noticeOfClaim?.receivedDate) + ? this.noticeOfClaim.receivedDate + : null, + comment: isValidString(this.noticeOfClaim?.comment?.trim()) + ? this.noticeOfClaim.comment.trim() + : null, + } + : null; + const hasNoticeOfClaim = + isValidString(noticeOfClaim?.receivedDate) || isValidString(noticeOfClaim?.comment); return { id: this.id ?? 0, @@ -81,7 +94,8 @@ export class ManagementFormModel implements WithManagementTeam { .filter(exists), fileProperties: sortedProperties ?? [], ...getEmptyBaseAudit(this.rowVersion), - noticeOfClaim: exists(this.noticeOfClaim) ? [this.noticeOfClaim] : [], + noticeOfClaim: hasNoticeOfClaim && noticeOfClaim ? [noticeOfClaim] : [], + ...getEmptyBaseAudit(this.rowVersion), }; }