From 24d05c0daf7c5b6b25674bb32f4c0313d5b836e6 Mon Sep 17 00:00:00 2001 From: Manuel Rodriguez Date: Wed, 31 Jan 2024 15:11:14 -0800 Subject: [PATCH 01/11] Tech debt| use new api files and null enhancements (#3739) * Refactoring of modals and changes on Smoke Test Automation Test Cases * Automation refactoring - modals, updating fields on Compensation, Digital Docs for Property Management * Refactoring modals * Automation refactoring - modals, updating fields on Compensation, Digital Docs for Property Management * Refactoring modals * Automation updates * Automation updates * Contacts elements update * Removal of old frontend api files * Improved checklist Api and backend services * Moved association models to the api project and added code types to be shared with the frontend * minor - backend import updates * major - fixed backend file models inheritance missalingments * major - fixed lease missing fields and improper inheritance * major - fixed backend typos * major - utilty method changes * minor - updated type references. This commit can be skimed as it should not contain logic changes * major - type updates and nullable conversions on form models * major - type changes with possible logic implications. isValidId changes. * minor - type code alignments and cleanup * major - more type changes. Removal of adjacent land form model * minor - test and mock updates * generation code changesa * manual merge * minor - added nullable property to controllers that use nullable models * major - lease fixes and removal of adjacent land code * major - improved handling and validation of ID related values and dates. Now 0 and EpochIsoDate are considered invalid id and invalid date respectively * major - test updates and regenerated snaps * major - lease fixes around address * minor - workspace change to force vscode to use the ts compiler specified in the package.json file * major - fixed backend references and cyclical parsing due to the file model inheritance * major - fixed disposition * PSP-7509 : Allow Contractor to Create or Edit Disposition File (#3735) Co-authored-by: Eduardo Herrera * CI: Bump version to v5.0.0-72.14 * Disposition File details, documents and notes. Refactoring on File Properties * PSP-7510 : Disallow Contractor to Remove Themself from Disposition File (#3745) Co-authored-by: Eduardo Herrera * CI: Bump version to v5.0.0-72.15 * correct disposition list view file number. (#3736) * CI: Bump version to v5.0.0-72.16 * Offers and sales tab automation * disposition offers and sales tab * Psp 7614, 7657, 7626, 7610 assorted text corrections. (#3737) * text corrections. * correct title text. * psp-7657 fix modal buttons. * test updates. * lint fixes. * CI: Bump version to v5.0.0-72.17 * CI: Bump version to v5.0.0-72.18 * Refactoring and Disposition file automation * Merge conflicts between local and remote * Disposition files - Offers and sale create and update * Deleting AcquisitionProperties class, replaced by SharedFileProperties * Text changes (#3752) * CI: Bump version to v5.0.0-72.19 * correct message text. (#3749) * CI: Bump version to v5.0.0-72.20 * Refactoring LeaseTenants class * CI: Bump version to v5.0.0-72.21 * Manual merge fixes * Addressed pr comments * fixed date formatting --------- Co-authored-by: Sue Tairaku Co-authored-by: Sue Tairaku <42981334+stairaku@users.noreply.github.com> Co-authored-by: Alejandro Sanchez Co-authored-by: Eduardo Co-authored-by: Eduardo Herrera Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: devinleighsmith <41091511+devinleighsmith@users.noreply.github.com> Co-authored-by: praveen Co-authored-by: pravkuma14 <133119915+pravkuma14@users.noreply.github.com> --- .../Controllers/AcquisitionFileController.cs | 10 + .../Controllers/AgreementController.cs | 2 + .../Controllers/ChecklistController.cs | 21 +- .../Controllers/SearchController.cs | 3 + .../Controllers/AccessRequestController.cs | 4 + .../Admin/Controllers/ClaimController.cs | 2 + .../Controllers/FinancialCodeController.cs | 5 + .../Areas/Admin/Controllers/RoleController.cs | 3 + .../Areas/Admin/Controllers/UserController.cs | 10 +- .../CompensationRequisitionController.cs | 2 + .../Controllers/H120CategoryController.cs | 2 + .../Controllers/ChecklistController.cs | 19 +- .../Controllers/DispositionFileController.cs | 5 +- .../Controllers/SearchController.cs | 3 + .../DocumentGenerationController.cs | 3 + .../DocumentGenerationRequest.cs | 1 + .../api/Areas/Documents/DocumentController.cs | 11 + .../DocumentRelationshipController.cs | 5 + .../ExpropriationPaymentController.cs | 2 + .../FinancialCodes/FinancialCodeController.cs | 6 + .../FormDocument/FormDocumentController.cs | 6 + .../Controllers/AccessRequestController.cs | 2 + .../Keycloak/Controllers/UserController.cs | 3 +- .../Leases/Controllers/LeaseController.cs | 1 + .../Areas/Notes/Controllers/NoteController.cs | 6 + .../Controllers/OrganizationController.cs | 2 + .../Persons/Controllers/PersonController.cs | 2 + .../Products/Controllers/ProductController.cs | 2 + .../Projects/Controllers/ProjectController.cs | 2 + .../Projects/Controllers/SearchController.cs | 2 + .../Controllers/PropertyController.cs | 10 +- .../Controllers/ResearchFileController.cs | 7 + .../Research/Controllers/SearchController.cs | 3 + .../Areas/Takes/Controllers/TakeController.cs | 12 +- .../Extensions/AcquisitionFileExtensions.cs | 20 +- .../Extensions/DispositionFileExtensions.cs | 41 ++ .../Helpers/Extensions/PrincipalExtensions.cs | 22 +- .../Middleware/ErrorHandlingMiddleware.cs | 8 +- source/backend/api/Pims.Api.csproj | 4 +- .../api/Services/AcquisitionFileService.cs | 34 +- .../CompensationRequisitionService.cs | 2 +- .../api/Services/DispositionFileService.cs | 37 +- .../api/Services/IAcquisitionFileService.cs | 2 +- .../api/Services/IDispositionFileService.cs | 2 +- .../Services/IDocumentGenerationService.cs | 2 +- source/backend/api/Services/TakeService.cs | 1 + .../api/Solvers/AcquisitionStatusSolver.cs | 2 +- .../api/Solvers/IAcquisitionStatusSolver.cs | 2 +- .../CodeType}/AcquisitionStatusTypes.cs | 2 +- .../CodeType}/AgreementStatusTypes.cs | 2 +- .../apimodels/CodeType/AgreementTypes.cs | 21 + .../CodeType}/FormDocumentType.cs | 2 +- .../apimodels/Models/Base/CodeTypeModel.cs | 1 + .../AcquisitionFile/AcquisitionFileMap.cs | 5 +- .../AcquisitionFile/AcquisitionFileModel.cs | 2 +- .../AcquisitionFilePropertyMap.cs | 2 + .../AcquisitionFilePropertyModel.cs | 39 +- .../DispositionFile/DispositionFileMap.cs | 5 +- .../DispositionFile/DispositionFileModel.cs | 3 +- .../DispositionFilePropertyModel.cs | 37 +- .../Models/Concepts/File/FileModel.cs | 6 + .../Models/Concepts/File/FilePropertyModel.cs | 44 ++ .../Models/Concepts/Lease/LeaseMap.cs | 16 +- .../Models/Concepts/Lease/LeaseModel.cs | 41 +- .../Models/Concepts/Lease/LeaseTermModel.cs | 5 - .../Models/Concepts/Lease/PaymentModel.cs | 5 - .../Models/Concepts/Lease/PropertyLeaseMap.cs | 8 +- .../Concepts/Lease/PropertyLeaseModel.cs | 18 +- .../Models/Concepts/Project/ProjectModel.cs | 2 +- .../Concepts}/Property/AssociationMap.cs | 13 +- .../Concepts}/Property/AssociationModel.cs | 2 +- .../Concepts/Property/PropertyActivityMap.cs | 2 - .../Property/PropertyAssociationsModel.cs} | 6 +- .../Models/Concepts/Property/PropertyMap.cs | 2 +- .../Models/Concepts/Property/PropertyModel.cs | 2 +- .../Concepts/ResearchFile/ResearchFileMap.cs | 6 +- .../ResearchFile/ResearchFileModel.cs | 2 +- .../ResearchFile/ResearchFilePropertyMap.cs | 2 +- .../ResearchFile/ResearchFilePropertyModel.cs | 27 +- .../ResearchFile/ResearchFilePurposeModel.cs | 2 +- .../Repositories/DispositionFileRepository.cs | 13 +- .../Interfaces/IDispositionFileRepository.cs | 2 +- .../Acquisition/ChecklistControllerTest.cs | 30 +- .../Disposition/ChecklistControllerTest.cs | 33 +- .../Disposition/DispositionControllerTest.cs | 157 +++++ .../Services/AcquisitionFileServiceTest.cs | 41 +- .../CompensationRequisitionServiceTest.cs | 1 + .../Services/DispositionFileServiceTest.cs | 204 ++++++- .../unit/api/Services/TakeServiceTest.cs | 1 + .../Solvers/AcquisitionStatusSolverTests.cs | 1 + .../DispositionFileRepositoryTest.cs | 1 + source/frontend/package.json | 2 +- source/frontend/public/mockServiceWorker.js | 169 +++--- source/frontend/src/AppRouter.test.tsx | 4 +- .../frontend/src/components/Table/helpers.tsx | 5 +- .../src/components/common/ContactLink.tsx | 11 +- .../components/common/ExpandableTextList.tsx | 14 +- .../common/List/ExpandableFileProperties.tsx | 6 +- .../common/MultiselectTextList.test.tsx | 25 +- .../components/common/form/ContactInput.tsx | 5 +- .../components/common/form/ParentSelect.tsx | 4 +- .../PrimaryContactSelector.test.tsx | 10 +- .../PrimaryContactSelector.tsx | 4 +- .../UserRegionSelectContainer.tsx | 9 +- .../ContactResultComponent/columns.tsx | 4 +- .../ContactResultComponent/summaryColumns.tsx | 5 +- .../FilePropertiesTable.tsx | 16 +- .../AdvancedFilter/FilterContentForm.test.tsx | 4 +- .../maps/leaflet/Layers/Spiderfier.ts | 3 +- .../components/measurements/AreaForm.test.tsx | 10 +- .../src/components/measurements/AreaForm.tsx | 4 +- .../propertySelector/MapClickMonitor.tsx | 2 +- .../MapSelectorContainer.test.tsx | 1 + .../propertySelector/MapSelectorContainer.tsx | 3 +- .../src/constants/acquisitionFileType.ts | 5 + .../constants/documentRelationshipType.ts2 | 9 - .../src/constants/financialCodeTypes.ts | 9 - source/frontend/src/constants/index.ts | 1 - .../AcquisitionFilter.test.tsx | 6 +- .../AcquisitionFilter/AcquisitionFilter.tsx | 20 +- .../acquisition/list/AcquisitionListView.tsx | 10 +- .../AcquisitionSearchResults.test.tsx | 56 +- .../list/AcquisitionSearchResults/columns.tsx | 67 +-- .../list/AcquisitionSearchResults/models.ts | 50 +- .../features/acquisition/list/interfaces.ts | 10 +- .../AccessRequestContainer.test.tsx | 2 +- .../access-request/AccessRequestContainer.tsx | 24 +- .../access-request/AccessRequestForm.test.tsx | 4 +- .../access-request/AccessRequestForm.tsx | 7 +- .../features/admin/access-request/models.ts | 42 +- .../admin/access/ManageAccessRequestsPage.tsx | 4 +- .../admin/access/components/RowActions.tsx | 9 +- .../DocumentTemplateManagementView.tsx | 9 +- .../edit-user/EditUserContainer.test.tsx | 4 +- .../admin/edit-user/EditUserContainer.tsx | 11 +- .../features/admin/edit-user/EditUserForm.tsx | 8 +- .../add/AddFinancialCodeContainer.test.tsx | 8 +- .../add/AddFinancialCodeContainer.tsx | 19 +- .../add/AddFinancialCodeForm.test.tsx | 16 +- .../add/AddFinancialCodeForm.tsx | 4 +- .../financial-codes/financialCodeUtils.ts | 24 +- .../FinancialCodeFilter.test.tsx | 11 +- .../FinancialCodeFilter.tsx | 4 +- .../list/FinancialCodeListView.tsx | 6 +- .../FinancialCodeResults.test.tsx | 4 +- .../FinancialCodeResults.tsx | 10 +- .../list/FinancialCodeResults/columns.tsx | 12 +- .../features/admin/financial-codes/models.ts | 41 +- .../UpdateFinancialCodeContainer.test.tsx | 12 +- .../update/UpdateFinancialCodeContainer.tsx | 18 +- .../update/UpdateFinancialCodeForm.test.tsx | 8 +- .../update/UpdateFinancialCodeForm.tsx | 12 +- .../features/admin/users/ManageUsersPage.tsx | 6 +- .../src/features/admin/users/models.ts | 109 ++-- .../CreateOrganizationForm.test.tsx | 19 +- .../Organization/CreateOrganizationForm.tsx | 3 +- .../create/Person/CreatePersonForm.test.tsx | 24 +- .../create/Person/CreatePersonForm.tsx | 3 +- .../contact/detail/Organization.test.tsx | 10 + .../contacts/contact/detail/Person.test.tsx | 10 + .../features/contacts/contact/detail/utils.ts | 13 +- .../UpdateOrganizationForm.test.tsx | 7 +- .../Organization/UpdateOrganizationForm.tsx | 3 +- .../edit/Person/UpdatePersonForm.test.tsx | 38 +- .../contact/edit/Person/UpdatePersonForm.tsx | 3 +- .../edit/UpdateContactContainer.test.tsx | 8 +- .../src/features/contacts/contactUtils.ts | 55 +- .../repositories/useOrganizationRepository.ts | 4 +- .../repositories/usePersonRepository.ts | 4 +- .../DispositionFilter/DispositionFilter.tsx | 4 +- .../list/DispositionListView.test.tsx | 12 +- .../disposition/list/DispositionListView.tsx | 4 +- .../DispositionSearchResults.test.tsx | 73 ++- .../DispositionSearchResults.test.tsx.snap | 4 +- .../list/DispositionSearchResults/columns.tsx | 65 +-- .../DispositionListView.test.tsx.snap | 4 +- .../src/features/disposition/list/models.ts | 14 +- .../DocumentDetailContainer.tsx | 7 +- .../leases/add/AddLeaseContainer.test.tsx | 58 +- .../features/leases/add/AddLeaseContainer.tsx | 6 +- .../leases/add/AdministrationSubForm.tsx | 13 +- .../features/leases/context/LeaseContext.tsx | 13 +- .../detail/LeaseHeaderAddresses.test.tsx | 65 ++- .../leases/detail/LeaseHeaderAddresses.tsx | 35 +- .../detail/LeasePages/AddressSubForm.test.tsx | 125 ++-- .../detail/LeasePages/AddressSubForm.tsx | 62 +- .../AddressSubForm.test.tsx.snap | 31 +- .../LeasePages/deposits/DepositsContainer.tsx | 30 +- .../DepositsReceivedContainer.test.tsx | 2 +- .../DepositsReceivedContainer.tsx | 4 +- .../DepositsReceivedContainer/columns.tsx | 31 +- .../DepositsReturnedContainer.test.tsx | 16 +- .../DepositsReturnedContainer.tsx | 9 +- .../DepositsReturnedContainer/columns.tsx | 27 +- .../ReceivedDepositForm.tsx | 3 +- .../ReceivedDepositModal.tsx | 3 +- .../ReturnedDepositModal.tsx | 3 +- .../deposits/models/FormLeaseDeposit.ts | 20 +- .../deposits/models/FormLeaseDepositReturn.ts | 37 +- .../details/DetailAdministration.test.tsx | 18 +- .../details/DetailAdministration.tsx | 4 +- .../LeasePages/details/DetailConsultation.tsx | 34 +- .../details/DetailDocumentation.test.tsx | 18 +- .../details/DetailDocumentation.tsx | 4 +- .../details/DetailTermInformation.tsx | 8 +- .../LeasePages/details/DetailTerms.test.tsx | 14 +- .../detail/LeasePages/details/DetailTerms.tsx | 6 +- .../LeasePages/details/LeaseDetailsForm.tsx | 4 +- .../details/PropertiesInformation.test.tsx | 45 +- .../details/PropertiesInformation.tsx | 22 +- .../details/PropertyInformation.test.tsx | 56 +- .../details/PropertyInformation.tsx | 22 +- .../details/UpdateLeaseContainer.test.tsx | 37 +- .../details/UpdateLeaseContainer.tsx | 7 +- .../PropertiesInformation.test.tsx.snap | 90 ++- .../PropertyInformation.test.tsx.snap | 8 +- .../detail/LeasePages/details/columns.tsx | 12 +- .../AddImprovementsContainer.test.tsx | 24 +- .../improvements/AddImprovementsContainer.tsx | 4 +- .../LeasePages/improvements/Improvements.tsx | 6 +- .../ImprovementsContainer.test.tsx | 4 +- .../detail/LeasePages/improvements/models.ts | 12 +- .../insurance/InsuranceContainer.tsx | 31 +- .../insurance/details/Insurance.test.tsx | 8 +- .../insurance/details/Insurance.tsx | 16 +- .../LeasePages/insurance/details/Policy.tsx | 6 +- .../edit/EditInsuranceContainer.test.tsx | 11 +- .../insurance/edit/EditInsuranceContainer.tsx | 17 +- .../insurance/edit/InsuranceForm.tsx | 4 +- .../LeasePages/insurance/edit/models.ts | 22 +- .../payment/TermPaymentsContainer.tsx | 35 +- .../payment/TermsPaymentsContainer.test.tsx | 19 +- .../payment/hooks/useDeleteTermsPayments.tsx | 6 +- .../payment/modal/payment/PaymentForm.tsx | 7 +- .../modal/payment/PaymentModal.test.tsx | 3 + .../payment/modal/payment/PaymentModal.tsx | 4 +- .../payment/modal/term/TermForm.tsx | 7 +- .../detail/LeasePages/payment/models.ts | 89 +-- .../table/payments/PaymentsForm.test.tsx | 7 +- .../table/payments/paymentsColumns.tsx | 12 +- .../payment/table/terms/TermsForm.test.tsx | 49 +- .../LeasePages/surplus/Surplus.test.tsx | 61 +- .../detail/LeasePages/surplus/Surplus.tsx | 6 +- .../tenant/AddLeaseTenantContainer.test.tsx | 39 +- .../tenant/AddLeaseTenantContainer.tsx | 27 +- .../tenant/AddLeaseTenantForm.test.tsx | 31 +- .../PrimaryContactWarningModal.test.tsx | 24 +- .../LeasePages/tenant/TenantContainer.tsx | 4 +- .../tenant/TenantOrganizationContactInfo.tsx | 2 +- .../LeasePages/tenant/ViewTenantForm.test.tsx | 41 +- .../detail/LeasePages/tenant/columns.tsx | 7 +- .../leases/detail/LeasePages/tenant/models.ts | 126 ++-- .../leases/detail/LeaseStatusSummary.test.tsx | 8 +- .../leases/detail/LeaseStatusSummary.tsx | 8 +- .../detail/StackedTenantFields.test.tsx | 20 +- .../leases/detail/StackedTenantFields.tsx | 5 +- .../leases/hooks/useAddLease.test.tsx | 10 +- .../src/features/leases/hooks/useAddLease.ts | 8 +- .../features/leases/hooks/useLeaseDetail.ts | 6 +- .../leases/hooks/useUpdateLease.test.tsx | 4 +- .../features/leases/hooks/useUpdateLease.ts | 8 +- .../src/features/leases/leaseUtils.ts | 9 +- source/frontend/src/features/leases/models.ts | 149 ++--- .../propertyPicker/LeasePropertySelector.tsx | 11 +- .../acquisition/AcquisitionContainer.tsx | 32 +- .../acquisition/AcquisitionView.test.tsx | 22 + .../acquisition/AcquisitionView.tsx | 10 +- .../add/AddAcquisitionContainer.test.tsx | 4 +- .../add/AddAcquisitionContainer.tsx | 4 +- .../acquisition/add/AddAcquisitionForm.tsx | 19 +- .../mapSideBar/acquisition/add/models.ts | 103 ++-- .../common/AcquisitionHeader.test.tsx | 4 +- .../acquisition/common/AcquisitionHeader.tsx | 4 +- .../GenerateForm/GenerateFormContainer.tsx | 5 +- .../hooks/useGenerateAgreement.test.tsx | 45 +- .../hooks/useGenerateAgreement.ts | 31 +- .../useGenerateExpropriationForm1.test.tsx | 16 +- .../hooks/useGenerateExpropriationForm1.tsx | 5 +- .../useGenerateExpropriationForm5.test.tsx | 16 +- .../hooks/useGenerateExpropriationForm5.tsx | 5 +- .../useGenerateExpropriationForm8.test.tsx | 9 +- .../useGenerateExpropriationForm9.test.tsx | 16 +- .../hooks/useGenerateExpropriationForm9.tsx | 5 +- .../hooks/useGenerateH0443.test.tsx | 73 ++- .../GenerateForm/hooks/useGenerateH0443.ts | 11 +- .../hooks/useGenerateH1005a.test.tsx | 11 +- .../GenerateForm/hooks/useGenerateH1005a.ts | 12 +- .../hooks/useGenerateH120.test.tsx | 36 +- .../GenerateForm/hooks/useGenerateH120.ts | 11 +- .../hooks/useGenerateLetter.test.tsx | 25 +- .../GenerateForm/hooks/useGenerateLetter.ts | 11 +- .../modals/models/LetterRecipientModel.tsx | 21 +- .../mapSideBar/acquisition/common/models.ts | 113 ++-- .../hooks/useAddAcquisitionFormManagement.ts | 7 +- .../models/DetailAcquisitionFileOwner.ts | 15 +- .../acquisition/models/PayeeOptionModel.ts | 41 +- .../acquisition/router/AcquisitionRouter.tsx | 8 +- .../tabs/AcquisitionFileTabs.test.tsx | 2 + .../acquisition/tabs/AcquisitionFileTabs.tsx | 5 +- .../agreement/detail/AgreementContainer.tsx | 3 +- .../tabs/agreement/detail/AgreementView.tsx | 10 +- .../agreement/update/AgreementSubForm.tsx | 8 +- .../update/UpdateAgreementsContainer.test.tsx | 4 +- .../agreement/update/UpdateAgreementsForm.tsx | 10 +- .../tabs/agreement/update/models.ts | 68 ++- ...dateAcquisitionChecklistContainer.test.tsx | 13 +- .../UpdateAcquisitionChecklistContainer.tsx | 10 +- .../CompensationRequisitionTrayContainer.tsx | 13 +- .../CompensationRequisitionTrayView.tsx | 8 +- ...nsationRequisitionDetailContainer.test.tsx | 21 +- ...CompensationRequisitionDetailContainer.tsx | 34 +- ...CompensationRequisitionDetailView.test.tsx | 9 +- .../CompensationRequisitionDetailView.tsx | 35 +- ...nsationRequisitionDetailView.test.tsx.snap | 4 +- .../list/CompensationListContainer.tsx | 14 +- .../list/CompensationListView.test.tsx | 7 +- .../list/CompensationListView.tsx | 18 +- .../compensation/list/CompensationResults.tsx | 6 +- .../tabs/compensation/list/columns.tsx | 18 +- .../CompensationRequisitionYupSchema.ts | 4 +- ...eCompensationRequisitionContainer.test.tsx | 14 +- ...UpdateCompensationRequisitionContainer.tsx | 13 +- ...UpdateCompensationRequisitionForm.test.tsx | 4 +- .../UpdateCompensationRequisitionForm.tsx | 11 +- .../tabs/compensation/update/models.ts | 93 +-- .../ExpropriationTabContainer.tsx | 13 +- .../ExpropriationTabContainerView.test.tsx | 2 +- .../ExpropriationTabContainerView.tsx | 9 +- .../form1/ExpropriationForm1.tsx | 8 +- .../form5/ExpropriationForm5.tsx | 8 +- .../expropriation/form8/UpdateForm8Form.tsx | 6 +- .../form8/add/AddForm8Container.test.tsx | 6 +- .../form8/add/AddForm8Container.tsx | 4 +- .../details/ExpropriationForm8Details.tsx | 14 +- .../ExpropriationPaymentItemsTable.tsx | 16 +- .../form8/models/Form8FormModel.ts | 29 +- .../form8/models/Form8FormYupSchema.tsx | 6 +- .../form8/update/UpdateForm8Container.tsx | 4 +- .../form9/ExpropriationForm9.tsx | 8 +- .../acquisition/tabs/expropriation/models.ts | 12 +- .../AcquisitionOwnersSummaryContainer.tsx | 10 +- .../AcquisitionOwnersSummaryView.test.tsx | 16 +- .../detail/AcquisitionSummaryView.test.tsx | 33 +- .../detail/AcquisitionSummaryView.tsx | 20 +- ...AcquisitionOwnersSummaryView.test.tsx.snap | 8 +- .../tabs/fileDetails/detail/models.ts | 25 +- .../fileDetails/detail/statusUpdateSolver.ts | 10 +- .../UpdateAcquisitionContainer.test.tsx | 4 +- .../update/UpdateAcquisitionContainer.tsx | 9 +- .../update/UpdateAcquisitionForm.tsx | 19 +- .../tabs/fileDetails/update/models.ts | 87 +-- .../PropertyInterestHoldersViewTable.tsx | 12 +- .../detail/StakeHolderContainer.test.tsx | 4 +- .../detail/StakeHolderContainer.tsx | 4 +- .../detail/StakeHolderView.test.tsx | 17 +- .../stakeholders/detail/StakeHolderView.tsx | 8 +- .../detail/stakeholderOrganizer.test.ts | 51 +- .../detail/stakeholderOrganizer.ts | 54 +- .../update/InterestHolderSubForm.tsx | 15 +- .../update/UpdateStakeHolderContainer.tsx | 4 +- .../update/UpdateStakeHolderForm.tsx | 17 +- .../tabs/stakeholders/update/models.test.ts | 131 ++++- .../tabs/stakeholders/update/models.ts | 71 +-- .../mapSideBar/context/sidebarContext.tsx | 25 +- .../disposition/DispositionContainer.tsx | 14 +- .../disposition/DispositionView.tsx | 8 +- .../DispositionView.test.tsx.snap | 2 +- .../add/AddDispositionContainer.test.tsx | 51 +- .../add/AddDispositionContainer.tsx | 66 ++- .../add/AddDispositionContainerView.tsx | 6 +- .../common/DispositionHeader.test.tsx | 11 +- .../disposition/common/DispositionHeader.tsx | 5 +- .../disposition/form/DispositionForm.tsx | 10 +- .../form/DispositionTeamSubForm.tsx | 3 +- .../hooks/useAddDispositionFormManagement.ts | 47 -- .../models/DispositionAppraisalFormModel.ts | 12 +- .../models/DispositionFormModel.ts | 77 ++- .../models/DispositionSaleFormModel.ts | 13 +- .../models/DispositionTeamSubFormModel.ts | 36 +- .../disposition/router/DispositionRouter.tsx | 8 +- .../disposition/tabs/DispositionFileTabs.tsx | 4 +- .../DispositionFileTabs.test.tsx.snap | 2 +- ...dateDispositionChecklistContainer.test.tsx | 12 +- .../UpdateDispositionChecklistContainer.tsx | 10 +- .../detail/DispositionSummaryView.test.tsx | 36 +- .../detail/DispositionSummaryView.tsx | 8 +- .../DispositionSummaryView.test.tsx.snap | 2 +- .../UpdateDispositionContainer.test.tsx | 26 +- .../update/UpdateDispositionContainer.tsx | 53 +- .../detail/update/UpdateDispositionForm.tsx | 4 +- .../offersAndSale/OffersAndSaleContainer.tsx | 16 +- .../OffersAndSaleContainerView.test.tsx | 2 +- .../OffersAndSaleContainerView.tsx | 26 +- .../OffersAndSaleContainerView.test.tsx.snap | 2 +- .../form/DispositionAppraisalForm.tsx | 6 +- ...dateDispositionAppraisalContainer.test.tsx | 10 +- .../UpdateDispositionAppraisalContainer.tsx | 4 +- .../add/AddDispositionOfferContainer.test.tsx | 6 +- .../add/AddDispositionOfferContainer.tsx | 4 +- .../DispositionOfferDetails.tsx | 6 +- .../form/DispositionOfferForm.tsx | 6 +- .../models/DispositionOfferFormModel.ts | 16 +- .../UpdateDispositionOfferContainer.test.tsx | 6 +- .../UpdateDispositionOfferContainer.tsx | 4 +- .../mapSideBar/hooks/usePropertyDetails.ts | 11 +- .../mapSideBar/lease/ViewSelector.tsx | 6 +- .../mapSideBar/lease/common/LeaseHeader.tsx | 8 +- .../lease/common/LeaseHeaderTenants.tsx | 4 +- .../__snapshots__/LeaseHeader.test.tsx.snap | 1 + .../lease/detail/LeaseTabsContainer.tsx | 6 +- .../mapSideBar/project/ProjectContainer.tsx | 8 +- .../mapSideBar/project/ViewSelector.tsx | 6 +- .../project/add/AddProjectContainer.test.tsx | 14 +- .../project/add/AddProjectContainer.tsx | 23 +- .../project/add/AddProjectForm.test.tsx | 10 +- .../mapSideBar/project/add/ProductSubForm.tsx | 3 +- .../project/add/ProductsArrayForm.tsx | 5 +- .../project/common/ProjectHeader.test.tsx | 4 +- .../project/common/ProjectHeader.tsx | 4 +- .../hooks/useAddProjectFormManagement.tsx | 7 +- .../src/features/mapSideBar/project/models.ts | 54 +- .../project/tabs/ProjectTabsContainer.tsx | 6 +- .../detail/ProjectProductView.tsx | 13 +- .../detail/ProjectSummaryView.tsx | 4 +- .../update/UpdateProjectContainer.test.tsx | 2 +- .../update/UpdateProjectContainer.tsx | 26 +- .../mapSideBar/property/ComposedProperty.ts | 7 +- .../property/MotiInventoryContainer.tsx | 10 +- .../property/MotiInventoryHeader.test.tsx | 15 +- .../property/MotiInventoryHeader.tsx | 8 +- .../mapSideBar/property/PropertyContainer.tsx | 7 +- .../AssociationContent.tsx | 4 +- .../PropertyAssociationTabView.test.tsx | 10 +- .../PropertyAssociationTabView.tsx | 12 +- .../detail/PropertyDetailsTabView.helpers.tsx | 31 +- .../detail/PropertyDetailsTabView.test.tsx | 87 ++- .../detail/PropertyDetailsTabView.tsx | 14 +- .../UpdatePropertyDetailsContainer.test.tsx | 78 ++- .../update/UpdatePropertyDetailsContainer.tsx | 23 +- .../update/UpdatePropertyDetailsForm.test.tsx | 48 +- .../update/UpdatePropertyDetailsForm.tsx | 8 +- .../models/PropertyAdjacentLandFormModel.ts | 39 -- .../update/models/PropertyAnomalyFormModel.ts | 25 +- .../update/models/PropertyRoadFormModel.ts | 25 +- .../update/models/PropertyTenureFormModel.ts | 25 +- .../models/UpdatePropertyDetailsFormModel.ts | 225 ++++---- .../propertyDetails/update/models/index.ts | 1 - .../tabs/propertyDetails/update/validation.ts | 8 - .../activity/detail/InvoiceView.tsx | 4 +- .../PropertyActivityDetailContainer.tsx | 23 +- .../detail/PropertyActivityDetailView.tsx | 10 +- .../edit/PropertyActivityEditContainer.tsx | 32 +- .../edit/PropertyActivityEditForm.tsx | 20 +- .../activity/edit/models.ts | 113 ++-- .../activity/hooks.ts | 25 +- .../list/ManagementActivitiesList.tsx | 6 +- .../ManagementActivitiesListView.test.tsx | 14 +- .../list/ManagementActivitiesListView.tsx | 4 +- .../list/models/PropertyActivityRow.ts | 17 +- .../detail/PropertyContactList.tsx | 29 +- .../detail/PropertyContactListContainer.tsx | 4 +- .../detail/PropertyContactListView.test.tsx | 18 +- .../detail/PropertyContactListView.tsx | 4 +- .../detail/PropertyManagementTabView.tsx | 7 +- ...PropertyManagementDetailContainer.test.tsx | 5 +- .../PropertyManagementDetailView.test.tsx | 10 +- .../summary/PropertyManagementDetailView.tsx | 4 +- .../update/PropertyContactEditContainer.tsx | 14 +- .../update/PropertyContactEditForm.test.tsx | 11 +- .../update/PropertyContactEditForm.tsx | 17 +- .../update/models.test.ts | 19 +- .../update/models.ts | 9 +- ...PropertyManagementUpdateContainer.test.tsx | 4 +- .../PropertyManagementUpdateContainer.tsx | 9 +- .../PropertyManagementUpdateForm.test.tsx | 8 +- .../summary/PropertyManagementUpdateForm.tsx | 6 +- .../update/summary/models.test.ts | 23 +- .../update/summary/models.ts | 20 +- .../detail/PropertyResearchTabView.test.tsx | 36 +- .../detail/PropertyResearchTabView.tsx | 27 +- .../hooks/useUpdatePropertyResearch.ts | 10 +- .../update/UpdatePropertyForm.test.tsx | 14 +- .../UpdatePropertyResearchContainer.tsx | 8 +- .../tabs/propertyResearch/update/models.ts | 104 ++-- .../takes/detail/TakesDetailContainer.tsx | 4 +- .../takes/detail/TakesDetailView.test.tsx | 24 +- .../tabs/takes/detail/TakesDetailView.tsx | 8 +- .../takes/repositories/useTakesRepository.tsx | 13 +- .../update/TakesUpdateContainer.test.tsx | 4 +- .../takes/update/TakesUpdateContainer.tsx | 8 +- .../tabs/takes/update/TakesUpdateForm.tsx | 15 +- .../tabs/takes/update/models.test.tsx | 15 +- .../property/tabs/takes/update/models.ts | 21 +- .../research/ResearchContainer.test.tsx | 4 +- .../mapSideBar/research/ResearchContainer.tsx | 40 +- .../mapSideBar/research/ResearchRouter.tsx | 8 +- .../mapSideBar/research/ResearchView.tsx | 4 +- .../research/add/AddResearchContainer.tsx | 6 +- .../mapSideBar/research/add/models.ts | 55 +- .../research/common/ResearchHeader.test.tsx | 11 +- .../research/common/ResearchHeader.tsx | 16 +- .../mapSideBar/research/common/models.ts | 34 +- .../research/hooks/useAddResearch.test.tsx | 28 +- .../research/hooks/useAddResearch.ts | 6 +- .../research/hooks/useGetResearch.ts | 7 +- .../research/hooks/useUpdateResearch.ts | 8 +- .../hooks/useUpdateResearchProperties.ts | 8 +- .../research/tabs/ResearchTabsContainer.tsx | 4 +- .../details/ResearchSummaryView.tsx | 78 ++- .../update/UpdateSummaryContainer.tsx | 8 +- .../update/UpdateSummaryForm.test.tsx | 12 +- .../tabs/fileDetails/update/models.ts | 106 ++-- .../mapSideBar/router/FilePropertyRouter.tsx | 18 +- .../detail/PropertyFileContainer.test.tsx | 3 +- .../shared/detail/PropertyFileContainer.tsx | 19 +- .../src/features/mapSideBar/shared/models.ts | 180 ++++-- .../tabs/checklist/detail/ChecklistView.tsx | 14 +- .../checklist/update/UpdateChecklistForm.tsx | 12 +- .../shared/tabs/checklist/update/models.ts | 84 +-- .../properties/UpdateProperties.test.tsx | 13 +- .../update/properties/UpdateProperties.tsx | 13 +- .../UpdateProperties.test.tsx.snap | 20 +- .../notes/add/AddNotesContainer.test.tsx | 8 +- .../frontend/src/features/notes/add/models.ts | 9 +- .../detail/NoteDetailsFormModal.test.tsx | 4 +- .../notes/detail/NoteDetailsFormModal.tsx | 14 +- .../hooks/useAddNotesFormManagement.test.tsx | 2 +- .../notes/hooks/useAddNotesFormManagement.ts | 3 +- .../useUpdateNotesFormManagement.test.tsx | 2 +- .../hooks/useUpdateNotesFormManagement.ts | 7 +- .../src/features/notes/list/NoteListView.tsx | 16 +- .../notes/list/NoteResults/NoteResults.tsx | 14 +- .../notes/list/NoteResults/columns.tsx | 10 +- source/frontend/src/features/notes/models.ts | 42 +- .../notes/update/UpdateNoteContainer.test.tsx | 6 +- .../notes/update/UpdateNoteContainer.tsx | 4 +- .../projects/context/ProjectContext.tsx | 15 +- .../projects/list/ProjectListView.tsx | 4 +- .../ProjectSearchResults.test.tsx | 4 +- .../list/ProjectSearchResults/models.ts | 9 +- .../projects/reports/ProjectExportForm.tsx | 8 +- .../map/form/detail/FormContainer.tsx | 7 +- .../properties/map/form/detail/FormView.tsx | 4 +- .../properties/map/form/list/FormListView.tsx | 11 +- .../form/list/FormListViewContainer.test.tsx | 10 +- .../map/form/list/FormListViewContainer.tsx | 11 +- .../properties/map/form/list/FormResults.tsx | 13 +- .../properties/map/form/list/columns.tsx | 10 +- .../properties/map/hooks/useBcaAddress.tsx | 3 +- .../research/list/ResearchListView.test.tsx | 58 +- .../ResearchSearchResults.tsx | 4 +- .../pims-api/useApiAccessRequests.test.ts | 3 +- .../hooks/pims-api/useApiAccessRequests.ts | 25 +- .../hooks/pims-api/useApiAcquisitionFile.ts | 78 +-- .../src/hooks/pims-api/useApiAgreements.ts | 11 +- .../src/hooks/pims-api/useApiContacts.ts | 8 +- .../hooks/pims-api/useApiDispositionFile.ts | 78 ++- .../hooks/pims-api/useApiFinancialCodes.ts | 35 +- .../src/hooks/pims-api/useApiForm8.ts | 8 +- .../src/hooks/pims-api/useApiFormDocument.ts | 15 +- .../src/hooks/pims-api/useApiH120Category.ts | 4 +- .../src/hooks/pims-api/useApiInsurances.ts | 6 +- .../hooks/pims-api/useApiInterestHolders.ts | 11 +- .../src/hooks/pims-api/useApiLeaseDeposits.ts | 14 +- .../pims-api/useApiLeaseDepositsReturn.ts | 12 +- .../src/hooks/pims-api/useApiLeasePayments.ts | 12 +- .../src/hooks/pims-api/useApiLeaseTenants.ts | 10 +- .../src/hooks/pims-api/useApiLeaseTerms.ts | 16 +- .../src/hooks/pims-api/useApiLeases.ts | 12 +- .../src/hooks/pims-api/useApiNotes.ts | 14 +- .../src/hooks/pims-api/useApiProducts.ts | 4 +- .../src/hooks/pims-api/useApiProjects.ts | 22 +- .../src/hooks/pims-api/useApiProperties.ts | 14 +- .../pims-api/useApiPropertyActivities.ts | 24 +- .../hooks/pims-api/useApiPropertyContacts.ts | 17 +- .../pims-api/useApiPropertyImprovements.ts | 8 +- .../hooks/pims-api/useApiPropertyLeases.ts | 4 +- .../pims-api/useApiPropertyManagement.ts | 8 +- .../useApiRequisitionCompensations.ts | 10 +- .../src/hooks/pims-api/useApiResearchFile.ts | 27 +- .../src/hooks/pims-api/useApiTakes.ts | 14 +- .../src/hooks/pims-api/useApiUsers.ts | 15 +- .../repositories/useAcquisitionProvider.ts | 84 +-- .../repositories/useAgreementProvider.ts | 11 +- .../repositories/useComposedProperties.ts | 12 +- .../repositories/useDispositionProvider.ts | 87 +-- .../useFinancialCodeRepository.ts | 47 +- .../hooks/repositories/useForm8Repository.ts | 10 +- .../repositories/useFormDocumentRepository.ts | 21 +- .../repositories/useH120CategoryRepository.ts | 4 +- .../repositories/useInsuranceRepository.ts | 11 +- .../useInterestHolderRepository.ts | 10 +- .../repositories/useLeasePaymentRepository.ts | 21 +- .../repositories/useLeaseTenantRepository.ts | 10 +- .../repositories/useLeaseTermRepository.ts | 25 +- .../hooks/repositories/useNoteRepository.ts | 19 +- .../repositories/usePimsPropertyRepository.ts | 4 +- .../hooks/repositories/useProductProvider.ts | 4 +- .../hooks/repositories/useProjectProvider.ts | 25 +- .../src/hooks/repositories/useProperties.ts | 12 +- .../usePropertyActivityRepository.ts | 24 +- .../repositories/usePropertyAssociations.ts | 4 +- .../usePropertyContactRepository.ts | 21 +- .../usePropertyImprovementRepository.ts | 10 +- .../usePropertyLeaseRepository.ts | 4 +- .../usePropertyManagementRepository.ts | 4 +- .../useRequisitionCompensationRepository.ts | 10 +- .../useSecurityDepositRepository.ts | 23 +- .../useSecurityDepositReturnRepository.ts | 18 +- .../repositories/useUserInfoRepository.ts | 32 +- .../frontend/src/hooks/useApiUserOverride.ts | 101 ++-- .../frontend/src/hooks/useKeycloakWrapper.tsx | 13 +- source/frontend/src/interfaces/IContact.ts | 6 +- .../src/interfaces/IContactSearchResult.ts | 76 +-- .../src/interfaces/ILeaseSearchResult.ts | 4 +- source/frontend/src/interfaces/IProperty.ts | 3 - .../src/interfaces/IPropertySurplus.ts | 4 +- .../src/interfaces/IResearchSearchResult.ts | 12 +- .../src/interfaces/ISelectedTenant.ts | 24 +- source/frontend/src/interfaces/ITenant.ts | 5 +- source/frontend/src/interfaces/ITypeCode.ts | 17 +- source/frontend/src/interfaces/IUser.ts | 4 +- .../src/interfaces/editable-contact/index.ts | 7 +- source/frontend/src/interfaces/index.ts | 1 - .../src/mocks/ExpropriationPayment.mock.ts | 25 +- .../mocks/PropertyManagementActivity.mock.ts | 24 +- .../frontend/src/mocks/accessRequest.mock.ts | 35 +- .../src/mocks/acquisitionFiles.mock.ts | 170 +++++- source/frontend/src/mocks/address.mock.ts | 34 +- source/frontend/src/mocks/agreements.mock.ts | 10 +- .../frontend/src/mocks/compensations.mock.ts | 216 ++++--- source/frontend/src/mocks/contacts.mock.ts | 73 ++- source/frontend/src/mocks/deposits.mock.ts | 32 +- .../src/mocks/dispositionFiles.mock.ts | 44 +- source/frontend/src/mocks/filterData.mock.ts | 129 ++++- .../frontend/src/mocks/financialCode.mock.ts | 11 +- source/frontend/src/mocks/form.mock.ts | 18 +- source/frontend/src/mocks/insurance.mock.ts | 11 +- .../frontend/src/mocks/interestHolder.mock.ts | 13 +- .../src/mocks/interestHolders.mock.ts | 19 +- source/frontend/src/mocks/lease.mock.ts | 130 ++++- .../src/mocks/mockCompReqH120s.mock.ts | 49 +- .../src/mocks/mockH120Categories.mock.ts | 47 +- .../frontend/src/mocks/noteResponses.mock.ts | 31 +- .../frontend/src/mocks/organization.mock.ts | 47 +- source/frontend/src/mocks/projects.mock.ts | 59 +- source/frontend/src/mocks/properties.mock.ts | 115 +++- .../src/mocks/propertyManagement.mock.ts | 16 +- .../frontend/src/mocks/researchFile.mock.ts | 31 +- source/frontend/src/mocks/takes.mock.ts | 21 +- source/frontend/src/mocks/user.mock.ts | 55 +- .../frontend/src/models/api/AccessRequest.ts | 17 - .../src/models/api/AcquisitionFile.ts | 83 --- source/frontend/src/models/api/Address.ts | 38 -- source/frontend/src/models/api/Agreement.ts | 39 -- source/frontend/src/models/api/AuditFields.ts | 10 - source/frontend/src/models/api/CodeType.ts | 8 - .../src/models/api/CompensationFinancial.ts | 15 - .../src/models/api/CompensationRequisition.ts | 46 -- .../src/models/api/ConcurrentVersion.ts | 7 - source/frontend/src/models/api/Contact.ts | 8 - .../frontend/src/models/api/ContactMethod.ts | 9 - .../src/models/api/DispositionFile.ts | 92 --- .../src/models/api/ExpropriationPayment.ts | 32 -- source/frontend/src/models/api/File.ts | 76 --- .../frontend/src/models/api/FinancialCode.ts | 12 - .../frontend/src/models/api/FormDocument.ts | 14 - .../frontend/src/models/api/H120Category.ts | 7 - source/frontend/src/models/api/Insurance.ts | 15 - .../frontend/src/models/api/InterestHolder.ts | 29 - source/frontend/src/models/api/Lease.ts | 92 --- .../frontend/src/models/api/LeasePayment.ts | 16 - source/frontend/src/models/api/LeaseTenant.ts | 20 - source/frontend/src/models/api/LeaseTerm.ts | 23 - source/frontend/src/models/api/Note.ts | 18 - .../frontend/src/models/api/Organization.ts | 23 - source/frontend/src/models/api/Person.ts | 22 - source/frontend/src/models/api/Project.ts | 44 -- source/frontend/src/models/api/Property.ts | 157 ----- .../src/models/api/PropertyActivity.ts | 77 --- .../frontend/src/models/api/PropertyFile.ts | 18 - .../src/models/api/PropertyImprovement.ts | 13 - .../frontend/src/models/api/PropertyLease.ts | 15 - source/frontend/src/models/api/RegionUser.ts | 12 - .../frontend/src/models/api/ResearchFile.ts | 55 -- source/frontend/src/models/api/Role.ts | 14 - .../src/models/api/SecurityDeposit.ts | 28 - source/frontend/src/models/api/Take.ts | 32 -- source/frontend/src/models/api/TypeCode.ts | 6 - source/frontend/src/models/api/User.ts | 23 - source/frontend/src/models/api/UserRole.ts | 13 - .../frontend/src/models/api/UtcIsoDateTime.ts | 1 + ...ApiGen_CodeTypes_AcquisitionStatusTypes.ts | 13 + .../ApiGen_CodeTypes_AgreementStatusTypes.ts | 9 + .../ApiGen_CodeTypes_AgreementTypes.ts | 10 + .../ApiGen_CodeTypes_FormDocumentType.ts | 18 + ...ApiGen_Concepts_AcquisitionFileProperty.ts | 15 +- .../generated/ApiGen_Concepts_Association.ts | 15 + ...ApiGen_Concepts_DispositionFileProperty.ts | 11 +- .../api/generated/ApiGen_Concepts_File.ts | 2 + .../generated/ApiGen_Concepts_FileProperty.ts | 18 + .../api/generated/ApiGen_Concepts_Lease.ts | 15 +- .../generated/ApiGen_Concepts_LeaseTerm.ts | 1 - .../api/generated/ApiGen_Concepts_Payment.ts | 1 - .../api/generated/ApiGen_Concepts_Project.ts | 2 +- .../api/generated/ApiGen_Concepts_Property.ts | 2 +- .../ApiGen_Concepts_PropertyAssociations.ts | 15 + .../ApiGen_Concepts_PropertyLease.ts | 12 +- .../ApiGen_Concepts_ResearchFileProperty.ts | 9 +- .../ApiGen_Concepts_ResearchFilePurpose.ts | 2 +- .../src/models/defaultInitializers.ts | 223 ++++++++ .../src/models/generate/GenerateAddress.ts | 4 +- .../src/models/generate/GenerateAgreement.ts | 8 +- .../GenerateExpropriationPaymentItem.ts | 4 +- .../src/models/generate/GenerateLetter.ts | 7 +- .../generate/GenerateOrganization.test.ts | 6 + .../models/generate/GenerateOrganization.ts | 4 +- .../src/models/generate/GenerateOwner.ts | 22 +- .../models/generate/GeneratePerson.test.ts | 6 + .../src/models/generate/GeneratePerson.ts | 5 +- .../src/models/generate/GenerateProduct.ts | 4 +- .../src/models/generate/GenerateProject.ts | 4 +- .../src/models/generate/GenerateProperty.ts | 17 +- .../GenerateAcquisitionFile.test.ts | 257 ++++++++- .../acquisition/GenerateAcquisitionFile.ts | 31 +- .../acquisition/GenerateCompensation.ts | 12 +- .../GenerateCompensationFinancial.ts | 4 +- .../GenerateCompensationFinancialSummary.ts | 10 +- .../GenerateCompensationPayee.test.ts | 25 +- .../acquisition/GenerateCompensationPayee.ts | 8 +- .../acquisition/GenerateExpropriationForm8.ts | 6 +- .../GenerateExpropriationFormBase.ts | 19 +- .../acquisition/GenerateH120InterestHolder.ts | 11 +- .../acquisition/GenerateH120Property.ts | 18 +- .../acquisition/GenerateInterestHolder.ts | 4 +- .../generate/lease/GenerateLease.test.ts | 97 ++-- .../models/generate/lease/GenerateLease.ts | 26 +- .../generate/lease/GenerateLeaseProperty.ts | 4 +- .../generate/lease/GenerateSecurityDeposit.ts | 4 +- .../models/generate/lease/GenerateTenant.ts | 4 +- source/frontend/src/utils/columnUtils.tsx | 2 +- .../frontend/src/utils/contactMethodUtil.ts | 18 +- source/frontend/src/utils/dateUtils.ts | 14 +- source/frontend/src/utils/fileUtils.ts | 42 ++ .../frontend/src/utils/financialCodeUtils.ts | 12 +- source/frontend/src/utils/formUtils.ts | 108 +++- source/frontend/src/utils/mapPropertyUtils.ts | 45 +- source/frontend/src/utils/personUtils.ts | 7 +- source/frontend/src/utils/projectUtils.ts | 8 +- source/frontend/src/utils/propertyUtils.ts | 19 +- source/frontend/src/utils/test-utils.tsx | 2 + source/frontend/src/utils/utils.ts | 29 +- .../Classes/AcquisitionFile.cs | 8 +- .../Classes/DispositionFile.cs | 50 +- .../Data/PIMS_Testing_Data.xlsx | Bin 96280 -> 98804 bytes .../Features/DispositionFiles.feature | 36 +- .../Features/DispositionFiles.feature.cs | 134 ++++- .../Features/Leases.feature | 6 +- .../Features/Leases.feature.cs | 38 +- .../PIMS.Tests.Automation.csproj | 4 +- .../PageObjects/AcquisitionAgreements.cs | 8 +- .../PageObjects/AcquisitionChecklist.cs | 2 +- .../PageObjects/AcquisitionCompensations.cs | 32 +- .../PageObjects/AcquisitionDetails.cs | 58 +- .../PageObjects/AcquisitionExpropriation.cs | 22 +- .../PageObjects/AcquisitionProperties.cs | 84 --- .../PageObjects/AcquisitionStakeholders.cs | 6 +- .../PageObjects/CDOGSTemplates.cs | 38 +- .../PageObjects/Contacts.cs | 76 +-- .../PageObjects/DigitalDocuments.cs | 30 +- .../PageObjects/DispositionFileDetails.cs | 541 ++++++++++++++++-- .../PageObjects/DispositionOfferSale.cs | 305 ++++++++++ .../PageObjects/FinancialCodes.cs | 4 +- .../PageObjects/LeaseDetails.cs | 16 +- .../PageObjects/LeaseTenants.cs | 19 +- .../PageObjects/PageObjectBase.cs | 23 +- .../PageObjects/Projects.cs | 52 +- .../PageObjects/PropertyInformation.cs | 267 +++++---- .../PageObjects/PropertyManagementTab.cs | 6 +- .../PageObjects/ResearchFiles.cs | 41 +- .../PageObjects/SearchAcquisitionFiles.cs | 66 ++- .../PageObjects/SearchDispositionFiles.cs | 285 +++++++++ .../PageObjects/SearchLease.cs | 62 +- .../PageObjects/SearchProperties.cs | 2 +- ...hProperties.cs => SharedFileProperties.cs} | 121 ++-- .../StepDefinitions/AcquisitionFileSteps.cs | 103 ++-- .../StepDefinitions/AdminToolSteps.cs | 14 +- .../StepDefinitions/ContactsSteps.cs | 2 +- .../StepDefinitions/DispositionFileSteps.cs | 366 +++++++++++- .../StepDefinitions/LeaseLicenseSteps.cs | 22 +- .../StepDefinitions/PropertiesSteps.cs | 21 - .../StepDefinitions/ResearchFileSteps.cs | 50 +- .../DELETE AUTOMATION TEST DATA.sql | 2 - .../Converters/FileNameConverter.cs | 13 +- .../Specifications/PimsGenerationSpec.cs | 26 +- .../TsModelGenerator/TsModelGenerator.csproj | 2 +- workspace.code-workspace | 3 +- 797 files changed, 11479 insertions(+), 7153 deletions(-) create mode 100644 source/backend/api/Helpers/Extensions/DispositionFileExtensions.cs rename source/backend/{api/Constants => apimodels/CodeType}/AcquisitionStatusTypes.cs (94%) rename source/backend/{api/Constants => apimodels/CodeType}/AgreementStatusTypes.cs (90%) create mode 100644 source/backend/apimodels/CodeType/AgreementTypes.cs rename source/backend/{api/Constants => apimodels/CodeType}/FormDocumentType.cs (97%) create mode 100644 source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs rename source/backend/{api/Areas/Property/Mapping => apimodels/Models/Concepts}/Property/AssociationMap.cs (88%) rename source/backend/{api/Areas/Property/Models => apimodels/Models/Concepts}/Property/AssociationModel.cs (88%) rename source/backend/{api/Areas/Property/Models/Property/PropertyAssociationModel.cs => apimodels/Models/Concepts/Property/PropertyAssociationsModel.cs} (75%) create mode 100644 source/frontend/src/constants/acquisitionFileType.ts delete mode 100644 source/frontend/src/constants/documentRelationshipType.ts2 delete mode 100644 source/frontend/src/constants/financialCodeTypes.ts delete mode 100644 source/frontend/src/features/mapSideBar/disposition/hooks/useAddDispositionFormManagement.ts delete mode 100644 source/frontend/src/features/mapSideBar/property/tabs/propertyDetails/update/models/PropertyAdjacentLandFormModel.ts delete mode 100644 source/frontend/src/models/api/AccessRequest.ts delete mode 100644 source/frontend/src/models/api/AcquisitionFile.ts delete mode 100644 source/frontend/src/models/api/Address.ts delete mode 100644 source/frontend/src/models/api/Agreement.ts delete mode 100644 source/frontend/src/models/api/AuditFields.ts delete mode 100644 source/frontend/src/models/api/CodeType.ts delete mode 100644 source/frontend/src/models/api/CompensationFinancial.ts delete mode 100644 source/frontend/src/models/api/CompensationRequisition.ts delete mode 100644 source/frontend/src/models/api/ConcurrentVersion.ts delete mode 100644 source/frontend/src/models/api/Contact.ts delete mode 100644 source/frontend/src/models/api/ContactMethod.ts delete mode 100644 source/frontend/src/models/api/DispositionFile.ts delete mode 100644 source/frontend/src/models/api/ExpropriationPayment.ts delete mode 100644 source/frontend/src/models/api/FinancialCode.ts delete mode 100644 source/frontend/src/models/api/FormDocument.ts delete mode 100644 source/frontend/src/models/api/H120Category.ts delete mode 100644 source/frontend/src/models/api/Insurance.ts delete mode 100644 source/frontend/src/models/api/InterestHolder.ts delete mode 100644 source/frontend/src/models/api/Lease.ts delete mode 100644 source/frontend/src/models/api/LeasePayment.ts delete mode 100644 source/frontend/src/models/api/LeaseTenant.ts delete mode 100644 source/frontend/src/models/api/LeaseTerm.ts delete mode 100644 source/frontend/src/models/api/Note.ts delete mode 100644 source/frontend/src/models/api/Organization.ts delete mode 100644 source/frontend/src/models/api/Person.ts delete mode 100644 source/frontend/src/models/api/Project.ts delete mode 100644 source/frontend/src/models/api/Property.ts delete mode 100644 source/frontend/src/models/api/PropertyActivity.ts delete mode 100644 source/frontend/src/models/api/PropertyFile.ts delete mode 100644 source/frontend/src/models/api/PropertyImprovement.ts delete mode 100644 source/frontend/src/models/api/PropertyLease.ts delete mode 100644 source/frontend/src/models/api/RegionUser.ts delete mode 100644 source/frontend/src/models/api/ResearchFile.ts delete mode 100644 source/frontend/src/models/api/Role.ts delete mode 100644 source/frontend/src/models/api/SecurityDeposit.ts delete mode 100644 source/frontend/src/models/api/Take.ts delete mode 100644 source/frontend/src/models/api/TypeCode.ts delete mode 100644 source/frontend/src/models/api/User.ts delete mode 100644 source/frontend/src/models/api/UserRole.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_CodeTypes_AcquisitionStatusTypes.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_CodeTypes_AgreementStatusTypes.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_CodeTypes_AgreementTypes.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_CodeTypes_FormDocumentType.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_Concepts_Association.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_Concepts_FileProperty.ts create mode 100644 source/frontend/src/models/api/generated/ApiGen_Concepts_PropertyAssociations.ts create mode 100644 source/frontend/src/models/defaultInitializers.ts create mode 100644 source/frontend/src/utils/fileUtils.ts delete mode 100644 testing/PIMS.Tests.Automation/PageObjects/AcquisitionProperties.cs create mode 100644 testing/PIMS.Tests.Automation/PageObjects/DispositionOfferSale.cs create mode 100644 testing/PIMS.Tests.Automation/PageObjects/SearchDispositionFiles.cs rename testing/PIMS.Tests.Automation/PageObjects/{SharedSearchProperties.cs => SharedFileProperties.cs} (76%) diff --git a/source/backend/api/Areas/Acquisition/Controllers/AcquisitionFileController.cs b/source/backend/api/Areas/Acquisition/Controllers/AcquisitionFileController.cs index 7567517652..224326dee4 100644 --- a/source/backend/api/Areas/Acquisition/Controllers/AcquisitionFileController.cs +++ b/source/backend/api/Areas/Acquisition/Controllers/AcquisitionFileController.cs @@ -68,6 +68,7 @@ public AcquisitionFileController(IAcquisitionFileService acquisitionService, ICo [Produces("application/json")] [ProducesResponseType(typeof(AcquisitionFileModel), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFile(long id) { // RECOMMENDED - Add valuable metadata to logs @@ -109,6 +110,7 @@ public IActionResult GetLastUpdatedBy(long id) [Produces("application/json")] [ProducesResponseType(typeof(AcquisitionFileModel), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddAcquisitionFile([FromBody] AcquisitionFileModel model, [FromQuery] string[] userOverrideCodes) { _logger.LogInformation( @@ -136,6 +138,7 @@ public IActionResult AddAcquisitionFile([FromBody] AcquisitionFileModel model, [ [ProducesResponseType(typeof(AcquisitionFileModel), 200)] [ProducesResponseType(typeof(ErrorResponseModel), 409)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateAcquisitionFile(long id, [FromBody] AcquisitionFileModel model, [FromQuery] string[] userOverrideCodes) { _logger.LogInformation( @@ -161,6 +164,7 @@ public IActionResult UpdateAcquisitionFile(long id, [FromBody] AcquisitionFileMo [Produces("application/json")] [ProducesResponseType(typeof(AcquisitionFileModel), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateAcquisitionFileProperties([FromBody] AcquisitionFileModel acquisitionFileModel, [FromQuery] string[] userOverrideCodes) { var acquisitionFileEntity = _mapper.Map(acquisitionFileModel); @@ -177,6 +181,7 @@ public IActionResult UpdateAcquisitionFileProperties([FromBody] AcquisitionFileM [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFileProperties(long id) { var acquisitionFileProperties = _acquisitionService.GetProperties(id); @@ -193,6 +198,7 @@ public IActionResult GetAcquisitionFileProperties(long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFileOwners([FromRoute] long id) { var owners = _acquisitionService.GetOwners(id); @@ -210,6 +216,7 @@ public IActionResult GetAcquisitionFileOwners([FromRoute] long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionTeamMembers() { var team = _acquisitionService.GetTeamMembers(); @@ -243,6 +250,7 @@ public IActionResult GetFileCompensations(long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "comp-req-h120s" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFileCompReqH120(long id, bool? finalOnly) { _logger.LogInformation( @@ -269,6 +277,7 @@ public IActionResult GetFileCompReqH120(long id, bool? finalOnly) [Produces("application/json")] [ProducesResponseType(typeof(CompensationRequisitionModel), 201)] [SwaggerOperation(Tags = new[] { "compensation-requisition" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddCompensationRequisition([FromRoute] long id, [FromBody] CompensationRequisitionModel compensationRequisition) { _logger.LogInformation( @@ -323,6 +332,7 @@ public IActionResult GetAcquisitionFileExpropriationPayments([FromRoute] long id [Produces("application/json")] [ProducesResponseType(typeof(ExpropriationPaymentModel), 201)] [SwaggerOperation(Tags = new[] { "expropriation-payments" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddExpropriationPayment([FromRoute] long id, [FromBody] ExpropriationPaymentModel expropriationPayment) { _logger.LogInformation( diff --git a/source/backend/api/Areas/Acquisition/Controllers/AgreementController.cs b/source/backend/api/Areas/Acquisition/Controllers/AgreementController.cs index 9b159f658a..c52d79ccab 100644 --- a/source/backend/api/Areas/Acquisition/Controllers/AgreementController.cs +++ b/source/backend/api/Areas/Acquisition/Controllers/AgreementController.cs @@ -5,6 +5,7 @@ using Pims.Api.Models.Concepts.AcquisitionFile; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -67,6 +68,7 @@ public IActionResult GetAcquisitionFileAgreements([FromRoute] long id) [Produces("application/json")] [ProducesResponseType(typeof(AgreementModel), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateAcquisitionFileAgreements([FromRoute] long id, [FromBody] List agreements) { var agreementEntities = _mapper.Map>(agreements); diff --git a/source/backend/api/Areas/Acquisition/Controllers/ChecklistController.cs b/source/backend/api/Areas/Acquisition/Controllers/ChecklistController.cs index 0dd7935589..73265a5bac 100644 --- a/source/backend/api/Areas/Acquisition/Controllers/ChecklistController.cs +++ b/source/backend/api/Areas/Acquisition/Controllers/ChecklistController.cs @@ -2,10 +2,12 @@ using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Pims.Api.Helpers.Exceptions; using Pims.Api.Models.Concepts.AcquisitionFile; using Pims.Api.Models.Concepts.File; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -53,6 +55,7 @@ public ChecklistController(IAcquisitionFileService acquisitionService, IMapper m [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFileChecklist([FromRoute] long id) { var checklist = _acquisitionService.GetChecklistItems(id); @@ -63,15 +66,25 @@ public IActionResult GetAcquisitionFileChecklist([FromRoute] long id) /// Update the acquisition file checklist. /// /// The updated checklist items. - [HttpPut("{id:long}/checklist")] + [HttpPut("{acquisitionFileId:long}/checklist")] [HasPermission(Permissions.AcquisitionFileEdit)] [Produces("application/json")] [ProducesResponseType(typeof(AcquisitionFileModel), 200)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] - public IActionResult UpdateAcquisitionFileChecklist([FromBody] AcquisitionFileModel acquisitionFileModel) + [TypeFilter(typeof(NullJsonResultFilter))] + public IActionResult UpdateAcquisitionFileChecklist(long acquisitionFileId, [FromBody] IList checklistItems) { - var acquisitionFileEntity = _mapper.Map(acquisitionFileModel); - var acquisitionFile = _acquisitionService.UpdateChecklistItems(acquisitionFileEntity); + + foreach (var item in checklistItems) + { + if (item.FileId != acquisitionFileId) + { + throw new BadRequestException("All checklist items file id must match the acquisition file id"); + } + } + + var checklistItemEntities = _mapper.Map>(checklistItems); + var acquisitionFile = _acquisitionService.UpdateChecklistItems(checklistItemEntities); return new JsonResult(_mapper.Map(acquisitionFile)); } diff --git a/source/backend/api/Areas/Acquisition/Controllers/SearchController.cs b/source/backend/api/Areas/Acquisition/Controllers/SearchController.cs index 106dce067c..e24f742915 100644 --- a/source/backend/api/Areas/Acquisition/Controllers/SearchController.cs +++ b/source/backend/api/Areas/Acquisition/Controllers/SearchController.cs @@ -15,6 +15,7 @@ namespace Pims.Api.Areas.Acquisition.Controllers using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Extensions; + using Pims.Core.Json; using Pims.Dal.Entities.Models; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -66,6 +67,7 @@ public SearchController(IAcquisitionFileService acquisitionService, IMapper mapp [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFiles() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -83,6 +85,7 @@ public IActionResult GetAcquisitionFiles() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "acquisitionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAcquisitionFiles([FromBody] AcquisitionFilterModel filter) { // RECOMMENDED - Add valuable metadata to logs diff --git a/source/backend/api/Areas/Admin/Controllers/AccessRequestController.cs b/source/backend/api/Areas/Admin/Controllers/AccessRequestController.cs index 20970c040b..84da375c97 100644 --- a/source/backend/api/Areas/Admin/Controllers/AccessRequestController.cs +++ b/source/backend/api/Areas/Admin/Controllers/AccessRequestController.cs @@ -4,6 +4,7 @@ using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.AccessRequest; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -57,6 +58,7 @@ public AccessRequestController(IAccessRequestRepository accessRequestRepository, [ProducesResponseType(typeof(PageModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-access-requests" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetPage(int page = 1, int quantity = 10, string searchText = null, string sort = null) { if (page < 1) @@ -94,6 +96,7 @@ public IActionResult GetPage(int page = 1, int quantity = 10, string searchText [ProducesResponseType(typeof(ErrorResponseModel), 400)] [ProducesResponseType(typeof(ErrorResponseModel), 403)] [SwaggerOperation(Tags = new[] { "user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetAccessRequest(long id) { var accessRequest = _accessRequestRepository.GetById(id); @@ -111,6 +114,7 @@ public IActionResult GetAccessRequest(long id) [ProducesResponseType(typeof(AccessRequestModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-access-requests" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult Delete(long id, [FromBody] AccessRequestModel model) { var entity = _mapper.Map(model); diff --git a/source/backend/api/Areas/Admin/Controllers/ClaimController.cs b/source/backend/api/Areas/Admin/Controllers/ClaimController.cs index 62baa7c038..c508d9b0d6 100644 --- a/source/backend/api/Areas/Admin/Controllers/ClaimController.cs +++ b/source/backend/api/Areas/Admin/Controllers/ClaimController.cs @@ -4,6 +4,7 @@ using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.Claim; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -55,6 +56,7 @@ public ClaimController(IClaimRepository claimRepository, IMapper mapper) [ProducesResponseType(typeof(PageModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-claim" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetClaims(int page = 1, int quantity = 10, string name = null) { if (page < 1) diff --git a/source/backend/api/Areas/Admin/Controllers/FinancialCodeController.cs b/source/backend/api/Areas/Admin/Controllers/FinancialCodeController.cs index b1db920f2a..b0666781fb 100644 --- a/source/backend/api/Areas/Admin/Controllers/FinancialCodeController.cs +++ b/source/backend/api/Areas/Admin/Controllers/FinancialCodeController.cs @@ -5,6 +5,7 @@ using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Exceptions; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -49,6 +50,7 @@ public FinancialCodeController(IFinancialCodeService financialCodeService) [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFinancialCodes() { var allCodes = _financialCodeService.GetAllFinancialCodes(); @@ -67,6 +69,7 @@ public IActionResult GetFinancialCodes() [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 409)] [SwaggerOperation(Tags = new[] { "admin-financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFinancialCode(FinancialCodeTypes type, long codeId) { return new JsonResult(_financialCodeService.GetById(type, codeId)); @@ -84,6 +87,7 @@ public IActionResult GetFinancialCode(FinancialCodeTypes type, long codeId) [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 409)] [SwaggerOperation(Tags = new[] { "admin-financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddFinancialCode(FinancialCodeTypes type, [FromBody] FinancialCodeModel codeModel) { try @@ -110,6 +114,7 @@ public IActionResult AddFinancialCode(FinancialCodeTypes type, [FromBody] Financ [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 409)] [SwaggerOperation(Tags = new[] { "admin-financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateFinancialCode(FinancialCodeTypes type, long codeId, [FromBody] FinancialCodeModel codeModel) { try diff --git a/source/backend/api/Areas/Admin/Controllers/RoleController.cs b/source/backend/api/Areas/Admin/Controllers/RoleController.cs index 4402b190e1..b26b8bc93c 100644 --- a/source/backend/api/Areas/Admin/Controllers/RoleController.cs +++ b/source/backend/api/Areas/Admin/Controllers/RoleController.cs @@ -4,6 +4,7 @@ using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.Role; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -55,6 +56,7 @@ public RoleController(IRoleRepository roleRepository, IMapper mapper) [ProducesResponseType(typeof(PageModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-role" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetRoles(int page = 1, int quantity = 10, string name = null) { if (page < 1) @@ -87,6 +89,7 @@ public IActionResult GetRoles(int page = 1, int quantity = 10, string name = nul [ProducesResponseType(typeof(RoleModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-role" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetRole(Guid key) { var entity = _roleRepository.GetByKey(key); diff --git a/source/backend/api/Areas/Admin/Controllers/UserController.cs b/source/backend/api/Areas/Admin/Controllers/UserController.cs index 47b4e51737..6f0a9ebe18 100644 --- a/source/backend/api/Areas/Admin/Controllers/UserController.cs +++ b/source/backend/api/Areas/Admin/Controllers/UserController.cs @@ -5,6 +5,7 @@ using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.User; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Entities; using Pims.Dal.Entities.Models; using Pims.Dal.Repositories; @@ -54,6 +55,7 @@ public UserController(IUserRepository userRepository, IMapper mapper) [ProducesResponseType(typeof(PageModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetUsers() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -71,6 +73,7 @@ public IActionResult GetUsers() [ProducesResponseType(typeof(PageModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetUsers(UserFilter filter) { var page = _userRepository.GetAllByFilter(filter); @@ -88,6 +91,7 @@ public IActionResult GetUsers(UserFilter filter) [ProducesResponseType(typeof(UserModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetUser(long id) { var entity = _userRepository.GetById(id); @@ -105,6 +109,7 @@ public IActionResult GetUser(long id) [ProducesResponseType(typeof(UserModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetUser(Guid key) { var entity = _userRepository.GetByKeycloakUserId(key); @@ -122,6 +127,7 @@ public IActionResult GetUser(Guid key) [ProducesResponseType(typeof(UserModel), 201)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddUser([FromBody] UserModel model) { var entity = _mapper.Map(model); @@ -143,7 +149,7 @@ public IActionResult AddUser([FromBody] UserModel model) [ProducesResponseType(typeof(UserModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter 'id' is required for route.")] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateUser(Guid key, [FromBody] UserModel model) { var entity = _mapper.Map(model); @@ -164,7 +170,7 @@ public IActionResult UpdateUser(Guid key, [FromBody] UserModel model) [ProducesResponseType(typeof(UserModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter 'key' is required for route.")] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult DeleteUser(Guid key, [FromBody] UserModel model) { var entity = _mapper.Map(model); diff --git a/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs b/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs index b3bd8e8578..8038503313 100644 --- a/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs +++ b/source/backend/api/Areas/CompensationRequisition/Controllers/CompensationRequisitionController.cs @@ -46,6 +46,7 @@ public CompensationRequisitionController(IMapper mapper, ILogger), 200)] [SwaggerOperation(Tags = new[] { "h120" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetH120Categories() { _logger.LogInformation( diff --git a/source/backend/api/Areas/Disposition/Controllers/ChecklistController.cs b/source/backend/api/Areas/Disposition/Controllers/ChecklistController.cs index 62c3cfbdae..0587a22ce6 100644 --- a/source/backend/api/Areas/Disposition/Controllers/ChecklistController.cs +++ b/source/backend/api/Areas/Disposition/Controllers/ChecklistController.cs @@ -2,10 +2,12 @@ using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Pims.Api.Helpers.Exceptions; using Pims.Api.Models.Concepts.DispositionFile; using Pims.Api.Models.Concepts.File; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -53,6 +55,7 @@ public ChecklistController(IDispositionFileService dispositionService, IMapper m [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDispositionFileChecklist([FromRoute] long id) { var checklist = _dispositionService.GetChecklistItems(id); @@ -68,10 +71,20 @@ public IActionResult GetDispositionFileChecklist([FromRoute] long id) [Produces("application/json")] [ProducesResponseType(typeof(DispositionFileModel), 200)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] - public IActionResult UpdateDispositionFileChecklist([FromBody] DispositionFileModel dispositionFileModel) + [TypeFilter(typeof(NullJsonResultFilter))] + public IActionResult UpdateDispositionFileChecklist(long id, [FromBody] IList checklistItems) { - var dispositionFileEntity = _mapper.Map(dispositionFileModel); - var dispositionFile = _dispositionService.UpdateChecklistItems(dispositionFileEntity); + + foreach (var item in checklistItems) + { + if (item.FileId != id) + { + throw new BadRequestException("All checklist items file id must match the disposition file id"); + } + } + + var checklistItemEntities = _mapper.Map>(checklistItems); + var dispositionFile = _dispositionService.UpdateChecklistItems(checklistItemEntities); return new JsonResult(_mapper.Map(dispositionFile)); } diff --git a/source/backend/api/Areas/Disposition/Controllers/DispositionFileController.cs b/source/backend/api/Areas/Disposition/Controllers/DispositionFileController.cs index 6a2f5e677c..361e7e527b 100644 --- a/source/backend/api/Areas/Disposition/Controllers/DispositionFileController.cs +++ b/source/backend/api/Areas/Disposition/Controllers/DispositionFileController.cs @@ -5,10 +5,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using Pims.Api.Areas.Acquisition.Controllers; using Pims.Api.Helpers.Exceptions; using Pims.Api.Models.Concepts.DispositionFile; -using Pims.Api.Models.Concepts.DispositionFile; using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Exceptions; @@ -91,6 +89,7 @@ public IActionResult GetDispositionFile(long id) [Produces("application/json")] [ProducesResponseType(typeof(DispositionFileModel), 200)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddDispositionFile([FromBody] DispositionFileModel model, [FromQuery] string[] userOverrideCodes) { _logger.LogInformation( @@ -114,6 +113,7 @@ public IActionResult AddDispositionFile([FromBody] DispositionFileModel model, [ [Produces("application/json")] [ProducesResponseType(typeof(DispositionFileModel), 200)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateDispositionFile([FromRoute]long id, [FromBody] DispositionFileModel model, [FromQuery] string[] userOverrideCodes) { _logger.LogInformation( @@ -188,6 +188,7 @@ public IActionResult GetDispositionFileProperties(long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDispositionTeamMembers() { _logger.LogInformation( diff --git a/source/backend/api/Areas/Disposition/Controllers/SearchController.cs b/source/backend/api/Areas/Disposition/Controllers/SearchController.cs index ebe88062d4..ad9a59ed4f 100644 --- a/source/backend/api/Areas/Disposition/Controllers/SearchController.cs +++ b/source/backend/api/Areas/Disposition/Controllers/SearchController.cs @@ -13,6 +13,7 @@ using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Extensions; +using Pims.Core.Json; using Pims.Dal.Entities.Models; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -65,6 +66,7 @@ public SearchController(IDispositionFileService dispositionService, IMapper mapp [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDispositionFiles() { var uri = new Uri(Request.GetDisplayUrl()); @@ -82,6 +84,7 @@ public IActionResult GetDispositionFiles() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "dispositionfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDispositionFiles([FromBody] DispositionFilterModel filter) { _logger.LogInformation( diff --git a/source/backend/api/Areas/DocumentGeneration/DocumentGenerationController.cs b/source/backend/api/Areas/DocumentGeneration/DocumentGenerationController.cs index 1dc9ed979e..afaabf981d 100644 --- a/source/backend/api/Areas/DocumentGeneration/DocumentGenerationController.cs +++ b/source/backend/api/Areas/DocumentGeneration/DocumentGenerationController.cs @@ -7,6 +7,7 @@ using Pims.Api.Models.Requests.Http; using Pims.Api.Services; +using Pims.Core.Json; using Swashbuckle.AspNetCore.Annotations; namespace Pims.Api.Controllers @@ -49,6 +50,7 @@ public DocumentGenerationController(IDocumentGenerationService documentGeneratio [Produces("application/json")] [ProducesResponseType(typeof(ExternalResponse), 200)] [SwaggerOperation(Tags = new[] { "document-generation" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task GetSupportedDocumentTypes() { var supportedFileTypes = await _documentGenerationService.GetSupportedFileTypes(); @@ -63,6 +65,7 @@ public async Task GetSupportedDocumentTypes() // [HasPermission(Permissions.GenerateDocuments)] [ProducesResponseType(typeof(ExternalResponse), 200)] [SwaggerOperation(Tags = new[] { "document-generation" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task UploadTemplate([FromForm] IFormFile file) { var result = await _documentGenerationService.UploadFileTemplate(file); diff --git a/source/backend/api/Areas/DocumentGeneration/DocumentGenerationRequest.cs b/source/backend/api/Areas/DocumentGeneration/DocumentGenerationRequest.cs index aac8a38585..6fcc9b93f1 100644 --- a/source/backend/api/Areas/DocumentGeneration/DocumentGenerationRequest.cs +++ b/source/backend/api/Areas/DocumentGeneration/DocumentGenerationRequest.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; namespace Pims.Api.Models.DocumentGeneration { diff --git a/source/backend/api/Areas/Documents/DocumentController.cs b/source/backend/api/Areas/Documents/DocumentController.cs index bb38ce59ab..e587c450d8 100644 --- a/source/backend/api/Areas/Documents/DocumentController.cs +++ b/source/backend/api/Areas/Documents/DocumentController.cs @@ -11,6 +11,7 @@ using Pims.Api.Models.Requests.Http; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -56,6 +57,7 @@ public DocumentController(IDocumentService documentService, IMapper mapper) [Produces("application/json")] [ProducesResponseType(typeof(List), 200)] [SwaggerOperation(Tags = new[] { "document-types" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDocumentTypes() { var documentTypes = _documentService.GetPimsDocumentTypes(); @@ -74,6 +76,7 @@ public IActionResult GetDocumentTypes() [HasPermission(Permissions.DocumentEdit)] [ProducesResponseType(typeof(DocumentUpdateResponse), 200)] [SwaggerOperation(Tags = new[] { "documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task UpdateDocumentMetadata( long documentId, [FromBody] DocumentUpdateRequest updateRequest) @@ -94,6 +97,7 @@ public async Task UpdateDocumentMetadata( [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(ExternalResponse), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task DownloadWrappedFile(long mayanDocumentId, long mayanFileId) { var result = await _documentService.DownloadFileAsync(mayanDocumentId, mayanFileId); @@ -128,6 +132,7 @@ public async Task DownloadFile(long mayanDocumentId, long mayanFi [Produces("application/json")] [ProducesResponseType(typeof(ExternalResponse>), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDocumentList() { var result = _documentService.GetStorageDocumentList(); @@ -141,6 +146,7 @@ public IActionResult GetDocumentList() [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(ExternalResponse>), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDocumentStorageTypes() { var result = _documentService.GetStorageDocumentTypes(); @@ -154,6 +160,7 @@ public IActionResult GetDocumentStorageTypes() [HasPermission(Permissions.DocumentAdd)] [ProducesResponseType(typeof(ExternalResponse>), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task GetDocumentStorageTypeMetadata(long mayanDocumentTypeId) { var result = await _documentService.GetDocumentTypeMetadataType(mayanDocumentTypeId); @@ -167,6 +174,7 @@ public async Task GetDocumentStorageTypeMetadata(long mayanDocume [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(ExternalResponse), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task GetDocumentStorageTypeDetail(long mayanDocumentId) { var result = await _documentService.GetStorageDocumentDetail(mayanDocumentId); @@ -180,6 +188,7 @@ public async Task GetDocumentStorageTypeDetail(long mayanDocument [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(ExternalResponse), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task DownloadWrappedFile(long mayanDocumentId) { var result = await _documentService.DownloadFileLatestAsync(mayanDocumentId); @@ -197,6 +206,7 @@ public async Task DownloadWrappedFile(long mayanDocumentId) [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(FileContentResult), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task DownloadFile(long mayanDocumentId) { var result = await _documentService.DownloadFileLatestAsync(mayanDocumentId); @@ -216,6 +226,7 @@ public async Task DownloadFile(long mayanDocumentId) [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(ExternalResponse>), 200)] [SwaggerOperation(Tags = new[] { "storage-documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task GetDocumentMetadata(long mayanDocumentId) { var result = await _documentService.GetStorageDocumentMetadata(mayanDocumentId); diff --git a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs index dfecbca9bf..e58747614f 100644 --- a/source/backend/api/Areas/Documents/DocumentRelationshipController.cs +++ b/source/backend/api/Areas/Documents/DocumentRelationshipController.cs @@ -10,6 +10,7 @@ using Pims.Api.Models.Requests.Document.Upload; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Entities; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -66,6 +67,7 @@ public DocumentRelationshipController( [Produces("application/json")] [ProducesResponseType(typeof(List), 200)] [SwaggerOperation(Tags = new[] { "document-types" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetDocumentRelationshipTypes(DocumentRelationType relationshipType) { var documentTypes = _documentService.GetPimsDocumentTypes(relationshipType); @@ -84,6 +86,7 @@ public IActionResult GetDocumentRelationshipTypes(DocumentRelationType relations [HasPermission(Permissions.DocumentView)] [ProducesResponseType(typeof(IList), 200)] [SwaggerOperation(Tags = new[] { "document" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetRelationshipDocuments(DocumentRelationType relationshipType, string parentId) { switch (relationshipType) @@ -133,6 +136,7 @@ public IActionResult GetRelationshipDocuments(DocumentRelationType relationshipT [HasPermission(Permissions.DocumentAdd)] [ProducesResponseType(typeof(DocumentUploadResponse), 200)] [SwaggerOperation(Tags = new[] { "documents" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task UploadDocumentWithParent( DocumentRelationType relationshipType, string parentId, @@ -164,6 +168,7 @@ public async Task UploadDocumentWithParent( [HasPermission(Permissions.DocumentDelete)] [ProducesResponseType(typeof(bool), 200)] [SwaggerOperation(Tags = new[] { "document" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task DeleteDocumentRelationship(DocumentRelationType relationshipType, [FromBody] DocumentRelationshipModel model) { switch (relationshipType) diff --git a/source/backend/api/Areas/ExpropriationPayment/ExpropriationPaymentController.cs b/source/backend/api/Areas/ExpropriationPayment/ExpropriationPaymentController.cs index 0183051ece..794acce65a 100644 --- a/source/backend/api/Areas/ExpropriationPayment/ExpropriationPaymentController.cs +++ b/source/backend/api/Areas/ExpropriationPayment/ExpropriationPaymentController.cs @@ -46,6 +46,7 @@ public ExpropriationPaymentController(IMapper mapper, ILogger), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFinancialCodesByType(FinancialCodeTypes type) { return new JsonResult(_financialCodeService.GetFinancialCodesByType(type)); @@ -61,6 +63,7 @@ public IActionResult GetFinancialCodesByType(FinancialCodeTypes type) [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFinancialActivityCodes() { return new JsonResult(_financialCodeService.GetFinancialCodesByType(FinancialCodeTypes.FinancialActivity)); @@ -75,6 +78,7 @@ public IActionResult GetFinancialActivityCodes() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetChartOfAccounts() { return new JsonResult(_financialCodeService.GetFinancialCodesByType(FinancialCodeTypes.ChartOfAccounts)); @@ -89,6 +93,7 @@ public IActionResult GetChartOfAccounts() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetResponsibilities() { return new JsonResult(_financialCodeService.GetFinancialCodesByType(FinancialCodeTypes.Responsibility)); @@ -103,6 +108,7 @@ public IActionResult GetResponsibilities() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "financialcodes" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetYearlyFinancials() { return new JsonResult(_financialCodeService.GetFinancialCodesByType(FinancialCodeTypes.YearlyFinancial)); diff --git a/source/backend/api/Areas/FormDocument/FormDocumentController.cs b/source/backend/api/Areas/FormDocument/FormDocumentController.cs index b9a29895e4..a68f8d2728 100644 --- a/source/backend/api/Areas/FormDocument/FormDocumentController.cs +++ b/source/backend/api/Areas/FormDocument/FormDocumentController.cs @@ -7,6 +7,7 @@ using Pims.Api.Models.Concepts.FormDocument; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Entities; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -57,6 +58,7 @@ public FormDocumentController(IFormDocumentService formDocumentService, IMapper [Produces("application/json")] [ProducesResponseType(typeof(List), 200)] [SwaggerOperation(Tags = new[] { "form-document" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFormDocumentTypes() { var supportedFileTypes = _formDocumentService.GetAllFormDocumentTypes(); @@ -74,6 +76,7 @@ public IActionResult GetFormDocumentTypes() [Produces("application/json")] [ProducesResponseType(typeof(FormDocumentFileModel), 200)] [SwaggerOperation(Tags = new[] { "form" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddFormDocumentFile(FileType fileType, [FromBody] FormDocumentFileModel formFileModel) { switch (fileType) @@ -98,6 +101,7 @@ public IActionResult AddFormDocumentFile(FileType fileType, [FromBody] FormDocum [Produces("application/json")] [ProducesResponseType(typeof(FormDocumentFileModel), 200)] [SwaggerOperation(Tags = new[] { "form" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFileForms(FileType fileType, long fileId) { IEnumerable forms; @@ -120,6 +124,7 @@ public IActionResult GetFileForms(FileType fileType, long fileId) [Produces("application/json")] [ProducesResponseType(typeof(FormDocumentFileModel), 200)] [SwaggerOperation(Tags = new[] { "form" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFileForm(FileType fileType, long fileFormId) { FormDocumentFileModel form; @@ -142,6 +147,7 @@ public IActionResult GetFileForm(FileType fileType, long fileFormId) [HasPermission(Permissions.FormDelete)] [ProducesResponseType(typeof(bool), 200)] [SwaggerOperation(Tags = new[] { "activity" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult DeleteFileForm(FileType fileType, long fileFormId) { var deleted = fileType switch diff --git a/source/backend/api/Areas/Keycloak/Controllers/AccessRequestController.cs b/source/backend/api/Areas/Keycloak/Controllers/AccessRequestController.cs index 3923992245..e9cda2d23d 100644 --- a/source/backend/api/Areas/Keycloak/Controllers/AccessRequestController.cs +++ b/source/backend/api/Areas/Keycloak/Controllers/AccessRequestController.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Mvc; using Pims.Api.Models.Concepts.AccessRequest; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Keycloak; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -53,6 +54,7 @@ public AccessRequestController(IMapper mapper, IPimsKeycloakService keycloakServ [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "keycloak-user" })] [HasPermission(Permissions.AdminUsers)] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task UpdateAccessRequestAsync(AccessRequestModel model) { var accessRequest = _mapper.Map(model); diff --git a/source/backend/api/Areas/Keycloak/Controllers/UserController.cs b/source/backend/api/Areas/Keycloak/Controllers/UserController.cs index ad500d2c22..3a2bd84da3 100644 --- a/source/backend/api/Areas/Keycloak/Controllers/UserController.cs +++ b/source/backend/api/Areas/Keycloak/Controllers/UserController.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Mvc; using Pims.Api.Models.Concepts.User; using Pims.Api.Policies; +using Pims.Core.Json; using Pims.Dal.Keycloak; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -55,7 +56,7 @@ public UserController(IMapper mapper, IPimsKeycloakService keycloakService) [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "admin-user" })] [HasPermission(Permissions.AdminUsers)] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "Parameter 'key' is required for route.")] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task UpdateUserAsync(Guid key, [FromBody] UserModel model) { var user = _mapper.Map(model); diff --git a/source/backend/api/Areas/Leases/Controllers/LeaseController.cs b/source/backend/api/Areas/Leases/Controllers/LeaseController.cs index e96c332749..fd5e0f2887 100644 --- a/source/backend/api/Areas/Leases/Controllers/LeaseController.cs +++ b/source/backend/api/Areas/Leases/Controllers/LeaseController.cs @@ -85,6 +85,7 @@ public IActionResult GetLease(int id) [Produces("application/json")] [ProducesResponseType(typeof(Dal.Entities.Models.LastUpdatedByModel), 200)] [SwaggerOperation(Tags = new[] { "lease" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetLastUpdatedBy(long id) { var lastUpdated = _leaseService.GetLastUpdateInformation(id); diff --git a/source/backend/api/Areas/Notes/Controllers/NoteController.cs b/source/backend/api/Areas/Notes/Controllers/NoteController.cs index 4123dd4c61..30e03e860d 100644 --- a/source/backend/api/Areas/Notes/Controllers/NoteController.cs +++ b/source/backend/api/Areas/Notes/Controllers/NoteController.cs @@ -6,6 +6,7 @@ using Pims.Api.Models.Concepts.Note; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -55,6 +56,7 @@ public NoteController(INoteService noteService, IMapper mapper) [Produces("application/json")] [ProducesResponseType(typeof(EntityNoteModel), 200)] [SwaggerOperation(Tags = new[] { "note" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddNote(NoteType type, [FromBody] EntityNoteModel noteModel) { var createdNote = _noteService.Add(type, noteModel); @@ -72,6 +74,7 @@ public IActionResult AddNote(NoteType type, [FromBody] EntityNoteModel noteModel [HasPermission(Permissions.NoteView)] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "note" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetNotes(NoteType type, long entityId) { var notes = _noteService.GetNotes(type, entityId); @@ -89,6 +92,7 @@ public IActionResult GetNotes(NoteType type, long entityId) [HasPermission(Permissions.NoteView)] [ProducesResponseType(typeof(NoteModel), 200)] [SwaggerOperation(Tags = new[] { "note" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetNoteById(long noteId) { var note = _noteService.GetById(noteId); @@ -106,6 +110,7 @@ public IActionResult GetNoteById(long noteId) [HasPermission(Permissions.NoteEdit)] [ProducesResponseType(typeof(NoteModel), 200)] [SwaggerOperation(Tags = new[] { "note" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateNote(long noteId, [FromBody] NoteModel noteModel) { if (noteId != noteModel.Id) @@ -127,6 +132,7 @@ public IActionResult UpdateNote(long noteId, [FromBody] NoteModel noteModel) [HasPermission(Permissions.NoteDelete)] [ProducesResponseType(typeof(bool), 200)] [SwaggerOperation(Tags = new[] { "note" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult DeleteNote(NoteType type, long noteId) { return new JsonResult(_noteService.DeleteNote(type, noteId)); diff --git a/source/backend/api/Areas/Organizations/Controllers/OrganizationController.cs b/source/backend/api/Areas/Organizations/Controllers/OrganizationController.cs index 4cab26bf88..69da5be52d 100644 --- a/source/backend/api/Areas/Organizations/Controllers/OrganizationController.cs +++ b/source/backend/api/Areas/Organizations/Controllers/OrganizationController.cs @@ -6,6 +6,7 @@ using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Exceptions; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -72,6 +73,7 @@ public IActionResult GetOrganization(int id) [Produces("application/json")] [ProducesResponseType(typeof(OrganizationModel), 200)] [SwaggerOperation(Tags = new[] { "organization" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetOrganizationConcept(int id) { var organization = _organizationService.GetOrganization(id); diff --git a/source/backend/api/Areas/Persons/Controllers/PersonController.cs b/source/backend/api/Areas/Persons/Controllers/PersonController.cs index 343401976a..5b98237cf8 100644 --- a/source/backend/api/Areas/Persons/Controllers/PersonController.cs +++ b/source/backend/api/Areas/Persons/Controllers/PersonController.cs @@ -6,6 +6,7 @@ using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Exceptions; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -71,6 +72,7 @@ public IActionResult GetPerson(int id) [HasPermission(Permissions.ContactView)] [Produces("application/json")] [ProducesResponseType(typeof(PersonModel), 200)] + [TypeFilter(typeof(NullJsonResultFilter))] [SwaggerOperation(Tags = new[] { "person" })] public IActionResult GetPersonConcept(int id) { diff --git a/source/backend/api/Areas/Products/Controllers/ProductController.cs b/source/backend/api/Areas/Products/Controllers/ProductController.cs index 2388f9b13f..52ef630a32 100644 --- a/source/backend/api/Areas/Products/Controllers/ProductController.cs +++ b/source/backend/api/Areas/Products/Controllers/ProductController.cs @@ -5,6 +5,7 @@ using Pims.Api.Models.Concepts.AcquisitionFile; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -49,6 +50,7 @@ public ProductController(IProjectService projectService, IMapper mapper) [ProducesResponseType(typeof(List), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "product" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetFiles(long productId) { var acquisitionFiles = _projectService.GetProductFiles(productId); diff --git a/source/backend/api/Areas/Projects/Controllers/ProjectController.cs b/source/backend/api/Areas/Projects/Controllers/ProjectController.cs index d40feea872..aced1ea4b5 100644 --- a/source/backend/api/Areas/Projects/Controllers/ProjectController.cs +++ b/source/backend/api/Areas/Projects/Controllers/ProjectController.cs @@ -116,6 +116,7 @@ public IActionResult GetAll() [ProducesResponseType(typeof(ProjectModel), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "project" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddProject(ProjectModel projectModel, [FromQuery] string[] userOverrideCodes) { try @@ -138,6 +139,7 @@ public IActionResult AddProject(ProjectModel projectModel, [FromQuery] string[] [Produces("application/json")] [ProducesResponseType(typeof(ProjectModel), 200)] [SwaggerOperation(Tags = new[] { "project" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateProject([FromRoute] long id, [FromBody] ProjectModel model, [FromQuery] string[] userOverrideCodes) { if (id != model.Id) diff --git a/source/backend/api/Areas/Projects/Controllers/SearchController.cs b/source/backend/api/Areas/Projects/Controllers/SearchController.cs index 6c73641eb6..0b4ea92dfa 100644 --- a/source/backend/api/Areas/Projects/Controllers/SearchController.cs +++ b/source/backend/api/Areas/Projects/Controllers/SearchController.cs @@ -8,6 +8,7 @@ using Pims.Api.Models.Concepts.Project; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Entities.Models; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -37,6 +38,7 @@ public SearchController(IProjectService projectService, IMapper mapper) [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "project" })] + [TypeFilter(typeof(NullJsonResultFilter))] public async Task GetProject([FromQuery] ProjectFilterModel filter) { var projects = await _projectService.GetPage((ProjectFilter)filter); diff --git a/source/backend/api/Areas/Property/Controllers/PropertyController.cs b/source/backend/api/Areas/Property/Controllers/PropertyController.cs index 9e67cfd5b4..24ff510b2b 100644 --- a/source/backend/api/Areas/Property/Controllers/PropertyController.cs +++ b/source/backend/api/Areas/Property/Controllers/PropertyController.cs @@ -2,10 +2,10 @@ using MapsterMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Pims.Api.Areas.Property.Models.Property; using Pims.Api.Models.Concepts.Property; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Repositories; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -55,13 +55,14 @@ public PropertyController(IPropertyRepository propertyRepository, IPropertyServi [HttpGet("{id}/associations")] [HasPermission(Permissions.PropertyView)] [Produces("application/json")] - [ProducesResponseType(typeof(PropertyAssociationModel), 200)] + [ProducesResponseType(typeof(PropertyAssociationsModel), 200)] [SwaggerOperation(Tags = new[] { "property" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetPropertyAssociationsWithId(long id) { var property = _propertyRepository.GetAllAssociationsById(id); - return new JsonResult(_mapper.Map(property)); + return new JsonResult(_mapper.Map(property)); } #endregion @@ -77,6 +78,7 @@ public IActionResult GetPropertyAssociationsWithId(long id) [Produces("application/json")] [ProducesResponseType(typeof(PropertyModel), 200)] [SwaggerOperation(Tags = new[] { "property" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetConceptPropertyWithId(long id) { var property = _propertyService.GetById(id); @@ -92,6 +94,7 @@ public IActionResult GetConceptPropertyWithId(long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "property" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetMultipleConceptPropertyWithId([FromQuery] long[] ids) { var property = _propertyService.GetMultipleById(new List(ids)); @@ -107,6 +110,7 @@ public IActionResult GetMultipleConceptPropertyWithId([FromQuery] long[] ids) [Produces("application/json")] [ProducesResponseType(typeof(PropertyModel), 200)] [SwaggerOperation(Tags = new[] { "property" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateConceptProperty([FromBody] PropertyModel propertyModel) { var propertyEntity = _mapper.Map(propertyModel); diff --git a/source/backend/api/Areas/Research/Controllers/ResearchFileController.cs b/source/backend/api/Areas/Research/Controllers/ResearchFileController.cs index 5b683de5f0..97cc1574dd 100644 --- a/source/backend/api/Areas/Research/Controllers/ResearchFileController.cs +++ b/source/backend/api/Areas/Research/Controllers/ResearchFileController.cs @@ -7,6 +7,7 @@ using Pims.Api.Models.Concepts.ResearchFile; using Pims.Api.Policies; using Pims.Api.Services; +using Pims.Core.Json; using Pims.Dal.Entities; using Pims.Dal.Exceptions; using Pims.Dal.Security; @@ -56,6 +57,7 @@ public ResearchFileController(IResearchFileService researchFileService, IMapper [Produces("application/json")] [ProducesResponseType(typeof(ResearchFileModel), 200)] [SwaggerOperation(Tags = new[] { "researchfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetResearchFile(long id) { var researchFile = _researchFileService.GetById(id); @@ -86,6 +88,7 @@ public IActionResult GetLastUpdatedBy(long id) [Produces("application/json")] [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "researchfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetResearchFileProperties(long id) { var researchFileProperties = _researchFileService.GetProperties(id); @@ -102,6 +105,7 @@ public IActionResult GetResearchFileProperties(long id) [Produces("application/json")] [ProducesResponseType(typeof(ResearchFileModel), 200)] [SwaggerOperation(Tags = new[] { "researchfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult AddResearchFile(ResearchFileModel researchFileModel, [FromQuery] string[] userOverrideCodes) { var researchFileEntity = _mapper.Map(researchFileModel); @@ -119,6 +123,7 @@ public IActionResult AddResearchFile(ResearchFileModel researchFileModel, [FromQ [Produces("application/json")] [ProducesResponseType(typeof(ResearchFileModel), 200)] [SwaggerOperation(Tags = new[] { "researchfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateResearchFile([FromBody] ResearchFileModel researchFileModel) { var researchFileEntity = _mapper.Map(researchFileModel); @@ -136,6 +141,7 @@ public IActionResult UpdateResearchFile([FromBody] ResearchFileModel researchFil [Produces("application/json")] [ProducesResponseType(typeof(ResearchFileModel), 200)] [SwaggerOperation(Tags = new[] { "researchfile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateResearchFileProperties([FromBody] ResearchFileModel researchFileModel, [FromQuery] string[] userOverrideCodes) { var researchFileEntity = _mapper.Map(researchFileModel); @@ -153,6 +159,7 @@ public IActionResult UpdateResearchFileProperties([FromBody] ResearchFileModel r [Produces("application/json")] [ProducesResponseType(typeof(ResearchFilePropertyModel), 200)] [SwaggerOperation(Tags = new[] { "researchFile" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateResearchFileProperty(long researchFileId, long researchFilePropertyId, [FromBody] ResearchFilePropertyModel researchFilePropertyModel) { if (researchFilePropertyId != researchFilePropertyModel.Id) diff --git a/source/backend/api/Areas/Research/Controllers/SearchController.cs b/source/backend/api/Areas/Research/Controllers/SearchController.cs index fddc6cdabb..74ed6af17d 100644 --- a/source/backend/api/Areas/Research/Controllers/SearchController.cs +++ b/source/backend/api/Areas/Research/Controllers/SearchController.cs @@ -13,6 +13,7 @@ namespace Pims.Api.Areas.Research.Controllers using Pims.Api.Models.Concepts.ResearchFile; using Pims.Api.Policies; using Pims.Api.Services; + using Pims.Core.Json; using Pims.Dal.Entities.Models; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -61,6 +62,7 @@ public SearchController(IResearchFileService researchFileService, IMapper mapper [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "research", "file" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetResearchFiles() { var uri = new Uri(this.Request.GetDisplayUrl()); @@ -79,6 +81,7 @@ public IActionResult GetResearchFiles() [ProducesResponseType(typeof(IEnumerable), 200)] [ProducesResponseType(typeof(Api.Models.ErrorResponseModel), 400)] [SwaggerOperation(Tags = new[] { "research", "file" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetResearchFiles([FromBody] ResearchFilterModel filter) { filter.ThrowBadRequestIfNull($"The request must include a filter."); diff --git a/source/backend/api/Areas/Takes/Controllers/TakeController.cs b/source/backend/api/Areas/Takes/Controllers/TakeController.cs index 283eb6346f..6061efe24c 100644 --- a/source/backend/api/Areas/Takes/Controllers/TakeController.cs +++ b/source/backend/api/Areas/Takes/Controllers/TakeController.cs @@ -8,6 +8,7 @@ using Pims.Api.Policies; using Pims.Api.Services; using Pims.Core.Extensions; +using Pims.Core.Json; using Pims.Dal.Entities; using Pims.Dal.Security; using Swashbuckle.AspNetCore.Annotations; @@ -57,8 +58,9 @@ public TakeController(ITakeService takeService, IMapper mapper, ILogger), 200)] [SwaggerOperation(Tags = new[] { "take" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetTakesByAcquisitionFileId(long fileId) { _logger.LogInformation( @@ -83,8 +85,9 @@ public IActionResult GetTakesByAcquisitionFileId(long fileId) [HttpGet("acquisition/{fileId:long}/property/{acquisitionFilePropertyId:long}")] [HasPermission(Permissions.AcquisitionFileView, Permissions.PropertyView)] [Produces("application/json")] - [ProducesResponseType(typeof(TakeModel), 200)] + [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "take" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult GetTakesByPropertyId([FromRoute] long fileId, [FromRoute] long acquisitionFilePropertyId) { _logger.LogInformation( @@ -107,8 +110,9 @@ public IActionResult GetTakesByPropertyId([FromRoute] long fileId, [FromRoute] l [HttpPut("acquisition/property/{acquisitionFilePropertyId:long}")] [HasPermission(Permissions.AcquisitionFileEdit, Permissions.PropertyEdit)] [Produces("application/json")] - [ProducesResponseType(typeof(TakeModel), 200)] + [ProducesResponseType(typeof(IEnumerable), 200)] [SwaggerOperation(Tags = new[] { "take" })] + [TypeFilter(typeof(NullJsonResultFilter))] public IActionResult UpdateAcquisitionPropertyTakes(long acquisitionFilePropertyId, [FromBody] IEnumerable takes) { _logger.LogInformation( @@ -131,7 +135,7 @@ public IActionResult UpdateAcquisitionPropertyTakes(long acquisitionFileProperty [HttpGet("property/{propertyId:long}/count")] [HasPermission(Permissions.AcquisitionFileView, Permissions.PropertyView)] [Produces("application/json")] - [ProducesResponseType(typeof(TakeModel), 200)] + [ProducesResponseType(typeof(int), 200)] [SwaggerOperation(Tags = new[] { "take" })] public IActionResult GetTakesCountByPropertyId([FromRoute] long propertyId) { diff --git a/source/backend/api/Helpers/Extensions/AcquisitionFileExtensions.cs b/source/backend/api/Helpers/Extensions/AcquisitionFileExtensions.cs index 3d24e2606f..a3592a3cde 100644 --- a/source/backend/api/Helpers/Extensions/AcquisitionFileExtensions.cs +++ b/source/backend/api/Helpers/Extensions/AcquisitionFileExtensions.cs @@ -13,15 +13,9 @@ public static class AcquisitionFileExtensions { public static void ThrowMissingContractorInTeam(this PimsAcquisitionFile acquisitionFile, ClaimsPrincipal principal, IUserRepository userRepository) { - if (acquisitionFile is null) - { - throw new ArgumentNullException(nameof(acquisitionFile)); - } + ArgumentNullException.ThrowIfNull(acquisitionFile); - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + ArgumentNullException.ThrowIfNull(principal); var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); @@ -33,15 +27,9 @@ public static void ThrowMissingContractorInTeam(this PimsAcquisitionFile acquisi public static void ThrowContractorRemovedFromTeam(this PimsAcquisitionFile acquisitionFile, ClaimsPrincipal principal, IUserRepository userRepository) { - if (acquisitionFile is null) - { - throw new ArgumentNullException(nameof(acquisitionFile)); - } + ArgumentNullException.ThrowIfNull(acquisitionFile); - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + ArgumentNullException.ThrowIfNull(principal); var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); diff --git a/source/backend/api/Helpers/Extensions/DispositionFileExtensions.cs b/source/backend/api/Helpers/Extensions/DispositionFileExtensions.cs new file mode 100644 index 0000000000..f066e47fa8 --- /dev/null +++ b/source/backend/api/Helpers/Extensions/DispositionFileExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Linq; +using System.Security.Claims; +using Pims.Core.Exceptions; +using Pims.Core.Extensions; +using Pims.Dal.Entities; +using Pims.Dal.Repositories; + +namespace Pims.Api.Helpers.Extensions +{ + public static class DispositionFileExtensions + { + public static void ThrowMissingContractorInTeam(this PimsDispositionFile dispositionFile, ClaimsPrincipal principal, IUserRepository userRepository) + { + ArgumentNullException.ThrowIfNull(dispositionFile); + + ArgumentNullException.ThrowIfNull(principal); + + var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); + + if (pimsUser?.IsContractor == true && !dispositionFile.PimsDispositionFileTeams.Any(x => x.PersonId == pimsUser.PersonId)) + { + throw new ContractorNotInTeamException("As a contractor, you must add yourself as a team member to the file in order to create or save changes."); + } + } + + public static void ThrowContractorRemovedFromTeam(this PimsDispositionFile dispositionFile, ClaimsPrincipal principal, IUserRepository userRepository) + { + ArgumentNullException.ThrowIfNull(dispositionFile); + + ArgumentNullException.ThrowIfNull(principal); + + var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); + + if (pimsUser?.IsContractor == true && !dispositionFile.PimsDispositionFileTeams.Any(x => x.PersonId == pimsUser.PersonId)) + { + throw new ContractorNotInTeamException("Contractors cannot remove themselves from a Disposition file. Please contact the admin at pims@gov.bc.ca"); + } + } + } +} diff --git a/source/backend/api/Helpers/Extensions/PrincipalExtensions.cs b/source/backend/api/Helpers/Extensions/PrincipalExtensions.cs index e746fa59e3..6ff0b7cf27 100644 --- a/source/backend/api/Helpers/Extensions/PrincipalExtensions.cs +++ b/source/backend/api/Helpers/Extensions/PrincipalExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using System.Security.Claims; -using Pims.Dal.Entities.Models; +using Pims.Dal.Entities; using Pims.Dal.Exceptions; using Pims.Dal.Repositories; @@ -11,18 +11,28 @@ public static class PrincipalExtensions { public static void ThrowInvalidAccessToAcquisitionFile(this ClaimsPrincipal principal, IUserRepository userRepository, IAcquisitionFileRepository acquisitionFileRepository, long acquisitionFileId) { - if (principal is null) - { - throw new ArgumentNullException(nameof(principal)); - } + ArgumentNullException.ThrowIfNull(principal); var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); - var acquisitionFile = acquisitionFileRepository.GetById(acquisitionFileId); + PimsAcquisitionFile acquisitionFile = acquisitionFileRepository.GetById(acquisitionFileId); if (pimsUser?.IsContractor == true && !acquisitionFile.PimsAcquisitionFileTeams.Any(x => x.PersonId == pimsUser.PersonId)) { throw new NotAuthorizedException("Contractor is not assigned to the Acquisition File's team"); } } + + public static void ThrowInvalidAccessToDispositionFile(this ClaimsPrincipal principal, IUserRepository userRepository, IDispositionFileRepository dispositionFileRepository, long dispositionFileId) + { + ArgumentNullException.ThrowIfNull(principal); + + var pimsUser = userRepository.GetUserInfoByKeycloakUserId(principal.GetUserKey()); + PimsDispositionFile dispostionFile = dispositionFileRepository.GetById(dispositionFileId); + + if (pimsUser?.IsContractor == true && !dispostionFile.PimsDispositionFileTeams.Any(x => x.PersonId == pimsUser.PersonId)) + { + throw new NotAuthorizedException("Contractor is not assigned to the Disposition File's team"); + } + } } } diff --git a/source/backend/api/Helpers/Middleware/ErrorHandlingMiddleware.cs b/source/backend/api/Helpers/Middleware/ErrorHandlingMiddleware.cs index 6c5c6dbea4..2fe48f4173 100644 --- a/source/backend/api/Helpers/Middleware/ErrorHandlingMiddleware.cs +++ b/source/backend/api/Helpers/Middleware/ErrorHandlingMiddleware.cs @@ -171,12 +171,10 @@ private async Task HandleExceptionAsync(HttpContext context, Exception ex) } else if (ex is ContractorNotInTeamException) { - var exception = ex as ContractorNotInTeamException; - code = HttpStatusCode.Conflict; - message = exception.Message; - errorCode = null; + code = HttpStatusCode.BadRequest; + message = ex.Message; - _logger.LogError(ex, "Contractor User missing as a team member on the creation of Acquisition File"); + _logger.LogError(ex, ex.Message); } else if (ex is ApiHttpRequestException) { diff --git a/source/backend/api/Pims.Api.csproj b/source/backend/api/Pims.Api.csproj index 13d82c6d6d..3886eac9a7 100644 --- a/source/backend/api/Pims.Api.csproj +++ b/source/backend/api/Pims.Api.csproj @@ -2,8 +2,8 @@ 0ef6255f-9ea0-49ec-8c65-c172304b4926 - 5.0.0-72.13 - 5.0.0-72.13 + 5.0.0-72.21 + 5.0.0-72.21 5.0.0.72 true 16BC0468-78F6-4C91-87DA-7403C919E646 diff --git a/source/backend/api/Services/AcquisitionFileService.cs b/source/backend/api/Services/AcquisitionFileService.cs index ef6d216419..132d2a64d0 100644 --- a/source/backend/api/Services/AcquisitionFileService.cs +++ b/source/backend/api/Services/AcquisitionFileService.cs @@ -4,7 +4,6 @@ using System.Security.Claims; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Pims.Api.Constants; using Pims.Api.Helpers.Exceptions; using Pims.Api.Helpers.Extensions; using Pims.Api.Models.CodeTypes; @@ -87,7 +86,7 @@ public AcquisitionFileService( public Paged GetPage(AcquisitionFilter filter) { _logger.LogInformation("Searching for acquisition files..."); - _logger.LogDebug("Acquisition file search with filter", filter); + _logger.LogDebug("Acquisition file search with filter: {filter}", filter); _user.ThrowIfNotAuthorized(Permissions.AcquisitionFileView); @@ -347,23 +346,30 @@ public PimsAcquisitionFile UpdateProperties(PimsAcquisitionFile acquisitionFile, return _acqFileRepository.GetById(acquisitionFile.Internal_Id); } - public PimsAcquisitionFile UpdateChecklistItems(PimsAcquisitionFile acquisitionFile) + public PimsAcquisitionFile UpdateChecklistItems(IList checklistItems) { - acquisitionFile.ThrowIfNull(nameof(acquisitionFile)); - _logger.LogInformation("Updating acquisition file checklist with AcquisitionFile id: {id}", acquisitionFile.Internal_Id); + checklistItems.ThrowIfNull(nameof(checklistItems)); + if (checklistItems.Count == 0) + { + throw new BadRequestException("Checklist items must be greater than zero"); + } + + var acquisitionFileId = checklistItems.FirstOrDefault().AcquisitionFileId; + + _logger.LogInformation("Updating acquisition file checklist with AcquisitionFile id: {id}", acquisitionFileId); _user.ThrowIfNotAuthorized(Permissions.AcquisitionFileEdit); - _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFile.Internal_Id); + _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFileId); - var currentAcquisitionStatus = GetCurrentAcquisitionStatus(acquisitionFile.Internal_Id); + var currentAcquisitionStatus = GetCurrentAcquisitionStatus(acquisitionFileId); if (!_statusSolver.CanEditChecklists(currentAcquisitionStatus) && !_user.HasPermission(Permissions.SystemAdmin)) { throw new BusinessRuleViolationException("The file you are editing is not active or draft, so you cannot save changes. Refresh your browser to see file state."); } // Get the current checklist items for this acquisition file. - var currentItems = _checklistRepository.GetAllChecklistItemsByAcquisitionFileId(acquisitionFile.Internal_Id).ToDictionary(ci => ci.Internal_Id); + var currentItems = _checklistRepository.GetAllChecklistItemsByAcquisitionFileId(acquisitionFileId).ToDictionary(ci => ci.Internal_Id); - foreach (var incomingItem in acquisitionFile.PimsAcquisitionChecklistItems) + foreach (var incomingItem in checklistItems) { if (!currentItems.TryGetValue(incomingItem.Internal_Id, out var existingItem) && incomingItem.Internal_Id != 0) { @@ -382,7 +388,7 @@ public PimsAcquisitionFile UpdateChecklistItems(PimsAcquisitionFile acquisitionF } _checklistRepository.CommitTransaction(); - return _acqFileRepository.GetById(acquisitionFile.Internal_Id); + return _acqFileRepository.GetById(acquisitionFileId); } public IEnumerable GetAgreements(long id) @@ -496,7 +502,7 @@ public IEnumerable UpdateInterestHolders(long acquisitionFil public IList GetAcquisitionCompensations(long acquisitionFileId) { - _logger.LogInformation("Getting compensations for acquisition file id ...", acquisitionFileId); + _logger.LogInformation("Getting compensations for acquisition file id: {acquisitionFileId}", acquisitionFileId); _user.ThrowIfNotAuthorized(Permissions.CompensationRequisitionView, Permissions.AcquisitionFileView); _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFileId); @@ -505,7 +511,7 @@ public IList GetAcquisitionCompensations(long acqui public PimsCompensationRequisition AddCompensationRequisition(long acquisitionFileId, PimsCompensationRequisition compensationRequisition) { - _logger.LogInformation("Adding compensation requisition for acquisition file id ...", acquisitionFileId); + _logger.LogInformation("Adding compensation requisition for acquisition file id: {acquisitionFileId}", acquisitionFileId); _user.ThrowIfNotAuthorized(Permissions.CompensationRequisitionAdd); _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFileId); @@ -529,7 +535,7 @@ public PimsCompensationRequisition AddCompensationRequisition(long acquisitionFi public PimsExpropriationPayment AddExpropriationPayment(long acquisitionFileId, PimsExpropriationPayment expPayment) { - _logger.LogInformation("Adding Expropiation Payment for acquisition file id ...", acquisitionFileId); + _logger.LogInformation("Adding Expropiation Payment for acquisition file id: {acquisitionFileId}", acquisitionFileId); _user.ThrowIfNotAuthorized(Permissions.AcquisitionFileEdit); _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFileId); @@ -551,7 +557,7 @@ public PimsExpropriationPayment AddExpropriationPayment(long acquisitionFileId, public IList GetAcquisitionExpropriationPayments(long acquisitionFileId) { - _logger.LogInformation("Getting Expropiation Payments for acquisition file id ...", acquisitionFileId); + _logger.LogInformation("Getting Expropiation Payments for acquisition file id: {acquisitionFileId}", acquisitionFileId); _user.ThrowIfNotAuthorized(Permissions.AcquisitionFileView); _user.ThrowInvalidAccessToAcquisitionFile(_userRepository, _acqFileRepository, acquisitionFileId); diff --git a/source/backend/api/Services/CompensationRequisitionService.cs b/source/backend/api/Services/CompensationRequisitionService.cs index 82fe17692b..08b170feec 100644 --- a/source/backend/api/Services/CompensationRequisitionService.cs +++ b/source/backend/api/Services/CompensationRequisitionService.cs @@ -3,7 +3,7 @@ using System.Linq; using System.Security.Claims; using Microsoft.Extensions.Logging; -using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; using Pims.Core.Exceptions; using Pims.Core.Extensions; using Pims.Dal.Entities; diff --git a/source/backend/api/Services/DispositionFileService.cs b/source/backend/api/Services/DispositionFileService.cs index 20d2bfe3d1..b5f663442a 100644 --- a/source/backend/api/Services/DispositionFileService.cs +++ b/source/backend/api/Services/DispositionFileService.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; +using DocumentFormat.OpenXml.Office2010.Excel; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Pims.Api.Constants; using Pims.Api.Helpers.Exceptions; +using Pims.Api.Helpers.Extensions; using Pims.Api.Models.CodeTypes; using Pims.Core.Exceptions; using Pims.Core.Extensions; @@ -24,6 +26,7 @@ public class DispositionFileService : IDispositionFileService { private readonly ClaimsPrincipal _user; private readonly ILogger _logger; + private readonly IUserRepository _userRepository; private readonly IDispositionFileRepository _dispositionFileRepository; private readonly IDispositionFilePropertyRepository _dispositionFilePropertyRepository; private readonly ICoordinateTransformService _coordinateService; @@ -43,7 +46,8 @@ public DispositionFileService( IPropertyService propertyService, ILookupRepository lookupRepository, IDispositionFileChecklistRepository checklistRepository, - IEntityNoteRepository entityNoteRepository) + IEntityNoteRepository entityNoteRepository, + IUserRepository userRepository) { _user = user; _logger = logger; @@ -55,12 +59,14 @@ public DispositionFileService( _lookupRepository = lookupRepository; _checklistRepository = checklistRepository; _entityNoteRepository = entityNoteRepository; + _userRepository = userRepository; } public PimsDispositionFile Add(PimsDispositionFile dispositionFile, IEnumerable userOverrides) { _logger.LogInformation("Creating Disposition File {dispositionFile}", dispositionFile); _user.ThrowIfNotAuthorized(Permissions.DispositionAdd); + dispositionFile.ThrowMissingContractorInTeam(_user, _userRepository); dispositionFile.DispositionStatusTypeCode ??= EnumDispositionStatusTypeCode.UNKNOWN.ToString(); dispositionFile.DispositionFileStatusTypeCode ??= EnumDispositionFileStatusTypeCode.ACTIVE.ToString(); @@ -79,6 +85,7 @@ public PimsDispositionFile GetById(long id) { _logger.LogInformation("Getting disposition file with id {id}", id); _user.ThrowIfNotAuthorized(Permissions.DispositionView); + _user.ThrowInvalidAccessToDispositionFile(_userRepository, _dispositionFileRepository, id); var dispositionFile = _dispositionFileRepository.GetById(id); @@ -91,6 +98,7 @@ public PimsDispositionFile Update(long id, PimsDispositionFile dispositionFile, _logger.LogInformation("Updating acquisition file with id {id}", id); _user.ThrowIfNotAuthorized(Permissions.DispositionEdit); + _user.ThrowInvalidAccessToDispositionFile(_userRepository, _dispositionFileRepository, id); if (id != dispositionFile.DispositionFileId) { @@ -100,12 +108,14 @@ public PimsDispositionFile Update(long id, PimsDispositionFile dispositionFile, ValidateStaff(dispositionFile); ValidateVersion(id, dispositionFile.ConcurrencyControlNumber); + dispositionFile.ThrowContractorRemovedFromTeam(_user, _userRepository); + if (!userOverrides.Contains(UserOverrideCode.DispositionFileFinalStatus)) { var doNotAddToStatuses = new List() { EnumDispositionFileStatusTypeCode.COMPLETE.ToString(), EnumDispositionFileStatusTypeCode.ARCHIVED.ToString() }; if (doNotAddToStatuses.Contains(dispositionFile.DispositionFileStatusTypeCode)) { - throw new UserOverrideException(UserOverrideCode.DispositionFileFinalStatus, "You are changing this file to a non-editable state. Only system administrators can edit the file when set to Archived, Cancelled or Completed state). Do you wish to continue?"); + throw new UserOverrideException(UserOverrideCode.DispositionFileFinalStatus, "You are changing this file to a non-editable state. (Only system administrators can edit the file when set to Archived, Cancelled or Completed state). Do you wish to continue?"); } } if (!userOverrides.Contains(UserOverrideCode.UpdateRegion)) @@ -134,7 +144,10 @@ public Paged GetPage(DispositionFilter filter) _logger.LogDebug("Disposition file search with filter: {filter}", filter); _user.ThrowIfNotAuthorized(Permissions.DispositionView); - return _dispositionFileRepository.GetPageDeep(filter); + var pimsUser = _userRepository.GetUserInfoByKeycloakUserId(_user.GetUserKey()); + long? contractorPersonId = (pimsUser != null && pimsUser.IsContractor) ? pimsUser.PersonId : null; + + return _dispositionFileRepository.GetPageDeep(filter, contractorPersonId); } public IEnumerable GetProperties(long id) @@ -333,16 +346,22 @@ public IEnumerable GetChecklistItems(long id) return checklistItems; } - public PimsDispositionFile UpdateChecklistItems(PimsDispositionFile dispositionFile) + public PimsDispositionFile UpdateChecklistItems(IList checklistItems) { - dispositionFile.ThrowIfNull(nameof(dispositionFile)); - _logger.LogInformation("Updating disposition file checklist with DispositionFile id: {id}", dispositionFile.Internal_Id); + checklistItems.ThrowIfNull(nameof(checklistItems)); + if (checklistItems.Count == 0) + { + throw new BadRequestException("Checklist items must be greater than zero"); + } + + var dispositionFileId = checklistItems.FirstOrDefault().DispositionFileId; + _logger.LogInformation("Updating disposition file checklist with DispositionFile id: {id}", dispositionFileId); _user.ThrowIfNotAuthorized(Permissions.DispositionEdit); // Get the current checklist items for this disposition file. - var currentItems = _checklistRepository.GetAllChecklistItemsByDispositionFileId(dispositionFile.Internal_Id).ToDictionary(ci => ci.Internal_Id); + var currentItems = _checklistRepository.GetAllChecklistItemsByDispositionFileId(dispositionFileId).ToDictionary(ci => ci.Internal_Id); - foreach (var incomingItem in dispositionFile.PimsDispositionChecklistItems) + foreach (var incomingItem in checklistItems) { if (!currentItems.TryGetValue(incomingItem.Internal_Id, out var existingItem) && incomingItem.Internal_Id != 0) { @@ -361,7 +380,7 @@ public PimsDispositionFile UpdateChecklistItems(PimsDispositionFile dispositionF } _checklistRepository.CommitTransaction(); - return _dispositionFileRepository.GetById(dispositionFile.Internal_Id); + return _dispositionFileRepository.GetById(dispositionFileId); } public List GetDispositionFileExport(DispositionFilter filter) diff --git a/source/backend/api/Services/IAcquisitionFileService.cs b/source/backend/api/Services/IAcquisitionFileService.cs index 57ff33969a..00bef2d6d8 100644 --- a/source/backend/api/Services/IAcquisitionFileService.cs +++ b/source/backend/api/Services/IAcquisitionFileService.cs @@ -27,7 +27,7 @@ public interface IAcquisitionFileService IEnumerable GetChecklistItems(long id); - PimsAcquisitionFile UpdateChecklistItems(PimsAcquisitionFile acquisitionFile); + PimsAcquisitionFile UpdateChecklistItems(IList checklistItems); IEnumerable GetAgreements(long id); diff --git a/source/backend/api/Services/IDispositionFileService.cs b/source/backend/api/Services/IDispositionFileService.cs index b94692dd33..0cd5c925ed 100644 --- a/source/backend/api/Services/IDispositionFileService.cs +++ b/source/backend/api/Services/IDispositionFileService.cs @@ -47,6 +47,6 @@ public interface IDispositionFileService List GetDispositionFileExport(DispositionFilter filter); - PimsDispositionFile UpdateChecklistItems(PimsDispositionFile dispositionFile); + PimsDispositionFile UpdateChecklistItems(IList checklistItems); } } diff --git a/source/backend/api/Services/IDocumentGenerationService.cs b/source/backend/api/Services/IDocumentGenerationService.cs index e566bba1d5..7209e67e8e 100644 --- a/source/backend/api/Services/IDocumentGenerationService.cs +++ b/source/backend/api/Services/IDocumentGenerationService.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Http; using Pims.Api.Constants; using Pims.Api.Models.Cdogs; - +using Pims.Api.Models.CodeTypes; using Pims.Api.Models.Requests.Http; namespace Pims.Api.Services diff --git a/source/backend/api/Services/TakeService.cs b/source/backend/api/Services/TakeService.cs index 13733d5bc8..e179a27f98 100644 --- a/source/backend/api/Services/TakeService.cs +++ b/source/backend/api/Services/TakeService.cs @@ -3,6 +3,7 @@ using System.Security.Claims; using Microsoft.Extensions.Logging; using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; using Pims.Core.Exceptions; using Pims.Dal.Entities; using Pims.Dal.Helpers.Extensions; diff --git a/source/backend/api/Solvers/AcquisitionStatusSolver.cs b/source/backend/api/Solvers/AcquisitionStatusSolver.cs index a49179f70e..c73d0bc949 100644 --- a/source/backend/api/Solvers/AcquisitionStatusSolver.cs +++ b/source/backend/api/Solvers/AcquisitionStatusSolver.cs @@ -1,4 +1,4 @@ -using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; namespace Pims.Api.Services { diff --git a/source/backend/api/Solvers/IAcquisitionStatusSolver.cs b/source/backend/api/Solvers/IAcquisitionStatusSolver.cs index 3b04fdf928..8d27709664 100644 --- a/source/backend/api/Solvers/IAcquisitionStatusSolver.cs +++ b/source/backend/api/Solvers/IAcquisitionStatusSolver.cs @@ -1,4 +1,4 @@ -using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; namespace Pims.Api.Services { diff --git a/source/backend/api/Constants/AcquisitionStatusTypes.cs b/source/backend/apimodels/CodeType/AcquisitionStatusTypes.cs similarity index 94% rename from source/backend/api/Constants/AcquisitionStatusTypes.cs rename to source/backend/apimodels/CodeType/AcquisitionStatusTypes.cs index eeea86e59d..85ad177ac6 100644 --- a/source/backend/api/Constants/AcquisitionStatusTypes.cs +++ b/source/backend/apimodels/CodeType/AcquisitionStatusTypes.cs @@ -1,7 +1,7 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace Pims.Api.Constants +namespace Pims.Api.Models.CodeTypes { [JsonConverter(typeof(JsonStringEnumMemberConverter))] public enum AcquisitionStatusTypes diff --git a/source/backend/api/Constants/AgreementStatusTypes.cs b/source/backend/apimodels/CodeType/AgreementStatusTypes.cs similarity index 90% rename from source/backend/api/Constants/AgreementStatusTypes.cs rename to source/backend/apimodels/CodeType/AgreementStatusTypes.cs index 8c8f1a9cb9..cfe608b2dd 100644 --- a/source/backend/api/Constants/AgreementStatusTypes.cs +++ b/source/backend/apimodels/CodeType/AgreementStatusTypes.cs @@ -1,7 +1,7 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace Pims.Api.Constants +namespace Pims.Api.Models.CodeTypes { [JsonConverter(typeof(JsonStringEnumMemberConverter))] public enum AgreementStatusTypes diff --git a/source/backend/apimodels/CodeType/AgreementTypes.cs b/source/backend/apimodels/CodeType/AgreementTypes.cs new file mode 100644 index 0000000000..e23574ef81 --- /dev/null +++ b/source/backend/apimodels/CodeType/AgreementTypes.cs @@ -0,0 +1,21 @@ +using System.Runtime.Serialization; +using System.Text.Json.Serialization; + +namespace Pims.Api.Models.CodeTypes +{ + [JsonConverter(typeof(JsonStringEnumMemberConverter))] + public enum AgreementTypes + { + [EnumMember(Value = "H0074")] + H0074, + + [EnumMember(Value = "H179A")] + H179A, + + [EnumMember(Value = "H179P")] + H179P, + + [EnumMember(Value = "H179T")] + H179T, + } +} diff --git a/source/backend/api/Constants/FormDocumentType.cs b/source/backend/apimodels/CodeType/FormDocumentType.cs similarity index 97% rename from source/backend/api/Constants/FormDocumentType.cs rename to source/backend/apimodels/CodeType/FormDocumentType.cs index 2c9e8d9491..8df3d1ec60 100644 --- a/source/backend/api/Constants/FormDocumentType.cs +++ b/source/backend/apimodels/CodeType/FormDocumentType.cs @@ -1,7 +1,7 @@ using System.Runtime.Serialization; using System.Text.Json.Serialization; -namespace Pims.Api.Constants +namespace Pims.Api.Models.CodeTypes { [JsonConverter(typeof(JsonStringEnumMemberConverter))] public enum FormDocumentType diff --git a/source/backend/apimodels/Models/Base/CodeTypeModel.cs b/source/backend/apimodels/Models/Base/CodeTypeModel.cs index 7cf33a532d..e84e92fed0 100644 --- a/source/backend/apimodels/Models/Base/CodeTypeModel.cs +++ b/source/backend/apimodels/Models/Base/CodeTypeModel.cs @@ -5,6 +5,7 @@ namespace Pims.Api.Models.Base /// /// The actual type of this TypeModel. public class CodeTypeModel + where T : notnull { /// /// get/set - Primary key to identify type. diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs index 903d83bd96..533da31c3e 100644 --- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs +++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileMap.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using Mapster; using Pims.Api.Models.Base; using Pims.Core.Extensions; @@ -11,6 +12,7 @@ public class AcquisitionFileMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Id, src => src.AcquisitionFileId) .Map(dest => dest.FileNo, src => src.FileNo) .Map(dest => dest.FileNumber, src => src.FileNumber) @@ -40,6 +42,7 @@ public void Register(TypeAdapterConfig config) .Inherits(); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.AcquisitionFileId, src => src.Id) .Map(dest => dest.FileNo, src => src.FileNo) .Map(dest => dest.FileNumber, src => src.FileNumber) @@ -57,7 +60,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.AcqPhysFileStatusTypeCode, src => src.AcquisitionPhysFileStatusTypeCode.Id) .Map(dest => dest.AcquisitionTypeCode, src => src.AcquisitionTypeCode.Id) .Map(dest => dest.RegionCode, src => src.RegionCode.Id) - .Map(dest => dest.PimsPropertyAcquisitionFiles, src => src.FileProperties) + .Map(dest => dest.PimsPropertyAcquisitionFiles, src => src.FileProperties.ToImmutableList()) .Map(dest => dest.PimsAcquisitionFileTeams, src => src.AcquisitionTeam) .Map(dest => dest.PimsAcquisitionOwners, src => src.AcquisitionFileOwners) .Map(dest => dest.PimsInterestHolders, src => src.AcquisitionFileInterestHolders) diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs index c772cf794c..4fdad731ef 100644 --- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs +++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFileModel.cs @@ -96,7 +96,7 @@ public class AcquisitionFileModel : FileWithChecklistModel /// /// get/set - A list of research property relationships. /// - public IList FileProperties { get; set; } + public new IList FileProperties { get; set; } /// /// get/set - A list of acquisition file team relationships. diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs index 6da24648c8..438dc81333 100644 --- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs +++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyMap.cs @@ -9,6 +9,7 @@ public class AcquisitionFilePropertyMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Id, src => src.PropertyAcquisitionFileId) .Map(dest => dest.PropertyName, src => src.PropertyName) .Map(dest => dest.DisplayOrder, src => src.DisplayOrder) @@ -19,6 +20,7 @@ public void Register(TypeAdapterConfig config) .Inherits(); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.PropertyAcquisitionFileId, src => src.Id) .Map(dest => dest.Property, src => src.Property) .Map(dest => dest.PropertyId, src => src.Property.Id) diff --git a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyModel.cs b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyModel.cs index bd75d57e50..7ad465aab6 100644 --- a/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyModel.cs +++ b/source/backend/apimodels/Models/Concepts/AcquisitionFile/AcquisitionFilePropertyModel.cs @@ -1,47 +1,12 @@ -using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.File; -using Pims.Api.Models.Concepts.Property; namespace Pims.Api.Models.Concepts.AcquisitionFile { - public class AcquisitionFilePropertyModel : BaseConcurrentModel + public class AcquisitionFilePropertyModel : FilePropertyModel { #region Properties - /// - /// get/set - The relationship id. - /// - public long Id { get; set; } - - /// - /// get/set - The descriptive name of the property for this acquisition file. - /// - public string PropertyName { get; set; } - - /// - /// get/set - The order to display the relationship. - /// - public int? DisplayOrder { get; set; } - - /// - /// get/set - The relationship's property. - /// - public PropertyModel Property { get; set; } - - /// - /// get/set - The relationship's property id. - /// - public long PropertyId { get; set; } - - /// - /// get/set - The relationship's acquisition file. - /// - public FileModel File { get; set; } - - /// - /// get/set - The relationship's acquisition file id. - /// - public long FileId { get; set; } + public new AcquisitionFileModel File { get; set; } #endregion } diff --git a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileMap.cs b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileMap.cs index bcdab9acc8..e2ec98df71 100644 --- a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileMap.cs +++ b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileMap.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using Mapster; using Entity = Pims.Dal.Entities; @@ -10,6 +11,7 @@ public class DispositionFileMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Id, src => src.DispositionFileId) .Map(dest => dest.FileNumber, src => src.FileNumber) .Map(dest => dest.FileName, src => src.FileName) @@ -36,6 +38,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.FileChecklistItems, src => src.PimsDispositionChecklistItems); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.DispositionFileId, src => src.Id) .Map(dest => dest.FileNumber, src => src.FileNumber) .Map(dest => dest.FileName, src => src.FileName) @@ -57,7 +60,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.PimsDispositionFileTeams, src => src.DispositionTeam) .Map(dest => dest.PimsDispositionOffers, src => src.DispositionOffers) .Map(dest => dest.PimsDispositionSales, src => src.DispositionSale == null ? null : new List { src.DispositionSale }) - .Map(dest => dest.PimsDispositionFileProperties, src => src.FileProperties) + .Map(dest => dest.PimsDispositionFileProperties, src => src.FileProperties.ToImmutableList()) .Map(dest => dest.PimsDispositionChecklistItems, src => src.FileChecklistItems); } } diff --git a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileModel.cs b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileModel.cs index d90d63289e..aec81575d3 100644 --- a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileModel.cs +++ b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFileModel.cs @@ -81,7 +81,7 @@ public class DispositionFileModel : FileWithChecklistModel /// /// get/set - A list of disposition property relationships. /// - public IList FileProperties { get; set; } + public new IList FileProperties { get; set; } /// /// get/set - A list of disposition file team relationships. @@ -102,6 +102,7 @@ public class DispositionFileModel : FileWithChecklistModel /// get/set - A list of disposition file sales. /// public DispositionFileAppraisalModel DispositionAppraisal { get; set; } + #endregion } } diff --git a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFilePropertyModel.cs b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFilePropertyModel.cs index f6abda175f..1c6b86b04d 100644 --- a/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFilePropertyModel.cs +++ b/source/backend/apimodels/Models/Concepts/DispositionFile/DispositionFilePropertyModel.cs @@ -1,48 +1,15 @@ -using Pims.Api.Models.Base; using Pims.Api.Models.Concepts.File; -using Pims.Api.Models.Concepts.Property; namespace Pims.Api.Models.Concepts.DispositionFile { - public class DispositionFilePropertyModel : BaseConcurrentModel + public class DispositionFilePropertyModel : FilePropertyModel { #region Properties - /// - /// get/set - The relationship id. - /// - public long Id { get; set; } - - /// - /// get/set - The descriptive name of the property for this disposition file. - /// - public string PropertyName { get; set; } - - /// - /// get/set - The order to display the relationship. - /// - public int? DisplayOrder { get; set; } - - /// - /// get/set - The relationship's property. - /// - public PropertyModel Property { get; set; } - - /// - /// get/set - The relationship's property id. - /// - public long PropertyId { get; set; } - /// /// get/set - The relationship's disposition file. /// - public DispositionFileModel File { get; set; } - - /// - /// get/set - The relationship's disposition file id. - /// - public long FileId { get; set; } - + public new DispositionFileModel File { get; set; } #endregion } } diff --git a/source/backend/apimodels/Models/Concepts/File/FileModel.cs b/source/backend/apimodels/Models/Concepts/File/FileModel.cs index aadbdf820f..7f53d2cdec 100644 --- a/source/backend/apimodels/Models/Concepts/File/FileModel.cs +++ b/source/backend/apimodels/Models/Concepts/File/FileModel.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Pims.Api.Models.Base; namespace Pims.Api.Models.Concepts.File @@ -26,6 +27,11 @@ public class FileModel : BaseAuditModel /// public CodeTypeModel FileStatusTypeCode { get; set; } + /// + /// get/set - A list of file property releationships. + /// + public virtual List FileProperties { get; set; } + #endregion } } diff --git a/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs b/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs new file mode 100644 index 0000000000..f0e6be807a --- /dev/null +++ b/source/backend/apimodels/Models/Concepts/File/FilePropertyModel.cs @@ -0,0 +1,44 @@ +using Pims.Api.Models.Base; +using Pims.Api.Models.Concepts.Property; + +namespace Pims.Api.Models.Concepts.File +{ + public class FilePropertyModel : BaseConcurrentModel + { + #region Properties + + /// + /// get/set - The relationship id. + /// + public long Id { get; set; } + + /// + /// get/set - The descriptive name of the property for this acquisition file. + /// + public string PropertyName { get; set; } + + /// + /// get/set - The order to display the relationship. + /// + public int? DisplayOrder { get; set; } + + /// + /// get/set - The relationship's property. + /// + public PropertyModel Property { get; set; } + + /// + /// get/set - The relationship's property id. + /// + public long PropertyId { get; set; } + + /// + /// get/set - The relationship's acquisition file id. + /// + public long FileId { get; set; } + + public virtual FileModel File { get; set; } + + #endregion + } +} diff --git a/source/backend/apimodels/Models/Concepts/Lease/LeaseMap.cs b/source/backend/apimodels/Models/Concepts/Lease/LeaseMap.cs index 0b8d4bc98f..d763697e3a 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/LeaseMap.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/LeaseMap.cs @@ -1,9 +1,10 @@ +using System; +using System.Collections.Immutable; using Mapster; using Pims.Api.Helpers.Extensions; using Pims.Core.Extensions; using Pims.Dal.Entities; using Pims.Dal.Helpers.Extensions; -using System; namespace Pims.Api.Models.Concepts.Lease { @@ -12,11 +13,12 @@ public class LeaseMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Id, src => src.LeaseId) .Map(dest => dest.RowVersion, src => src.ConcurrencyControlNumber) .Map(dest => dest.Amount, src => src.LeaseAmount) .Map(dest => dest.RenewalCount, src => src.PimsLeaseTerms.Count) - .Map(dest => dest.Properties, src => src.PimsPropertyLeases) + .Map(dest => dest.FileProperties, src => src.PimsPropertyLeases) .Map(dest => dest.LFileNo, src => src.LFileNo) .Map(dest => dest.TfaFileNumber, src => src.TfaFileNumber) .Map(dest => dest.PsFileNo, src => src.PsFileNo) @@ -35,7 +37,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.Type, src => src.LeaseLicenseTypeCodeNavigation) .Map(dest => dest.InitiatorType, src => src.LeaseInitiatorTypeCodeNavigation) .Map(dest => dest.PurposeType, src => src.LeasePurposeTypeCodeNavigation) - .Map(dest => dest.StatusType, src => src.LeaseStatusTypeCodeNavigation) + .Map(dest => dest.FileStatusTypeCode, src => src.LeaseStatusTypeCodeNavigation) .Map(dest => dest.ResponsibilityType, src => src.LeaseResponsibilityTypeCodeNavigation) .Map(dest => dest.ResponsibilityEffectiveDate, src => src.ResponsibilityEffectiveDate.ToNullableDateOnly()) .Map(dest => dest.DocumentationReference, src => src.DocumentationReference) @@ -52,13 +54,15 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.HasDigitalFile, src => src.HasDigitalFile) .Map(dest => dest.HasPhysicalLicense, src => src.HasPhysicialLicense) .Map(dest => dest.Project, src => src.Project) - .Map(dest => dest.Tenants, src => src.PimsLeaseTenants); + .Map(dest => dest.Tenants, src => src.PimsLeaseTenants) + .Map(dest => dest.Terms, src => src.PimsLeaseTerms); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.LeaseId, src => src.Id) .Map(dest => dest.ConcurrencyControlNumber, src => src.RowVersion) .Map(dest => dest.LeaseAmount, src => src.Amount) - .Map(dest => dest.PimsPropertyLeases, src => src.Properties) + .Map(dest => dest.PimsPropertyLeases, src => src.FileProperties.ToImmutableList()) .Map(dest => dest.LFileNo, src => src.LFileNo) .Map(dest => dest.PsFileNo, src => src.PsFileNo) .Map(dest => dest.TfaFileNumber, src => src.TfaFileNumber) @@ -78,7 +82,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.LeaseInitiatorTypeCode, src => src.InitiatorType.GetTypeId()) .Map(dest => dest.LeasePurposeTypeCode, src => src.PurposeType.GetTypeId()) .Map(dest => dest.LeaseResponsibilityTypeCode, src => src.ResponsibilityType.GetTypeId()) - .Map(dest => dest.LeaseStatusTypeCode, src => src.StatusType.GetTypeId()) + .Map(dest => dest.LeaseStatusTypeCode, src => src.FileStatusTypeCode.GetTypeId()) .Map(dest => dest.ResponsibilityEffectiveDate, src => src.ResponsibilityEffectiveDate.ToNullableDateTime()) .Map(dest => dest.DocumentationReference, src => src.DocumentationReference) .Map(dest => dest.LeaseNotes, src => src.Note) diff --git a/source/backend/apimodels/Models/Concepts/Lease/LeaseModel.cs b/source/backend/apimodels/Models/Concepts/Lease/LeaseModel.cs index fd2198c95c..420d4344ac 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/LeaseModel.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/LeaseModel.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using Pims.Api.Models.Base; -using Pims.Api.Models.Concepts.Deposit; +using Pims.Api.Models.Concepts.File; using Pims.Api.Models.Concepts.Project; namespace Pims.Api.Models.Concepts.Lease @@ -9,25 +9,15 @@ namespace Pims.Api.Models.Concepts.Lease /// /// Provides a lease-oriented model. /// - public class LeaseModel : BaseAuditModel + public class LeaseModel : FileModel { #region Properties - /// - /// get/set - The primary key to identify the lease. - /// - public long Id { get; set; } - /// /// get/set - The lease amount. /// public decimal? Amount { get; set; } - /// - /// get/set - The value of the tenant name. - /// - public string TenantName { get; set; } - /// /// get/set - The value of the moti resource assigned to this lease. /// @@ -53,11 +43,6 @@ public class LeaseModel : BaseAuditModel /// public string Description { get; set; } - /// - /// get/set - The string value of the street address. - /// - public string Address { get; set; } - /// /// get/set - The LIS L File #. /// @@ -103,11 +88,6 @@ public class LeaseModel : BaseAuditModel /// public DateOnly StartDate { get; set; } - /// - /// get/set - The most recent renewal date on the lease. - /// - public DateOnly? RenewalDate { get; set; } - /// /// get/set - The lease renewal count. /// @@ -143,11 +123,6 @@ public class LeaseModel : BaseAuditModel /// public CodeTypeModel PurposeType { get; set; } - /// - /// get/set - The status of this lease within PIMS, Draft by default. - /// - public CodeTypeModel StatusType { get; set; } - /// /// get/set - The region of this lease within PIMS. /// @@ -171,12 +146,7 @@ public class LeaseModel : BaseAuditModel /// /// get/set - A list of properties associated with this lease. /// - public IEnumerable Properties { get; set; } - - /// - /// get/set - A collection of Security Deposits associated to this Lease. - /// - public IEnumerable SecurityDeposits { get; set; } + public new IList FileProperties { get; set; } /// /// get/set - A collection the consultations for this lease. @@ -188,6 +158,11 @@ public class LeaseModel : BaseAuditModel /// public IEnumerable Tenants { get; set; } + /// + /// get/set - A collection of the terms for this lease. + /// + public IEnumerable Terms { get; set; } + /// /// get/set - Whether this improvement contains a building that is subject to RTA (Residential Tenancy Act). /// diff --git a/source/backend/apimodels/Models/Concepts/Lease/LeaseTermModel.cs b/source/backend/apimodels/Models/Concepts/Lease/LeaseTermModel.cs index d034858ecc..9d62b99d0c 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/LeaseTermModel.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/LeaseTermModel.cs @@ -21,11 +21,6 @@ public class LeaseTermModel : BaseAuditModel /// public long LeaseId { get; set; } - /// - /// get/set - The Rowversion on the parent lease, must be up to date to allow lease add/update operations. - /// - public long LeaseRowVersion { get; set; } - /// /// get/set - The stored calculated gst amount based on the total payment and the system gst constant. /// diff --git a/source/backend/apimodels/Models/Concepts/Lease/PaymentModel.cs b/source/backend/apimodels/Models/Concepts/Lease/PaymentModel.cs index c02a73d79e..cff46689bd 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/PaymentModel.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/PaymentModel.cs @@ -20,11 +20,6 @@ public class PaymentModel : BaseAuditModel /// public long LeaseTermId { get; set; } - /// - /// get/set - The Rowversion on the parent lease, must be up to date to allow lease add/update operations. - /// - public long LeaseRowVersion { get; set; } - /// /// get/set - The payment method, such as cheque, transfer. /// diff --git a/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseMap.cs b/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseMap.cs index 12f0f41f33..1559a9b1f2 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseMap.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseMap.cs @@ -8,10 +8,11 @@ public class PropertyLeaseMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Property, src => src.Property) .Map(dest => dest.PropertyId, src => src.PropertyId) - .Map(dest => dest.Lease, src => src.Lease) - .Map(dest => dest.LeaseId, src => src.LeaseId) + .Map(dest => dest.File, src => src.Lease) + .Map(dest => dest.FileId, src => src.LeaseId) .Map(dest => dest.AreaUnitType, src => src.AreaUnitTypeCodeNavigation) .Map(dest => dest.LeaseArea, src => src.LeaseArea) .Map(dest => dest.PropertyName, src => src.Name) @@ -19,8 +20,9 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.Id, src => src.Internal_Id); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Property, src => src.Property) - .Map(dest => dest.LeaseId, src => src.LeaseId) + .Map(dest => dest.LeaseId, src => src.FileId) .Map(dest => dest.AreaUnitTypeCode, src => src.AreaUnitType.Id) .Map(dest => dest.LeaseArea, src => src.LeaseArea) .Map(dest => dest.Name, src => src.PropertyName) diff --git a/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseModel.cs b/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseModel.cs index 3bce5a3cef..8275c4c3ad 100644 --- a/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseModel.cs +++ b/source/backend/apimodels/Models/Concepts/Lease/PropertyLeaseModel.cs @@ -1,23 +1,13 @@ using Pims.Api.Models.Base; -using Pims.Api.Models.Concepts.Property; +using Pims.Api.Models.Concepts.File; namespace Pims.Api.Models.Concepts.Lease { - public class PropertyLeaseModel : BaseAuditModel + public class PropertyLeaseModel : FilePropertyModel { - #region Properties + #region Propertie - public long? Id { get; set; } - - public long? LeaseId { get; set; } - - public long? PropertyId { get; set; } - - public PropertyModel Property { get; set; } - - public LeaseModel Lease { get; set; } - - public string PropertyName { get; set; } + public new LeaseModel File { get; set; } public double? LeaseArea { get; set; } diff --git a/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs b/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs index f3ce15e80f..ae34f57772 100644 --- a/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs +++ b/source/backend/apimodels/Models/Concepts/Project/ProjectModel.cs @@ -15,7 +15,7 @@ public class ProjectModel : BaseAuditModel /// /// get/set - The project id. /// - public long? Id { get; set; } + public long Id { get; set; } /// /// get/set - The status type code. diff --git a/source/backend/api/Areas/Property/Mapping/Property/AssociationMap.cs b/source/backend/apimodels/Models/Concepts/Property/AssociationMap.cs similarity index 88% rename from source/backend/api/Areas/Property/Mapping/Property/AssociationMap.cs rename to source/backend/apimodels/Models/Concepts/Property/AssociationMap.cs index 322c662a59..c0c4fbae4c 100644 --- a/source/backend/api/Areas/Property/Mapping/Property/AssociationMap.cs +++ b/source/backend/apimodels/Models/Concepts/Property/AssociationMap.cs @@ -1,15 +1,14 @@ using Mapster; using Entity = Pims.Dal.Entities; -using Model = Pims.Api.Areas.Property.Models.Property; -namespace Pims.Api.Areas.Property.Mapping.Property +namespace Pims.Api.Models.Concepts.Property { public class AssociationMap : IRegister { public void Register(TypeAdapterConfig config) { - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.PropertyId) .Map(dest => dest.Pid, src => src.Pid) .Map(dest => dest.LeaseAssociations, src => src.PimsPropertyLeases) @@ -17,7 +16,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.AcquisitionAssociations, src => src.PimsPropertyAcquisitionFiles) .Map(dest => dest.DispositionAssociations, src => src.PimsDispositionFileProperties); - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.LeaseId) .Map(dest => dest.FileNumber, src => src.Lease.LFileNo) .Map(dest => dest.FileName, src => "-") @@ -26,7 +25,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.CreatedDateTime, src => src.Lease.AppCreateTimestamp) .Map(dest => dest.Status, src => src.Lease.LeaseStatusTypeCodeNavigation.Description); - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.ResearchFileId) .Map(dest => dest.FileNumber, src => src.ResearchFile.RfileNumber) .Map(dest => dest.FileName, src => src.ResearchFile.Name) @@ -35,7 +34,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.CreatedDateTime, src => src.ResearchFile.AppCreateTimestamp) .Map(dest => dest.Status, src => src.ResearchFile.ResearchFileStatusTypeCodeNavigation.Description); - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.AcquisitionFileId) .Map(dest => dest.FileNumber, src => src.AcquisitionFile.FileNumber) .Map(dest => dest.FileName, src => src.AcquisitionFile.FileName) @@ -44,7 +43,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.CreatedDateTime, src => src.AcquisitionFile.AppCreateTimestamp) .Map(dest => dest.Status, src => src.AcquisitionFile.AcquisitionFileStatusTypeCodeNavigation.Description); - config.NewConfig() + config.NewConfig() .Map(dest => dest.Id, src => src.DispositionFileId) .Map(dest => dest.FileNumber, src => "D-" + src.DispositionFile.FileNumber) .Map(dest => dest.FileName, src => src.DispositionFile.FileName) diff --git a/source/backend/api/Areas/Property/Models/Property/AssociationModel.cs b/source/backend/apimodels/Models/Concepts/Property/AssociationModel.cs similarity index 88% rename from source/backend/api/Areas/Property/Models/Property/AssociationModel.cs rename to source/backend/apimodels/Models/Concepts/Property/AssociationModel.cs index 52eebd5ec9..14f62db974 100644 --- a/source/backend/api/Areas/Property/Models/Property/AssociationModel.cs +++ b/source/backend/apimodels/Models/Concepts/Property/AssociationModel.cs @@ -1,6 +1,6 @@ using System; -namespace Pims.Api.Areas.Property.Models.Property +namespace Pims.Api.Models.Concepts.Property { public class AssociationModel { diff --git a/source/backend/apimodels/Models/Concepts/Property/PropertyActivityMap.cs b/source/backend/apimodels/Models/Concepts/Property/PropertyActivityMap.cs index cd11593d35..804bf6ee92 100644 --- a/source/backend/apimodels/Models/Concepts/Property/PropertyActivityMap.cs +++ b/source/backend/apimodels/Models/Concepts/Property/PropertyActivityMap.cs @@ -1,7 +1,5 @@ -using System; using Mapster; using Pims.Api.Models.Base; -using Pims.Core.Extensions; using Entity = Pims.Dal.Entities; namespace Pims.Api.Models.Concepts.Property diff --git a/source/backend/api/Areas/Property/Models/Property/PropertyAssociationModel.cs b/source/backend/apimodels/Models/Concepts/Property/PropertyAssociationsModel.cs similarity index 75% rename from source/backend/api/Areas/Property/Models/Property/PropertyAssociationModel.cs rename to source/backend/apimodels/Models/Concepts/Property/PropertyAssociationsModel.cs index b545cd23fb..9fd5fa7e85 100644 --- a/source/backend/api/Areas/Property/Models/Property/PropertyAssociationModel.cs +++ b/source/backend/apimodels/Models/Concepts/Property/PropertyAssociationsModel.cs @@ -1,10 +1,10 @@ using System.Collections.Generic; -namespace Pims.Api.Areas.Property.Models.Property +namespace Pims.Api.Models.Concepts.Property { - public class PropertyAssociationModel + public class PropertyAssociationsModel { - public string Id { get; set; } + public long Id { get; set; } public string Pid { get; set; } diff --git a/source/backend/apimodels/Models/Concepts/Property/PropertyMap.cs b/source/backend/apimodels/Models/Concepts/Property/PropertyMap.cs index cd4af7f3b6..6e06cd0799 100644 --- a/source/backend/apimodels/Models/Concepts/Property/PropertyMap.cs +++ b/source/backend/apimodels/Models/Concepts/Property/PropertyMap.cs @@ -25,7 +25,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.GeneralLocation, src => src.GeneralLocation) .Map(dest => dest.DataSource, src => src.PropertyDataSourceTypeCodeNavigation) - .Map(dest => dest.DataSourceEffectiveDate, src => src.PropertyDataSourceEffectiveDate) + .Map(dest => dest.DataSourceEffectiveDateOnly, src => src.PropertyDataSourceEffectiveDate) .Map(dest => dest.Name, src => src.Name) .Map(dest => dest.Description, src => src.Description) diff --git a/source/backend/apimodels/Models/Concepts/Property/PropertyModel.cs b/source/backend/apimodels/Models/Concepts/Property/PropertyModel.cs index a8446887f0..9dee44218f 100644 --- a/source/backend/apimodels/Models/Concepts/Property/PropertyModel.cs +++ b/source/backend/apimodels/Models/Concepts/Property/PropertyModel.cs @@ -61,7 +61,7 @@ public class PropertyModel : BaseConcurrentModel /// /// get/set - The data source effective date. /// - public DateOnly DataSourceEffectiveDate { get; set; } + public DateOnly DataSourceEffectiveDateOnly { get; set; } /// /// get/set - The GIS latitude location of the property. diff --git a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileMap.cs b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileMap.cs index 54254f24fc..b671de4e31 100644 --- a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileMap.cs +++ b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileMap.cs @@ -1,6 +1,6 @@ +using System.Collections.Immutable; using Mapster; using Pims.Api.Models.Base; -using Pims.Core.Extensions; using Pims.Dal.Entities; namespace Pims.Api.Models.Concepts.ResearchFile @@ -10,6 +10,7 @@ public class ResearchFileMap : IRegister public void Register(TypeAdapterConfig config) { config.NewConfig() + .PreserveReference(true) .Map(dest => dest.Id, src => src.ResearchFileId) .Map(dest => dest.FileName, src => src.Name) .Map(dest => dest.FileNumber, src => src.RfileNumber) @@ -32,13 +33,14 @@ public void Register(TypeAdapterConfig config) .Inherits(); config.NewConfig() + .PreserveReference(true) .Map(dest => dest.ResearchFileId, src => src.Id) .Map(dest => dest.Name, src => src.FileName) .Map(dest => dest.RfileNumber, src => src.FileNumber) .Map(dest => dest.RoadAlias, src => src.RoadAlias) .Map(dest => dest.RoadName, src => src.RoadName) .Map(dest => dest.ResearchFileStatusTypeCode, src => src.FileStatusTypeCode.Id) - .Map(dest => dest.PimsPropertyResearchFiles, src => src.FileProperties) + .Map(dest => dest.PimsPropertyResearchFiles, src => src.FileProperties.ToImmutableList()) .Map(dest => dest.RequestDate, src => src.RequestDate) .Map(dest => dest.RequestDescription, src => src.RequestDescription) .Map(dest => dest.RequestSourceDescription, src => src.RequestSourceDescription) diff --git a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileModel.cs b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileModel.cs index 979534f576..18f3c19306 100644 --- a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileModel.cs +++ b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFileModel.cs @@ -18,7 +18,7 @@ public class ResearchFileModel : FileModel /// /// get/set - A list of research property relationships. /// - public IList FileProperties { get; set; } + public new IList FileProperties { get; set; } public DateOnly? RequestDate { get; set; } diff --git a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyMap.cs b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyMap.cs index 79ecef40cc..1b1306e455 100644 --- a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyMap.cs +++ b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyMap.cs @@ -25,7 +25,7 @@ public void Register(TypeAdapterConfig config) .Map(dest => dest.PropertyResearchFileId, src => src.Id) .Map(dest => dest.Property, src => src.Property) .Map(dest => dest.PropertyId, src => src.Property.Id) - .Map(dest => dest.ResearchFileId, src => src.File.Id) + .Map(dest => dest.ResearchFileId, src => src.FileId) .Map(dest => dest.PropertyName, src => src.PropertyName) .Map(dest => dest.DisplayOrder, src => src.DisplayOrder) .Map(dest => dest.IsLegalOpinionRequired, src => src.IsLegalOpinionRequired) diff --git a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyModel.cs b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyModel.cs index 338164a023..efb9e88016 100644 --- a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyModel.cs +++ b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePropertyModel.cs @@ -1,28 +1,12 @@ using System.Collections.Generic; -using Pims.Api.Models.Base; -using Pims.Api.Models.Concepts.Property; +using Pims.Api.Models.Concepts.File; namespace Pims.Api.Models.Concepts.ResearchFile { - public class ResearchFilePropertyModel : BaseConcurrentModel + public class ResearchFilePropertyModel : FilePropertyModel { #region Properties - /// - /// get/set - The relationship id. - /// - public long Id { get; set; } - - /// - /// get/set - The name of the property for this research file. - /// - public string PropertyName { get; set; } - - /// - /// get/set - The order to display the relationship. - /// - public int? DisplayOrder { get; set; } - /// /// get/set - Flag to mark if legal option is required. /// @@ -43,15 +27,10 @@ public class ResearchFilePropertyModel : BaseConcurrentModel /// public string ResearchSummary { get; set; } - /// - /// get/set - The relationship's property. - /// - public PropertyModel Property { get; set; } - /// /// get/set - The relationship's research file. /// - public ResearchFileModel File { get; set; } + public new ResearchFileModel File { get; set; } /// /// get/set - The property's purpose types. diff --git a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePurposeModel.cs b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePurposeModel.cs index c50b468ac3..26117ad530 100644 --- a/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePurposeModel.cs +++ b/source/backend/apimodels/Models/Concepts/ResearchFile/ResearchFilePurposeModel.cs @@ -9,7 +9,7 @@ public class ResearchFilePurposeModel : BaseAuditModel /// /// get/set - Research file purpose id. /// - public string Id { get; set; } + public long Id { get; set; } /// /// get/set - Purpose type code. diff --git a/source/backend/dal/Repositories/DispositionFileRepository.cs b/source/backend/dal/Repositories/DispositionFileRepository.cs index bd3ea70061..550ca971c0 100644 --- a/source/backend/dal/Repositories/DispositionFileRepository.cs +++ b/source/backend/dal/Repositories/DispositionFileRepository.cs @@ -198,8 +198,9 @@ public PimsDispositionFile Update(long dispositionFileId, PimsDispositionFile di /// Note that the 'filter' will control the 'page' and 'quantity'. /// /// + /// /// - public Paged GetPageDeep(DispositionFilter filter) + public Paged GetPageDeep(DispositionFilter filter, long? contractorPersonId = null) { using var scope = Logger.QueryScope(); @@ -209,7 +210,7 @@ public Paged GetPageDeep(DispositionFilter filter) throw new ArgumentException("Argument must have a valid filter", nameof(filter)); } - var query = GetCommonDispositionFileQueryDeep(filter); + var query = GetCommonDispositionFileQueryDeep(filter, contractorPersonId); var skip = (filter.Page - 1) * filter.Quantity; var pageItems = query.Skip(skip).Take(filter.Quantity).ToList(); @@ -391,8 +392,9 @@ public List GetDispositionFileExportDeep(DispositionFilter /// Generate a Common IQueryable for Disposition Files. /// /// The filter to apply. + /// Filter for Contractors. /// - private IQueryable GetCommonDispositionFileQueryDeep(DispositionFilter filter) + private IQueryable GetCommonDispositionFileQueryDeep(DispositionFilter filter, long? contractorPersonId = null) { var predicate = PredicateBuilder.New(disp => true); if (!string.IsNullOrWhiteSpace(filter.Pid)) @@ -439,6 +441,11 @@ private IQueryable GetCommonDispositionFileQueryDeep(Dispos predicate = predicate.And(disp => disp.DispositionTypeCode == filter.DispositionTypeCode); } + if (contractorPersonId is not null) + { + predicate = predicate.And(x => x.PimsDispositionFileTeams.Any(x => x.PersonId == contractorPersonId)); + } + // filter by team members if (filter.TeamMemberPersonId.HasValue) { diff --git a/source/backend/dal/Repositories/Interfaces/IDispositionFileRepository.cs b/source/backend/dal/Repositories/Interfaces/IDispositionFileRepository.cs index 855e324cc7..3abe2836d3 100644 --- a/source/backend/dal/Repositories/Interfaces/IDispositionFileRepository.cs +++ b/source/backend/dal/Repositories/Interfaces/IDispositionFileRepository.cs @@ -6,7 +6,7 @@ namespace Pims.Dal.Repositories { public interface IDispositionFileRepository : IRepository { - Paged GetPageDeep(DispositionFilter filter); + Paged GetPageDeep(DispositionFilter filter, long? contractorPersonId = null); PimsDispositionFile GetById(long id); diff --git a/source/backend/tests/unit/api/Controllers/Acquisition/ChecklistControllerTest.cs b/source/backend/tests/unit/api/Controllers/Acquisition/ChecklistControllerTest.cs index 808335a40a..3dc83d50bd 100644 --- a/source/backend/tests/unit/api/Controllers/Acquisition/ChecklistControllerTest.cs +++ b/source/backend/tests/unit/api/Controllers/Acquisition/ChecklistControllerTest.cs @@ -10,6 +10,10 @@ using Pims.Dal.Entities; using Pims.Dal.Security; using Xunit; +using Pims.Api.Models.Concepts.File; +using System; +using Pims.Api.Helpers.Exceptions; +using FluentAssertions; namespace Pims.Api.Test.Controllers { @@ -58,13 +62,33 @@ public void UpdateAcquisitionFileChecklist_Success() { // Arrange var acqFile = EntityHelper.CreateAcquisitionFile(); - this._service.Setup(m => m.UpdateChecklistItems(It.IsAny())).Returns(acqFile); + var checklistItems = new List() { new FileChecklistItemModel() { FileId = acqFile.AcquisitionFileId }, new FileChecklistItemModel() { FileId = acqFile.AcquisitionFileId } }; + this._service.Setup(m => m.UpdateChecklistItems(It.IsAny>())).Returns(acqFile); // Act - var result = this._controller.UpdateAcquisitionFileChecklist(this._mapper.Map(acqFile)); + var result = this._controller.UpdateAcquisitionFileChecklist(acqFile.AcquisitionFileId, checklistItems); // Assert - this._service.Verify(m => m.UpdateChecklistItems(It.IsAny()), Times.Once()); + this._service.Verify(m => m.UpdateChecklistItems(It.IsAny>()), Times.Once()); + } + + /// + /// Fails if the checklists items do not belong to the requested acquisition file. + /// + [Fact] + public void UpdateAcquisitionFileChecklist_InvalidIds() + { + // Arrange + var acqFile = EntityHelper.CreateAcquisitionFile(); + var checklistItems = new List() { new FileChecklistItemModel() { FileId = acqFile.AcquisitionFileId }, new FileChecklistItemModel() { FileId = acqFile.AcquisitionFileId + 1 } }; + this._service.Setup(m => m.UpdateChecklistItems(It.IsAny>())).Returns(acqFile); + + // Act + Action act = () => this._controller.UpdateAcquisitionFileChecklist(acqFile.AcquisitionFileId, checklistItems); + + // Assert + act.Should().Throw(); + this._service.Verify(m => m.UpdateChecklistItems(It.IsAny>()), Times.Never()); } #endregion diff --git a/source/backend/tests/unit/api/Controllers/Disposition/ChecklistControllerTest.cs b/source/backend/tests/unit/api/Controllers/Disposition/ChecklistControllerTest.cs index d2b5d87360..07f09e90ab 100644 --- a/source/backend/tests/unit/api/Controllers/Disposition/ChecklistControllerTest.cs +++ b/source/backend/tests/unit/api/Controllers/Disposition/ChecklistControllerTest.cs @@ -10,6 +10,10 @@ using Pims.Dal.Entities; using Pims.Dal.Security; using Xunit; +using Pims.Api.Models.Concepts.File; +using System; +using Pims.Api.Helpers.Exceptions; +using FluentAssertions; namespace Pims.Api.Test.Controllers { @@ -57,14 +61,35 @@ public void GetDispositionFileChecklist_Success() public void UpdateDispositionFileChecklist_Success() { // Arrange - var acqFile = EntityHelper.CreateDispositionFile(); - this._service.Setup(m => m.UpdateChecklistItems(It.IsAny())).Returns(acqFile); + var checklistItems = new List(); + var dispFile = EntityHelper.CreateDispositionFile(); + this._service.Setup(m => m.UpdateChecklistItems(It.IsAny>())).Returns(dispFile); // Act - var result = this._controller.UpdateDispositionFileChecklist(this._mapper.Map(acqFile)); + var result = this._controller.UpdateDispositionFileChecklist(dispFile.DispositionFileId, checklistItems); // Assert - this._service.Verify(m => m.UpdateChecklistItems(It.IsAny()), Times.Once()); + this._service.Verify(m => m.UpdateChecklistItems(It.IsAny>()), Times.Once()); + } + + + /// + /// Fails if the checklists items do not belong to the requested acquisition file. + /// + [Fact] + public void UpdateAcquisitionFileChecklist_InvalidIds() + { + // Arrange + var dispFile = EntityHelper.CreateDispositionFile(); + var checklistItems = new List() { new FileChecklistItemModel() { FileId = dispFile.DispositionFileId }, new FileChecklistItemModel() { FileId = dispFile.DispositionFileId + 1 } }; + this._service.Setup(m => m.UpdateChecklistItems(It.IsAny>())).Returns(dispFile); + + // Act + Action act = () => this._controller.UpdateDispositionFileChecklist(dispFile.DispositionFileId, checklistItems); + + // Assert + act.Should().Throw(); + this._service.Verify(m => m.UpdateChecklistItems(It.IsAny>()), Times.Never()); } #endregion diff --git a/source/backend/tests/unit/api/Controllers/Disposition/DispositionControllerTest.cs b/source/backend/tests/unit/api/Controllers/Disposition/DispositionControllerTest.cs index d7c7169008..3467cd9d3c 100644 --- a/source/backend/tests/unit/api/Controllers/Disposition/DispositionControllerTest.cs +++ b/source/backend/tests/unit/api/Controllers/Disposition/DispositionControllerTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.IO.Compression; using FluentAssertions; using MapsterMapper; using Microsoft.AspNetCore.Mvc; @@ -188,6 +189,162 @@ public void UpdateDispositionFileSale_Success() // Assert this._service.Verify(m => m.UpdateDispositionFileSale(It.IsAny()), Times.Once()); } + + /// + /// Get All Offers by Disposition File's Id. + /// + [Fact] + public void GetDispositionFileOffers_Success() + { + // Arrange + this._service.Setup(m => m.GetOffers(It.IsAny())).Returns(new List()); + + // Act + var result = this._controller.GetDispositionFileOffers(1); + + // Assert + this._service.Verify(m => m.GetOffers(It.IsAny()), Times.Once()); + } + + /// + /// Get Offer by Id. + /// + [Fact] + public void GetDispositionFileOfferById_Success() + { + // Arrange + this._service.Setup(m => m.GetDispositionOfferById(It.IsAny(), It.IsAny())).Returns(new PimsDispositionOffer()); + + // Act + var result = this._controller.GetDispositionFileOfferById(1, 10); + + // Assert + this._service.Verify(m => m.GetDispositionOfferById(It.IsAny(), It.IsAny()), Times.Once()); + } + + /// + /// Add Disposition Offer to Disposition File. + /// + [Fact] + public void AddDispositionFileOffer_Success() + { + // Arrange + this._service.Setup(m => m.AddDispositionFileOffer(It.IsAny(), It.IsAny())).Returns(new PimsDispositionOffer()); + var dispositionFileOffer = new PimsDispositionOffer(); + + // Act + var model = _mapper.Map(dispositionFileOffer); + var result = this._controller.AddDispositionFileOffer(1, model); + + // Assert + this._service.Verify(m => m.AddDispositionFileOffer(It.IsAny(), It.IsAny()), Times.Once()); + } + + /// + /// Update Disposition Offer to Disposition File. + /// + [Fact] + public void UpdateDispositionFileOffer_Success() + { + // Arrange + this._service.Setup(m => m.UpdateDispositionFileOffer(It.IsAny(), It.IsAny(), It.IsAny())).Returns(new PimsDispositionOffer()); + var dispositionFileOffer = new PimsDispositionOffer(); + dispositionFileOffer.DispositionOfferId = 10; + dispositionFileOffer.DispositionFileId = 1; + + // Act + var model = _mapper.Map(dispositionFileOffer); + var result = this._controller.UpdateDispositionFileOffer(1, 10, model); + + // Assert + this._service.Verify(m => m.UpdateDispositionFileOffer(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + } + + + /// + /// Delete Disposition Offer to Disposition File. + /// + [Fact] + public void DeleteDispositionFileOffer_Success() + { + // Arrange + this._service.Setup(m => m.DeleteDispositionFileOffer(It.IsAny(), It.IsAny())).Returns(true); + + // Act + var result = this._controller.DeleteDispositionFileOffer(1, 10); + + // Assert + this._service.Verify(m => m.DeleteDispositionFileOffer(It.IsAny(), It.IsAny()), Times.Once()); + } + + /// + /// Get DispositionFile's Sale by Id. + /// + [Fact] + public void GetDispositionFileSales_Success() + { + // Arrange + this._service.Setup(m => m.GetDispositionFileSale(It.IsAny())).Returns(new PimsDispositionSale()); + + // Act + var result = this._controller.GetDispositionFileSales(1); + + // Assert + this._service.Verify(m => m.GetDispositionFileSale(It.IsAny()), Times.Once()); + } + + /// + /// Get DispositionFile's Appraisal by Id. + /// + [Fact] + public void GetDispositionFileAppraisal_Success() + { + // Arrange + this._service.Setup(m => m.GetDispositionFileAppraisal(It.IsAny())).Returns(new PimsDispositionAppraisal()); + + // Act + var result = this._controller.GetDispositionFileAppraisal(1); + + // Assert + this._service.Verify(m => m.GetDispositionFileAppraisal(It.IsAny()), Times.Once()); + } + + /// + /// Add DispositionFile's Appraisal. + /// + [Fact] + public void AddDispositionFileAppraisal_Success() + { + // Arrange + PimsDispositionAppraisal appraisal = new PimsDispositionAppraisal(); + this._service.Setup(m => m.AddDispositionFileAppraisal(It.IsAny(), It.IsAny())).Returns(new PimsDispositionAppraisal()); + + // Act + var model = _mapper.Map(appraisal); + var result = this._controller.AddDispositionFileAppraisal(1, model); + + // Assert + this._service.Verify(m => m.AddDispositionFileAppraisal(It.IsAny(), It.IsAny()), Times.Once()); + } + + /// + /// Update DispositionFile's Appraisal. + /// + [Fact] + public void UpdateDispositionFileAppraisal_Success() + { + // Arrange + PimsDispositionAppraisal appraisal = new PimsDispositionAppraisal(); + this._service.Setup(m => m.AddDispositionFileAppraisal(It.IsAny(), It.IsAny())).Returns(new PimsDispositionAppraisal()); + + // Act + var model = _mapper.Map(appraisal); + var result = this._controller.UpdateDispositionFileAppraisal(1, 10, model); + + // Assert + this._service.Verify(m => m.UpdateDispositionFileAppraisal(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + } + #endregion } } diff --git a/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs b/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs index 4bbbd21640..e5d55ba134 100644 --- a/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs +++ b/source/backend/tests/unit/api/Services/AcquisitionFileServiceTest.cs @@ -10,6 +10,7 @@ using NetTopologySuite.Geometries; using Pims.Api.Constants; using Pims.Api.Helpers.Exceptions; +using Pims.Api.Models.CodeTypes; using Pims.Api.Models.Concepts; using Pims.Api.Services; using Pims.Core.Exceptions; @@ -1696,8 +1697,9 @@ public void UpdateChecklist_Success() // Arrange var service = this.CreateAcquisitionServiceWithPermissions(Permissions.AcquisitionFileEdit); + var checklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 1, AcqChklstItemStatusTypeCode = "COMPLT" } }; + var acqFile = EntityHelper.CreateAcquisitionFile(); - acqFile.PimsAcquisitionChecklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 1, AcqChklstItemStatusTypeCode = "COMPLT" } }; var repository = this._helper.GetService>(); repository.Setup(x => x.GetById(It.IsAny())).Returns(acqFile); @@ -1713,7 +1715,7 @@ public void UpdateChecklist_Success() solver.Setup(x => x.CanEditChecklists(It.IsAny())).Returns(true); // Act - service.UpdateChecklistItems(acqFile); + service.UpdateChecklistItems(checklistItems); // Assert fileChecklistRepository.Verify(x => x.GetAllChecklistItemsByAcquisitionFileId(It.IsAny()), Times.Once); @@ -1721,14 +1723,35 @@ public void UpdateChecklist_Success() repository.Verify(x => x.GetById(It.IsAny()), Times.Exactly(3)); } + + [Fact] + public void UpdateChecklist_NoEmptyList() + { + // Arrange + var service = this.CreateAcquisitionServiceWithPermissions(); + + var acqFile = EntityHelper.CreateAcquisitionFile(); + + var repository = this._helper.GetService>(); + repository.Setup(x => x.GetAllChecklistItemsByAcquisitionFileId(It.IsAny())).Returns(acqFile.PimsAcquisitionChecklistItems.ToList()); + + // Act + Action act = () => service.UpdateChecklistItems(new List()); + + // Assert + act.Should().Throw(); + repository.Verify(x => x.GetAllChecklistItemsByAcquisitionFileId(It.IsAny()), Times.Never); + } + [Fact] public void UpdateChecklist_ItemNotFound() { // Arrange var service = this.CreateAcquisitionServiceWithPermissions(Permissions.AcquisitionFileEdit); + var checklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 999, AcqChklstItemStatusTypeCode = "COMPLT" } }; + var acqFile = EntityHelper.CreateAcquisitionFile(); - acqFile.PimsAcquisitionChecklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 999, AcqChklstItemStatusTypeCode = "COMPLT" } }; acqFile.AcquisitionFileStatusTypeCode = AcquisitionStatusTypes.ACTIVE.ToString(); var acqRepository = this._helper.GetService>(); @@ -1745,7 +1768,7 @@ public void UpdateChecklist_ItemNotFound() solver.Setup(x => x.CanEditChecklists(It.IsAny())).Returns(true); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); @@ -1761,13 +1784,14 @@ public void UpdateChecklist_NoPermission() // Arrange var service = this.CreateAcquisitionServiceWithPermissions(); + var checklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 999, AcqChklstItemStatusTypeCode = "COMPLT" } }; var acqFile = EntityHelper.CreateAcquisitionFile(); var repository = this._helper.GetService>(); repository.Setup(x => x.GetAllChecklistItemsByAcquisitionFileId(It.IsAny())).Returns(acqFile.PimsAcquisitionChecklistItems.ToList()); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); @@ -1780,6 +1804,7 @@ public void UpdateChecklist_NotAuthorized_Contractor() // Arrange var service = this.CreateAcquisitionServiceWithPermissions(Permissions.AcquisitionFileEdit); + var checklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 999, AcqChklstItemStatusTypeCode = "COMPLT" } }; var acqFile = EntityHelper.CreateAcquisitionFile(); var repository = this._helper.GetService>(); @@ -1793,7 +1818,7 @@ public void UpdateChecklist_NotAuthorized_Contractor() userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); @@ -1806,7 +1831,7 @@ public void UpdateChecklist_InvalidStatus() var service = this.CreateAcquisitionServiceWithPermissions(Permissions.AcquisitionFileEdit); var acqFile = EntityHelper.CreateAcquisitionFile(); - acqFile.PimsAcquisitionChecklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 1, AcqChklstItemStatusTypeCode = "COMPLT" } }; + var checklistItems = new List() { new PimsAcquisitionChecklistItem() { Internal_Id = 1, AcqChklstItemStatusTypeCode = "COMPLT" } }; var repository = this._helper.GetService>(); repository.Setup(x => x.GetById(It.IsAny())).Returns(acqFile); @@ -1822,7 +1847,7 @@ public void UpdateChecklist_InvalidStatus() solver.Setup(x => x.CanEditChecklists(It.IsAny())).Returns(false); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); diff --git a/source/backend/tests/unit/api/Services/CompensationRequisitionServiceTest.cs b/source/backend/tests/unit/api/Services/CompensationRequisitionServiceTest.cs index b1e31d550e..63090f260e 100644 --- a/source/backend/tests/unit/api/Services/CompensationRequisitionServiceTest.cs +++ b/source/backend/tests/unit/api/Services/CompensationRequisitionServiceTest.cs @@ -8,6 +8,7 @@ using Moq; using Pims.Api.Constants; using Pims.Api.Helpers.Exceptions; +using Pims.Api.Models.CodeTypes; using Pims.Api.Services; using Pims.Core.Exceptions; using Pims.Core.Test; diff --git a/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs b/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs index 060513e00f..d7a0bc8c47 100644 --- a/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs +++ b/source/backend/tests/unit/api/Services/DispositionFileServiceTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Threading.Channels; using DocumentFormat.OpenXml.Office2010.Excel; using FluentAssertions; using MapsterMapper; @@ -60,7 +61,7 @@ public void GetById_Success() var result = service.GetById(1); // Assert - repository.Verify(x => x.GetById(It.IsAny()), Times.Once); + repository.Verify(x => x.GetById(It.IsAny()), Times.Exactly(2)); } [Fact] @@ -188,13 +189,13 @@ public void GetPage_Success() var dispFile = EntityHelper.CreateDispositionFile(); var repository = this._helper.GetService>(); - repository.Setup(x => x.GetPageDeep(It.IsAny())).Returns(new Paged(new[] { dispFile })); + repository.Setup(x => x.GetPageDeep(It.IsAny(), null)).Returns(new Paged(new[] { dispFile })); // Act var result = service.GetPage(new DispositionFilter()); // Assert - repository.Verify(x => x.GetPageDeep(It.IsAny()), Times.Once); + repository.Verify(x => x.GetPageDeep(It.IsAny(), null), Times.Once); } [Fact] @@ -229,6 +230,24 @@ public void Add_Should_Fail_NoPermission() act.Should().Throw(); } + [Fact] + public void Add_ThrowIfNull() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionAdd); + + var acqFile = EntityHelper.CreateDispositionFile(); + + var repository = this._helper.GetService>(); + + // Act + Action act = () => service.Add(null, new List()); + + // Assert + act.Should().Throw(); + repository.Verify(x => x.Add(It.IsAny()), Times.Never); + } + [Fact] public void Add_Fails_Duplicate_Team() { @@ -244,6 +263,55 @@ public void Add_Fails_Duplicate_Team() // Assert act.Should().Throw(); } + + [Fact] + public void Add_ContractorNotInTeamException_Fail_IsContractor() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionAdd); + + var dispositionFile = EntityHelper.CreateDispositionFile(); + + var userRepository = this._helper.GetService>(); + var contractorUser = EntityHelper.CreateUser(1, Guid.NewGuid(), username: "Test", isContractor: true); + userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); + + // Act + Action act = () => service.Add(dispositionFile, new List() { UserOverrideCode.UpdateRegion }); + + // Assert + var ex = act.Should().Throw(); + } + + [Fact] + public void Add_Success_IsContractor_AssignedToTeam() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionAdd); + + var dispositionFile = EntityHelper.CreateDispositionFile(); + dispositionFile.PimsDispositionFileTeams.Add(new PimsDispositionFileTeam() { PersonId = 1, DspFlTeamProfileTypeCode = "test" }); + + var userRepository = this._helper.GetService>(); + + var newGuid = Guid.NewGuid(); + var contractorUser = EntityHelper.CreateUser(1, newGuid, username: "Test", isContractor: true); + contractorUser.PersonId = 1; + userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); + + var repository = this._helper.GetService>(); + repository.Setup(x => x.Add(It.IsAny())).Returns(dispositionFile); + + var lookupRepository = this._helper.GetService>(); + lookupRepository.Setup(x => x.GetAllRegions()).Returns(new List() { new PimsRegion() { Code = 4, RegionName = "Cannot determine" } }); + + // Act + var result = service.Add(dispositionFile, new List()); + + // Assert + repository.Verify(x => x.Add(It.IsAny()), Times.Once); + } + #endregion #region Update @@ -342,6 +410,99 @@ public void Update_UserOverride_Final_Validation() ex.Which.UserOverride.Should().Be(UserOverrideCode.DispositionFileFinalStatus); } + [Fact] + public void Update_NotAuthorized_IsContractor() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.AcquisitionFileEdit); + var dispositionFile = EntityHelper.CreateDispositionFile(1); + + var repository = this._helper.GetService>(); + repository.Setup(x => x.GetRowVersion(It.IsAny())).Returns(1); + repository.Setup(x => x.Update(It.IsAny(),It.IsAny())).Returns(dispositionFile); + + var userRepository = this._helper.GetService>(); + var contractorUser = EntityHelper.CreateUser(1, Guid.NewGuid(), username: "Test", isContractor: true); + userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); + + // Act + Action act = () => service.Update(1, dispositionFile, new List() { UserOverrideCode.UpdateRegion }); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void Update_IsContractor_SelfRemoved_ShouldFail() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); + var dispositionFile = EntityHelper.CreateDispositionFile(1); + dispositionFile.PimsDispositionFileTeams = new List() + { + new PimsDispositionFileTeam() + { + DispositionFileTeamId = 100, + DispositionFileId = 1, + PersonId = 20, + } + }; + + var repository = this._helper.GetService>(); + repository.Setup(x => x.GetRowVersion(It.IsAny())).Returns(1); + repository.Setup(x => x.GetById(It.IsAny())).Returns(dispositionFile); + repository.Setup(x => x.Update(It.IsAny(), It.IsAny())).Returns(dispositionFile); + + var userRepository = this._helper.GetService>(); + var contractorUser = EntityHelper.CreateUser(1, Guid.NewGuid(), username: "Test", isContractor: true); + contractorUser.PersonId = 20; + + userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); + + var updateDispositionFile = EntityHelper.CreateDispositionFile(1); + + // Act + Action act = () => service.Update(1, updateDispositionFile, new List() { UserOverrideCode.UpdateRegion }); + + // Assert + var ex = act.Should().Throw(); + } + + [Fact] + public void Update_IsContractor_Success() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); + var dispositionFile = EntityHelper.CreateDispositionFile(1); + dispositionFile.PimsDispositionFileTeams = new List() + { + new PimsDispositionFileTeam() + { + DispositionFileTeamId = 100, + DispositionFileId = 1, + PersonId = 20, + } + }; + + var repository = this._helper.GetService>(); + repository.Setup(x => x.GetRowVersion(It.IsAny())).Returns(1); + repository.Setup(x => x.GetById(It.IsAny())).Returns(dispositionFile); + repository.Setup(x => x.Update(It.IsAny(), It.IsAny())).Returns(dispositionFile); + + var userRepository = this._helper.GetService>(); + var contractorUser = EntityHelper.CreateUser(1, Guid.NewGuid(), username: "Test", isContractor: true); + contractorUser.PersonId = 20; + + userRepository.Setup(x => x.GetUserInfoByKeycloakUserId(It.IsAny())).Returns(contractorUser); + + + // Act + var result = service.Update(1, dispositionFile, new List() { UserOverrideCode.UpdateRegion }); + + // Assert + Assert.NotNull(result); + } + [Fact] public void Update_Success() { @@ -556,8 +717,8 @@ public void UpdateChecklist_Success() // Arrange var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); + var checklistItems = new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "COMPLT" } }; var acqFile = EntityHelper.CreateDispositionFile(); - acqFile.PimsDispositionChecklistItems = new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "COMPLT" } }; var repository = this._helper.GetService>(); repository.Setup(x => x.GetById(It.IsAny())).Returns(acqFile); @@ -567,7 +728,7 @@ public void UpdateChecklist_Success() .Returns(new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "INCOMP" } }); // Act - service.UpdateChecklistItems(acqFile); + service.UpdateChecklistItems(checklistItems); // Assert fileChecklistRepository.Verify(x => x.GetAllChecklistItemsByDispositionFileId(It.IsAny()), Times.Once); @@ -575,14 +736,40 @@ public void UpdateChecklist_Success() repository.Verify(x => x.GetById(It.IsAny()), Times.Exactly(1)); } + [Fact] + public void UpdateChecklist_NoEmptyList() + { + // Arrange + var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); + + var acqFile = EntityHelper.CreateDispositionFile(); + acqFile.DispositionFileStatusTypeCode = "ACTIV"; + + var acqRepository = this._helper.GetService>(); + acqRepository.Setup(x => x.GetById(It.IsAny())).Returns(acqFile); + + var fileChecklistRepository = this._helper.GetService>(); + fileChecklistRepository.Setup(x => x.GetAllChecklistItemsByDispositionFileId(It.IsAny())) + .Returns(new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "INCOMP" } }); + + // Act + Action act = () => service.UpdateChecklistItems(new List()); + + // Assert + act.Should().Throw(); + + fileChecklistRepository.Verify(x => x.GetAllChecklistItemsByDispositionFileId(It.IsAny()), Times.Never); + fileChecklistRepository.Verify(x => x.Update(It.IsAny()), Times.Never); + } + [Fact] public void UpdateChecklist_ItemNotFound() { // Arrange var service = this.CreateDispositionServiceWithPermissions(Permissions.DispositionEdit); + var checklistItems = new List() { new PimsDispositionChecklistItem() { Internal_Id = 999, DspChklstItemStatusTypeCode = "COMPLT" } }; var acqFile = EntityHelper.CreateDispositionFile(); - acqFile.PimsDispositionChecklistItems = new List() { new PimsDispositionChecklistItem() { Internal_Id = 999, DspChklstItemStatusTypeCode = "COMPLT" } }; acqFile.DispositionFileStatusTypeCode = "ACTIV"; var acqRepository = this._helper.GetService>(); @@ -593,7 +780,7 @@ public void UpdateChecklist_ItemNotFound() .Returns(new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "INCOMP" } }); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); @@ -608,13 +795,14 @@ public void UpdateChecklist_NoPermission() // Arrange var service = this.CreateDispositionServiceWithPermissions(); + var checklistItems = new List() { new PimsDispositionChecklistItem() { Internal_Id = 1, DspChklstItemStatusTypeCode = "COMPLT" } }; var acqFile = EntityHelper.CreateDispositionFile(); var repository = this._helper.GetService>(); repository.Setup(x => x.GetAllChecklistItemsByDispositionFileId(It.IsAny())).Returns(acqFile.PimsDispositionChecklistItems.ToList()); // Act - Action act = () => service.UpdateChecklistItems(acqFile); + Action act = () => service.UpdateChecklistItems(checklistItems); // Assert act.Should().Throw(); diff --git a/source/backend/tests/unit/api/Services/TakeServiceTest.cs b/source/backend/tests/unit/api/Services/TakeServiceTest.cs index ff3d059a65..f2759aba9c 100644 --- a/source/backend/tests/unit/api/Services/TakeServiceTest.cs +++ b/source/backend/tests/unit/api/Services/TakeServiceTest.cs @@ -4,6 +4,7 @@ using FluentAssertions; using Moq; using Pims.Api.Constants; +using Pims.Api.Models.CodeTypes; using Pims.Api.Services; using Pims.Core.Exceptions; using Pims.Core.Test; diff --git a/source/backend/tests/unit/api/Solvers/AcquisitionStatusSolverTests.cs b/source/backend/tests/unit/api/Solvers/AcquisitionStatusSolverTests.cs index 26b9bc4d11..8a04e3a25b 100644 --- a/source/backend/tests/unit/api/Solvers/AcquisitionStatusSolverTests.cs +++ b/source/backend/tests/unit/api/Solvers/AcquisitionStatusSolverTests.cs @@ -10,6 +10,7 @@ using NetTopologySuite.Geometries; using Pims.Api.Constants; using Pims.Api.Helpers.Exceptions; +using Pims.Api.Models.CodeTypes; using Pims.Api.Models.Concepts; using Pims.Api.Services; using Pims.Core.Exceptions; diff --git a/source/backend/tests/unit/dal/Repositories/DispositionFileRepositoryTest.cs b/source/backend/tests/unit/dal/Repositories/DispositionFileRepositoryTest.cs index f8b99b2ad6..4e1b2b8d68 100644 --- a/source/backend/tests/unit/dal/Repositories/DispositionFileRepositoryTest.cs +++ b/source/backend/tests/unit/dal/Repositories/DispositionFileRepositoryTest.cs @@ -732,6 +732,7 @@ public void GetPageDeep_Type_Success() // Assert result.Should().HaveCount(1); } + #endregion #region Export diff --git a/source/frontend/package.json b/source/frontend/package.json index 41764eabf0..f7e531e301 100644 --- a/source/frontend/package.json +++ b/source/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "5.0.0-72.13", + "version": "5.0.0-72.21", "private": true, "dependencies": { "@bcgov/bc-sans": "1.0.1", diff --git a/source/frontend/public/mockServiceWorker.js b/source/frontend/public/mockServiceWorker.js index a8aa7b555f..4147a4044c 100644 --- a/source/frontend/public/mockServiceWorker.js +++ b/source/frontend/public/mockServiceWorker.js @@ -8,121 +8,121 @@ * - Please do NOT serve this file on production. */ -const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70' -const activeClientIds = new Set() +const INTEGRITY_CHECKSUM = '3d6b9f06410d179a7f7404d4bf4c3c70'; +const activeClientIds = new Set(); self.addEventListener('install', function () { - self.skipWaiting() -}) + self.skipWaiting(); +}); self.addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) + event.waitUntil(self.clients.claim()); +}); self.addEventListener('message', async function (event) { - const clientId = event.source.id + const clientId = event.source.id; if (!clientId || !self.clients) { - return + return; } - const client = await self.clients.get(clientId) + const client = await self.clients.get(clientId); if (!client) { - return + return; } const allClients = await self.clients.matchAll({ type: 'window', - }) + }); switch (event.data) { case 'KEEPALIVE_REQUEST': { sendToClient(client, { type: 'KEEPALIVE_RESPONSE', - }) - break + }); + break; } case 'INTEGRITY_CHECK_REQUEST': { sendToClient(client, { type: 'INTEGRITY_CHECK_RESPONSE', payload: INTEGRITY_CHECKSUM, - }) - break + }); + break; } case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) + activeClientIds.add(clientId); sendToClient(client, { type: 'MOCKING_ENABLED', payload: true, - }) - break + }); + break; } case 'MOCK_DEACTIVATE': { - activeClientIds.delete(clientId) - break + activeClientIds.delete(clientId); + break; } case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) + activeClientIds.delete(clientId); - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) + const remainingClients = allClients.filter(client => { + return client.id !== clientId; + }); // Unregister itself when there are no more clients if (remainingClients.length === 0) { - self.registration.unregister() + self.registration.unregister(); } - break + break; } } -}) +}); self.addEventListener('fetch', function (event) { - const { request } = event - const accept = request.headers.get('accept') || '' + const { request } = event; + const accept = request.headers.get('accept') || ''; // Bypass server-sent events. if (accept.includes('text/event-stream')) { - return + return; } // Bypass navigation requests. if (request.mode === 'navigate') { - return + return; } // Opening the DevTools triggers the "only-if-cached" request // that cannot be handled by the worker. Bypass such requests. if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { - return + return; } // Bypass all requests when there are no active clients. // Prevents the self-unregistered worked from handling requests // after it's been deleted (still remains active until the next reload). if (activeClientIds.size === 0) { - return + return; } // Generate unique request ID. - const requestId = Math.random().toString(16).slice(2) + const requestId = Math.random().toString(16).slice(2); event.respondWith( - handleRequest(event, requestId).catch((error) => { + handleRequest(event, requestId).catch(error => { if (error.name === 'NetworkError') { console.warn( '[MSW] Successfully emulated a network error for the "%s %s" request.', request.method, request.url, - ) - return + ); + return; } // At this point, any exception indicates an issue with the original request/response. @@ -132,21 +132,21 @@ self.addEventListener('fetch', function (event) { request.method, request.url, `${error.name}: ${error.message}`, - ) + ); }), - ) -}) + ); +}); async function handleRequest(event, requestId) { - const client = await resolveMainClient(event) - const response = await getResponse(event, client, requestId) + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); // Send back the response clone for the "response:*" life-cycle events. // Ensure MSW is active and ready to handle the message, otherwise // this message will pend indefinitely. if (client && activeClientIds.has(client.id)) { - ;(async function () { - const clonedResponse = response.clone() + (async function () { + const clonedResponse = response.clone(); sendToClient(client, { type: 'RESPONSE', payload: { @@ -155,16 +155,15 @@ async function handleRequest(event, requestId) { ok: clonedResponse.ok, status: clonedResponse.status, statusText: clonedResponse.statusText, - body: - clonedResponse.body === null ? null : await clonedResponse.text(), + body: clonedResponse.body === null ? null : await clonedResponse.text(), headers: Object.fromEntries(clonedResponse.headers.entries()), redirected: clonedResponse.redirected, }, - }) - })() + }); + })(); } - return response + return response; } // Resolve the main client for the given event. @@ -172,49 +171,49 @@ async function handleRequest(event, requestId) { // that registered the worker. It's with the latter the worker should // communicate with during the response resolving phase. async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) + const client = await self.clients.get(event.clientId); if (client?.frameType === 'top-level') { - return client + return client; } const allClients = await self.clients.matchAll({ type: 'window', - }) + }); return allClients - .filter((client) => { + .filter(client => { // Get only those clients that are currently visible. - return client.visibilityState === 'visible' + return client.visibilityState === 'visible'; }) - .find((client) => { + .find(client => { // Find the client ID that's recorded in the // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) + return activeClientIds.has(client.id); + }); } async function getResponse(event, client, requestId) { - const { request } = event - const clonedRequest = request.clone() + const { request } = event; + const clonedRequest = request.clone(); function passthrough() { // Clone the request because it might've been already used // (i.e. its body has been read and sent to the client). - const headers = Object.fromEntries(clonedRequest.headers.entries()) + const headers = Object.fromEntries(clonedRequest.headers.entries()); // Remove MSW-specific request headers so the bypassed requests // comply with the server's CORS preflight check. // Operate with the headers as an object because request "Headers" // are immutable. - delete headers['x-msw-bypass'] + delete headers['x-msw-bypass']; - return fetch(clonedRequest, { headers }) + return fetch(clonedRequest, { headers }); } // Bypass mocking when the client is not active. if (!client) { - return passthrough() + return passthrough(); } // Bypass initial page load requests (i.e. static assets). @@ -222,13 +221,13 @@ async function getResponse(event, client, requestId) { // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet // and is not ready to handle requests. if (!activeClientIds.has(client.id)) { - return passthrough() + return passthrough(); } // Bypass requests with the explicit bypass header. // Such requests can be issued by "ctx.fetch()". if (request.headers.get('x-msw-bypass') === 'true') { - return passthrough() + return passthrough(); } // Notify the client that a request has been intercepted. @@ -251,53 +250,53 @@ async function getResponse(event, client, requestId) { bodyUsed: request.bodyUsed, keepalive: request.keepalive, }, - }) + }); switch (clientMessage.type) { case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) + return respondWithMock(clientMessage.data); } case 'MOCK_NOT_FOUND': { - return passthrough() + return passthrough(); } case 'NETWORK_ERROR': { - const { name, message } = clientMessage.data - const networkError = new Error(message) - networkError.name = name + const { name, message } = clientMessage.data; + const networkError = new Error(message); + networkError.name = name; // Rejecting a "respondWith" promise emulates a network error. - throw networkError + throw networkError; } } - return passthrough() + return passthrough(); } function sendToClient(client, message) { return new Promise((resolve, reject) => { - const channel = new MessageChannel() + const channel = new MessageChannel(); - channel.port1.onmessage = (event) => { + channel.port1.onmessage = event => { if (event.data && event.data.error) { - return reject(event.data.error) + return reject(event.data.error); } - resolve(event.data) - } + resolve(event.data); + }; - client.postMessage(message, [channel.port2]) - }) + client.postMessage(message, [channel.port2]); + }); } function sleep(timeMs) { - return new Promise((resolve) => { - setTimeout(resolve, timeMs) - }) + return new Promise(resolve => { + setTimeout(resolve, timeMs); + }); } async function respondWithMock(response) { - await sleep(response.delay) - return new Response(response.body, response) + await sleep(response.delay); + return new Response(response.body, response); } diff --git a/source/frontend/src/AppRouter.test.tsx b/source/frontend/src/AppRouter.test.tsx index dc652e8eb5..a1eb96d534 100644 --- a/source/frontend/src/AppRouter.test.tsx +++ b/source/frontend/src/AppRouter.test.tsx @@ -16,7 +16,7 @@ import { ILeaseSearchResult, IPagedItems, IProperty } from './interfaces'; import { IResearchSearchResult } from './interfaces/IResearchSearchResult'; import { mockLookups } from './mocks/lookups.mock'; import { getMockPagedUsers, getUserMock } from './mocks/user.mock'; -import { Api_AcquisitionFile } from './models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from './models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { lookupCodesSlice } from './store/slices/lookupCodes'; import { networkSlice } from './store/slices/network/networkSlice'; import { tenantsSlice } from './store/slices/tenants'; @@ -108,7 +108,7 @@ jest.mock('./hooks/pims-api/useApiAcquisitionFile'); (useApiAcquisitionFile as jest.MockedFunction).mockReturnValue({ getAcquisitionFiles: jest .fn() - .mockResolvedValue({ data: {} as IPagedItems }), + .mockResolvedValue({ data: {} as IPagedItems }), getAcquisitionFile: jest.fn(), getLastUpdatedByApi: jest.fn(), getAgreementReport: jest.fn(), diff --git a/source/frontend/src/components/Table/helpers.tsx b/source/frontend/src/components/Table/helpers.tsx index abbf1ac82d..ab80bddd64 100644 --- a/source/frontend/src/components/Table/helpers.tsx +++ b/source/frontend/src/components/Table/helpers.tsx @@ -1,7 +1,6 @@ import { CellProps } from 'react-table'; import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; -import Api_TypeCode from '@/models/api/TypeCode'; import { formatMoney, formatNumber, prettyFormatDate, stringToFragment } from '@/utils'; /** @@ -25,7 +24,9 @@ export const renderPercentage = ({ cell: { value } }: CellProp export const renderBooleanAsYesNo = ({ value }: CellProps) => stringToFragment(value ? 'Y' : 'N'); -export const renderTypeCode = ({ value }: CellProps | undefined | null>) => +export const renderTypeCode = ({ + value, +}: CellProps | undefined | null>) => stringToFragment(value?.description ?? ''); export const renderGenTypeCode = ({ diff --git a/source/frontend/src/components/common/ContactLink.tsx b/source/frontend/src/components/common/ContactLink.tsx index 10b72e51ed..6cb4069daf 100644 --- a/source/frontend/src/components/common/ContactLink.tsx +++ b/source/frontend/src/components/common/ContactLink.tsx @@ -1,18 +1,19 @@ import { FaExternalLinkAlt } from 'react-icons/fa'; -import { Api_Organization } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { StyledLink } from '../maps/leaflet/LayerPopup/styles'; type ContactPersonLink = { - person: Api_Person; + person: ApiGen_Concepts_Person; organization?: never; }; type ContactOrganizationLink = { person?: never; - organization: Api_Organization; + organization: ApiGen_Concepts_Organization; }; export type IContactLinkProps = ContactPersonLink | ContactOrganizationLink; @@ -20,7 +21,7 @@ export type IContactLinkProps = ContactPersonLink | ContactOrganizationLink; function isPersonLink( contactLink: ContactPersonLink | ContactOrganizationLink, ): contactLink is ContactPersonLink { - return contactLink.person !== undefined; + return exists(contactLink.person); } export const ContactLink: React.FunctionComponent< diff --git a/source/frontend/src/components/common/ExpandableTextList.tsx b/source/frontend/src/components/common/ExpandableTextList.tsx index fa307ea8e1..6ed2f05a70 100644 --- a/source/frontend/src/components/common/ExpandableTextList.tsx +++ b/source/frontend/src/components/common/ExpandableTextList.tsx @@ -1,5 +1,7 @@ import { ReactElement, useState } from 'react'; +import { exists } from '@/utils/utils'; + import { LinkButton } from './buttons'; export interface IExpandableTextListProps { @@ -32,13 +34,11 @@ export function ExpandableTextList({ {index < items.length - 1 && delimiter} ))} - {maxCollapsedLength !== undefined && - maxCollapsedLength !== null && - maxCollapsedLength < items.length && ( - setIsExpanded(collapse => !collapse)}> - {isExpanded ? 'hide' : `[+${items.length - displayedItemsLength} more...]`} - - )} + {exists(maxCollapsedLength) && maxCollapsedLength < items.length && ( + setIsExpanded(collapse => !collapse)}> + {isExpanded ? 'hide' : `[+${items.length - displayedItemsLength} more...]`} + + )} ); } diff --git a/source/frontend/src/components/common/List/ExpandableFileProperties.tsx b/source/frontend/src/components/common/List/ExpandableFileProperties.tsx index b3a329fab3..e393df25ee 100644 --- a/source/frontend/src/components/common/List/ExpandableFileProperties.tsx +++ b/source/frontend/src/components/common/List/ExpandableFileProperties.tsx @@ -3,11 +3,11 @@ import { Col, Row } from 'react-bootstrap'; import styled from 'styled-components'; import { LinkButton } from '@/components/common/buttons'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; import { formatApiAddress } from '@/utils'; export interface IExpandableFilePropertiesProps { - fileProperties?: Api_PropertyFile[]; + fileProperties?: ApiGen_Concepts_FileProperty[]; maxDisplayCount: number; } @@ -17,7 +17,7 @@ const ExpandableFileProperties: React.FunctionComponent[] = [ - { id: 'FOO', description: 'Foo' }, - { id: 'BAR', description: 'Bar' }, - { id: 'BAZ', description: 'Baz' }, +const mockOptions: ApiGen_Base_CodeType[] = [ + { + id: 'FOO', + description: 'Foo', + isDisabled: false, + displayOrder: null, + }, + { + id: 'BAR', + description: 'Bar', + isDisabled: false, + displayOrder: null, + }, + { + id: 'BAZ', + description: 'Baz', + isDisabled: false, + displayOrder: null, + }, ]; describe('MultiselectTextList component', () => { diff --git a/source/frontend/src/components/common/form/ContactInput.tsx b/source/frontend/src/components/common/form/ContactInput.tsx index 03d31e3aa7..8c522cdb01 100644 --- a/source/frontend/src/components/common/form/ContactInput.tsx +++ b/source/frontend/src/components/common/form/ContactInput.tsx @@ -11,6 +11,7 @@ import styled from 'styled-components'; import { Button } from '@/components/common/buttons/Button'; import { Input } from '@/components/common/form'; import { IContactSearchResult } from '@/interfaces'; +import { isValidId } from '@/utils'; import { formatNames } from '@/utils/personUtils'; import { LinkButton } from '../buttons'; @@ -48,9 +49,9 @@ export const ContactInput: React.FC> var text = 'Select from contacts'; if (contactInfo !== undefined) { - if (contactInfo.personId !== undefined) { + if (isValidId(contactInfo.personId)) { text = formatNames([contactInfo.firstName, contactInfo.middleNames, contactInfo.surname]); - } else if (contactInfo.organizationId !== undefined) { + } else if (isValidId(contactInfo.organizationId)) { text = contactInfo.organizationName || ''; } } diff --git a/source/frontend/src/components/common/form/ParentSelect.tsx b/source/frontend/src/components/common/form/ParentSelect.tsx index 35e30bd14b..d3611e1a83 100644 --- a/source/frontend/src/components/common/form/ParentSelect.tsx +++ b/source/frontend/src/components/common/form/ParentSelect.tsx @@ -5,6 +5,8 @@ import React, { Fragment, useEffect, useState } from 'react'; import { Highlighter, Menu, MenuItem } from 'react-bootstrap-typeahead'; import styled from 'styled-components'; +import { exists } from '@/utils'; + import { Label } from '../Label'; import { SelectOption, SelectOptions } from './Select'; import { TypeaheadField } from './Typeahead'; @@ -101,7 +103,7 @@ export const ParentSelect: React.FC> = ({ if (value?.value) { return [value]; } - if (value !== undefined && !_.isEmpty(value?.toString())) { + if (exists(value) && !_.isEmpty(value?.toString())) { /** select appropriate organization to set the field value to when present */ const option = options.find(x => x.value === value?.toString() || x.value === value); return option ? [option] : []; diff --git a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.test.tsx b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.test.tsx index a3ceaa8f69..aac5402ce2 100644 --- a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.test.tsx +++ b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.test.tsx @@ -6,7 +6,7 @@ import { fromApiOrganization, fromApiPerson } from '@/interfaces/IContactSearchR import { getMockPerson } from '@/mocks/contacts.mock'; import { mockLookups } from '@/mocks/lookups.mock'; import { getMockOrganization } from '@/mocks/organization.mock'; -import { Api_Organization } from '@/models/api/Organization'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { getByName, render, RenderOptions, screen, waitForEffects } from '@/utils/test-utils'; @@ -58,7 +58,6 @@ describe('PrimaryContactSelector component', () => { firstName: 'chester', surname: 'tester', }, - isDisabled: false, rowVersion: 1, }, { @@ -73,7 +72,7 @@ describe('PrimaryContactSelector component', () => { rowVersion: 1, }, ], - } as Api_Organization, + } as ApiGen_Concepts_Organization, }); }); @@ -121,11 +120,10 @@ describe('PrimaryContactSelector component', () => { firstName: 'chester', surname: 'tester', }, - isDisabled: false, rowVersion: 1, }, ], - } as Api_Organization, + } as ApiGen_Concepts_Organization, }); setup({ props: { contactInfo: fromApiOrganization(getMockOrganization()) } }); @@ -137,7 +135,7 @@ describe('PrimaryContactSelector component', () => { it(`shows message 'No contacts available' when no primary contact is available`, async () => { getOrganizationConceptFn.mockResolvedValue({ - data: { ...getMockOrganization(), organizationPersons: [] } as Api_Organization, + data: { ...getMockOrganization(), organizationPersons: [] } as ApiGen_Concepts_Organization, }); setup({ props: { contactInfo: fromApiOrganization(getMockOrganization()) } }); diff --git a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.tsx b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.tsx index 7419221cae..c5c0d6ac19 100644 --- a/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.tsx +++ b/source/frontend/src/components/common/form/PrimaryContactSelector/PrimaryContactSelector.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { useOrganizationRepository } from '@/features/contacts/repositories/useOrganizationRepository'; import { IContactSearchResult } from '@/interfaces'; -import { Api_OrganizationPerson } from '@/models/api/Organization'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; import { formatApiPersonNames } from '@/utils/personUtils'; import { Select, SelectOption } from '../Select'; @@ -40,7 +40,7 @@ export const PrimaryContactSelector: React.FC = ({ }, [field, orgPersons, setFieldValue]); const primaryContacts: SelectOption[] = - orgPersons?.map((orgPerson: Api_OrganizationPerson) => { + orgPersons?.map((orgPerson: ApiGen_Concepts_OrganizationPerson) => { return { label: `${formatApiPersonNames(orgPerson.person)}`, value: orgPerson.personId ?? '', diff --git a/source/frontend/src/components/common/form/UserRegionSelect/UserRegionSelectContainer.tsx b/source/frontend/src/components/common/form/UserRegionSelect/UserRegionSelectContainer.tsx index 6229535242..908dee1187 100644 --- a/source/frontend/src/components/common/form/UserRegionSelect/UserRegionSelectContainer.tsx +++ b/source/frontend/src/components/common/form/UserRegionSelect/UserRegionSelectContainer.tsx @@ -5,6 +5,7 @@ import * as API from '@/constants/API'; import { useUserInfoRepository } from '@/hooks/repositories/useUserInfoRepository'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; +import { exists } from '@/utils'; import { Select, SelectProps } from '../Select'; @@ -26,10 +27,10 @@ export const UserRegionSelectContainer: React.FunctionComponent< ); const { retrieveUserInfo, retrieveUserInfoResponse } = useUserInfoRepository(); const regionTypes = getOptionsByType(API.REGION_TYPES); - const userRegionCodes = retrieveUserInfoResponse?.userRegions?.map(ur => - ur.regionCode?.toString(), - ); - const userRegionTypes = regionTypes.filter(r => userRegionCodes?.includes(r.code)); + const userRegionCodes = + retrieveUserInfoResponse?.userRegions?.map(ur => ur.regionCode?.toString()).filter(exists) ?? + []; + const userRegionTypes = regionTypes.filter(r => userRegionCodes?.includes(r.code ?? '')); useEffect(() => { formattedGuid && retrieveUserInfo(formattedGuid); diff --git a/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/columns.tsx b/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/columns.tsx index 949ecda6e2..6cb3654f99 100644 --- a/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/columns.tsx +++ b/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/columns.tsx @@ -13,7 +13,7 @@ import { ColumnWithProps } from '@/components/Table'; import { Claims } from '@/constants/claims'; import { useKeycloakWrapper } from '@/hooks/useKeycloakWrapper'; import { IContactSearchResult } from '@/interfaces'; -import { stringToFragment } from '@/utils'; +import { isValidId, stringToFragment } from '@/utils'; const columns: ColumnWithProps[] = [ { @@ -33,7 +33,7 @@ const columns: ColumnWithProps[] = [ width: 20, maxWidth: 20, Cell: (props: CellProps) => - props.row.original.personId !== undefined ? ( + isValidId(props.row.original.personId) ? ( ) : ( diff --git a/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/summaryColumns.tsx b/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/summaryColumns.tsx index 7e591e0dfe..8c4cc61783 100644 --- a/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/summaryColumns.tsx +++ b/source/frontend/src/components/contact/ContactManagerView/ContactResultComponent/summaryColumns.tsx @@ -3,6 +3,7 @@ import { CellProps } from 'react-table'; import { ColumnWithProps } from '@/components/Table'; import { IContactSearchResult } from '@/interfaces'; +import { isValidId } from '@/utils'; const summaryColumns: ColumnWithProps[] = [ { @@ -12,7 +13,7 @@ const summaryColumns: ColumnWithProps[] = [ width: 20, maxWidth: 20, Cell: (props: CellProps) => - props.row.original.personId !== undefined ? ( + isValidId(props.row.original.personId) ? ( ) : ( @@ -27,7 +28,7 @@ const summaryColumns: ColumnWithProps[] = [ width: 80, maxWidth: 120, Cell: (props: CellProps) => - props.row.original.personId !== undefined ? ( + isValidId(props.row.original.personId) ? ( {props.row.original.firstName + ' ' + props.row.original.surname} ) : ( diff --git a/source/frontend/src/components/filePropertiesTable/FilePropertiesTable.tsx b/source/frontend/src/components/filePropertiesTable/FilePropertiesTable.tsx index 256d8b15a0..5796049d58 100644 --- a/source/frontend/src/components/filePropertiesTable/FilePropertiesTable.tsx +++ b/source/frontend/src/components/filePropertiesTable/FilePropertiesTable.tsx @@ -3,25 +3,25 @@ import { CellProps } from 'react-table'; import OverflowTip from '@/components/common/OverflowTip'; import { ColumnWithProps, Table } from '@/components/Table'; -import { Api_Property } from '@/models/api/Property'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { getFilePropertyName } from '@/utils/mapPropertyUtils'; interface IFilePropertiesTableProps { - fileProperties: Api_PropertyFile[]; - setSelectedFileProperties: (properties: Api_PropertyFile[]) => void; - selectedFileProperties: Api_PropertyFile[]; + fileProperties: ApiGen_Concepts_FileProperty[]; + setSelectedFileProperties: (properties: ApiGen_Concepts_FileProperty[]) => void; + selectedFileProperties: ApiGen_Concepts_FileProperty[]; disabledSelection: boolean; } -const columns: ColumnWithProps[] = [ +const columns: ColumnWithProps[] = [ { Header: 'Identifier', accessor: 'property', align: 'left', minWidth: 60, maxWidth: 60, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { const propertyName = getFilePropertyName(cellProps.row.original); return ; }, @@ -36,7 +36,7 @@ const FilePropertiesTable: React.FunctionComponent = }) => { return ( <> - + name="selectableFileProperties" hideHeaders hideToolbar diff --git a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx index acce1d6491..cbef5d9722 100644 --- a/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx +++ b/source/frontend/src/components/maps/leaflet/Control/AdvancedFilter/FilterContentForm.test.tsx @@ -2,7 +2,7 @@ import { createMemoryHistory } from 'history'; import { mockLookups } from '@/mocks/lookups.mock'; import { getMockApiPropertyManagement } from '@/mocks/propertyManagement.mock'; -import { Api_PropertyManagement } from '@/models/api/Property'; +import { ApiGen_Concepts_PropertyManagement } from '@/models/api/generated/ApiGen_Concepts_PropertyManagement'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, render, RenderOptions, userEvent } from '@/utils/test-utils'; @@ -15,7 +15,7 @@ const storeState = { const mockGetApi = { error: undefined, - response: undefined as Api_PropertyManagement | undefined, + response: undefined as ApiGen_Concepts_PropertyManagement | undefined, execute: jest.fn(), loading: false, }; diff --git a/source/frontend/src/components/maps/leaflet/Layers/Spiderfier.ts b/source/frontend/src/components/maps/leaflet/Layers/Spiderfier.ts index 8557f5ca61..b4e635b6b2 100644 --- a/source/frontend/src/components/maps/leaflet/Layers/Spiderfier.ts +++ b/source/frontend/src/components/maps/leaflet/Layers/Spiderfier.ts @@ -13,6 +13,7 @@ import { AnyProps, PointFeature } from 'supercluster'; import invariant from 'tiny-invariant'; import { ICluster } from '@/components/maps/types'; +import { exists } from '@/utils/utils'; export interface SpiderfierOptions

{ /** Increase from 1 to increase the distance away from the center that spiderfied markers are placed. Use if you are using big marker icons (Default: 1). */ @@ -176,7 +177,7 @@ export class Spiderfier

{ const geojsonObj = (layer as Marker)?.feature; const id = geojsonObj ? getClusterId(geojsonObj) : null; const targetId = getClusterId(cluster); - return id !== null && id !== undefined && id === targetId; + return exists(id) && id === targetId; } private generatePointsCircle(count: number, center: LeafletPoint): LeafletPoint[] { diff --git a/source/frontend/src/components/measurements/AreaForm.test.tsx b/source/frontend/src/components/measurements/AreaForm.test.tsx index 23da1670df..9d5b2ed6ba 100644 --- a/source/frontend/src/components/measurements/AreaForm.test.tsx +++ b/source/frontend/src/components/measurements/AreaForm.test.tsx @@ -38,13 +38,19 @@ describe('LandMeasurementTable component', () => { it('renders as expected', () => { const { asFragment } = setup({ onChange: () => {}, + area: undefined, + areaUnitTypeCode: undefined, }); expect(asFragment()).toMatchSnapshot(); }); it('calls onChange callback when values are changed', async () => { const onChange = jest.fn(); - const { container } = setup({ onChange }); + const { container } = setup({ + onChange, + area: undefined, + areaUnitTypeCode: undefined, + }); await fillInput(container, 'area-sq-meters', 15000); await waitFor(() => expect(onChange).toBeCalledWith(15000, AreaUnitTypes.SquareMeters)); }); @@ -52,6 +58,8 @@ describe('LandMeasurementTable component', () => { it('performs unit conversions when values are changed', async () => { const { container, getSqFeetInput, getHectaresInput, getAcresInput } = setup({ onChange: () => {}, + area: undefined, + areaUnitTypeCode: undefined, }); await fillInput(container, 'area-sq-meters', 15000); // assert diff --git a/source/frontend/src/components/measurements/AreaForm.tsx b/source/frontend/src/components/measurements/AreaForm.tsx index d846034f9d..b066c1779e 100644 --- a/source/frontend/src/components/measurements/AreaForm.tsx +++ b/source/frontend/src/components/measurements/AreaForm.tsx @@ -7,8 +7,8 @@ import { convertArea, round } from '@/utils'; import { StyledGreenCol, StyledGreenGrey, StyledInput } from './styles'; export interface IAreaFormProps { - area?: number; - areaUnitTypeCode?: string; + area: number | undefined; + areaUnitTypeCode: string | undefined; onChange: (landArea: number, areaUnitTypeCode: string) => void; } diff --git a/source/frontend/src/components/propertySelector/MapClickMonitor.tsx b/source/frontend/src/components/propertySelector/MapClickMonitor.tsx index 206c8a8885..25af6be2d1 100644 --- a/source/frontend/src/components/propertySelector/MapClickMonitor.tsx +++ b/source/frontend/src/components/propertySelector/MapClickMonitor.tsx @@ -26,7 +26,7 @@ export const MapClickMonitor: React.FunctionComponent< mapMachine.isSelecting && mapMachine.mapLocationFeatureDataset && previous !== mapMachine.mapLocationFeatureDataset && - previous !== undefined + previous !== undefined //todo: this is not validating as expected ) { addProperty(featuresetToMapProperty(mapMachine.mapLocationFeatureDataset)); } diff --git a/source/frontend/src/components/propertySelector/MapSelectorContainer.test.tsx b/source/frontend/src/components/propertySelector/MapSelectorContainer.test.tsx index 340a142168..18288bc8be 100644 --- a/source/frontend/src/components/propertySelector/MapSelectorContainer.test.tsx +++ b/source/frontend/src/components/propertySelector/MapSelectorContainer.test.tsx @@ -25,6 +25,7 @@ const store = mockStore({}); const onSelectedProperties = jest.fn(); const testProperty: IMapProperty = { + propertyId: 123, pid: '123-456-789', planNumber: 'SPS22411', address: 'Test address 123', diff --git a/source/frontend/src/components/propertySelector/MapSelectorContainer.tsx b/source/frontend/src/components/propertySelector/MapSelectorContainer.tsx index 4e9975a69f..f0b0713d82 100644 --- a/source/frontend/src/components/propertySelector/MapSelectorContainer.tsx +++ b/source/frontend/src/components/propertySelector/MapSelectorContainer.tsx @@ -5,6 +5,7 @@ import { toast } from 'react-toastify'; import { Button } from '@/components/common/buttons'; import { IMapProperty } from '@/components/propertySelector/models'; import { PropertyForm } from '@/features/mapSideBar/shared/models'; +import { isValidId } from '@/utils'; import { getPropertyName, NameSourceType } from '@/utils/mapPropertyUtils'; import PropertyMapSelectorFormView from './map/PropertyMapSelectorFormView'; @@ -26,7 +27,7 @@ export const MapSelectorContainer: React.FunctionComponent mp.toMapProperty()); const [lastSelectedProperty, setLastSelectedProperty] = React.useState( - modifiedProperties?.length === 1 && modifiedProperties[0].apiId === undefined // why? Because create from map needs to show the info differently + modifiedProperties?.length === 1 && isValidId(modifiedProperties[0].apiId) // why? Because create from map needs to show the info differently ? modifiedMapProperties[0] : undefined, ); diff --git a/source/frontend/src/constants/acquisitionFileType.ts b/source/frontend/src/constants/acquisitionFileType.ts new file mode 100644 index 0000000000..23b5115d5e --- /dev/null +++ b/source/frontend/src/constants/acquisitionFileType.ts @@ -0,0 +1,5 @@ +export enum EnumAcquisitionFileType { + CONSEN = 'CONSEN', + SECTN3 = 'SECTN3', + SECTN6 = 'SECTN6', +} diff --git a/source/frontend/src/constants/documentRelationshipType.ts2 b/source/frontend/src/constants/documentRelationshipType.ts2 deleted file mode 100644 index d39e06c877..0000000000 --- a/source/frontend/src/constants/documentRelationshipType.ts2 +++ /dev/null @@ -1,9 +0,0 @@ -export enum DocumentRelationshipType { - RESEARCH_FILES = 'researchFiles', - ACQUISITION_FILES = 'acquisitionFiles', - TEMPLATES = 'templates', - LEASES = 'leases', - PROJECTS = 'projects', - MANAGEMENT_FILES = 'managementFiles', - DISPOSITION_FILES = 'dispositionFiles', -} diff --git a/source/frontend/src/constants/financialCodeTypes.ts b/source/frontend/src/constants/financialCodeTypes.ts deleted file mode 100644 index 12a537e8e7..0000000000 --- a/source/frontend/src/constants/financialCodeTypes.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum FinancialCodeTypes { - BusinessFunction = 'BusinessFunction', - CostType = 'CostType', - WorkActivity = 'WorkActivity', - ChartOfAccounts = 'ChartOfAccounts', - FinancialActivity = 'FinancialActivity', - Responsibility = 'Responsibility', - YearlyFinancial = 'YearlyFinancial', -} diff --git a/source/frontend/src/constants/index.ts b/source/frontend/src/constants/index.ts index 931185474b..06767c5787 100644 --- a/source/frontend/src/constants/index.ts +++ b/source/frontend/src/constants/index.ts @@ -6,7 +6,6 @@ export * from './countryCodes'; export * from './districtCodes'; export * from './environment'; export * from './fileTypes'; -export * from './financialCodeTypes'; export * from './leaseStatusTypes'; export * from './noteTypes'; export * from './organizationIdentifierTypes'; diff --git a/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.test.tsx b/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.test.tsx index 18a17e767c..1a26be8ba6 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.test.tsx +++ b/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.test.tsx @@ -5,7 +5,7 @@ import { mockLookups } from '@/mocks/lookups.mock'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, fillInput, render, RenderOptions, waitFor } from '@/utils/test-utils'; -import { AcquisitionFilterModel, Api_AcquisitionFilter } from '../interfaces'; +import { AcquisitionFilterModel, ApiGen_Concepts_AcquisitionFilter } from '../interfaces'; import { AcquisitionFilter } from './AcquisitionFilter'; jest.mock('@react-keycloak/web'); @@ -97,7 +97,9 @@ describe('Acquisition Filter', () => { await act(async () => userEvent.click(resetButton)); expect(setFilter).toHaveBeenCalledWith( - expect.objectContaining(new AcquisitionFilterModel().toApi()), + expect.objectContaining( + new AcquisitionFilterModel().toApi(), + ), ); }); }); diff --git a/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.tsx b/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.tsx index 152b658aba..6dd66fe70c 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.tsx +++ b/source/frontend/src/features/acquisition/list/AcquisitionFilter/AcquisitionFilter.tsx @@ -8,16 +8,20 @@ import { Form, Input, Multiselect, Select } from '@/components/common/form'; import { SelectInput } from '@/components/common/List/SelectInput'; import { ACQUISITION_FILE_STATUS_TYPES } from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { mapLookupCode } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { exists, mapLookupCode } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; -import { AcquisitionFilterModel, Api_AcquisitionFilter, MultiSelectOption } from '../interfaces'; +import { + AcquisitionFilterModel, + ApiGen_Concepts_AcquisitionFilter, + MultiSelectOption, +} from '../interfaces'; export interface IAcquisitionFilterProps { - filter?: Api_AcquisitionFilter; - setFilter: (filter: Api_AcquisitionFilter) => void; - acquisitionTeam: Api_AcquisitionFileTeam[]; + filter?: ApiGen_Concepts_AcquisitionFilter; + setFilter: (filter: ApiGen_Concepts_AcquisitionFilter) => void; + acquisitionTeam: ApiGen_Concepts_AcquisitionFileTeam[]; } /** @@ -53,7 +57,7 @@ export const AcquisitionFilter: React.FC mapLookupCode(c)); const acquisitionTeamOptions = useMemo(() => { - if (acquisitionTeam !== undefined) { + if (exists(acquisitionTeam)) { return acquisitionTeam?.map(x => ({ id: x.personId ? `P-${x.personId}` : `O-${x.organizationId}`, text: x.personId ? formatApiPersonNames(x.person) : x.organization?.name ?? '', @@ -88,7 +92,7 @@ export const AcquisitionFilter: React.FC field="searchBy" defaultKey="address" diff --git a/source/frontend/src/features/acquisition/list/AcquisitionListView.tsx b/source/frontend/src/features/acquisition/list/AcquisitionListView.tsx index fb95bace60..bfa53bd6f8 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionListView.tsx +++ b/source/frontend/src/features/acquisition/list/AcquisitionListView.tsx @@ -14,7 +14,7 @@ import { useApiAcquisitionFile } from '@/hooks/pims-api/useApiAcquisitionFile'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; import { useSearch } from '@/hooks/useSearch'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { toFilteredApiPaginateParams } from '@/utils/CommonFunctions'; import { generateMultiSortCriteria } from '@/utils/utils'; @@ -22,7 +22,7 @@ import { useAcquisitionFileExport } from '../hooks/useAcquisitionFileExport'; import { AcquisitionFilter } from './AcquisitionFilter/AcquisitionFilter'; import { AcquisitionSearchResults } from './AcquisitionSearchResults/AcquisitionSearchResults'; import { AcquisitionSearchResultModel } from './AcquisitionSearchResults/models'; -import { AcquisitionFilterModel, Api_AcquisitionFilter } from './interfaces'; +import { AcquisitionFilterModel, ApiGen_Concepts_AcquisitionFilter } from './interfaces'; import * as Styled from './styles'; /** @@ -48,7 +48,7 @@ export const AcquisitionListView: React.FunctionComponent< setCurrentPage, setPageSize, loading, - } = useSearch( + } = useSearch( new AcquisitionFilterModel().toApi(), getAcquisitionFiles, 'No matching results can be found. Try widening your search criteria.', @@ -61,7 +61,7 @@ export const AcquisitionListView: React.FunctionComponent< */ const fetch = (accept: 'excel') => { // Call API with appropriate search parameters - const query = toFilteredApiPaginateParams( + const query = toFilteredApiPaginateParams( currentPage, pageSize, sort && !isEmpty(sort) ? generateMultiSortCriteria(sort) : undefined, @@ -73,7 +73,7 @@ export const AcquisitionListView: React.FunctionComponent< // update internal state whenever the filter bar changes const changeFilter = useCallback( - (filter: Api_AcquisitionFilter) => { + (filter: ApiGen_Concepts_AcquisitionFilter) => { setFilter(filter); }, [setFilter], diff --git a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/AcquisitionSearchResults.test.tsx b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/AcquisitionSearchResults.test.tsx index dbe10343bb..04766cbc60 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/AcquisitionSearchResults.test.tsx +++ b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/AcquisitionSearchResults.test.tsx @@ -1,7 +1,7 @@ import { Claims } from '@/constants/claims'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_Project } from '@/models/api/Project'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_Project } from '@/models/api/generated/ApiGen_Concepts_Project'; import { render, RenderOptions } from '@/utils/test-utils'; import { @@ -29,7 +29,7 @@ const setup = ( }; }; -const mockResults: Api_AcquisitionFile[] = []; +const mockResults: ApiGen_Concepts_AcquisitionFile[] = []; describe('Acquisition Search Results Table', () => { beforeEach(() => { @@ -67,8 +67,11 @@ describe('Acquisition Search Results Table', () => { { compensationRequisitions: [ { - alternateProject: { description: 'alternate project', code: '1234' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project', + code: '1234', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, ], }, ], @@ -82,11 +85,14 @@ describe('Acquisition Search Results Table', () => { const { findByText, queryByText } = setup({ results: [ { - project: { description: 'project', code: '4321' } as Api_Project, + project: { description: 'project', code: '4321' } as ApiGen_Concepts_Project, compensationRequisitions: [ { - alternateProject: { description: 'alternate project', code: '1234' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project', + code: '1234', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, ], }, ], @@ -103,11 +109,17 @@ describe('Acquisition Search Results Table', () => { { compensationRequisitions: [ { - alternateProject: { description: 'alternate project 1', code: '1' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project 1', + code: '1', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, { - alternateProject: { description: 'alternate project 2', code: '2' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project 2', + code: '2', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, ], }, ], @@ -123,11 +135,17 @@ describe('Acquisition Search Results Table', () => { { compensationRequisitions: [ { - alternateProject: { description: 'alternate project', code: '1' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project', + code: '1', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, { - alternateProject: { description: 'alternate project', code: '1' } as Api_Project, - } as Api_CompensationRequisition, + alternateProject: { + description: 'alternate project', + code: '1', + } as ApiGen_Concepts_Project, + } as ApiGen_Concepts_CompensationRequisition, ], }, ], @@ -164,8 +182,12 @@ describe('Acquisition Search Results Table', () => { id: 'PROPANLYS', description: 'Property analyst', isDisabled: false, + displayOrder: null, }, rowVersion: 1, + person: null, + personId: null, + primaryContact: null, }, ], }, diff --git a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/columns.tsx b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/columns.tsx index 996a00623c..5166b561ae 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/columns.tsx +++ b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/columns.tsx @@ -7,20 +7,19 @@ import ExpandableFileProperties from '@/components/common/List/ExpandableFilePro import { ColumnWithProps, renderTypeCode } from '@/components/Table'; import { Claims } from '@/constants/claims'; import { useKeycloakWrapper } from '@/hooks/useKeycloakWrapper'; -import { Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { Api_Organization } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; -import { Api_Project } from '@/models/api/Project'; -import Api_TypeCode from '@/models/api/TypeCode'; -import { stringToFragment } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { ApiGen_Concepts_Project } from '@/models/api/generated/ApiGen_Concepts_Project'; +import { exists, isValidId, stringToFragment } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { AcquisitionSearchResultModel } from './models'; interface MemberRoleGroup { id: string; - person: Api_Person | null; - organization: Api_Organization | null; + person: ApiGen_Concepts_Person | null; + organization: ApiGen_Concepts_Organization | null; roles: string[]; } @@ -84,12 +83,12 @@ export const columns: ColumnWithProps[] = [ const project = props.row.original.project; const altProjects = (props.row.original.compensationRequisitions ?? []) .filter(cr => !!cr?.alternateProject) - .map(cr => cr.alternateProject) as Api_Project[]; + .map(cr => cr.alternateProject) as ApiGen_Concepts_Project[]; return ( <> {[project?.code, project?.description].filter(Boolean).join(' ')} - + keyFunction={p => p?.id?.toString() ?? '0'} renderFunction={(p, index) => ( <>{['Alt Project:', p?.code, p?.description].filter(Boolean).join(' ')} @@ -111,37 +110,33 @@ export const columns: ColumnWithProps[] = [ maxWidth: 40, Cell: (props: CellProps) => { const acquisitionTeam = props.row.original.acquisitionTeam; - const personsInTeam = acquisitionTeam?.filter(x => x.personId !== undefined); - const organizationsInTeam = acquisitionTeam?.filter(x => x.organizationId !== undefined); + const personsInTeam = acquisitionTeam?.filter(x => isValidId(x.personId)); + const organizationsInTeam = acquisitionTeam?.filter(x => isValidId(x.organizationId)); const personsAsString: MemberRoleGroup[] = chain(personsInTeam) - .groupBy((groupedTeams: Api_AcquisitionFileTeam) => groupedTeams.personId) - .map(x => { - return { - id: x[0].id?.toString() || '', - person: x[0].person || {}, - organization: null, - roles: x - .map(t => t.teamProfileType) - .filter((z): z is Api_TypeCode => z !== undefined) - .flatMap(y => y.description || ''), - }; - }) + .groupBy((groupedTeams: ApiGen_Concepts_AcquisitionFileTeam) => groupedTeams.personId) + .map(x => ({ + id: x[0].id?.toString() || '', + person: x[0].person ?? null, + organization: null, + roles: x + .map(t => t.teamProfileType) + .filter(exists) + .flatMap(y => y.description || ''), + })) .value(); const organizationsAsString: MemberRoleGroup[] = chain(organizationsInTeam) - .groupBy((groupedTeams: Api_AcquisitionFileTeam) => groupedTeams.organizationId) - .map(x => { - return { - id: x[0].id?.toString() || '', - person: null, - organization: x[0].organization || {}, - roles: x - .map(t => t.teamProfileType) - .filter((z): z is Api_TypeCode => z !== undefined) - .flatMap(y => y.description || ''), - }; - }) + .groupBy((groupedTeams: ApiGen_Concepts_AcquisitionFileTeam) => groupedTeams.organizationId) + .map(x => ({ + id: x[0].id?.toString() || '', + person: null, + organization: x[0].organization ?? null, + roles: x + .map(t => t.teamProfileType) + .filter(exists) + .flatMap(y => y.description || ''), + })) .value(); const teamAsString = personsAsString.concat(organizationsAsString); diff --git a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/models.ts b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/models.ts index 45524f6a2f..1782212980 100644 --- a/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/models.ts +++ b/source/frontend/src/features/acquisition/list/AcquisitionSearchResults/models.ts @@ -1,11 +1,9 @@ -import { - Api_AcquisitionFile, - Api_AcquisitionFileProperty, - Api_AcquisitionFileTeam, -} from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_Project } from '@/models/api/Project'; -import Api_TypeCode from '@/models/api/TypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileProperty } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileProperty'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_Project } from '@/models/api/generated/ApiGen_Concepts_Project'; import { UtcIsoDateTime } from '@/models/api/UtcIsoDateTime'; export class AcquisitionSearchResultModel { @@ -18,29 +16,29 @@ export class AcquisitionSearchResultModel { appCreateTimestamp?: UtcIsoDateTime; appCreateUserid?: string; appLastUpdateTimestamp?: UtcIsoDateTime; - acquisitionFileStatusTypeCode?: Api_TypeCode; - fileProperties?: Api_AcquisitionFileProperty[]; - project?: Api_Project; - alternateProject?: Api_Project; - acquisitionTeam?: Api_AcquisitionFileTeam[]; - compensationRequisitions?: Api_CompensationRequisition[]; + acquisitionFileStatusTypeCode?: ApiGen_Base_CodeType; + fileProperties?: ApiGen_Concepts_AcquisitionFileProperty[]; + project?: ApiGen_Concepts_Project; + alternateProject?: ApiGen_Concepts_Project; + acquisitionTeam?: ApiGen_Concepts_AcquisitionFileTeam[]; + compensationRequisitions?: ApiGen_Concepts_CompensationRequisition[]; - static fromApi(base: Api_AcquisitionFile): AcquisitionSearchResultModel { + static fromApi(base: ApiGen_Concepts_AcquisitionFile): AcquisitionSearchResultModel { var newModel = new AcquisitionSearchResultModel(); newModel.id = base.id; - newModel.fileName = base.fileName; - newModel.fileNumber = base.fileNumber; - newModel.legacyFileNumber = base.legacyFileNumber; - newModel.regionCode = base.regionCode?.description; - newModel.appLastUpdateUserid = base.appLastUpdateUserid; + newModel.fileName = base.fileName ?? undefined; + newModel.fileNumber = base.fileNumber ?? undefined; + newModel.legacyFileNumber = base.legacyFileNumber ?? undefined; + newModel.regionCode = base.regionCode?.description ?? undefined; + newModel.appLastUpdateUserid = base.appLastUpdateUserid ?? undefined; newModel.appCreateTimestamp = base.appCreateTimestamp; - newModel.appCreateUserid = base.appCreateUserid; + newModel.appCreateUserid = base.appCreateUserid ?? undefined; newModel.appLastUpdateTimestamp = base.appLastUpdateTimestamp; - newModel.acquisitionFileStatusTypeCode = base.fileStatusTypeCode; - newModel.fileProperties = base.fileProperties; - newModel.project = base.project; - newModel.compensationRequisitions = base.compensationRequisitions; - newModel.acquisitionTeam = base.acquisitionTeam; + newModel.acquisitionFileStatusTypeCode = base.fileStatusTypeCode ?? undefined; + newModel.fileProperties = base.fileProperties ?? undefined; + newModel.project = base.project ?? undefined; + newModel.compensationRequisitions = base.compensationRequisitions ?? undefined; + newModel.acquisitionTeam = base.acquisitionTeam ?? undefined; return newModel; } } diff --git a/source/frontend/src/features/acquisition/list/interfaces.ts b/source/frontend/src/features/acquisition/list/interfaces.ts index da93d2844a..38f19ed854 100644 --- a/source/frontend/src/features/acquisition/list/interfaces.ts +++ b/source/frontend/src/features/acquisition/list/interfaces.ts @@ -1,9 +1,9 @@ -import { Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; import { formatApiPersonNames } from '@/utils/personUtils'; type IdSelector = 'O' | 'P'; -export interface Api_AcquisitionFilter { +export interface ApiGen_Concepts_AcquisitionFilter { acquisitionFileStatusTypeCode: string; acquisitionFileNameOrNumber: string; acquisitionTeamMemberPersonId: string; @@ -25,7 +25,7 @@ export class AcquisitionFilterModel { pid: string = ''; address: string = ''; - toApi(): Api_AcquisitionFilter { + toApi(): ApiGen_Concepts_AcquisitionFilter { return { acquisitionFileStatusTypeCode: this.acquisitionFileStatusTypeCode, acquisitionFileNameOrNumber: this.acquisitionFileNameOrNumber, @@ -43,8 +43,8 @@ export class AcquisitionFilterModel { } static fromApi( - model: Api_AcquisitionFilter, - teamMembers: Api_AcquisitionFileTeam[], + model: ApiGen_Concepts_AcquisitionFilter, + teamMembers: ApiGen_Concepts_AcquisitionFileTeam[], ): AcquisitionFilterModel { const newModel = new AcquisitionFilterModel(); newModel.acquisitionFileStatusTypeCode = model.acquisitionFileStatusTypeCode; diff --git a/source/frontend/src/features/admin/access-request/AccessRequestContainer.test.tsx b/source/frontend/src/features/admin/access-request/AccessRequestContainer.test.tsx index 61429a5e67..4928f1f573 100644 --- a/source/frontend/src/features/admin/access-request/AccessRequestContainer.test.tsx +++ b/source/frontend/src/features/admin/access-request/AccessRequestContainer.test.tsx @@ -94,7 +94,7 @@ describe('AccessRequestContainer component', () => { expect(mockAxios.history.put[0].url).toBe('/access/requests/8'); expect({ ...JSON.parse(mockAxios.history.put[0].data), - accessRequestStatusTypeCode: undefined, + accessRequestStatusTypeCode: null, userId: 30, }).toEqual({ ...new FormAccessRequest(getMockAccessRequest()).toApi(), diff --git a/source/frontend/src/features/admin/access-request/AccessRequestContainer.tsx b/source/frontend/src/features/admin/access-request/AccessRequestContainer.tsx index 80b0ecb5c1..90dcd85400 100644 --- a/source/frontend/src/features/admin/access-request/AccessRequestContainer.tsx +++ b/source/frontend/src/features/admin/access-request/AccessRequestContainer.tsx @@ -6,7 +6,10 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { AccessRequestStatus } from '@/constants/accessStatus'; import { useAccessRequests } from '@/hooks/pims-api/useAccessRequests'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_AccessRequest } from '@/models/api/AccessRequest'; +import { ApiGen_Concepts_AccessRequest } from '@/models/api/generated/ApiGen_Concepts_AccessRequest'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { isValidId } from '@/utils'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { AccessRequestForm as AccessRequestFormComponent } from './AccessRequestForm'; import { FormAccessRequest } from './models'; @@ -41,7 +44,7 @@ export const AccessRequestContainer: React.FunctionComponent< const userInfo = keycloak?.obj?.userInfo; useEffect(() => { - if (!accessRequestId) { + if (!isValidId(accessRequestId)) { getCurrentAccessRequest(); } else { getAccessRequestById(accessRequestId); @@ -50,17 +53,20 @@ export const AccessRequestContainer: React.FunctionComponent< const response = addAccessRequestResponse ?? accessRequestByIdResponse ?? accessRequestResponse; const initialValues: FormAccessRequest = new FormAccessRequest({ + role: null, + user: null, + ...getEmptyBaseAudit(), ...response, - id: response?.id, + id: response?.id ?? 0, userId: userInfo?.id, - accessRequestStatusTypeCode: { id: AccessRequestStatus.Received }, + accessRequestStatusTypeCode: toTypeCodeNullable(AccessRequestStatus.Received), note: response?.note ?? '', - roleId: response?.roleId, - regionCode: { id: response?.regionCode?.id }, + roleId: response?.roleId ?? null, + regionCode: toTypeCodeNullable(response?.regionCode?.id), }); initialValues.email = keycloak.email ?? ''; - if (!accessRequestId && !response) { + if (!isValidId(accessRequestId) && !response) { initialValues.email = keycloak.email ?? ''; initialValues.firstName = keycloak.firstName ?? ''; initialValues.surname = keycloak.surname ?? ''; @@ -71,12 +77,12 @@ export const AccessRequestContainer: React.FunctionComponent< return ( <> - {response?.id && !asAdmin && ( + {isValidId(response?.id) && !asAdmin && ( Your access request has been submitted )} { + addAccessRequest={async (accessRequest: ApiGen_Concepts_AccessRequest) => { const response = await addAccessRequest(accessRequest); onSave && onSave(); return response; diff --git a/source/frontend/src/features/admin/access-request/AccessRequestForm.test.tsx b/source/frontend/src/features/admin/access-request/AccessRequestForm.test.tsx index 5f95131ba7..8aa5b282f5 100644 --- a/source/frontend/src/features/admin/access-request/AccessRequestForm.test.tsx +++ b/source/frontend/src/features/admin/access-request/AccessRequestForm.test.tsx @@ -1,6 +1,6 @@ import { getMockAccessRequest } from '@/mocks/accessRequest.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_AccessRequest } from '@/models/api/AccessRequest'; +import { ApiGen_Concepts_AccessRequest } from '@/models/api/generated/ApiGen_Concepts_AccessRequest'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { fakeText, fillInput, render, RenderOptions, userEvent, waitFor } from '@/utils/test-utils'; @@ -75,7 +75,7 @@ describe('AccessRequestForm component', () => { const submitButton = getUpdateButton(); userEvent.click(submitButton); - const expectedValues: Api_AccessRequest = { ...initialValues.toApi() }; + const expectedValues: ApiGen_Concepts_AccessRequest = { ...initialValues.toApi() }; expectedValues.user!.position = 'position'; expectedValues.note = 'test note'; diff --git a/source/frontend/src/features/admin/access-request/AccessRequestForm.tsx b/source/frontend/src/features/admin/access-request/AccessRequestForm.tsx index 38fefa87fb..9132413452 100644 --- a/source/frontend/src/features/admin/access-request/AccessRequestForm.tsx +++ b/source/frontend/src/features/admin/access-request/AccessRequestForm.tsx @@ -12,7 +12,7 @@ import TooltipWrapper from '@/components/common/TooltipWrapper'; import * as API from '@/constants/API'; import { DISCLAIMER_URL, PRIVACY_POLICY_URL } from '@/constants/strings'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; -import { Api_AccessRequest } from '@/models/api/AccessRequest'; +import { ApiGen_Concepts_AccessRequest } from '@/models/api/generated/ApiGen_Concepts_AccessRequest'; import { mapLookupCode } from '@/utils'; import { AccessRequestSchema } from '@/utils/YupSchema'; @@ -21,7 +21,9 @@ import RolesToolTip from './RolesToolTip'; interface IAccessRequestFormProps { initialValues: AccessRequestFormModel; - addAccessRequest: (accessRequest: Api_AccessRequest) => Promise; + addAccessRequest: ( + accessRequest: ApiGen_Concepts_AccessRequest, + ) => Promise; onCancel?: () => void; } @@ -34,6 +36,7 @@ export const AccessRequestForm: React.FunctionComponent< region => region.label !== 'Cannot determine', ); const selectRoles = roles.map(c => mapLookupCode(c, initialValues?.roleId)); + return ( enableReinitialize={true} diff --git a/source/frontend/src/features/admin/access-request/models.ts b/source/frontend/src/features/admin/access-request/models.ts index b10361f63d..0f5cddd3e6 100644 --- a/source/frontend/src/features/admin/access-request/models.ts +++ b/source/frontend/src/features/admin/access-request/models.ts @@ -1,10 +1,16 @@ import { ContactMethodTypes } from '@/constants/contactMethodType'; import { UserTypes } from '@/constants/index'; -import { Api_AccessRequest } from '@/models/api/AccessRequest'; +import { ApiGen_Concepts_AccessRequest } from '@/models/api/generated/ApiGen_Concepts_AccessRequest'; import { UtcIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; import { getPreferredContactMethodValue } from '@/utils/contactMethodUtil'; -import { fromTypeCode, stringToUndefined, toTypeCode } from '@/utils/formUtils'; +import { + fromTypeCode, + stringToNumber, + stringToNumberOrNull, + toTypeCodeNullable, +} from '@/utils/formUtils'; export class FormAccessRequest { public id: NumberFieldValue; @@ -27,9 +33,9 @@ export class FormAccessRequest { public keycloakUserGuid: string; public rowVersion?: number; - constructor(accessRequest: Api_AccessRequest) { + constructor(accessRequest: ApiGen_Concepts_AccessRequest) { this.id = accessRequest.id ?? ''; - this.userId = accessRequest.userId; + this.userId = accessRequest.userId ?? ''; this.roleId = accessRequest.roleId ?? ''; this.roleName = accessRequest?.role?.name ?? ''; this.note = accessRequest.note ?? ''; @@ -52,25 +58,35 @@ export class FormAccessRequest { this.keycloakUserGuid = accessRequest?.user?.guidIdentifierValue ?? ''; this.position = accessRequest?.user?.position ?? ''; this.userTypeCode = fromTypeCode(accessRequest?.user?.userTypeCode) ?? UserTypes.Contractor; - this.rowVersion = accessRequest.rowVersion; + this.rowVersion = accessRequest.rowVersion ?? undefined; } - public toApi(): Api_AccessRequest { + public toApi(): ApiGen_Concepts_AccessRequest { return { - id: stringToUndefined(this.id), - userId: stringToUndefined(this.userId), - roleId: stringToUndefined(this.roleId), + id: stringToNumber(this.id), + userId: stringToNumberOrNull(this.userId), + roleId: stringToNumberOrNull(this.roleId), note: this.note, - accessRequestStatusTypeCode: toTypeCode(this.accessRequestStatusTypeCodeId), - regionCode: this.regionCodeId ? toTypeCode(this.regionCodeId) : undefined, + accessRequestStatusTypeCode: toTypeCodeNullable(this.accessRequestStatusTypeCodeId), + regionCode: this.regionCodeId ? toTypeCodeNullable(this.regionCodeId) : null, user: { guidIdentifierValue: this.keycloakUserGuid, position: this.position, - userTypeCode: toTypeCode(this.userTypeCode), + userTypeCode: toTypeCodeNullable(this.userTypeCode), userRoles: [], userRegions: [], + approvedById: 0, + businessIdentifierValue: null, + id: 0, + isDisabled: false, + issueDate: null, + lastLogin: null, + note: null, + person: null, + ...getEmptyBaseAudit(), }, - rowVersion: this.rowVersion, + role: null, + ...getEmptyBaseAudit(this.rowVersion), }; } } diff --git a/source/frontend/src/features/admin/access/ManageAccessRequestsPage.tsx b/source/frontend/src/features/admin/access/ManageAccessRequestsPage.tsx index c152b402b6..a4ba79cd55 100644 --- a/source/frontend/src/features/admin/access/ManageAccessRequestsPage.tsx +++ b/source/frontend/src/features/admin/access/ManageAccessRequestsPage.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { Table } from '@/components/Table'; import { useApiAccessRequests } from '@/hooks/pims-api/useApiAccessRequests'; import { useSearch } from '@/hooks/useSearch'; -import { Api_AccessRequest } from '@/models/api/AccessRequest'; +import { ApiGen_Concepts_AccessRequest } from '@/models/api/generated/ApiGen_Concepts_AccessRequest'; import { IAccessRequestsFilterData } from '../access-request/IAccessRequestsFilterData'; import { FormAccessRequest } from '../access-request/models'; @@ -27,7 +27,7 @@ const ManageAccessRequestsPage = () => { setPageSize, loading, execute, - } = useSearch( + } = useSearch( defaultFilter, getAccessRequestsPaged, 'No matching results can be found. Try widening your search criteria.', diff --git a/source/frontend/src/features/admin/access/components/RowActions.tsx b/source/frontend/src/features/admin/access/components/RowActions.tsx index 1497b6934b..646a53c34f 100644 --- a/source/frontend/src/features/admin/access/components/RowActions.tsx +++ b/source/frontend/src/features/admin/access/components/RowActions.tsx @@ -5,6 +5,7 @@ import { Menu } from '@/components/menu/Menu'; import { AccessRequestStatus } from '@/constants/accessStatus'; import { FormAccessRequest } from '@/features/admin/access-request/models'; import { useAccessRequests } from '@/hooks/pims-api/useAccessRequests'; +import { toTypeCodeNullable } from '@/utils/formUtils'; export const RowActions = (props: CellProps & { refresh: () => void }) => { const accessRequest = props.row.original; @@ -20,7 +21,7 @@ export const RowActions = (props: CellProps & { refresh: () = if (accessRequest) { await update({ ...accessRequest.toApi(), - accessRequestStatusTypeCode: { id: AccessRequestStatus.Approved }, + accessRequestStatusTypeCode: toTypeCodeNullable(AccessRequestStatus.Approved), }); props.refresh(); } @@ -29,7 +30,7 @@ export const RowActions = (props: CellProps & { refresh: () = if (accessRequest) { await update({ ...accessRequest.toApi(), - accessRequestStatusTypeCode: { id: AccessRequestStatus.Denied }, + accessRequestStatusTypeCode: toTypeCodeNullable(AccessRequestStatus.Denied), }); props.refresh(); } @@ -39,7 +40,7 @@ export const RowActions = (props: CellProps & { refresh: () = if (accessRequest) { await update({ ...accessRequest.toApi(), - accessRequestStatusTypeCode: { id: AccessRequestStatus.Received }, + accessRequestStatusTypeCode: toTypeCodeNullable(AccessRequestStatus.Received), }); props.refresh(); } @@ -49,7 +50,7 @@ export const RowActions = (props: CellProps & { refresh: () = if (accessRequest) { await remove({ ...accessRequest.toApi(), - accessRequestStatusTypeCode: { id: AccessRequestStatus.Denied }, + accessRequestStatusTypeCode: toTypeCodeNullable(AccessRequestStatus.Denied), }); props.refresh(); } diff --git a/source/frontend/src/features/admin/document-template/DocumentTemplateManagementView.tsx b/source/frontend/src/features/admin/document-template/DocumentTemplateManagementView.tsx index bb972d083c..d9de8a2259 100644 --- a/source/frontend/src/features/admin/document-template/DocumentTemplateManagementView.tsx +++ b/source/frontend/src/features/admin/document-template/DocumentTemplateManagementView.tsx @@ -6,12 +6,12 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { Scrollable as ScrollableBase } from '@/components/common/Scrollable/Scrollable'; import { Section } from '@/components/common/Section/Section'; import DocumentListContainer from '@/features/documents/list/DocumentListContainer'; -import { Api_FormDocumentType } from '@/models/api/FormDocument'; import { ApiGen_CodeTypes_DocumentRelationType } from '@/models/api/generated/ApiGen_CodeTypes_DocumentRelationType'; +import { ApiGen_Concepts_FormDocumentType } from '@/models/api/generated/ApiGen_Concepts_FormDocumentType'; export interface IDocumentTemplateManagementViewProp { isLoading: boolean; - formDocumentTypes: Api_FormDocumentType[] | undefined; + formDocumentTypes: ApiGen_Concepts_FormDocumentType[] | undefined; selectedFormDocumentTypeCode: string | undefined; setSelectedFormDocumentTypeCode: (formTypeCode: string) => void; } @@ -39,7 +39,10 @@ export const DocumentTemplateManagementView: React.FunctionComponent< {props.formDocumentTypes?.map(types => { return ( - ); diff --git a/source/frontend/src/features/admin/edit-user/EditUserContainer.test.tsx b/source/frontend/src/features/admin/edit-user/EditUserContainer.test.tsx index 473ca4604c..2c05b81d98 100644 --- a/source/frontend/src/features/admin/edit-user/EditUserContainer.test.tsx +++ b/source/frontend/src/features/admin/edit-user/EditUserContainer.test.tsx @@ -85,8 +85,8 @@ describe('EditUserContainer component', () => { ); expect({ ...JSON.parse(mockAxios.history.put[0].data), - approvedById: undefined, - issueDate: undefined, + approvedById: 0, + issueDate: null, }).toEqual({ ...new FormUser(getUserMock()).toApi(), note: 'test note', diff --git a/source/frontend/src/features/admin/edit-user/EditUserContainer.tsx b/source/frontend/src/features/admin/edit-user/EditUserContainer.tsx index 60bf68187a..cfe25e6596 100644 --- a/source/frontend/src/features/admin/edit-user/EditUserContainer.tsx +++ b/source/frontend/src/features/admin/edit-user/EditUserContainer.tsx @@ -4,7 +4,8 @@ import { useHistory } from 'react-router-dom'; import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { UserTypes } from '@/constants/index'; import useIsMounted from '@/hooks/util/useIsMounted'; -import { Api_User } from '@/models/api/User'; +import { ApiGen_Concepts_User } from '@/models/api/generated/ApiGen_Concepts_User'; +import { toTypeCode } from '@/utils/formUtils'; import { useUsers } from '../users/hooks/useUsers'; import { FormUser } from '../users/models'; @@ -16,7 +17,7 @@ export interface IEditUserContainerProps { const EditUserContainer: React.FunctionComponent = ({ userId }) => { const history = useHistory(); - const [user, setUser] = React.useState(); + const [user, setUser] = React.useState(); const isMounted = useIsMounted(); const { updateUser: { execute: updateUserDetail }, @@ -50,9 +51,9 @@ const EditUserContainer: React.FunctionComponent = ({ u regions: [], note: '', position: '', - userTypeCode: { id: UserTypes.Contractor }, + userTypeCode: toTypeCode(UserTypes.Contractor), lastLogin: '', - toApi: () => ({} as Api_User), + toApi: () => ({} as ApiGen_Concepts_User), }; const formUser = user !== undefined ? new FormUser(user) : initialValues; return ( @@ -60,7 +61,7 @@ const EditUserContainer: React.FunctionComponent = ({ u { + updateUserDetail={async (updateUser: ApiGen_Concepts_User) => { const response = await updateUserDetail(updateUser); if (isMounted()) { setUser(response); diff --git a/source/frontend/src/features/admin/edit-user/EditUserForm.tsx b/source/frontend/src/features/admin/edit-user/EditUserForm.tsx index 074be2f5d6..a642cf2cc0 100644 --- a/source/frontend/src/features/admin/edit-user/EditUserForm.tsx +++ b/source/frontend/src/features/admin/edit-user/EditUserForm.tsx @@ -11,8 +11,8 @@ import { SectionField } from '@/components/common/Section/SectionField'; import TooltipWrapper from '@/components/common/TooltipWrapper'; import * as API from '@/constants/API'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; -import Api_TypeCode from '@/models/api/TypeCode'; -import { Api_User } from '@/models/api/User'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_User } from '@/models/api/generated/ApiGen_Concepts_User'; import { ILookupCode } from '@/store/slices/lookupCodes'; import { UserUpdateSchema } from '@/utils/YupSchema'; @@ -20,7 +20,7 @@ import RolesToolTip from '../access-request/RolesToolTip'; import { FormUser, userTypeCodeValues } from '../users/models'; interface IEditUserFormProps { - updateUserDetail: (user: Api_User) => void; + updateUserDetail: (user: ApiGen_Concepts_User) => void; formUser: FormUser; onCancel: () => void; } @@ -115,7 +115,7 @@ const EditUserForm: React.FunctionComponent - > + > placeholder="" field="regions" options={regions} diff --git a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.test.tsx b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.test.tsx index 0509d899e8..ac9da29845 100644 --- a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.test.tsx @@ -1,7 +1,7 @@ import { createMemoryHistory } from 'history'; import { mockFinancialCode } from '@/mocks/index.mock'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; import { act, createAxiosError, render, RenderOptions, screen } from '@/utils/test-utils'; import AddFinancialCodeContainer, { IAddFinancialCodeFormProps } from './AddFinancialCodeContainer'; @@ -75,9 +75,9 @@ describe('AddFinancialCode container', () => { setup(); mockApi.execute.mockResolvedValue(mockFinancialCode()); - let createdCode: Api_FinancialCode | undefined; + let createdCode: ApiGen_Concepts_FinancialCode | undefined; await act(async () => { - createdCode = await viewProps?.onSave({} as Api_FinancialCode); + createdCode = await viewProps?.onSave({} as ApiGen_Concepts_FinancialCode); }); expect(mockApi.execute).toHaveBeenCalled(); @@ -95,7 +95,7 @@ describe('AddFinancialCode container', () => { it('navigates back to financial codes list and displays a toast when code is saved', async () => { setup(); await act(async () => { - await viewProps?.onSuccess({} as Api_FinancialCode); + await viewProps?.onSuccess({} as ApiGen_Concepts_FinancialCode); }); expect(history.location.pathname).toBe(`/admin/financial-code/list`); expect(await screen.findByText(/Financial code saved/)).toBeVisible(); diff --git a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.tsx b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.tsx index 281b2d1585..7bebf71cdc 100644 --- a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.tsx +++ b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeContainer.tsx @@ -6,18 +6,20 @@ import { toast } from 'react-toastify'; import styled from 'styled-components'; import { H1 } from '@/components/common/styles'; -import { FinancialCodeTypes } from '@/constants/index'; import { useFinancialCodeRepository } from '@/hooks/repositories/useFinancialCodeRepository'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { AddFinancialCodeYupSchema } from './AddFinancialCodeYupSchema'; export interface IAddFinancialCodeFormProps { validationSchema?: any; - onSave: (financialCode: Api_FinancialCode) => Promise; + onSave: ( + financialCode: ApiGen_Concepts_FinancialCode, + ) => Promise; onCancel: () => void; - onSuccess: (financialCode: Api_FinancialCode) => Promise; + onSuccess: (financialCode: ApiGen_Concepts_FinancialCode) => Promise; onError: (e: AxiosError) => void; } @@ -32,9 +34,12 @@ export const AddFinancialCodeContainer: React.FC { + const createFinancialCode = async (financialCode: ApiGen_Concepts_FinancialCode) => { setDuplicateError(false); - return addFinancialCode(financialCode.type as FinancialCodeTypes, financialCode); + return addFinancialCode( + financialCode.type as ApiGen_Concepts_FinancialCodeTypes, + financialCode, + ); }; // navigate back to list view @@ -42,7 +47,7 @@ export const AddFinancialCodeContainer: React.FC { + const onCreateSuccess = async (financialCode: ApiGen_Concepts_FinancialCode) => { toast.success(`Financial code saved`); history.replace(`/admin/financial-code/list`); }; diff --git a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.test.tsx b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.test.tsx index 26bdca1a9c..2be635176a 100644 --- a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.test.tsx @@ -1,8 +1,8 @@ import * as Yup from 'yup'; -import { FinancialCodeTypes } from '@/constants/index'; import { mockFinancialCode } from '@/mocks/index.mock'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { act, createAxiosError, @@ -121,13 +121,15 @@ describe('AddFinancialCode form', () => { const codeType = document.querySelector(`select[name="type"]`) as HTMLSelectElement; const description = document.querySelector(`input[name="description"]`) as HTMLInputElement; const saveButton = screen.getByText('Save'); - await act(async () => userEvent.selectOptions(codeType, FinancialCodeTypes.BusinessFunction)); + await act(async () => + userEvent.selectOptions(codeType, ApiGen_Concepts_FinancialCodeTypes.BusinessFunction), + ); await act(async () => userEvent.paste(description, `another description`)); await act(async () => userEvent.click(saveButton)); expect(mockProps.onSave).toHaveBeenCalledWith( - expect.objectContaining>({ - type: FinancialCodeTypes.BusinessFunction, + expect.objectContaining>({ + type: ApiGen_Concepts_FinancialCodeTypes.BusinessFunction, description: `another description`, }), ); @@ -141,7 +143,9 @@ describe('AddFinancialCode form', () => { const codeType = document.querySelector(`select[name="type"]`) as HTMLSelectElement; const description = document.querySelector(`input[name="description"]`) as HTMLInputElement; const saveButton = screen.getByText('Save'); - await act(async () => userEvent.selectOptions(codeType, FinancialCodeTypes.BusinessFunction)); + await act(async () => + userEvent.selectOptions(codeType, ApiGen_Concepts_FinancialCodeTypes.BusinessFunction), + ); await act(async () => userEvent.paste(description, `another description`)); await act(async () => userEvent.click(saveButton)); diff --git a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.tsx b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.tsx index fa152a107e..ae1622e0b6 100644 --- a/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.tsx +++ b/source/frontend/src/features/admin/financial-codes/add/AddFinancialCodeForm.tsx @@ -9,6 +9,7 @@ import { FastDatePicker, Input, Select, SelectOption } from '@/components/common import { SectionField } from '@/components/common/Section/SectionField'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; +import { exists, isValidId } from '@/utils'; import { formatAsSelectOptions } from '../financialCodeUtils'; import { FinancialCodeForm } from '../models'; @@ -52,7 +53,8 @@ export const AddFinancialCodeForm: React.FC = ({ onSubmit={async (values, formikHelpers) => { try { const createdCode = await onSave(values.toApi()); - if (!!createdCode?.id) { + // TODO: the isValidId check is sufficient but current ts (4.3) does not see it as valid. This works correctly on 5.3 + if (exists(createdCode) && isValidId(createdCode?.id)) { formikHelpers.resetForm({ values: FinancialCodeForm.fromApi(createdCode), }); diff --git a/source/frontend/src/features/admin/financial-codes/financialCodeUtils.ts b/source/frontend/src/features/admin/financial-codes/financialCodeUtils.ts index 56b04ac4e9..9ffdfc2292 100644 --- a/source/frontend/src/features/admin/financial-codes/financialCodeUtils.ts +++ b/source/frontend/src/features/admin/financial-codes/financialCodeUtils.ts @@ -1,21 +1,21 @@ import { SelectOption } from '@/components/common/form'; -import { FinancialCodeTypes } from '@/constants/financialCodeTypes'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; -export function formatFinancialCodeType(value: FinancialCodeTypes): string { +export function formatFinancialCodeType(value: ApiGen_Concepts_FinancialCodeTypes): string { switch (value) { - case FinancialCodeTypes.BusinessFunction: + case ApiGen_Concepts_FinancialCodeTypes.BusinessFunction: return 'Business function'; - case FinancialCodeTypes.CostType: + case ApiGen_Concepts_FinancialCodeTypes.CostType: return 'Cost types'; - case FinancialCodeTypes.WorkActivity: + case ApiGen_Concepts_FinancialCodeTypes.WorkActivity: return 'Work activity'; - case FinancialCodeTypes.ChartOfAccounts: + case ApiGen_Concepts_FinancialCodeTypes.ChartOfAccounts: return 'Chart of accounts'; - case FinancialCodeTypes.FinancialActivity: + case ApiGen_Concepts_FinancialCodeTypes.FinancialActivity: return 'Financial activity'; - case FinancialCodeTypes.Responsibility: + case ApiGen_Concepts_FinancialCodeTypes.Responsibility: return 'Responsibility'; - case FinancialCodeTypes.YearlyFinancial: + case ApiGen_Concepts_FinancialCodeTypes.YearlyFinancial: return 'Yearly financial'; default: return 'Unknown'; @@ -26,8 +26,10 @@ export function formatFinancialCodeType(value: FinancialCodeTypes): string { * Converts the FinancialCodeTypes enum to SELECT options for display in the UI */ export function formatAsSelectOptions(): SelectOption[] { - return Object.keys(FinancialCodeTypes).map(key => ({ + return Object.keys(ApiGen_Concepts_FinancialCodeTypes).map(key => ({ value: key, - label: formatFinancialCodeType(FinancialCodeTypes[key as FinancialCodeTypes]), + label: formatFinancialCodeType( + ApiGen_Concepts_FinancialCodeTypes[key as ApiGen_Concepts_FinancialCodeTypes], + ), })); } diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.test.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.test.tsx index 432d831dcd..5c55214c98 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.test.tsx @@ -1,8 +1,8 @@ import userEvent from '@testing-library/user-event'; -import { FinancialCodeTypes } from '@/constants/financialCodeTypes'; import { Roles } from '@/constants/index'; import { mockLookups } from '@/mocks/lookups.mock'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, fillInput, render, RenderOptions, waitFor } from '@/utils/test-utils'; @@ -54,12 +54,17 @@ describe('Financial Code Filter', () => { it('searches by code type', async () => { const { container, searchButton } = setup(); - fillInput(container, 'financialCodeType', FinancialCodeTypes.ChartOfAccounts, 'select'); + fillInput( + container, + 'financialCodeType', + ApiGen_Concepts_FinancialCodeTypes.ChartOfAccounts, + 'select', + ); await act(async () => userEvent.click(searchButton)); expect(setFilter).toHaveBeenCalledWith( expect.objectContaining({ - financialCodeType: FinancialCodeTypes.ChartOfAccounts, + financialCodeType: ApiGen_Concepts_FinancialCodeTypes.ChartOfAccounts, codeValueOrDescription: '', showExpiredCodes: false, }), diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.tsx index d4f175aeab..f6b35648cc 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeFilter/FinancialCodeFilter.tsx @@ -6,12 +6,12 @@ import styled from 'styled-components'; import { ResetButton, SearchButton } from '@/components/common/buttons'; import { Form, Input, Select } from '@/components/common/form'; import ActiveFilterCheck from '@/components/common/form/ActiveFilterCheck'; -import { FinancialCodeTypes } from '@/constants/financialCodeTypes'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { formatAsSelectOptions } from '../../financialCodeUtils'; export interface IFinancialCodeFilter { - financialCodeType?: FinancialCodeTypes; + financialCodeType?: ApiGen_Concepts_FinancialCodeTypes; codeValueOrDescription: string; showExpiredCodes: boolean; } diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeListView.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeListView.tsx index 7d842a60b8..4af983b5c8 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeListView.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeListView.tsx @@ -10,7 +10,7 @@ import { TableSort } from '@/components/Table/TableSort'; import { Roles } from '@/constants/roles'; import { useFinancialCodeRepository } from '@/hooks/repositories/useFinancialCodeRepository'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; import { isExpiredCode } from '@/utils/financialCodeUtils'; import { @@ -46,7 +46,7 @@ export const FinancialCodeListView: React.FC = () => { }, [fetchData, financialCodeResults]); // Sorting and filtering for this list view is performed client-side - const [sort, setSort] = React.useState>({}); + const [sort, setSort] = React.useState>({}); const [filter, setFilter] = React.useState(defaultFinancialCodeFilter); const sortedFilteredFinancialCodes = useMemo(() => { @@ -76,7 +76,7 @@ export const FinancialCodeListView: React.FC = () => { if (sort) { const sortFields = Object.keys(sort); if (sortFields?.length > 0) { - const sortBy = sortFields[0] as keyof Api_FinancialCode; + const sortBy = sortFields[0] as keyof ApiGen_Concepts_FinancialCode; const sortDirection = sort[sortBy]; records = orderBy(records, sortBy, sortDirection); } else { diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.test.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.test.tsx index a9349aedb7..a738e945d2 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.test.tsx @@ -1,5 +1,5 @@ import { Roles } from '@/constants/index'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; import { render, RenderOptions } from '@/utils/test-utils'; import { FinancialCodeResults, IFinancialCodeResultsProps } from './FinancialCodeResults'; @@ -28,7 +28,7 @@ const setup = ( }; }; -const mockResults: Api_FinancialCode[] = []; +const mockResults: ApiGen_Concepts_FinancialCode[] = []; describe('Financial Code Results Table', () => { beforeEach(() => { diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.tsx index 50f7dd5c69..25eba8042c 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/FinancialCodeResults.tsx @@ -1,21 +1,21 @@ import { Table } from '@/components/Table'; import { TableSort } from '@/components/Table/TableSort'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; import { columns } from './columns'; export interface IFinancialCodeResultsProps { - results: Api_FinancialCode[]; + results: ApiGen_Concepts_FinancialCode[]; loading?: boolean; - sort: TableSort; - setSort: (value: TableSort) => void; + sort: TableSort; + setSort: (value: TableSort) => void; } export function FinancialCodeResults(props: IFinancialCodeResultsProps) { const { results, setSort, sort, ...rest } = props; return ( - + name="FinancialCodeTable" manualSortBy={false} lockPageSize={false} diff --git a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/columns.tsx b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/columns.tsx index 0f78bebf0f..6a1df10f78 100644 --- a/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/columns.tsx +++ b/source/frontend/src/features/admin/financial-codes/list/FinancialCodeResults/columns.tsx @@ -2,15 +2,15 @@ import { Link } from 'react-router-dom'; import { CellProps } from 'react-table'; import { ColumnWithProps, DateCell } from '@/components/Table'; -import { FinancialCodeTypes } from '@/constants/financialCodeTypes'; import { Roles } from '@/constants/roles'; import { useKeycloakWrapper } from '@/hooks/useKeycloakWrapper'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { stringToFragment } from '@/utils'; import { formatFinancialCodeType } from '../../financialCodeUtils'; -export const columns: ColumnWithProps[] = [ +export const columns: ColumnWithProps[] = [ { Header: 'Code value', accessor: 'code', @@ -19,7 +19,7 @@ export const columns: ColumnWithProps[] = [ sortable: true, width: 10, maxWidth: 20, - Cell: (props: CellProps) => { + Cell: (props: CellProps) => { const { hasRole } = useKeycloakWrapper(); const financialCode = props.row.original; if (hasRole(Roles.SYSTEM_ADMINISTRATOR)) { @@ -49,9 +49,9 @@ export const columns: ColumnWithProps[] = [ sortable: true, width: 10, maxWidth: 20, - Cell: (props: CellProps) => { + Cell: (props: CellProps) => { return stringToFragment( - formatFinancialCodeType(props.row.original.type as FinancialCodeTypes), + formatFinancialCodeType(props.row.original.type as ApiGen_Concepts_FinancialCodeTypes), ); }, }, diff --git a/source/frontend/src/features/admin/financial-codes/models.ts b/source/frontend/src/features/admin/financial-codes/models.ts index f92cefe0a7..d1c80ced96 100644 --- a/source/frontend/src/features/admin/financial-codes/models.ts +++ b/source/frontend/src/features/admin/financial-codes/models.ts @@ -1,7 +1,10 @@ import moment from 'moment'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; -import { stringToUndefined } from '@/utils/formUtils'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; +import { EpochIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { exists, isValidIsoDateTime } from '@/utils/utils'; export class FinancialCodeForm { id?: number; @@ -13,29 +16,31 @@ export class FinancialCodeForm { effectiveDate?: string = moment().format('YYYY-MM-DD'); expiryDate?: string; - toApi(): Api_FinancialCode { + toApi(): ApiGen_Concepts_FinancialCode { return { - id: this.id, - rowVersion: this.rowVersion, - type: this.type, - code: this.code, - description: this.description, - displayOrder: this.displayOrder !== undefined ? Number(this.displayOrder) : undefined, - effectiveDate: this.effectiveDate, - expiryDate: stringToUndefined(this.expiryDate), + id: this.id ?? 0, + type: exists(this.type) + ? (this.type as ApiGen_Concepts_FinancialCodeTypes) + : ApiGen_Concepts_FinancialCodeTypes.BusinessFunction, + code: this.code ?? null, + description: this.description ?? null, + displayOrder: this.displayOrder !== undefined ? Number(this.displayOrder) : null, + effectiveDate: isValidIsoDateTime(this.effectiveDate) ? this.effectiveDate : EpochIsoDateTime, + expiryDate: isValidIsoDateTime(this.expiryDate) ? this.expiryDate : null, + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_FinancialCode): FinancialCodeForm { + static fromApi(model: ApiGen_Concepts_FinancialCode): FinancialCodeForm { const newForm = new FinancialCodeForm(); newForm.id = model.id; - newForm.rowVersion = model.rowVersion; + newForm.rowVersion = model.rowVersion ?? undefined; newForm.type = model.type; - newForm.code = model.code; - newForm.description = model.description; - newForm.displayOrder = model.displayOrder; - newForm.effectiveDate = model.effectiveDate ?? ''; - newForm.expiryDate = model.expiryDate ?? ''; + newForm.code = model.code ?? undefined; + newForm.description = model.description ?? undefined; + newForm.displayOrder = model.displayOrder ?? undefined; + newForm.effectiveDate = isValidIsoDateTime(model.effectiveDate) ? model.effectiveDate : ''; + newForm.expiryDate = isValidIsoDateTime(model.expiryDate) ? model.expiryDate : ''; return newForm; } diff --git a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.test.tsx b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.test.tsx index 116e2973c3..47a50c6a46 100644 --- a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.test.tsx @@ -1,8 +1,8 @@ import { createMemoryHistory } from 'history'; -import { FinancialCodeTypes } from '@/constants/index'; import { mockFinancialCode } from '@/mocks/index.mock'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { act, createAxiosError, render, RenderOptions, screen } from '@/utils/test-utils'; import UpdateFinancialCodeContainer, { @@ -45,7 +45,7 @@ describe('UpdateFinancialCode container', () => { const setup = (renderOptions: RenderOptions = {}) => { const utils = render( , @@ -94,9 +94,9 @@ describe('UpdateFinancialCode container', () => { setup(); mockUpdateApi.execute.mockResolvedValue(mockFinancialCode()); - let createdCode: Api_FinancialCode | undefined; + let createdCode: ApiGen_Concepts_FinancialCode | undefined; await act(async () => { - createdCode = await viewProps?.onSave({} as Api_FinancialCode); + createdCode = await viewProps?.onSave({} as ApiGen_Concepts_FinancialCode); }); expect(mockUpdateApi.execute).toHaveBeenCalled(); @@ -114,7 +114,7 @@ describe('UpdateFinancialCode container', () => { it('navigates back to financial codes list and displays a toast when code is saved', async () => { setup(); await act(async () => { - await viewProps?.onSuccess({} as Api_FinancialCode); + await viewProps?.onSuccess({} as ApiGen_Concepts_FinancialCode); }); expect(history.location.pathname).toBe(`/admin/financial-code/list`); expect(await screen.findByText(/Financial code saved/)).toBeVisible(); diff --git a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.tsx b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.tsx index ab47b7c2d0..a75f8d819e 100644 --- a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.tsx +++ b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeContainer.tsx @@ -7,24 +7,26 @@ import styled from 'styled-components'; import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { H1 } from '@/components/common/styles'; -import { FinancialCodeTypes } from '@/constants/index'; import { useFinancialCodeRepository } from '@/hooks/repositories/useFinancialCodeRepository'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { UpdateFinancialCodeYupSchema } from './UpdateFinancialCodeYupSchema'; export interface IUpdateFinancialCodeFormProps { - financialCode?: Api_FinancialCode; + financialCode?: ApiGen_Concepts_FinancialCode; validationSchema?: any; - onSave: (financialCode: Api_FinancialCode) => Promise; + onSave: ( + financialCode: ApiGen_Concepts_FinancialCode, + ) => Promise; onCancel: () => void; - onSuccess: (financialCode: Api_FinancialCode) => Promise; + onSuccess: (financialCode: ApiGen_Concepts_FinancialCode) => Promise; onError: (e: AxiosError) => void; } export interface IUpdateFinancialCodeContainerProps { - type: FinancialCodeTypes; + type: ApiGen_Concepts_FinancialCodeTypes; id: number; View: React.FC; } @@ -51,7 +53,7 @@ export const UpdateFinancialCodeContainer: React.FC { + const onSave = async (financialCode: ApiGen_Concepts_FinancialCode) => { setDuplicateError(false); return updateFinancialCode(financialCode); }; @@ -61,7 +63,7 @@ export const UpdateFinancialCodeContainer: React.FC { + const onUpdateSuccess = async (financialCode: ApiGen_Concepts_FinancialCode) => { toast.success(`Financial code saved`); history.replace(`/admin/financial-code/list`); }; diff --git a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.test.tsx b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.test.tsx index 28b4707766..a0caf82acf 100644 --- a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.test.tsx +++ b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.test.tsx @@ -1,8 +1,8 @@ import * as Yup from 'yup'; -import { FinancialCodeTypes } from '@/constants/index'; import { mockFinancialCode } from '@/mocks/index.mock'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; import { act, createAxiosError, @@ -123,8 +123,8 @@ describe('UpdateFinancialCode form', () => { await act(async () => userEvent.click(saveButton)); expect(mockProps.onSave).toHaveBeenCalledWith( - expect.objectContaining>({ - type: FinancialCodeTypes.BusinessFunction, + expect.objectContaining>({ + type: ApiGen_Concepts_FinancialCodeTypes.BusinessFunction, code: 'FOO', description: `another description`, }), diff --git a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.tsx b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.tsx index fbb894811d..5c8fb4decb 100644 --- a/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.tsx +++ b/source/frontend/src/features/admin/financial-codes/update/UpdateFinancialCodeForm.tsx @@ -7,9 +7,10 @@ import styled from 'styled-components'; import { Button } from '@/components/common/buttons'; import { FastDatePicker, Input } from '@/components/common/form'; import { SectionField } from '@/components/common/Section/SectionField'; -import { FinancialCodeTypes } from '@/constants/index'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; +import { exists, isValidId } from '@/utils'; import { formatFinancialCodeType } from '../financialCodeUtils'; import { FinancialCodeForm } from '../models'; @@ -54,7 +55,8 @@ export const UpdateFinancialCodeForm: React.FC = onSubmit={async (values, formikHelpers) => { try { const createdCode = await onSave(values.toApi()); - if (!!createdCode?.id) { + // TODO: the isValidId check is sufficient but current ts (4.3) does not see it as valid. This works correctly on 5.3 + if (exists(createdCode) && isValidId(createdCode?.id)) { formikHelpers.resetForm({ values: FinancialCodeForm.fromApi(createdCode), }); @@ -73,7 +75,11 @@ export const UpdateFinancialCodeForm: React.FC = {formikProps => ( - {formatFinancialCodeType(formikProps.values.type as FinancialCodeTypes)} + + {formatFinancialCodeType( + formikProps.values.type as ApiGen_Concepts_FinancialCodeTypes, + )} + diff --git a/source/frontend/src/features/admin/users/ManageUsersPage.tsx b/source/frontend/src/features/admin/users/ManageUsersPage.tsx index d129e82927..e0f9841f3d 100644 --- a/source/frontend/src/features/admin/users/ManageUsersPage.tsx +++ b/source/frontend/src/features/admin/users/ManageUsersPage.tsx @@ -11,7 +11,7 @@ import { Table } from '@/components/Table'; import { useApiUsers } from '@/hooks/pims-api/useApiUsers'; import { useSearch } from '@/hooks/useSearch'; import { IUsersFilter } from '@/interfaces'; -import { Api_User } from '@/models/api/User'; +import { ApiGen_Concepts_User } from '@/models/api/generated/ApiGen_Concepts_User'; import { generateMultiSortCriteria } from '@/utils'; import { toFilteredApiPaginateParams } from '@/utils/CommonFunctions'; @@ -43,7 +43,7 @@ export const ManageUsersPage = () => { sort, setSort, execute, - } = useSearch( + } = useSearch( defaultUserFilter, getUsersPaged, 'No matching results can be found. Try widening your search criteria.', @@ -56,7 +56,7 @@ export const ManageUsersPage = () => { ); const columns = useMemo(() => getUserColumns(execute), [execute]); - let userList = results.map((u: Api_User): FormUser => new FormUser(u)); + let userList = results.map((u: ApiGen_Concepts_User): FormUser => new FormUser(u)); /** * @param {'csv' | 'excel'} accept Whether the fetch is for type of CSV or EXCEL diff --git a/source/frontend/src/features/admin/users/models.ts b/source/frontend/src/features/admin/users/models.ts index b652e87aa5..ab6e6b2dc1 100644 --- a/source/frontend/src/features/admin/users/models.ts +++ b/source/frontend/src/features/admin/users/models.ts @@ -1,12 +1,17 @@ import { ContactMethodTypes } from '@/constants/contactMethodType'; import { UserTypes } from '@/constants/userTypes'; -import { Api_Person } from '@/models/api/Person'; -import { Api_Role } from '@/models/api/Role'; -import Api_TypeCode from '@/models/api/TypeCode'; -import { Api_User } from '@/models/api/User'; -import { UtcIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { ApiGen_Concepts_RegionUser } from '@/models/api/generated/ApiGen_Concepts_RegionUser'; +import { ApiGen_Concepts_Role } from '@/models/api/generated/ApiGen_Concepts_Role'; +import { ApiGen_Concepts_User } from '@/models/api/generated/ApiGen_Concepts_User'; +import { ApiGen_Concepts_UserRole } from '@/models/api/generated/ApiGen_Concepts_UserRole'; +import { EpochIsoDateTime, UtcIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; import { getPreferredContactMethodValue } from '@/utils/contactMethodUtil'; +import { toTypeCodeNullable } from '@/utils/formUtils'; +import { exists, isValidIsoDateTime } from '@/utils/utils'; export class FormUser { id?: number; @@ -18,17 +23,17 @@ export class FormUser { surname?: string; isDisabled?: boolean; position?: string; - userTypeCode?: Api_TypeCode; + userTypeCode?: ApiGen_Base_CodeType; lastLogin?: string; appCreateTimestamp?: UtcIsoDateTime; issueDate?: string; - rowVersion?: NumberFieldValue; + rowVersion?: number; note?: string; - person?: Api_Person; - regions?: Api_TypeCode[]; - roles?: (Api_Role | undefined)[]; + person?: ApiGen_Concepts_Person; + regions?: ApiGen_Base_CodeType[]; + roles?: ApiGen_Concepts_Role[]; - constructor(user: Api_User) { + constructor(user: ApiGen_Concepts_User) { this.id = user.id; this.keycloakUserId = user.guidIdentifierValue; this.email = @@ -39,7 +44,7 @@ export class FormUser { ContactMethodTypes.PersonalEmail, )) ?? ''; - this.businessIdentifierValue = user.businessIdentifierValue; + this.businessIdentifierValue = user.businessIdentifierValue ?? undefined; this.firstName = user?.person?.firstName ?? ''; this.surname = user?.person?.surname ?? ''; this.isDisabled = user.isDisabled; @@ -47,49 +52,71 @@ export class FormUser { this.userTypeCode = { id: user?.userTypeCode?.id ?? UserTypes.Contractor, description: user?.userTypeCode?.description ?? '', + displayOrder: null, + isDisabled: false, }; - this.lastLogin = user.lastLogin; + this.lastLogin = user.lastLogin ?? undefined; this.appCreateTimestamp = user.appCreateTimestamp; - this.note = user.note; - this.roles = user?.userRoles?.map(userRole => userRole?.role) ?? []; - this.regions = user?.userRegions?.map(userRegion => userRegion?.region) ?? []; - this.person = user.person; - this.rowVersion = user.rowVersion; + this.note = user.note ?? undefined; + this.roles = user?.userRoles?.map(userRole => userRole?.role).filter(exists) ?? []; + this.regions = user?.userRegions?.map(userRegion => userRegion?.region).filter(exists) ?? []; + this.person = user.person ?? undefined; + this.rowVersion = user.rowVersion ?? undefined; } - public toApi(): Api_User { + public toApi(): ApiGen_Concepts_User { return { - id: this.id, - businessIdentifierValue: this.businessIdentifierValue, - guidIdentifierValue: this.keycloakUserId, - approvedById: this.approvedById ? this.approvedById : undefined, - position: this.position, - userTypeCode: this.userTypeCode, - note: this.note, - isDisabled: this.isDisabled, - issueDate: this.issueDate, - lastLogin: this.lastLogin, - appCreateTimestamp: this.appCreateTimestamp, + id: this.id ?? 0, + businessIdentifierValue: this.businessIdentifierValue ?? null, + guidIdentifierValue: this.keycloakUserId ?? '', + approvedById: this.approvedById ? this.approvedById : 0, + position: this.position ?? null, + userTypeCode: this.userTypeCode ?? null, + note: this.note ?? null, + isDisabled: this.isDisabled ?? false, + issueDate: isValidIsoDateTime(this.issueDate) ? this.issueDate : null, + lastLogin: isValidIsoDateTime(this.lastLogin) ? this.lastLogin : null, userRoles: - this.roles?.map(role => ({ - userId: this.id, - roleId: role?.id, + this.roles?.map(role => ({ + userId: this.id ?? 0, + roleId: role?.id ?? 0, + id: 0, + role: null, + user: null, + ...getEmptyBaseAudit(), })) ?? [], userRegions: - this.regions?.map(region => ({ - userId: this.id, - regionCode: region.id, - region: { id: region.id }, + this.regions?.map(region => ({ + userId: this.id ?? 0, + regionCode: region.id ?? 0, + region: toTypeCodeNullable(region.id), + id: 0, + user: null, + ...getEmptyBaseAudit(), })) ?? [], person: { ...this.person, - firstName: this.firstName, - surname: this.surname, + firstName: this.firstName ?? '', + surname: this.surname ?? '', contactMethods: [ - { contactMethodType: { id: ContactMethodTypes.WorkEmail }, value: this.email }, + { + contactMethodType: toTypeCodeNullable(ContactMethodTypes.WorkEmail), + value: this.email ?? null, + id: 0, + rowVersion: null, + }, ], + comment: null, + id: 0, + isDisabled: false, + middleNames: null, + personAddresses: null, + personOrganizations: null, + preferredName: null, + rowVersion: null, }, - rowVersion: this.rowVersion ? this.rowVersion : undefined, + ...getEmptyBaseAudit(this.rowVersion), + appCreateTimestamp: this.appCreateTimestamp ?? EpochIsoDateTime, }; } } diff --git a/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.test.tsx b/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.test.tsx index ade0c42ac0..1cd59eab08 100644 --- a/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.test.tsx +++ b/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.test.tsx @@ -134,7 +134,17 @@ const expectedFormData: IEditableOrganization = { comment: '', persons: undefined, addresses: [], - contactMethods: [{ contactMethodTypeCode: { id: 'WORKEMAIL' }, value: 'foo@bar.com' }], + contactMethods: [ + { + contactMethodTypeCode: { + id: 'WORKEMAIL', + description: null, + isDisabled: false, + displayOrder: null, + }, + value: 'foo@bar.com', + }, + ], }; const mockAddress: IEditableOrganizationAddress = { @@ -146,5 +156,10 @@ const mockAddress: IEditableOrganizationAddress = { countryId: 4, countryOther: 'Netherlands', postal: '123456', - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: { + id: AddressTypes.Mailing, + description: null, + isDisabled: false, + displayOrder: null, + }, }; diff --git a/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.tsx b/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.tsx index d898375928..a2706405bb 100644 --- a/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.tsx +++ b/source/frontend/src/features/contacts/contact/create/Organization/CreateOrganizationForm.tsx @@ -21,6 +21,7 @@ import { defaultCreateOrganization, IEditableOrganizationForm, } from '@/interfaces/editable-contact'; +import { isValidId } from '@/utils'; import OrganizationSubForm from '../../Organization/OrganizationSubForm'; import { onValidateOrganization } from '../../utils/contactUtils'; @@ -54,7 +55,7 @@ export const CreateOrganizationForm: React.FunctionComponent = () => { allowDuplicate, ); - if (!!organizationResponse?.id) { + if (isValidId(organizationResponse?.id)) { history.push(`/contact/O${organizationResponse?.id}`); } } finally { diff --git a/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.test.tsx b/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.test.tsx index d62f497587..838adbd186 100644 --- a/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.test.tsx +++ b/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.test.tsx @@ -34,7 +34,12 @@ const mockOrganization: IEditableOrganization = { organizationAddressRowVersion: 2, organizationId: 200, id: 1, - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: { + id: AddressTypes.Mailing, + description: null, + isDisabled: false, + displayOrder: null, + }, streetAddress1: '3000 Main Ave', streetAddress2: '', streetAddress3: '', @@ -47,7 +52,12 @@ const mockOrganization: IEditableOrganization = { ], contactMethods: [ { - contactMethodTypeCode: { id: ContactMethodTypes.WorkEmail }, + contactMethodTypeCode: { + id: ContactMethodTypes.WorkEmail, + description: null, + isDisabled: false, + displayOrder: null, + }, value: 'foo@bar.com', }, ], @@ -69,6 +79,9 @@ const mockPerson: IEditablePerson = { const mockContactMethod: IEditableContactMethod = { contactMethodTypeCode: { id: 'WORKEMAIL', + description: null, + isDisabled: false, + displayOrder: null, }, value: 'test@test.com', }; @@ -82,7 +95,12 @@ const mockAddress: IEditablePersonAddress = { countryId: 4, countryOther: 'Netherlands', postal: '123456', - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: { + id: AddressTypes.Mailing, + description: null, + isDisabled: false, + displayOrder: null, + }, }; // Mock API service calls diff --git a/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.tsx b/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.tsx index e52e8fd681..b1544304bd 100644 --- a/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.tsx +++ b/source/frontend/src/features/contacts/contact/create/Person/CreatePersonForm.tsx @@ -27,6 +27,7 @@ import { getDefaultAddress, IEditablePersonForm, } from '@/interfaces/editable-contact'; +import { isValidId } from '@/utils'; import PersonSubForm from '../../Person/PersonSubForm'; import { onValidatePerson } from '../../utils/contactUtils'; @@ -55,7 +56,7 @@ export const CreatePersonForm: React.FunctionComponent { id: ContactMethodTypes.PersonalEmail, description: 'Personal Email', isDisabled: false, + displayOrder: null, }, value: 'test@bench.com', }; @@ -100,6 +101,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.WorkEmail, description: 'Work Email', + displayOrder: null, isDisabled: false, }, value: 'test@bench.net', @@ -128,6 +130,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.Fax, description: 'Fax', + displayOrder: null, isDisabled: false, }, value: '123456789', @@ -138,6 +141,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.PersonalPhone, description: 'Personal Phone', + displayOrder: null, isDisabled: false, }, value: '800123123', @@ -148,6 +152,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.WorkPhone, description: 'Work Phone', + displayOrder: null, isDisabled: false, }, value: '555123123', @@ -158,6 +163,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.WorkMobile, description: 'Work mobil', + displayOrder: null, isDisabled: false, }, value: '800123123', @@ -168,6 +174,7 @@ describe('Contact OrganizationView component', () => { contactMethodType: { id: ContactMethodTypes.PersonalMobile, description: 'Personal Mobile', + displayOrder: null, isDisabled: false, }, value: '750748789', @@ -214,6 +221,7 @@ describe('Contact OrganizationView component', () => { addressType: { id: AddressTypes.Mailing, description: 'Mailing Address', + displayOrder: null, isDisabled: false, }, }; @@ -232,6 +240,7 @@ describe('Contact OrganizationView component', () => { addressType: { id: AddressTypes.Residential, description: 'Residential Address', + displayOrder: null, isDisabled: false, }, }; @@ -273,6 +282,7 @@ describe('Contact OrganizationView component', () => { addressType: { id: AddressTypes.Mailing, description: 'Mailing Address', + displayOrder: null, isDisabled: false, }, }; diff --git a/source/frontend/src/features/contacts/contact/detail/Person.test.tsx b/source/frontend/src/features/contacts/contact/detail/Person.test.tsx index 9db13e855b..97f7b6055c 100644 --- a/source/frontend/src/features/contacts/contact/detail/Person.test.tsx +++ b/source/frontend/src/features/contacts/contact/detail/Person.test.tsx @@ -85,6 +85,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.PersonalEmail, description: 'Personal Email', isDisabled: false, + displayOrder: null, }, value: 'test@bench.com', }; @@ -95,6 +96,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.WorkEmail, description: 'Work Email', isDisabled: false, + displayOrder: null, }, value: 'test@bench.net', }; @@ -123,6 +125,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.Fax, description: 'Fax', isDisabled: false, + displayOrder: null, }, value: '123456789', }; @@ -133,6 +136,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.PersonalPhone, description: 'Personal Phone', isDisabled: false, + displayOrder: null, }, value: '800123123', }; @@ -143,6 +147,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.WorkPhone, description: 'Work Phone', isDisabled: false, + displayOrder: null, }, value: '555123123', }; @@ -153,6 +158,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.WorkMobile, description: 'Work mobil', isDisabled: false, + displayOrder: null, }, value: '800123123', }; @@ -163,6 +169,7 @@ describe('Contact PersonView component', () => { id: ContactMethodTypes.PersonalMobile, description: 'Personal Mobile', isDisabled: false, + displayOrder: null, }, value: '750748789', }; @@ -234,6 +241,7 @@ describe('Contact PersonView component', () => { id: AddressTypes.Mailing, description: 'Mailing Address', isDisabled: false, + displayOrder: null, }, }; const residentialAddress: IContactAddress = { @@ -252,6 +260,7 @@ describe('Contact PersonView component', () => { id: AddressTypes.Residential, description: 'Residential Address', isDisabled: false, + displayOrder: null, }, }; @@ -293,6 +302,7 @@ describe('Contact PersonView component', () => { id: AddressTypes.Mailing, description: 'Mailing Address', isDisabled: false, + displayOrder: null, }, }; diff --git a/source/frontend/src/features/contacts/contact/detail/utils.ts b/source/frontend/src/features/contacts/contact/detail/utils.ts index 72395d9995..fe661d3f26 100644 --- a/source/frontend/src/features/contacts/contact/detail/utils.ts +++ b/source/frontend/src/features/contacts/contact/detail/utils.ts @@ -2,6 +2,7 @@ import { AddressTypes } from '@/constants/addressTypes'; import { ContactInfoField } from '@/features/contacts/interfaces'; import { Dictionary } from '@/interfaces/Dictionary'; import { IContactAddress, IContactMethod } from '@/interfaces/IContact'; +import { exists } from '@/utils'; // the order of this array corresponds to the expected display order const addressSortOrder = [AddressTypes.Mailing, AddressTypes.Residential, AddressTypes.Billing]; @@ -28,10 +29,13 @@ export function getContactInfo( // Get only the valid types let filteredFields = contactMethods.reduce( (accumulator: ContactInfoField[], method: IContactMethod) => { - if (Object.keys(validTypes).includes(method.contactMethodType.id)) { + if ( + exists(method.contactMethodType?.id) && + Object.keys(validTypes).includes(method.contactMethodType!.id) + ) { accumulator.push({ info: method.value, - label: validTypes[method.contactMethodType.id], + label: validTypes[method.contactMethodType!.id], }); } return accumulator; @@ -45,7 +49,7 @@ export function getContactInfo( }); } -export const fakeAddresses = [ +export const fakeAddresses: IContactAddress[] = [ { id: 1, rowVersion: 3, @@ -53,6 +57,7 @@ export const fakeAddresses = [ id: 'BILLING', description: 'Billing address', isDisabled: false, + displayOrder: null, }, streetAddress1: 'Billing address', municipality: 'Hollywood North', @@ -75,6 +80,7 @@ export const fakeAddresses = [ id: 'MAILING', description: 'Mailing address', isDisabled: false, + displayOrder: null, }, streetAddress1: 'Mailing address', streetAddress2: 'Living in a van', @@ -99,6 +105,7 @@ export const fakeAddresses = [ id: 'RESIDNT', description: 'Property address', isDisabled: false, + displayOrder: null, }, streetAddress1: 'Property address', streetAddress2: '', diff --git a/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.test.tsx b/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.test.tsx index e3c3179ea1..343169333e 100644 --- a/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.test.tsx +++ b/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.test.tsx @@ -8,6 +8,7 @@ import { useUpdateContact } from '@/features/contacts/hooks/useUpdateContact'; import { IEditableOrganization, IEditableOrganizationAddress } from '@/interfaces/editable-contact'; import { mockLookups } from '@/mocks/lookups.mock'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; +import { toTypeCode } from '@/utils/formUtils'; import { act, fillInput, render, RenderOptions } from '@/utils/test-utils'; import UpdateOrganizationForm from './UpdateOrganizationForm'; @@ -27,7 +28,7 @@ const mockOrganization: IEditableOrganization = { persons: undefined, addresses: [], contactMethods: [ - { contactMethodTypeCode: { id: ContactMethodTypes.WorkEmail }, value: 'foo@bar.com' }, + { contactMethodTypeCode: toTypeCode(ContactMethodTypes.WorkEmail), value: 'foo@bar.com' }, ], }; @@ -40,7 +41,7 @@ const mockAddress: IEditableOrganizationAddress = { countryId: 4, countryOther: 'Netherlands', postal: '123456', - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: toTypeCode(AddressTypes.Mailing), }; // Mock API service calls @@ -105,7 +106,7 @@ describe('UpdateOrganizationForm', () => { name: 'RandomName Property Management', contactMethods: [ { - contactMethodTypeCode: { id: ContactMethodTypes.PersonalEmail }, + contactMethodTypeCode: toTypeCode(ContactMethodTypes.PersonalEmail), value: 'test@test.com', }, ], diff --git a/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.tsx b/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.tsx index fc720ee272..25bb6bb7e0 100644 --- a/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.tsx +++ b/source/frontend/src/features/contacts/contact/edit/Organization/UpdateOrganizationForm.tsx @@ -26,6 +26,7 @@ import { IEditableOrganizationForm, } from '@/interfaces/editable-contact'; import { IContactPerson } from '@/interfaces/IContact'; +import { isValidId } from '@/utils'; import { OrganizationSubForm } from '../../Organization/OrganizationSubForm'; import { onValidateOrganization } from '../../utils/contactUtils'; @@ -47,7 +48,7 @@ export const UpdateOrganizationForm: React.FC<{ id: number }> = ({ id }) => { const organizationResponse = await updateOrganization(apiOrganization); const organizationId = organizationResponse?.id; - if (!!organizationId) { + if (isValidId(organizationId)) { history.push(`/contact/O${organizationId}`); } } finally { diff --git a/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.test.tsx b/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.test.tsx index 3a338c2b6b..aa1261dd81 100644 --- a/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.test.tsx +++ b/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.test.tsx @@ -35,7 +35,12 @@ const mockOrganization: IEditableOrganization = { organizationAddressRowVersion: 2, organizationId: 200, id: 1, - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: { + id: AddressTypes.Mailing, + description: null, + isDisabled: false, + displayOrder: null, + }, streetAddress1: '3000 Main Ave', streetAddress2: '', streetAddress3: '', @@ -48,7 +53,12 @@ const mockOrganization: IEditableOrganization = { ], contactMethods: [ { - contactMethodTypeCode: { id: ContactMethodTypes.WorkEmail }, + contactMethodTypeCode: { + id: ContactMethodTypes.WorkEmail, + description: null, + displayOrder: null, + isDisabled: false, + }, value: 'foo@bar.com', }, ], @@ -57,6 +67,9 @@ const mockOrganization: IEditableOrganization = { const mockContactMethod: IEditableContactMethod = { contactMethodTypeCode: { id: 'WORKEMAIL', + description: null, + displayOrder: null, + isDisabled: false, }, value: 'test@test.com', }; @@ -84,7 +97,12 @@ const mockAddress: IEditablePersonAddress = { countryId: 4, countryOther: 'Netherlands', postal: '123456', - addressTypeId: { id: AddressTypes.Mailing }, + addressTypeId: { + id: AddressTypes.Mailing, + description: null, + displayOrder: null, + isDisabled: false, + }, }; // Mock API service calls @@ -180,7 +198,12 @@ describe('UpdatePersonForm', () => { surname: 'UpdatedLastname', contactMethods: [ { - contactMethodTypeCode: { id: ContactMethodTypes.PersonalEmail }, + contactMethodTypeCode: { + id: ContactMethodTypes.PersonalEmail, + description: null, + displayOrder: null, + isDisabled: false, + }, value: 'newaddress@test.com', }, ], @@ -218,7 +241,12 @@ describe('UpdatePersonForm', () => { surname: 'UpdatedLastname', contactMethods: [ { - contactMethodTypeCode: { id: ContactMethodTypes.PersonalEmail }, + contactMethodTypeCode: { + id: ContactMethodTypes.PersonalEmail, + description: null, + displayOrder: null, + isDisabled: false, + }, value: 'newaddress@test.com', }, ], diff --git a/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.tsx b/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.tsx index 0ce448b13f..70e015cd72 100644 --- a/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.tsx +++ b/source/frontend/src/features/contacts/contact/edit/Person/UpdatePersonForm.tsx @@ -32,6 +32,7 @@ import { getDefaultAddress, IEditablePersonForm, } from '@/interfaces/editable-contact'; +import { isValidId } from '@/utils'; import PersonSubForm from '../../Person/PersonSubForm'; import { onValidatePerson } from '../../utils/contactUtils'; @@ -59,7 +60,7 @@ export const UpdatePersonForm: React.FC<{ id: number }> = ({ id }) => { const personResponse = await updatePerson(apiPerson); const personId = personResponse?.id; - if (!!personId) { + if (isValidId(personId)) { history.push(`/contact/P${personId}`); } } finally { diff --git a/source/frontend/src/features/contacts/contact/edit/UpdateContactContainer.test.tsx b/source/frontend/src/features/contacts/contact/edit/UpdateContactContainer.test.tsx index 689d26d7a4..2d0b5dc90f 100644 --- a/source/frontend/src/features/contacts/contact/edit/UpdateContactContainer.test.tsx +++ b/source/frontend/src/features/contacts/contact/edit/UpdateContactContainer.test.tsx @@ -7,6 +7,7 @@ import { usePersonDetail } from '@/features/contacts/hooks/usePersonDetail'; import { IEditableOrganization, IEditablePerson } from '@/interfaces/editable-contact'; import { mockLookups } from '@/mocks/lookups.mock'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; +import { toTypeCode } from '@/utils/formUtils'; import { act, render, RenderOptions, userEvent, waitFor } from '@/utils/test-utils'; import UpdateContactContainer from './UpdateContactContainer'; @@ -26,7 +27,10 @@ const mockPerson: IEditablePerson = { useOrganizationAddress: false, addresses: [], contactMethods: [ - { contactMethodTypeCode: { id: ContactMethodTypes.PersonalEmail }, value: 'foo@bar.com' }, + { + contactMethodTypeCode: toTypeCode(ContactMethodTypes.PersonalEmail), + value: 'foo@bar.com', + }, ], }; @@ -40,7 +44,7 @@ const mockOrganization: IEditableOrganization = { persons: [], addresses: [], contactMethods: [ - { contactMethodTypeCode: { id: ContactMethodTypes.WorkEmail }, value: 'foo@bar.com' }, + { contactMethodTypeCode: toTypeCode(ContactMethodTypes.WorkEmail), value: 'foo@bar.com' }, ], }; diff --git a/source/frontend/src/features/contacts/contactUtils.ts b/source/frontend/src/features/contacts/contactUtils.ts index 4909d5c047..0f8737582d 100644 --- a/source/frontend/src/features/contacts/contactUtils.ts +++ b/source/frontend/src/features/contacts/contactUtils.ts @@ -19,13 +19,19 @@ import { IEditablePersonForm, } from '@/interfaces/editable-contact'; import { IContactPerson } from '@/interfaces/IContact'; -import { Api_Organization, Api_OrganizationPerson } from '@/models/api/Organization'; -import { fromTypeCode, stringToBoolean, stringToUndefined, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { isValidId } from '@/utils'; +import { + fromTypeCode, + stringToBoolean, + stringToUndefined, + toTypeCodeNullable, +} from '@/utils/formUtils'; import { formatFullName, formatNames } from '@/utils/personUtils'; -import { Api_Address } from './../../models/api/Address'; -import { Api_Person } from './../../models/api/Person'; - export function formPersonToApiPerson(formValues: IEditablePersonForm): IEditablePerson { // exclude form-specific fields from API payload object const { @@ -168,17 +174,20 @@ export function getApiMailingAddress( } export function getApiPersonOrOrgMailingAddress( - contact: Api_Person | Api_Organization, -): Api_Address | undefined { - if (!contact) return undefined; + contact: ApiGen_Concepts_Person | ApiGen_Concepts_Organization, +): ApiGen_Concepts_Address | null { + if (!contact) { + return null; + } return ( - (contact as Api_Person).personAddresses?.find( + (contact as ApiGen_Concepts_Person).personAddresses?.find( addr => addr?.addressUsageType?.id === AddressTypes.Mailing && addr.address, )?.address ?? - (contact as Api_Organization).organizationAddresses?.find( + (contact as ApiGen_Concepts_Organization).organizationAddresses?.find( addr => addr?.addressUsageType?.id === AddressTypes.Mailing, - )?.address + )?.address ?? + null ); } @@ -213,7 +222,7 @@ export function formAddressToApiAddress( provinceId: isEmpty(formAddress?.provinceId.toString()) ? undefined : parseInt(formAddress?.provinceId.toString()), - addressTypeId: toTypeCode(formAddress?.addressTypeId), + addressTypeId: toTypeCodeNullable(formAddress?.addressTypeId), } as IEditablePersonAddress | IEditableOrganizationAddress; } @@ -230,7 +239,7 @@ function formContactMethodToApiContactMethod(formContactMethod: IEditableContact return { ...formContactMethod, value: stringToUndefined(formContactMethod.value), - contactMethodTypeCode: toTypeCode(formContactMethod.contactMethodTypeCode), + contactMethodTypeCode: toTypeCodeNullable(formContactMethod.contactMethodTypeCode), } as IEditableContactMethod; } @@ -258,21 +267,23 @@ function isPhone(contactMethod?: IEditableContactMethodForm): boolean { } export const getDefaultContact = (organization?: { - organizationPersons?: Api_OrganizationPerson[]; -}): Api_Person | undefined => { + organizationPersons: ApiGen_Concepts_OrganizationPerson[] | null; +}): ApiGen_Concepts_Person | null => { if (organization?.organizationPersons?.length === 1) { - return organization?.organizationPersons[0]?.person; + return organization.organizationPersons[0].person; } - return undefined; + return null; }; export const getPrimaryContact = ( primaryContactId: number, organization?: { - organizationPersons?: Api_OrganizationPerson[]; + organizationPersons?: ApiGen_Concepts_OrganizationPerson[]; }, -): Api_Person | undefined => { - return organization?.organizationPersons?.find(op => op.personId === primaryContactId)?.person; +): ApiGen_Concepts_Person | null => { + return ( + organization?.organizationPersons?.find(op => op.personId === primaryContactId)?.person ?? null + ); }; export function formatContactSearchResult( @@ -280,9 +291,9 @@ export function formatContactSearchResult( defaultText: string = '', ): string { let text = defaultText; - if (contact?.personId !== undefined) { + if (isValidId(contact?.personId)) { text = formatNames([contact.firstName, contact.middleNames, contact.surname]); - } else if (contact?.organizationId !== undefined) { + } else if (isValidId(contact?.organizationId)) { text = contact.organizationName || ''; } return text; diff --git a/source/frontend/src/features/contacts/repositories/useOrganizationRepository.ts b/source/frontend/src/features/contacts/repositories/useOrganizationRepository.ts index 943923ac74..9841db30d2 100644 --- a/source/frontend/src/features/contacts/repositories/useOrganizationRepository.ts +++ b/source/frontend/src/features/contacts/repositories/useOrganizationRepository.ts @@ -3,7 +3,7 @@ import { useCallback, useMemo } from 'react'; import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; -import { Api_Organization } from '@/models/api/Organization'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; /** * hook that interacts with the Organization API. @@ -12,7 +12,7 @@ export const useOrganizationRepository = () => { const { getOrganizationConcept } = useApiContacts(); const getOrganizationDetail = useApiRequestWrapper< - (orgId: number) => Promise> + (orgId: number) => Promise> >({ requestFunction: useCallback( async (orgId: number) => await getOrganizationConcept(orgId), diff --git a/source/frontend/src/features/contacts/repositories/usePersonRepository.ts b/source/frontend/src/features/contacts/repositories/usePersonRepository.ts index d57575c2ba..7b57da9168 100644 --- a/source/frontend/src/features/contacts/repositories/usePersonRepository.ts +++ b/source/frontend/src/features/contacts/repositories/usePersonRepository.ts @@ -3,7 +3,7 @@ import { useCallback, useMemo } from 'react'; import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; /** * hook that interacts with the Person API. @@ -12,7 +12,7 @@ export const usePersonRepository = () => { const { getPersonConcept } = useApiContacts(); const getPersonDetail = useApiRequestWrapper< - (personId: number) => Promise> + (personId: number) => Promise> >({ requestFunction: useCallback( async (personId: number) => await getPersonConcept(personId), diff --git a/source/frontend/src/features/disposition/list/DispositionFilter/DispositionFilter.tsx b/source/frontend/src/features/disposition/list/DispositionFilter/DispositionFilter.tsx index b476b9d8fd..1787d38498 100644 --- a/source/frontend/src/features/disposition/list/DispositionFilter/DispositionFilter.tsx +++ b/source/frontend/src/features/disposition/list/DispositionFilter/DispositionFilter.tsx @@ -6,8 +6,8 @@ import { ResetButton, SearchButton } from '@/components/common/buttons'; import { Input, Select, SelectOption, TypeaheadSelect } from '@/components/common/form'; import { SelectInput } from '@/components/common/List/SelectInput'; import { FilterBoxForm } from '@/components/common/styles'; -import { Api_DispositionFileTeam } from '@/models/api/DispositionFile'; import { Api_DispositionFilter } from '@/models/api/DispositionFilter'; +import { ApiGen_Concepts_DispositionFileTeam } from '@/models/api/generated/ApiGen_Concepts_DispositionFileTeam'; import { formatApiPersonNames } from '@/utils/personUtils'; import { DispositionFilterModel } from '../models'; @@ -16,7 +16,7 @@ import { ColButtons } from '../styles'; export interface IDispositionFilterProps { filter?: Api_DispositionFilter; setFilter: (filter: Api_DispositionFilter) => void; - dispositionTeam: Api_DispositionFileTeam[]; + dispositionTeam: ApiGen_Concepts_DispositionFileTeam[]; fileStatusOptions: SelectOption[]; dispositionStatusOptions: SelectOption[]; dispositionTypeOptions: SelectOption[]; diff --git a/source/frontend/src/features/disposition/list/DispositionListView.test.tsx b/source/frontend/src/features/disposition/list/DispositionListView.test.tsx index b73a7acd98..55a4a15e11 100644 --- a/source/frontend/src/features/disposition/list/DispositionListView.test.tsx +++ b/source/frontend/src/features/disposition/list/DispositionListView.test.tsx @@ -7,7 +7,8 @@ import { IPagedItems } from '@/interfaces'; import { getMockApiAddress } from '@/mocks/address.mock'; import { mockDispositionFileResponse } from '@/mocks/dispositionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { getEmptyProperty } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, @@ -35,8 +36,8 @@ const exportDispositionFilesFn = jest.fn(); }); const mockPagedResults = ( - searchResults?: Api_DispositionFile[], -): Partial, any>> => { + searchResults?: ApiGen_Concepts_DispositionFile[], +): Partial, any>> => { const results = searchResults ?? []; const len = results.length; return { @@ -115,10 +116,15 @@ describe('Disposition List View', () => { fileId: 1, propertyId: 1, property: { + ...getEmptyProperty(), id: 1, address: getMockApiAddress(), pid: 123, }, + displayOrder: null, + file: null, + propertyName: null, + rowVersion: null, }, ], }, diff --git a/source/frontend/src/features/disposition/list/DispositionListView.tsx b/source/frontend/src/features/disposition/list/DispositionListView.tsx index 63017afabc..e9901cfd96 100644 --- a/source/frontend/src/features/disposition/list/DispositionListView.tsx +++ b/source/frontend/src/features/disposition/list/DispositionListView.tsx @@ -19,8 +19,8 @@ import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvi import { useKeycloakWrapper } from '@/hooks/useKeycloakWrapper'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; import { useSearch } from '@/hooks/useSearch'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; import { Api_DispositionFilter } from '@/models/api/DispositionFilter'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { generateMultiSortCriteria, mapLookupCode } from '@/utils'; import { toFilteredApiPaginateParams } from '@/utils/CommonFunctions'; @@ -70,7 +70,7 @@ export const DispositionListView: React.FC = () => { setCurrentPage, setPageSize, loading, - } = useSearch( + } = useSearch( new DispositionFilterModel().toApi(), getDispositionFilesPagedApi, 'No matching results can be found. Try widening your search criteria.', diff --git a/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx b/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx index 720d9862b0..afc9bc4d76 100644 --- a/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx +++ b/source/frontend/src/features/disposition/list/DispositionSearchResults/DispositionSearchResults.test.tsx @@ -2,7 +2,7 @@ import { Claims } from '@/constants/claims'; import { mockDispositionFileResponse } from '@/mocks/dispositionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; import { getMockApiProperty } from '@/mocks/properties.mock'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { render, RenderOptions, screen } from '@/utils/test-utils'; @@ -15,7 +15,9 @@ import { jest.mock('@react-keycloak/web'); const setSort = jest.fn(); -const mockResults: Api_DispositionFile[] = [mockDispositionFileResponse(1, 'test disposition')]; +const mockResults: ApiGen_Concepts_DispositionFile[] = [ + mockDispositionFileResponse(1, 'test disposition'), +]; describe('Disposition search results table', () => { const getTableRows = () => document.querySelectorAll('.table .tbody .tr-wrapper'); @@ -63,7 +65,7 @@ describe('Disposition search results table', () => { it('displays disposition file number', async () => { setup({ results: mockResults.map(a => DispositionSearchResultModel.fromApi(a)) }); - const text = await screen.findByText(mockResults[0].fileNumber!); + const text = await screen.findByText(`D-${mockResults[0].fileNumber!}`); expect(text).toBeVisible(); }); @@ -84,18 +86,30 @@ describe('Disposition search results table', () => { fileId: 1, propertyId: 1, property: { ...getMockApiProperty(), id: 1 }, + displayOrder: null, + file: null, + propertyName: null, + rowVersion: null, }, { id: 200, fileId: 1, propertyId: 2, property: { ...getMockApiProperty(), id: 2 }, + displayOrder: null, + file: null, + propertyName: null, + rowVersion: null, }, { id: 300, fileId: 1, propertyId: 3, property: { ...getMockApiProperty(), id: 3 }, + displayOrder: null, + file: null, + propertyName: null, + rowVersion: null, }, ], }), @@ -133,7 +147,14 @@ describe('Disposition search results table', () => { id: 'MAJORPRJ', description: 'Major Projects', isDisabled: false, + displayOrder: null, }, + primaryContact: null, + primaryContactId: null, + rowVersion: null, + teamProfileTypeCode: null, + person: null, + personId: null, }, ], }), @@ -159,12 +180,25 @@ describe('Disposition search results table', () => { rowVersion: 1, firstName: 'chester', surname: 'tester', + comment: null, + contactMethods: null, + middleNames: null, + personAddresses: null, + personOrganizations: null, + preferredName: null, }, teamProfileType: { id: 'MoTIReg', description: 'MoTI Region', isDisabled: false, + displayOrder: null, }, + primaryContact: null, + primaryContactId: null, + rowVersion: null, + teamProfileTypeCode: null, + organization: null, + organizationId: null, }, ], }), @@ -200,7 +234,14 @@ describe('Disposition search results table', () => { id: 'MAJORPRJ', description: 'Major Projects', isDisabled: false, + displayOrder: null, }, + person: null, + personId: null, + primaryContact: null, + primaryContactId: null, + rowVersion: null, + teamProfileTypeCode: null, }, { id: 2, @@ -212,12 +253,25 @@ describe('Disposition search results table', () => { rowVersion: 1, firstName: 'chester', surname: 'tester', + comment: null, + contactMethods: null, + middleNames: null, + personAddresses: null, + personOrganizations: null, + preferredName: null, }, teamProfileType: { id: 'MoTIReg', description: 'MoTI Region', isDisabled: false, + displayOrder: null, }, + primaryContact: null, + primaryContactId: null, + rowVersion: null, + teamProfileTypeCode: null, + organization: null, + organizationId: null, }, { id: 3, @@ -229,12 +283,25 @@ describe('Disposition search results table', () => { rowVersion: 1, firstName: 'john', surname: 'doe', + comment: null, + contactMethods: null, + middleNames: null, + personAddresses: null, + personOrganizations: null, + preferredName: null, }, teamProfileType: { id: 'CAPPROG', description: 'Capital Program', isDisabled: false, + displayOrder: null, }, + primaryContact: null, + primaryContactId: null, + rowVersion: null, + teamProfileTypeCode: null, + organization: null, + organizationId: null, }, ], }), diff --git a/source/frontend/src/features/disposition/list/DispositionSearchResults/__snapshots__/DispositionSearchResults.test.tsx.snap b/source/frontend/src/features/disposition/list/DispositionSearchResults/__snapshots__/DispositionSearchResults.test.tsx.snap index 6d1ea02be5..cd313dca64 100644 --- a/source/frontend/src/features/disposition/list/DispositionSearchResults/__snapshots__/DispositionSearchResults.test.tsx.snap +++ b/source/frontend/src/features/disposition/list/DispositionSearchResults/__snapshots__/DispositionSearchResults.test.tsx.snap @@ -383,7 +383,7 @@ exports[`Disposition search results table matches snapshot 1`] = ` - FILE_NUMBER 3A8F46B + D-FILE_NUMBER 3A8F46B

[] = [ if (hasClaim(Claims.DISPOSITION_VIEW)) { return ( - {props.row.original.fileNumber} + D-{props.row.original.fileNumber} ); } @@ -75,8 +74,6 @@ export const columns: ColumnWithProps[] = [ Header: 'MOTI Region', accessor: 'region', align: 'left', - clickable: true, - width: 10, maxWidth: 20, }, { @@ -88,37 +85,33 @@ export const columns: ColumnWithProps[] = [ maxWidth: 40, Cell: (props: CellProps) => { const team = props.row.original.dispositionTeam; - const personsInTeam = team?.filter(x => x.personId !== undefined); - const organizationsInTeam = team?.filter(x => x.organizationId !== undefined); + const personsInTeam = team?.filter(x => isValidId(x.personId)); + const organizationsInTeam = team?.filter(x => isValidId(x.organizationId)); const personsAsString: MemberRoleGroup[] = chain(personsInTeam) - .groupBy((groupedTeams: Api_DispositionFileTeam) => groupedTeams.personId) - .map(x => { - return { - id: x[0].id?.toString() || '', - person: x[0].person || {}, - organization: null, - roles: x - .map(t => t.teamProfileType) - .filter((z): z is Api_TypeCode => z !== undefined) - .flatMap(y => y.description || ''), - }; - }) + .groupBy((groupedTeams: ApiGen_Concepts_DispositionFileTeam) => groupedTeams.personId) + .map(x => ({ + id: x[0].id?.toString() || '', + person: x[0].person || null, + organization: null, + roles: x + .map(t => t.teamProfileType) + .filter(exists) + .flatMap(y => y.description || ''), + })) .value(); const organizationsAsString: MemberRoleGroup[] = chain(organizationsInTeam) - .groupBy((groupedTeams: Api_DispositionFileTeam) => groupedTeams.organizationId) - .map(x => { - return { - id: x[0].id?.toString() || '', - person: null, - organization: x[0].organization || {}, - roles: x - .map(t => t.teamProfileType) - .filter((z): z is Api_TypeCode => z !== undefined) - .flatMap(y => y.description || ''), - }; - }) + .groupBy((groupedTeams: ApiGen_Concepts_DispositionFileTeam) => groupedTeams.organizationId) + .map(x => ({ + id: x[0].id?.toString() || '', + person: null, + organization: x[0].organization || null, + roles: x + .map(t => t.teamProfileType) + .filter(exists) + .flatMap(y => y.description || ''), + })) .value(); const teamAsString = personsAsString.concat(organizationsAsString); diff --git a/source/frontend/src/features/disposition/list/__snapshots__/DispositionListView.test.tsx.snap b/source/frontend/src/features/disposition/list/__snapshots__/DispositionListView.test.tsx.snap index 354463845f..5f16534a29 100644 --- a/source/frontend/src/features/disposition/list/__snapshots__/DispositionListView.test.tsx.snap +++ b/source/frontend/src/features/disposition/list/__snapshots__/DispositionListView.test.tsx.snap @@ -1062,7 +1062,7 @@ exports[`Disposition List View matches snapshot 1`] = ` - FILE_NUMBER 3A8F46B + D-FILE_NUMBER 3A8F46B
0 - ) { + if (exists(mayanMetadataResult) && mayanMetadataResult.length > 0) { const document = mayanMetadataResult[0].document; mayanFileId = document?.file_latest?.id; } diff --git a/source/frontend/src/features/leases/add/AddLeaseContainer.test.tsx b/source/frontend/src/features/leases/add/AddLeaseContainer.test.tsx index fdb703f9b2..1cd65c3f96 100644 --- a/source/frontend/src/features/leases/add/AddLeaseContainer.test.tsx +++ b/source/frontend/src/features/leases/add/AddLeaseContainer.test.tsx @@ -6,9 +6,11 @@ import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineCo import { useUserInfoRepository } from '@/hooks/repositories/useUserInfoRepository'; import { mockLookups } from '@/mocks/lookups.mock'; import { mapMachineBaseMock } from '@/mocks/mapFSM.mock'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { getEmptyBaseAudit, getEmptyLease } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, createAxiosError, @@ -202,18 +204,20 @@ describe('AddLeaseContainer component', () => { }); }); -const leaseData: Api_Lease = { +const leaseData: ApiGen_Concepts_Lease = { + ...getEmptyLease(), + rowVersion: 0, startDate: '2020-01-01', amount: 0, - paymentReceivableType: { id: 'RCVBL' }, - purposeType: { id: 'BCFERRIES' }, - statusType: { id: 'DRAFT' }, - type: { id: 'LIOCCTTLD' }, - region: { id: 1 }, - programType: { id: 'BCFERRIES' }, + paymentReceivableType: toTypeCodeNullable('RCVBL'), + purposeType: toTypeCodeNullable('BCFERRIES'), + fileStatusTypeCode: toTypeCodeNullable('DRAFT'), + type: toTypeCodeNullable('LIOCCTTLD'), + region: toTypeCodeNullable(1), + programType: toTypeCodeNullable('BCFERRIES'), returnNotes: '', motiName: '', - properties: [], + fileProperties: [], isResidential: false, isCommercialBuilding: false, isOtherImprovement: false, @@ -234,56 +238,62 @@ const leaseData: Api_Lease = { expiryDate: '2020-01-02', tenants: [], terms: [], - insurances: [], consultations: [ { id: 0, - consultationType: { id: '1STNATION' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('1STNATION'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'STRATRE' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('STRATRE'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'REGPLANG' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('REGPLANG'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'REGPRPSVC' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('REGPRPSVC'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'DISTRICT' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('DISTRICT'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'HQ' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('HQ'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, { id: 0, - consultationType: { id: 'OTHER' }, - consultationStatusType: { id: 'UNKNOWN' }, + consultationType: toTypeCodeNullable('OTHER'), + consultationStatusType: toTypeCodeNullable('UNKNOWN'), parentLeaseId: 0, otherDescription: null, + ...getEmptyBaseAudit(), }, ], }; diff --git a/source/frontend/src/features/leases/add/AddLeaseContainer.tsx b/source/frontend/src/features/leases/add/AddLeaseContainer.tsx index 71c3600692..38e832d3df 100644 --- a/source/frontend/src/features/leases/add/AddLeaseContainer.tsx +++ b/source/frontend/src/features/leases/add/AddLeaseContainer.tsx @@ -12,6 +12,7 @@ import SidebarFooter from '@/features/mapSideBar/shared/SidebarFooter'; import useApiUserOverride from '@/hooks/useApiUserOverride'; import { useInitialMapSelectorProperties } from '@/hooks/useInitialMapSelectorProperties'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { exists, isValidId } from '@/utils'; import { featuresetToMapProperty } from '@/utils/mapPropertyUtils'; import { useAddLease } from '../hooks/useAddLease'; @@ -57,8 +58,9 @@ export const AddLeaseContainer: React.FunctionComponent< const response = await addLease.execute(leaseApi, userOverrideCodes); formikHelpers.setSubmitting(false); - if (!!response?.id) { - if (leaseApi.properties?.find(p => !p.property?.address && !p.property?.id)) { + // TODO: the isValidId check is sufficient but current ts (4.3) does not see it as valid. This works correctly on 5.3 + if (exists(response) && isValidId(response?.id)) { + if (leaseApi.fileProperties?.find(p => !p.property?.address && !p.property?.id)) { toast.warn( 'Address could not be retrieved for this property, it will have to be provided manually in property details tab', { autoClose: 15000 }, diff --git a/source/frontend/src/features/leases/add/AdministrationSubForm.tsx b/source/frontend/src/features/leases/add/AdministrationSubForm.tsx index b30db83de9..c73f0b0f8a 100644 --- a/source/frontend/src/features/leases/add/AdministrationSubForm.tsx +++ b/source/frontend/src/features/leases/add/AdministrationSubForm.tsx @@ -10,6 +10,7 @@ import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; +import { isValidString } from '@/utils'; import { LeaseFormModel } from '../models'; import * as Styled from './styles'; @@ -33,26 +34,26 @@ const AdministrationSubForm: React.FunctionComponent< //clear the associated other fields if the corresponding type has its value changed from other to something else. useEffect(() => { - if (!!categoryTypeCode && categoryTypeCode !== 'OTHER') { + if (isValidString(categoryTypeCode) && categoryTypeCode !== 'OTHER') { setFieldValue('otherCategoryTypeDescription', ''); } - if (!!leaseTypeCode && leaseTypeCode !== 'OTHER') { + if (isValidString(leaseTypeCode) && leaseTypeCode !== 'OTHER') { setFieldValue('otherLeaseTypeDescription', ''); } - if (!!leaseTypeCode && !isLeaseCategoryVisible(leaseTypeCode)) { + if (isValidString(leaseTypeCode) && !isLeaseCategoryVisible(leaseTypeCode)) { setFieldValue('otherCategoryTypeDescription', ''); setFieldValue('categoryTypeCode', ''); } - if (!!purposeTypeCode && purposeTypeCode !== 'OTHER') { + if (isValidString(purposeTypeCode) && purposeTypeCode !== 'OTHER') { setFieldValue('otherPurposeTypeDescription', ''); } - if (!!programTypeCode && programTypeCode !== 'OTHER') { + if (isValidString(programTypeCode) && programTypeCode !== 'OTHER') { setFieldValue('otherProgramTypeDescription', ''); } }, [categoryTypeCode, leaseTypeCode, purposeTypeCode, programTypeCode, setFieldValue]); useEffect(() => { - if (!!leaseTypeCode && !isLeaseCategoryVisible(leaseTypeCode)) { + if (isValidString(leaseTypeCode) && !isLeaseCategoryVisible(leaseTypeCode)) { setFieldValue('categoryTypeCode', ''); } }, [leaseTypeCode, setFieldValue]); diff --git a/source/frontend/src/features/leases/context/LeaseContext.tsx b/source/frontend/src/features/leases/context/LeaseContext.tsx index b8a93d9150..6431893426 100644 --- a/source/frontend/src/features/leases/context/LeaseContext.tsx +++ b/source/frontend/src/features/leases/context/LeaseContext.tsx @@ -2,11 +2,11 @@ import { noop } from 'lodash'; import * as React from 'react'; import { useState } from 'react'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; export interface ILeaseState { - lease?: Api_Lease; - setLease: (lease: Api_Lease) => void; + lease?: ApiGen_Concepts_Lease; + setLease: (lease: ApiGen_Concepts_Lease) => void; } export const LeaseStateContext = React.createContext({ @@ -14,8 +14,11 @@ export const LeaseStateContext = React.createContext({ setLease: noop, }); -export const LeaseContextProvider = (props: { children?: any; initialLease?: Api_Lease }) => { - const [lease, setLease] = useState(props.initialLease); +export const LeaseContextProvider = (props: { + children?: any; + initialLease?: ApiGen_Concepts_Lease; +}) => { + const [lease, setLease] = useState(props.initialLease); return ( diff --git a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx index 4c87cbd51f..b858271757 100644 --- a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx +++ b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.test.tsx @@ -4,8 +4,8 @@ import { createMemoryHistory } from 'history'; import { IProperty } from '@/interfaces'; import { mockLookups } from '@/mocks/lookups.mock'; -import { getMockProperties } from '@/mocks/properties.mock'; -import { Api_Property } from '@/models/api/Property'; +import { getEmptyPropertyLease, getMockProperties } from '@/mocks/properties.mock'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, render, RenderOptions, userEvent } from '@/utils/test-utils'; @@ -41,11 +41,9 @@ describe('LeaseHeaderAddresses component', () => { it('renders 2 addresses by default', async () => { const { getAllByText, getByText } = setup({ propertyLeases: getMockProperties().map(p => ({ - leaseId: 1, + ...getEmptyPropertyLease(), + fileId: 1, property: p as any, - lease: null, - leaseArea: null, - areaUnitType: null, })), }); @@ -57,39 +55,64 @@ describe('LeaseHeaderAddresses component', () => { const { getByText, getAllByText } = setup({ propertyLeases: [ { - leaseId: 1, - property: noStreetOrMunicipality as Api_Property, - lease: null, + fileId: 1, + property: noStreetOrMunicipality as unknown as ApiGen_Concepts_Property, + file: null, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, { - leaseId: 1, - property: streetNoMunicipality as Api_Property, - lease: null, + fileId: 1, + property: streetNoMunicipality as unknown as ApiGen_Concepts_Property, + file: null, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, { - leaseId: 1, - property: noStreetButMunicipality as Api_Property, - lease: null, + fileId: 1, + property: noStreetButMunicipality as unknown as ApiGen_Concepts_Property, + file: null, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, { - leaseId: 1, - property: streetAndMunicipality as Api_Property, - lease: null, + fileId: 1, + property: streetAndMunicipality as unknown as ApiGen_Concepts_Property, + file: null, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, { - leaseId: 1, - property: undefinedAddress as Api_Property, - lease: null, + fileId: 1, + property: undefinedAddress as unknown as ApiGen_Concepts_Property, + file: null, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, ], }); diff --git a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.tsx b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.tsx index 067b867a47..4bd7f134c1 100644 --- a/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.tsx +++ b/source/frontend/src/features/leases/detail/LeaseHeaderAddresses.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; import ExpandableTextList from '@/components/common/ExpandableTextList'; -import { Api_Property } from '@/models/api/Property'; -import { Api_PropertyLease } from '@/models/api/PropertyLease'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; +import { ApiGen_Concepts_PropertyLease } from '@/models/api/generated/ApiGen_Concepts_PropertyLease'; +import { exists, isValidString } from '@/utils'; import { getPropertyName } from '@/utils/mapPropertyUtils'; export interface ILeaseHeaderAddressesProps { - propertyLeases?: Api_PropertyLease[]; + propertyLeases?: ApiGen_Concepts_PropertyLease[]; delimiter?: React.ReactElement | string; maxCollapsedLength?: number; } @@ -17,36 +18,38 @@ export const LeaseHeaderAddresses: React.FC = ({ maxCollapsedLength = 2, }) => { return ( - + items={propertyLeases ?? []} - keyFunction={(item: Api_PropertyLease, index: number) => + keyFunction={(item: ApiGen_Concepts_PropertyLease, index: number) => `lease-property-${item.id}-address-${item?.property?.address?.id ?? index}` } - renderFunction={(item: Api_PropertyLease) => <>{getFormattedAddress(item?.property)}} + renderFunction={(item: ApiGen_Concepts_PropertyLease) => ( + <>{getFormattedAddress(item?.property)} + )} delimiter={delimiter} maxCollapsedLength={maxCollapsedLength} /> ); }; -const getFormattedAddress = (property?: Api_Property) => { - if (!property) { +const getFormattedAddress = (property: ApiGen_Concepts_Property | null | undefined) => { + if (!exists(property)) { return ''; } const address = property?.address; - if (!!address?.streetAddress1) { - return !!address?.municipality - ? `${address.streetAddress1}, ${address.municipality}` - : address.streetAddress1; + if (isValidString(address?.streetAddress1)) { + return isValidString(address?.municipality) + ? `${address!.streetAddress1}, ${address!.municipality}` + : address!.streetAddress1; } else { - return !!address?.municipality - ? address.municipality + return isValidString(address?.municipality) + ? address!.municipality : `${ getPropertyName({ pid: property.pid?.toString(), pin: property.pin?.toString(), - latitude: property.latitude, - longitude: property.longitude, + latitude: property.latitude ?? undefined, + longitude: property.longitude ?? undefined, }).value } - Address not available in PIMS`; } diff --git a/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.test.tsx index 47f95b326a..7bf64aa881 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.test.tsx @@ -2,25 +2,36 @@ import { Formik } from 'formik'; import { createMemoryHistory } from 'history'; import { noop } from 'lodash'; -import { IAddress } from '@/interfaces/IAddress'; -import { mockParcel } from '@/mocks/filterData.mock'; +import { getEmptyAddress } from '@/mocks/address.mock'; +import { mockLeaseProperty } from '@/mocks/filterData.mock'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { getEmptyLease, getEmptyProperty } from '@/models/defaultInitializers'; +import { toTypeCode, toTypeCodeConcept } from '@/utils/formUtils'; import { render, RenderOptions } from '@/utils/test-utils'; -import { LeaseFormModel } from '../../models'; import AddressSubForm, { IAddressSubFormProps } from './AddressSubForm'; const history = createMemoryHistory(); -const defaultLeaseWithPropertyAddress = (address?: Partial) => { +const defaultLeaseWithPropertyAddress = ( + address?: Partial, +): ApiGen_Concepts_Lease => { return { - ...new LeaseFormModel(), - properties: [ + ...getEmptyLease(), + fileProperties: [ { - ...mockParcel, - areaUnitTypeCode: 'test', - landArea: '0', - address: address !== undefined ? { ...mockParcel.address, ...address } : (undefined as any), - leaseId: null, + ...mockLeaseProperty(), + areaUnitType: toTypeCode('test'), + leaseArea: 0, + property: { + ...getEmptyProperty(), + address: + address !== undefined + ? { ...getEmptyAddress(), ...mockLeaseProperty().property!.address, ...address } + : null, + }, + fileId: 0, }, ], }; @@ -28,13 +39,13 @@ const defaultLeaseWithPropertyAddress = (address?: Partial) => { describe('AddressSubForm component', () => { const setup = ( - renderOptions: RenderOptions & IAddressSubFormProps & { lease?: LeaseFormModel } = { - nameSpace: 'address', + renderOptions: RenderOptions & IAddressSubFormProps & { lease?: ApiGen_Concepts_Lease } = { + nameSpace: 'property.address', }, ) => { // render component under test const component = render( - + , { @@ -49,10 +60,17 @@ describe('AddressSubForm component', () => { }; it('renders minimally as expected', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: { - ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '0', leaseId: null }], + ...getEmptyLease(), + fileProperties: [ + { + ...mockLeaseProperty(), + areaUnitType: toTypeCode('test'), + leaseArea: 0, + fileId: 0, + }, + ], }, }); expect(component.asFragment()).toMatchSnapshot(); @@ -60,12 +78,12 @@ describe('AddressSubForm component', () => { it('does not render the street address 1 field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress1: '' }), }); const { container } = component; const streetAddress1 = container.querySelector( - `input[name="properties.0.address.streetAddress1"]`, + `input[name="fileProperties.0.property.address.streetAddress1"]`, ); expect(streetAddress1).toBeNull(); @@ -73,12 +91,12 @@ describe('AddressSubForm component', () => { it('renders the street address 1 field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress1: 'street address 1' }), }); const { container } = component; const streetAddress1 = container.querySelector( - `input[name="properties.0.address.streetAddress1"]`, + `input[name="fileProperties.0.property.address.streetAddress1"]`, ); expect(streetAddress1).toBeVisible(); @@ -86,12 +104,12 @@ describe('AddressSubForm component', () => { it('does not render the street address 2 field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress2: '' }), }); const { container } = component; const streetAddress2 = container.querySelector( - `input[name="properties.0.address.streetAddress2"]`, + `input[name="fileProperties.0.property.address.streetAddress2"]`, ); expect(streetAddress2).toBeNull(); @@ -99,12 +117,12 @@ describe('AddressSubForm component', () => { it('renders the street address 2 field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress2: 'street address 2' }), }); const { container } = component; const streetAddress2 = container.querySelector( - `input[name="properties.0.address.streetAddress2"]`, + `input[name="fileProperties.0.property.address.streetAddress2"]`, ); expect(streetAddress2).toBeVisible(); @@ -112,12 +130,12 @@ describe('AddressSubForm component', () => { it('does not render the street address 3 field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress3: '' }), }); const { container } = component; const streetAddress3 = container.querySelector( - `input[name="properties.0.address.streetAddress3"]`, + `input[name="fileProperties.0.property.address.streetAddress3"]`, ); expect(streetAddress3).toBeNull(); @@ -125,12 +143,12 @@ describe('AddressSubForm component', () => { it('renders the street address 3 field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ streetAddress3: 'street address 3' }), }); const { container } = component; const streetAddress3 = container.querySelector( - `input[name="properties.0.address.streetAddress3"]`, + `input[name="fileProperties.0.property.address.streetAddress3"]`, ); expect(streetAddress3).toBeVisible(); @@ -138,73 +156,92 @@ describe('AddressSubForm component', () => { it('does not render the municipality field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ municipality: '' }), }); const { container } = component; - const municipality = container.querySelector(`input[name="properties.0.address.municipality"]`); + const municipality = container.querySelector( + `input[name="fileProperties.0.property.address.municipality"]`, + ); expect(municipality).toBeNull(); }); it('renders the municipality field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ municipality: 'municipality' }), }); const { container } = component; - const municipality = container.querySelector(`input[name="properties.0.address.municipality"]`); + const municipality = container.querySelector( + `input[name="fileProperties.0.property.address.municipality"]`, + ); expect(municipality).toBeVisible(); }); it('does not render the postal field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ postal: '' }), }); const { container } = component; - const postal = container.querySelector(`input[name="properties.0.address.postal"]`); + const postal = container.querySelector( + `input[name="fileProperties.0.property.address.postal"]`, + ); expect(postal).toBeNull(); }); it('renders the postal field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress({ postal: 'postal' }), }); const { container } = component; - const postal = container.querySelector(`input[name="properties.0.address.postal"]`); + const postal = container.querySelector( + `input[name="fileProperties.0.property.address.postal"]`, + ); expect(postal).toBeVisible(); }); it('does not render the country field if not present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', - lease: defaultLeaseWithPropertyAddress({ country: '' }), + nameSpace: 'fileProperties.0.property.address', + lease: defaultLeaseWithPropertyAddress({ country: toTypeCodeConcept(1) }), }); const { container } = component; - const country = container.querySelector(`input[name="properties.0.address.country"]`); + const country = container.querySelector( + `input[name="fileProperties.0.property.address.country"]`, + ); expect(country).toBeNull(); }); it('renders the country field if present', () => { const { component } = setup({ - nameSpace: 'properties.0.address', - lease: defaultLeaseWithPropertyAddress({ country: 'country' }), + nameSpace: 'fileProperties.0.property.address', + lease: defaultLeaseWithPropertyAddress({ + country: { + id: 1337, + code: 'MD', + description: 'Madagascar', + displayOrder: 1337, + }, + }), }); const { container } = component; - const country = container.querySelector(`input[name="properties.0.address.country"]`); + const country = container.querySelector( + `input[name="fileProperties.0.property.address.country.description"]`, + ); expect(country).toBeVisible(); }); it('renders the warning text if the address is not defined', () => { const { component } = setup({ - nameSpace: 'properties.0.address', + nameSpace: 'fileProperties.0.property.address', lease: defaultLeaseWithPropertyAddress(undefined), }); const { getByText } = component; diff --git a/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.tsx index 73f07def08..aa9be80a78 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/AddressSubForm.tsx @@ -2,9 +2,12 @@ import { getIn, useFormikContext } from 'formik'; import * as React from 'react'; import { Form, Input } from '@/components/common/form'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { ApiGen_Concepts_CodeType } from '@/models/api/generated/ApiGen_Concepts_CodeType'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { exists, isValidString } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; -import { LeaseFormModel } from '../../models'; import { FieldValue } from '../styles'; export interface IAddressSubFormProps { @@ -15,16 +18,35 @@ export interface IAddressSubFormProps { export const AddressSubForm: React.FunctionComponent< React.PropsWithChildren > = ({ disabled, nameSpace }) => { - const formikProps = useFormikContext(); - const address = getIn(formikProps.values, withNameSpace(nameSpace)); - const municipality = getIn(formikProps.values, withNameSpace(nameSpace, 'municipality')); - const postal = getIn(formikProps.values, withNameSpace(nameSpace, 'postal')); - const country = getIn(formikProps.values, withNameSpace(nameSpace, 'country')); - const streetAddress1 = getIn(formikProps.values, withNameSpace(nameSpace, 'streetAddress1')); - const streetAddress2 = getIn(formikProps.values, withNameSpace(nameSpace, 'streetAddress2')); - const streetAddress3 = getIn(formikProps.values, withNameSpace(nameSpace, 'streetAddress3')); - - if (!address) { + const formikProps = useFormikContext(); + + const address: ApiGen_Concepts_Address | null = getIn( + formikProps.values, + withNameSpace(nameSpace), + ); + const municipality: string | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'municipality'), + ); + const postal: string | null = getIn(formikProps.values, withNameSpace(nameSpace, 'postal')); + const country: ApiGen_Concepts_CodeType | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'country'), + ); + const streetAddress1: string | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'streetAddress1'), + ); + const streetAddress2: string | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'streetAddress2'), + ); + const streetAddress3: string | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'streetAddress3'), + ); + + if (!exists(address)) { return ( <> Address: @@ -35,21 +57,25 @@ export const AddressSubForm: React.FunctionComponent< return ( <> - {streetAddress1 && ( + {isValidString(streetAddress1) && ( )} - {streetAddress2 && ( + {isValidString(streetAddress2) && ( )} - {streetAddress3 && ( + {isValidString(streetAddress3) && ( )} - {municipality && ( + {isValidString(municipality) && ( )} - {postal && } - - {country && } + {isValidString(postal) && ( + + )} + + {isValidString(country?.description) && ( + + )} ); }; diff --git a/source/frontend/src/features/leases/detail/LeasePages/__snapshots__/AddressSubForm.test.tsx.snap b/source/frontend/src/features/leases/detail/LeasePages/__snapshots__/AddressSubForm.test.tsx.snap index 623788212e..54d9d5fa77 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/__snapshots__/AddressSubForm.test.tsx.snap +++ b/source/frontend/src/features/leases/detail/LeasePages/__snapshots__/AddressSubForm.test.tsx.snap @@ -11,8 +11,8 @@ exports[`AddressSubForm component renders minimally as expected 1`] = ` > @@ -22,8 +22,8 @@ exports[`AddressSubForm component renders minimally as expected 1`] = ` > @@ -33,8 +33,8 @@ exports[`AddressSubForm component renders minimally as expected 1`] = ` > @@ -44,8 +44,8 @@ exports[`AddressSubForm component renders minimally as expected 1`] = ` > @@ -55,11 +55,22 @@ exports[`AddressSubForm component renders minimally as expected 1`] = ` >
+
+ +
`; diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/DepositsContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/DepositsContainer.tsx index f6351d8705..d3589ec6f8 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/DepositsContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/DepositsContainer.tsx @@ -8,7 +8,9 @@ import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { LeaseFormModel } from '@/features/leases/models'; import { useSecurityDepositRepository } from '@/hooks/repositories/useSecurityDepositRepository'; import { useSecurityDepositReturnRepository } from '@/hooks/repositories/useSecurityDepositReturnRepository'; -import { Api_SecurityDeposit, Api_SecurityDepositReturn } from '@/models/api/SecurityDeposit'; +import { ApiGen_Concepts_SecurityDeposit } from '@/models/api/generated/ApiGen_Concepts_SecurityDeposit'; +import { ApiGen_Concepts_SecurityDepositReturn } from '@/models/api/generated/ApiGen_Concepts_SecurityDepositReturn'; +import { exists, isValidId } from '@/utils/utils'; import DepositNotes from './components/DepositNotes/DepositNotes'; import DepositsReceivedContainer from './components/DepositsReceivedContainer/DepositsReceivedContainer'; @@ -46,12 +48,14 @@ export const DepositsContainer: React.FunctionComponent< deleteSecurityDepositReturn: { execute: deleteSecurityDepositReturn }, } = useSecurityDepositReturnRepository(); - const securityDeposits: Api_SecurityDeposit[] = securityDepositsResponse ?? []; + const securityDeposits: ApiGen_Concepts_SecurityDeposit[] = securityDepositsResponse ?? []; useEffect(() => { lease?.id && getSecurityDeposits(lease.id); }, [lease, getSecurityDeposits]); - const depositReturns: Api_SecurityDepositReturn[] = - securityDeposits?.flatMap((x: Api_SecurityDeposit) => x.depositReturns) ?? []; + const depositReturns: ApiGen_Concepts_SecurityDepositReturn[] = + securityDeposits + ?.flatMap((x: ApiGen_Concepts_SecurityDeposit) => x.depositReturns) + .filter(exists) ?? []; const [editNotes, setEditNotes] = useState(false); const [showDepositEditModal, setShowEditModal] = useState(false); @@ -78,7 +82,7 @@ export const DepositsContainer: React.FunctionComponent< }; const onEditDeposit = (id: number) => { - var deposit = securityDeposits.find((x: Api_SecurityDeposit) => x.id === id); + var deposit = securityDeposits.find((x: ApiGen_Concepts_SecurityDeposit) => x.id === id); if (deposit) { setEditDepositValue(FormLeaseDeposit.fromApi(deposit)); setShowEditModal(true); @@ -86,7 +90,7 @@ export const DepositsContainer: React.FunctionComponent< }; const onDeleteDeposit = (id: number) => { - var deposit = securityDeposits.find((x: Api_SecurityDeposit) => x.id === id); + var deposit = securityDeposits.find((x: ApiGen_Concepts_SecurityDeposit) => x.id === id); if (deposit) { setDepositToDelete(FormLeaseDeposit.fromApi(deposit)); setDeleteModalWarning(true); @@ -94,7 +98,7 @@ export const DepositsContainer: React.FunctionComponent< }; const onReturnDeposit = (id: number) => { - var deposit = securityDeposits.find((x: Api_SecurityDeposit) => x.id === id); + var deposit = securityDeposits.find((x: ApiGen_Concepts_SecurityDeposit) => x.id === id); if (deposit) { setEditReturnValue(FormLeaseDepositReturn.createEmpty(deposit)); setShowReturnEditModal(true); @@ -126,11 +130,11 @@ export const DepositsContainer: React.FunctionComponent< * @param depositForm */ const onSaveDeposit = async (depositForm: FormLeaseDeposit) => { - if (lease && lease.id) { + if (exists(lease) && isValidId(lease.id)) { const updatedSecurityDeposit = depositForm.id ? await updateSecurityDeposit(lease.id, depositForm.toApi()) : await addSecurityDeposit(lease.id, depositForm.toApi()); - if (!!updatedSecurityDeposit?.id) { + if (isValidId(updatedSecurityDeposit?.id)) { setEditDepositValue(FormLeaseDeposit.createEmpty(lease.id)); setShowEditModal(false); getSecurityDeposits(lease.id); @@ -159,7 +163,7 @@ export const DepositsContainer: React.FunctionComponent< var deposit = depositReturns.find(x => x.id === id); if (deposit) { var parentDeposit = securityDeposits.find( - (x: Api_SecurityDeposit) => x.id === deposit?.parentDepositId, + (x: ApiGen_Concepts_SecurityDeposit) => x.id === deposit?.parentDepositId, ); if (parentDeposit) { setDepositReturnToDelete(FormLeaseDepositReturn.fromApi(deposit, parentDeposit)); @@ -175,12 +179,12 @@ export const DepositsContainer: React.FunctionComponent< * @param returnDepositForm */ const onSaveReturnDeposit = async (returnDepositForm: FormLeaseDepositReturn) => { - if (lease && lease.id) { - let request: Api_SecurityDepositReturn = returnDepositForm.toApi(); + if (exists(lease) && isValidId(lease.id)) { + let request: ApiGen_Concepts_SecurityDepositReturn = returnDepositForm.toApi(); const securityDepositReturn = request?.id ? await updateSecurityDepositReturn(lease.id, request) : await addSecurityDepositReturn(lease.id, request); - if (!!securityDepositReturn?.id) { + if (isValidId(securityDepositReturn?.id)) { setDepositReturnToDelete(undefined); setShowReturnEditModal(false); getSecurityDeposits(lease.id); diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.test.tsx index befe292b75..d13c08b779 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.test.tsx @@ -84,7 +84,7 @@ describe('DepositsReceivedContainer component', () => { const dataRow = findFirstRow() as HTMLElement; expect(dataRow).not.toBeNull(); - expect(findCell(dataRow, 0)?.textContent).toBe(deposit.depositType.description ?? ''); + expect(findCell(dataRow, 0)?.textContent).toBe(deposit.depositType?.description ?? ''); expect(findCell(dataRow, 1)?.textContent).toBe(deposit.description); expect(findCell(dataRow, 2)?.textContent).toBe(formatMoney(deposit.amountPaid)); expect(findCell(dataRow, 3)?.textContent).toBe(prettyFormatDate(deposit.depositDateOnly)); diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.tsx index ad1384a1d0..6c969fb7b1 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/DepositsReceivedContainer.tsx @@ -3,12 +3,12 @@ import { Section } from '@/components/common/Section/Section'; import { Table } from '@/components/Table'; import Claims from '@/constants/claims'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_SecurityDeposit } from '@/models/api/SecurityDeposit'; +import { ApiGen_Concepts_SecurityDeposit } from '@/models/api/generated/ApiGen_Concepts_SecurityDeposit'; import { DepositListEntry, getColumns } from './columns'; export interface IDepositsReceivedContainerProps { - securityDeposits: Api_SecurityDeposit[]; + securityDeposits: ApiGen_Concepts_SecurityDeposit[]; onAdd: () => void; onEdit: (id: number) => void; onDelete: (id: number) => void; diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/columns.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/columns.tsx index 7034243993..5733f84194 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/columns.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReceivedContainer/columns.tsx @@ -11,9 +11,10 @@ import TooltipIcon from '@/components/common/TooltipIcon'; import { ColumnWithProps, renderDate, renderMoney } from '@/components/Table'; import Claims from '@/constants/claims'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_Contact } from '@/models/api/Contact'; -import { Api_SecurityDeposit } from '@/models/api/SecurityDeposit'; +import { ApiGen_Concepts_Contact } from '@/models/api/generated/ApiGen_Concepts_Contact'; +import { ApiGen_Concepts_SecurityDeposit } from '@/models/api/generated/ApiGen_Concepts_SecurityDeposit'; import { formatNames } from '@/utils/personUtils'; +import { exists, isValidIsoDateTime } from '@/utils/utils'; export class DepositListEntry { public id: number; @@ -21,34 +22,38 @@ export class DepositListEntry { public depositDescription: string; public amountPaid: number; public paidDate: string; - public contactHolder?: Api_Contact; + public contactHolder?: ApiGen_Concepts_Contact; public depositReturnCount: number; - public constructor(baseDeposit: Api_SecurityDeposit) { + public constructor(baseDeposit: ApiGen_Concepts_SecurityDeposit) { this.id = baseDeposit.id || -1; - if (baseDeposit.depositType.id === 'OTHER') { + if (baseDeposit.depositType?.id === 'OTHER') { this.depositTypeDescription = (baseDeposit.otherTypeDescription || '') + ' (Other)'; } else { - this.depositTypeDescription = baseDeposit.depositType.description || ''; + this.depositTypeDescription = baseDeposit.depositType?.description || ''; } - this.depositDescription = baseDeposit.description; + this.depositDescription = baseDeposit.description ?? ''; this.amountPaid = baseDeposit.amountPaid; - this.paidDate = baseDeposit.depositDateOnly || ''; + this.paidDate = isValidIsoDateTime(baseDeposit.depositDateOnly) + ? baseDeposit.depositDateOnly + : ''; this.contactHolder = baseDeposit.contactHolder || undefined; - this.depositReturnCount = baseDeposit.depositReturns.length; + this.depositReturnCount = baseDeposit.depositReturns?.length ?? 0; } } -function renderHolder({ row: { original } }: CellProps) { - if (original.contactHolder !== undefined && original.contactHolder !== null) { +function renderHolder({ + row: { original }, +}: CellProps) { + if (exists(original.contactHolder)) { const holder = original.contactHolder; - if (holder.person !== undefined && holder.person !== null) { + if (exists(holder.person)) { return ( {formatNames([holder.person.firstName, holder.person.middleNames, holder.person.surname])} ); - } else if (holder.organization !== undefined && holder.organization !== null) { + } else if (exists(holder.organization)) { return {holder.organization.name}; } } diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.test.tsx index 65127a6af1..3957480636 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.test.tsx @@ -1,6 +1,8 @@ import { useKeycloak } from '@react-keycloak/web'; +import { getEmptyPerson } from '@/mocks/contacts.mock'; import { getMockDepositReturns, getMockDeposits } from '@/mocks/deposits.mock'; +import { getEmptyOrganization } from '@/mocks/organization.mock'; import { formatMoney, prettyFormatDate } from '@/utils'; import { getAllByRole as getAllByRoleBase, render, RenderOptions } from '@/utils/test-utils'; @@ -82,7 +84,7 @@ describe('DepositsReturnedContainer component', () => { expect(dataRow).not.toBeNull(); expect(findCell(dataRow, 0)?.textContent).toBe( - getMockDeposits()[0].depositType.description ?? '', + getMockDeposits()[0].depositType?.description ?? '', ); expect(findCell(dataRow, 1)?.textContent).toBe(prettyFormatDate(depositReturn.terminationDate)); expect(findCell(dataRow, 2)?.textContent).toBe(formatMoney(getMockDeposits()[1].amountPaid)); @@ -102,11 +104,19 @@ describe('DepositsReturnedContainer component', () => { depositReturns: [ { ...depositReturn, - contactHolder: { id: 'O1', organization: { name: 'test organization' } }, + contactHolder: { + id: 'O1', + organization: { ...getEmptyOrganization(), name: 'test organization' }, + person: null, + }, }, { ...depositReturn, - contactHolder: { id: 'P1', person: { firstName: 'test', surname: 'person' } }, + contactHolder: { + id: 'P1', + person: { ...getEmptyPerson(), firstName: 'test', surname: 'person' }, + organization: null, + }, }, ], onEdit: mockCallback, diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.tsx index f0c8a4b284..769e7ab66f 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/DepositsReturnedContainer.tsx @@ -1,12 +1,13 @@ import { Section } from '@/components/common/Section/Section'; import { Table } from '@/components/Table'; -import { Api_SecurityDeposit, Api_SecurityDepositReturn } from '@/models/api/SecurityDeposit'; +import { ApiGen_Concepts_SecurityDeposit } from '@/models/api/generated/ApiGen_Concepts_SecurityDeposit'; +import { ApiGen_Concepts_SecurityDepositReturn } from '@/models/api/generated/ApiGen_Concepts_SecurityDepositReturn'; import { getColumns, ReturnListEntry } from './columns'; export interface IDepositsReturnedContainerProps { - securityDeposits: Api_SecurityDeposit[]; - depositReturns: Api_SecurityDepositReturn[]; + securityDeposits: ApiGen_Concepts_SecurityDeposit[]; + depositReturns: ApiGen_Concepts_SecurityDepositReturn[]; onEdit: (id: number) => void; onDelete: (id: number) => void; } @@ -16,7 +17,7 @@ const DepositsReturnedContainer: React.FC< > = ({ securityDeposits, depositReturns, onEdit, onDelete }) => { const columns = getColumns({ onEdit, onDelete }); const dataSource = depositReturns.reduce( - (accumulator: ReturnListEntry[], returnDeposit: Api_SecurityDepositReturn) => { + (accumulator: ReturnListEntry[], returnDeposit: ApiGen_Concepts_SecurityDepositReturn) => { var parentDeposit = securityDeposits.find(r => r?.id === returnDeposit?.parentDepositId); if (parentDeposit) { accumulator.push(new ReturnListEntry(returnDeposit, parentDeposit)); diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/columns.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/columns.tsx index 5620bf985b..8047e93f2a 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/columns.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/components/DepositsReturnedContainer/columns.tsx @@ -10,8 +10,10 @@ import { InlineFlexDiv } from '@/components/common/styles'; import { ColumnWithProps, renderDate, renderMoney } from '@/components/Table'; import Claims from '@/constants/claims'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_Contact } from '@/models/api/Contact'; -import { Api_SecurityDeposit, Api_SecurityDepositReturn } from '@/models/api/SecurityDeposit'; +import { ApiGen_Concepts_Contact } from '@/models/api/generated/ApiGen_Concepts_Contact'; +import { ApiGen_Concepts_SecurityDeposit } from '@/models/api/generated/ApiGen_Concepts_SecurityDeposit'; +import { ApiGen_Concepts_SecurityDepositReturn } from '@/models/api/generated/ApiGen_Concepts_SecurityDepositReturn'; +import { isValidIsoDateTime } from '@/utils'; import { formatNames } from '@/utils/personUtils'; export class ReturnListEntry { @@ -23,27 +25,34 @@ export class ReturnListEntry { public returnAmount: number; public interestPaid: number; public returnDate: string; - public contactHolder?: Api_Contact; + public contactHolder?: ApiGen_Concepts_Contact; - public constructor(baseDeposit: Api_SecurityDepositReturn, parentDeposit: Api_SecurityDeposit) { + public constructor( + baseDeposit: ApiGen_Concepts_SecurityDepositReturn, + parentDeposit: ApiGen_Concepts_SecurityDeposit, + ) { this.id = baseDeposit.id || -1; - if (parentDeposit.depositType.id === 'OTHER') { + if (parentDeposit.depositType?.id === 'OTHER') { this.depositTypeDescription = (parentDeposit.otherTypeDescription || '') + ' (Other)'; } else { - this.depositTypeDescription = parentDeposit.depositType.description || ''; + this.depositTypeDescription = parentDeposit.depositType?.description || ''; } - this.terminationDate = baseDeposit.terminationDate || ''; + this.terminationDate = isValidIsoDateTime(baseDeposit.terminationDate) + ? baseDeposit.terminationDate + : ''; this.depositAmount = parentDeposit.amountPaid; this.claimsAgainst = baseDeposit.claimsAgainst || 0; this.returnAmount = baseDeposit.returnAmount || 0; this.interestPaid = baseDeposit.interestPaid || 0; - this.returnDate = baseDeposit.returnDate || ''; + this.returnDate = isValidIsoDateTime(baseDeposit.returnDate) ? baseDeposit.returnDate : ''; this.contactHolder = baseDeposit.contactHolder || undefined; } } -function renderHolder({ row: { original } }: CellProps) { +function renderHolder({ + row: { original }, +}: CellProps) { if (!!original.contactHolder) { const holder = original.contactHolder; if (!!holder.person) { diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositForm.tsx index a717e0e784..5a298d2b7b 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositForm.tsx @@ -12,6 +12,7 @@ import { ContactManagerModal } from '@/components/contact/ContactManagerModal'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; import { IContactSearchResult } from '@/interfaces'; +import { isValidString } from '@/utils'; import { FormLeaseDeposit } from '../../models/FormLeaseDeposit'; import { ReceivedDepositYupSchema } from './ReceivedDepositYupSchema'; @@ -55,7 +56,7 @@ export const ReceivedDepositForm: React.FunctionComponent< options={depositTypeOptions} onChange={() => { let depositTypeCode = formikProps.values?.depositTypeCode; - if (!!depositTypeCode && depositTypeCode !== 'OTHER') { + if (isValidString(depositTypeCode) && depositTypeCode !== 'OTHER') { formikProps.setFieldValue('otherTypeDescription', ''); } }} diff --git a/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositModal.tsx b/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositModal.tsx index f610d24f86..b9ec63a013 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositModal.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/deposits/modal/receivedDepositModal/ReceivedDepositModal.tsx @@ -4,6 +4,7 @@ import { useRef } from 'react'; import { FaDollarSign } from 'react-icons/fa'; import GenericModal, { ModalSize } from '@/components/common/GenericModal'; +import { isValidId } from '@/utils'; import { FormLeaseDeposit } from '../../models/FormLeaseDeposit'; import { ReceivedDepositForm } from './ReceivedDepositForm'; @@ -23,7 +24,7 @@ export const ReceivedDepositModal: React.FunctionComponent< React.PropsWithChildren > = ({ initialValues, display, onCancel, onSave }) => { const formikRef = useRef>(null); - const modalTitle = initialValues.id === undefined ? 'Add a Deposit' : 'Edit Deposit'; + const modalTitle = !isValidId(initialValues.id) ? 'Add a Deposit' : 'Edit Deposit'; return ( > = ({ initialValues, display, onCancel, onSave }) => { const formikRef = useRef>(null); - const modalTitle = initialValues?.id === undefined ? 'Return a Deposit' : 'Edit a Deposit Return'; + const modalTitle = !isValidId(initialValues?.id) ? 'Return a Deposit' : 'Edit a Deposit Return'; return ( { const { component } = setup({ lease: { ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }], + properties: [ + { + ...mockParcel, + areaUnitTypeCode: 'test', + landArea: '123', + leaseId: null, + }, + ], }, }); expect(component.asFragment()).toMatchSnapshot(); @@ -45,7 +52,14 @@ describe('DetailAdministration component', () => { const { component } = setup({ lease: { ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }], + properties: [ + { + ...mockParcel, + areaUnitTypeCode: 'test', + landArea: '123', + leaseId: null, + }, + ], amount: 1, description: 'a test description', programName: 'A program', diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailAdministration.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailAdministration.tsx index b77896f7f9..9617a690df 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailAdministration.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailAdministration.tsx @@ -5,7 +5,7 @@ import styled from 'styled-components'; import { Input } from '@/components/common/form'; import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { prettyFormatDate } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; export interface IDetailAdministrationProps { @@ -20,7 +20,7 @@ export interface IDetailAdministrationProps { export const DetailAdministration: React.FunctionComponent< React.PropsWithChildren > = ({ nameSpace, disabled }) => { - const { values } = useFormikContext(); + const { values } = useFormikContext(); const responsibilityDate = getIn(values, withNameSpace(nameSpace, 'responsibilityEffectiveDate')); return ( <> diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailConsultation.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailConsultation.tsx index 55c2d4b524..0bca7a5391 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailConsultation.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailConsultation.tsx @@ -4,7 +4,10 @@ import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_Lease, Api_LeaseConsultation } from '@/models/api/Lease'; +import { ApiGen_Concepts_ConsultationLease } from '@/models/api/generated/ApiGen_Concepts_ConsultationLease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { exists } from '@/utils/utils'; export interface IDetailConsultationProps { nameSpace?: string; @@ -17,31 +20,38 @@ export interface IDetailConsultationProps { export const DetailConsultation: React.FunctionComponent< React.PropsWithChildren > = ({ nameSpace }) => { - const { values, setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); const { getByType } = useLookupCodeHelpers(); const consultationTypes = getByType(API.CONSULTATION_TYPES); // Not all consultations might be coming from the backend. Add the ones missing. - if (values.consultations.length !== consultationTypes.length) { - const newConsultations: Api_LeaseConsultation[] = []; + if (values.consultations?.length !== consultationTypes.length) { + const newConsultations: ApiGen_Concepts_ConsultationLease[] = []; consultationTypes.forEach(consultationType => { - const newConsultation: Api_LeaseConsultation = { + const newConsultation: ApiGen_Concepts_ConsultationLease = { id: 0, parentLeaseId: values.id || 0, consultationType: { id: consultationType.id.toString(), description: consultationType.name, + displayOrder: null, + isDisabled: false, + }, + consultationStatusType: { + id: 'UNKNOWN', + description: 'Unknown', + displayOrder: null, + isDisabled: false, }, - consultationStatusType: { id: 'UNKNOWN', description: 'Unknown' }, otherDescription: null, - rowVersion: 0, + ...getEmptyBaseAudit(0), }; // If there is a consultation with the type, set the status to the existing one - let existingConsultation = values.consultations.find( - consultation => consultation.consultationType === consultationType.id, + let existingConsultation = values.consultations?.find( + consultation => consultation.consultationType?.id === consultationType.id, ); if (existingConsultation !== undefined) { newConsultation.id = existingConsultation.id; @@ -53,9 +63,9 @@ export const DetailConsultation: React.FunctionComponent< setFieldValue('consultations', newConsultations); } - const generateLabel = (consultation: Api_LeaseConsultation): string => { + const generateLabel = (consultation: ApiGen_Concepts_ConsultationLease): string => { var label = consultation.consultationType?.description || ''; - if (consultation.otherDescription !== undefined && consultation.otherDescription !== null) { + if (exists(consultation.otherDescription)) { label += ' | ' + consultation.otherDescription; } @@ -64,7 +74,7 @@ export const DetailConsultation: React.FunctionComponent< return (
- {values.consultations.map((consultation, index) => ( + {values.consultations?.map((consultation, index) => ( { const { component } = setup({ lease: { ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }], + properties: [ + { + ...mockParcel, + areaUnitTypeCode: 'test', + landArea: '123', + leaseId: null, + }, + ], }, }); expect(component.asFragment()).toMatchSnapshot(); @@ -46,7 +53,14 @@ describe('DetailDocumentation component', () => { const { component } = setup({ lease: { ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }], + properties: [ + { + ...mockParcel, + areaUnitTypeCode: 'test', + landArea: '123', + leaseId: null, + }, + ], amount: 1, description: 'a test description', programName: 'A program', diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailDocumentation.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailDocumentation.tsx index 2fecddf01a..59f2ac8d45 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailDocumentation.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailDocumentation.tsx @@ -5,7 +5,7 @@ import { Input } from '@/components/common/form'; import { YesNoSelect } from '@/components/common/form/YesNoSelect'; import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; -import { LeaseFormModel } from '@/features/leases/models'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { withNameSpace } from '@/utils/formUtils'; export interface IDetailDocumentationProps { @@ -20,7 +20,7 @@ export interface IDetailDocumentationProps { export const DetailDocumentation: React.FunctionComponent< React.PropsWithChildren > = ({ nameSpace, disabled }) => { - const formikProps = useFormikContext(); + const formikProps = useFormikContext(); const note = getIn(formikProps.values, withNameSpace(nameSpace, 'note')); const documentationReference = getIn( formikProps.values, diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTermInformation.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTermInformation.tsx index 7d55ba5342..78643f6e1d 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTermInformation.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTermInformation.tsx @@ -5,8 +5,8 @@ import styled from 'styled-components'; import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; -import { Api_Lease } from '@/models/api/Lease'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; import { withNameSpace } from '@/utils/formUtils'; import { DetailTermInformationBox } from './DetailTermInformationBox'; @@ -22,12 +22,12 @@ export interface IDetailTermInformationProps { export const DetailTermInformation: React.FunctionComponent< React.PropsWithChildren > = ({ nameSpace }) => { - const { values } = useFormikContext(); + const { values } = useFormikContext(); const startDate = getIn(values, withNameSpace(nameSpace, 'startDate')); const expiryDate = getIn(values, withNameSpace(nameSpace, 'expiryDate')); const terms = getIn(values, withNameSpace(nameSpace, 'terms')); const currentTerm = terms.find( - (term: Api_LeaseTerm) => + (term: ApiGen_Concepts_LeaseTerm) => moment().isSameOrBefore(moment(term.expiryDate), 'day') || (moment().isSameOrAfter(moment(term.startDate), 'day') && term.expiryDate === null), ); diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.test.tsx index 30ca4d9544..abe0fd9339 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.test.tsx @@ -56,7 +56,12 @@ describe('DetailTermInformation component', () => { startDate: '2020-01-01T18:00', expiryDate: '2022-01-01T18:00', renewalDate: '2021-01-01T18:00', - statusTypeCode: { id: 'EX', description: 'exercised', isDisabled: false }, + statusTypeCode: { + id: 'EX', + description: 'exercised', + isDisabled: false, + displayOrder: null, + }, }, ], }, @@ -85,7 +90,12 @@ describe('DetailTermInformation component', () => { startDate: '', expiryDate: '', renewalDate: '', - statusTypeCode: { id: 'EX', description: 'exercised', isDisabled: false }, + statusTypeCode: { + id: 'EX', + description: 'exercised', + isDisabled: false, + displayOrder: null, + }, }, ], }, diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.tsx index 9e3b4ad3c6..4f6a07bb41 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/DetailTerms.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { InlineInput } from '@/components/common/form/styles'; import * as Styled from '@/features/leases/detail/styles'; import { LeaseFormModel } from '@/features/leases/models'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; import { withNameSpace } from '@/utils/formUtils'; import { leaseTermColumns } from './columns'; @@ -26,7 +26,7 @@ export const DetailTerms: React.FunctionComponent(); const { values } = formikProps; const terms = getIn(values, withNameSpace(nameSpace, 'terms')); - const currentTerm = terms.find((term: Api_LeaseTerm) => + const currentTerm = terms.find((term: ApiGen_Concepts_LeaseTerm) => moment().isSameOrBefore(moment(term.expiryDate), 'day'), ); @@ -47,7 +47,7 @@ export const DetailTerms: React.FunctionComponent - + name="leaseTermsTable" data={terms || []} columns={leaseTermColumns} diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/LeaseDetailsForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/LeaseDetailsForm.tsx index 7437919a9a..ccf9460759 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/LeaseDetailsForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/LeaseDetailsForm.tsx @@ -4,7 +4,7 @@ import React from 'react'; import styled from 'styled-components'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import DetailAdministration from './DetailAdministration'; import DetailConsultation from './DetailConsultation'; @@ -20,7 +20,7 @@ export const LeaseDetailsForm: React.FunctionComponent< const { lease } = React.useContext(LeaseStateContext); return ( diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.test.tsx index e5e13f10f4..c33d9df2e9 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.test.tsx @@ -3,9 +3,11 @@ import { createMemoryHistory } from 'history'; import { noop } from 'lodash'; import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext'; -import { LeaseFormModel } from '@/features/leases/models'; -import { mockParcel } from '@/mocks/filterData.mock'; +import { mockLeaseProperty } from '@/mocks/filterData.mock'; import { mapMachineBaseMock } from '@/mocks/mapFSM.mock'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { getEmptyLease } from '@/models/defaultInitializers'; +import { toTypeCode } from '@/utils/formUtils'; import { render, RenderOptions } from '@/utils/test-utils'; import PropertiesInformation, { IPropertiesInformationProps } from './PropertiesInformation'; @@ -16,11 +18,12 @@ jest.mock('@/components/common/mapFSM/MapStateMachineContext'); describe('PropertiesInformation component', () => { const setup = ( - renderOptions: RenderOptions & IPropertiesInformationProps & { lease?: LeaseFormModel } = {}, + renderOptions: RenderOptions & + IPropertiesInformationProps & { lease?: ApiGen_Concepts_Lease } = {}, ) => { // render component under test const component = render( - + , { @@ -42,8 +45,16 @@ describe('PropertiesInformation component', () => { it('renders as expected', () => { const { component } = setup({ lease: { - ...new LeaseFormModel(), - properties: [{ ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }], + ...getEmptyLease(), + fileProperties: [ + { + ...mockLeaseProperty(), + areaUnitType: toTypeCode('test'), + leaseArea: 123, + file: null, + fileId: 0, + }, + ], }, }); expect(component.asFragment()).toMatchSnapshot(); @@ -51,10 +62,22 @@ describe('PropertiesInformation component', () => { it('renders one Property Information section per property', () => { const { component } = setup({ lease: { - ...new LeaseFormModel(), - properties: [ - { ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }, - { ...mockParcel, areaUnitTypeCode: 'test', landArea: '123', leaseId: null }, + ...getEmptyLease(), + fileProperties: [ + { + ...mockLeaseProperty(), + areaUnitType: toTypeCode('test'), + leaseArea: 123, + file: null, + fileId: 0, + }, + { + ...mockLeaseProperty(), + areaUnitType: toTypeCode('test'), + leaseArea: 123, + file: null, + fileId: 0, + }, ], }, }); @@ -66,7 +89,7 @@ describe('PropertiesInformation component', () => { it('renders no property information section if there are no properties', () => { const { component } = setup({ - lease: { ...new LeaseFormModel(), properties: [] }, + lease: { ...getEmptyLease(), fileProperties: [] }, }); const { queryByText } = component; const propertyHeader = queryByText('Property Information'); diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.tsx index 7de69d6a5d..0e869f6131 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/PropertiesInformation.tsx @@ -5,7 +5,9 @@ import * as React from 'react'; import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext'; import { Section } from '@/components/common/Section/Section'; import { PropertyInformation } from '@/features/leases'; -import { FormLeaseProperty, LeaseFormModel } from '@/features/leases/models'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_PropertyLease } from '@/models/api/generated/ApiGen_Concepts_PropertyLease'; +import { exists } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; export interface IPropertiesInformationProps { @@ -21,10 +23,10 @@ export interface IPropertiesInformationProps { export const PropertiesInformation: React.FunctionComponent< React.PropsWithChildren > = ({ nameSpace, disabled, hideAddress }) => { - const { values } = useFormikContext(); + const { values } = useFormikContext(); - const properties: FormLeaseProperty[] = React.useMemo(() => { - return getIn(values, withNameSpace(nameSpace, 'properties')) ?? []; + const properties: ApiGen_Concepts_PropertyLease[] = React.useMemo(() => { + return getIn(values, withNameSpace(nameSpace, 'fileProperties')) ?? []; }, [values, nameSpace]); const { setFilePropertyLocations } = useMapStateMachine(); @@ -33,13 +35,13 @@ export const PropertiesInformation: React.FunctionComponent< return ( properties .map(x => { - if (x.property?.latitude !== undefined && x.property?.longitude !== undefined) { - return { lat: x.property?.latitude, lng: x.property?.longitude }; + if (exists(x.property?.latitude) && exists(x.property?.longitude)) { + return { lat: x.property!.latitude, lng: x.property!.longitude }; } else { return undefined; } }) - .filter((x): x is LatLngLiteral => x !== undefined) || [] + .filter(exists) || [] ); }, [properties]); @@ -50,12 +52,12 @@ export const PropertiesInformation: React.FunctionComponent< return properties?.length ? (
- properties.map((property: FormLeaseProperty, index) => ( + properties.map((property: ApiGen_Concepts_PropertyLease, index) => ( diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.test.tsx index 46d2eb98cc..a829c5f110 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.test.tsx @@ -4,7 +4,9 @@ import { noop } from 'lodash'; import { mockParcel } from '@/mocks/filterData.mock'; import { getMockApiLease } from '@/mocks/lease.mock'; -import { Api_Lease } from '@/models/api/Lease'; +import { getEmptyPropertyLease } from '@/mocks/properties.mock'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { render, RenderOptions } from '@/utils/test-utils'; import PropertyInformation, { IPropertyInformationProps } from './PropertyInformation'; @@ -13,8 +15,8 @@ const history = createMemoryHistory(); describe('PropertyInformation component', () => { const setup = ( - renderOptions: RenderOptions & IPropertyInformationProps & { lease?: Api_Lease } = { - nameSpace: 'properties', + renderOptions: RenderOptions & IPropertyInformationProps & { lease?: ApiGen_Concepts_Lease } = { + nameSpace: 'fileProperties', }, ) => { // render component under test @@ -37,16 +39,17 @@ describe('PropertyInformation component', () => { }; it('renders minimally as expected', () => { const { component } = setup({ - nameSpace: 'properties.0', + nameSpace: 'fileProperties.0', lease: { ...getMockApiLease(), - properties: [ + fileProperties: [ { + ...getEmptyPropertyLease(), ...mockParcel, - areaUnitType: { id: 'test' }, + areaUnitType: toTypeCodeNullable('test'), leaseArea: 123, - leaseId: null, - lease: null, + fileId: 0, + file: null, }, ], }, @@ -56,16 +59,17 @@ describe('PropertyInformation component', () => { it('renders a complete lease as expected', () => { const { component } = setup({ - nameSpace: 'properties.0', + nameSpace: 'fileProperties.0', lease: { ...getMockApiLease(), - properties: [ + fileProperties: [ { + ...getEmptyPropertyLease(), ...mockParcel, - areaUnitType: { id: 'test' }, + areaUnitType: toTypeCodeNullable('test'), leaseArea: 123, - leaseId: null, - lease: null, + fileId: 0, + file: null, }, ], amount: 1, @@ -84,11 +88,18 @@ describe('PropertyInformation component', () => { it('does not render the area if the value is not set', () => { const { component } = setup({ - nameSpace: 'properties.0', + nameSpace: 'fileProperties.0', lease: { ...getMockApiLease(), - properties: [ - { ...mockParcel, leaseArea: 1, areaUnitType: { id: 'test' }, leaseId: null, lease: null }, + fileProperties: [ + { + ...getEmptyPropertyLease(), + ...mockParcel, + leaseArea: 1, + areaUnitType: toTypeCodeNullable('test'), + fileId: 0, + file: null, + }, ], amount: 1, description: 'a test description', @@ -106,11 +117,18 @@ describe('PropertyInformation component', () => { it('will render the land area if no area unit is set', () => { const { component } = setup({ - nameSpace: 'properties.0', + nameSpace: 'fileProperties.0', lease: { ...getMockApiLease(), - properties: [ - { ...mockParcel, leaseArea: 123, areaUnitType: null, leaseId: null, lease: null }, + fileProperties: [ + { + ...getEmptyPropertyLease(), + ...mockParcel, + leaseArea: 123, + areaUnitType: null, + fileId: 0, + file: null, + }, ], amount: 1, description: 'a test description', diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.tsx index 4f96c316d4..12d4ac8f85 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/PropertyInformation.tsx @@ -4,8 +4,9 @@ import styled from 'styled-components'; import { Input } from '@/components/common/form'; import { SectionField } from '@/components/common/Section/SectionField'; -import { Api_Lease } from '@/models/api/Lease'; -import { formatNumber, pidFormatter } from '@/utils'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { formatNumber, isValidString, pidFormatter } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; import AddressSubForm from '../AddressSubForm'; @@ -23,14 +24,21 @@ export interface IPropertyInformationProps { export const PropertyInformation: React.FunctionComponent< React.PropsWithChildren> > = ({ nameSpace, disabled, hideAddress }) => { - const formikProps = useFormikContext(); - const landArea = getIn(formikProps.values, withNameSpace(nameSpace, 'leaseArea')); - const areaUnitType = getIn(formikProps.values, withNameSpace(nameSpace, 'areaUnitType')); + const formikProps = useFormikContext(); + + const landArea: number | null = getIn(formikProps.values, withNameSpace(nameSpace, 'leaseArea')); + + const areaUnitType: ApiGen_Base_CodeType | null = getIn( + formikProps.values, + withNameSpace(nameSpace, 'areaUnitType'), + ); + const legalDescription = getIn( formikProps.values, withNameSpace(nameSpace, 'property.landLegalDescription'), ); const pid = getIn(formikProps.values, withNameSpace(nameSpace, 'property.pid')); + const pidText = pid ? `PID: ${pidFormatter(pid)}` : ''; return ( @@ -41,8 +49,8 @@ export const PropertyInformation: React.FunctionComponent< - {formatNumber(landArea, 2, 2)}{' '} - {areaUnitType?.description ? ( + {formatNumber(landArea || 0, 2, 2)}{' '} + {isValidString(areaUnitType?.description) ? ( `${areaUnitType?.description}.` ) : ( <> diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.test.tsx index a082df3f07..2743c2fde2 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.test.tsx @@ -13,9 +13,12 @@ import { useLeaseDetail } from '@/features/leases/hooks/useLeaseDetail'; import { getDefaultFormLease } from '@/features/leases/models'; import { getMockApiLease } from '@/mocks/lease.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_Lease, defaultApiLease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { EpochIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { defaultApiLease, getEmptyLease } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { renderAsync, screen } from '@/utils/test-utils'; import UpdateLeaseContainer, { UpdateLeaseContainerProps } from './UpdateLeaseContainer'; @@ -66,7 +69,7 @@ describe('Update lease container component', () => { }; beforeEach(() => { - mockAxios.onGet().reply(200, { id: 1, ...defaultApiLease }); + mockAxios.onGet().reply(200, { ...defaultApiLease(), id: 1 }); mockAxios.resetHistory(); }); it('renders as expected', async () => { @@ -82,7 +85,7 @@ describe('Update lease container component', () => { viewProps.onSubmit({ ...getDefaultFormLease(), purposeTypeCode: 'BCFERRIES' }), ); - expect(JSON.parse(mockAxios.history.put[0].data)).toEqual(leaseData); + expect(JSON.parse(mockAxios.history.put[0].data)).toEqual(expectedLease); }); it('triggers the confirm popup', async () => { @@ -93,7 +96,7 @@ describe('Update lease container component', () => { viewProps.onSubmit({ ...getDefaultFormLease(), purposeTypeCode: 'BCFERRIES' }), ); - expect(JSON.parse(mockAxios.history.put[0].data)).toEqual(leaseData); + expect(JSON.parse(mockAxios.history.put[0].data)).toEqual(expectedLease); }); it('clicking on the save anyways popup saves the form', async () => { @@ -109,22 +112,23 @@ describe('Update lease container component', () => { const button = await screen.findByText('Yes'); await act(async () => userEvent.click(button)); - expect(JSON.parse(mockAxios.history.put[1].data)).toEqual(leaseData); + expect(JSON.parse(mockAxios.history.put[1].data)).toEqual(expectedLease); }); }); -const leaseData: Api_Lease = { - startDate: '', +const expectedLease: ApiGen_Concepts_Lease = { + ...getEmptyLease(), + startDate: EpochIsoDateTime, amount: 0, - paymentReceivableType: { id: 'RCVBL' }, - purposeType: { id: 'BCFERRIES' }, - statusType: { id: 'DRAFT' }, + paymentReceivableType: toTypeCodeNullable('RCVBL'), + purposeType: toTypeCodeNullable('BCFERRIES'), + fileStatusTypeCode: toTypeCodeNullable('DRAFT'), type: null, region: null, programType: null, returnNotes: '', motiName: '', - properties: [], + fileProperties: [], isResidential: false, isCommercialBuilding: false, isOtherImprovement: false, @@ -145,6 +149,15 @@ const leaseData: Api_Lease = { expiryDate: null, tenants: [], terms: [], - insurances: [], consultations: [], + programName: null, + renewalCount: 0, + hasPhysicalFile: false, + hasDigitalFile: false, + hasPhysicalLicense: null, + hasDigitalLicense: null, + isExpired: false, + project: null, + id: 0, + rowVersion: 0, }; diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.tsx index 2243342fde..2decb7ec23 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/UpdateLeaseContainer.tsx @@ -9,8 +9,9 @@ import { useLeaseDetail } from '@/features/leases/hooks/useLeaseDetail'; import { useUpdateLease } from '@/features/leases/hooks/useUpdateLease'; import { LeaseFormModel } from '@/features/leases/models'; import useApiUserOverride from '@/hooks/useApiUserOverride'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { isValidId } from '@/utils'; import { IUpdateLeaseFormProps } from './UpdateLeaseForm'; @@ -54,8 +55,8 @@ export const UpdateLeaseContainer: React.FunctionComponent< } }; - const afterSubmit = async (updatedLease?: Api_Lease) => { - if (!!updatedLease?.id) { + const afterSubmit = async (updatedLease?: ApiGen_Concepts_Lease) => { + if (isValidId(updatedLease?.id)) { formikRef?.current?.resetForm({ values: formikRef?.current?.values }); await refresh(); mapMachine.refreshMapProperties(); diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertiesInformation.test.tsx.snap b/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertiesInformation.test.tsx.snap index 6e3e927aa8..652c40d779 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertiesInformation.test.tsx.snap +++ b/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertiesInformation.test.tsx.snap @@ -36,14 +36,6 @@ exports[`PropertiesInformation component renders as expected 1`] = ` font-weight: bold; } -.c6 { - grid-column: controls; - border-left: 1px solid black !important; - padding: 0.6rem 1.2rem; - color: #495057; - font-family: 'BCSans-Bold'; -} - .c3 { margin-top: 4rem; } @@ -131,8 +123,8 @@ exports[`PropertiesInformation component renders as expected 1`] = ` >
@@ -153,7 +145,7 @@ exports[`PropertiesInformation component renders as expected 1`] = `
- NaN m + 123.00 m 2 @@ -174,16 +166,72 @@ exports[`PropertiesInformation component renders as expected 1`] = `
- -

+

+
+ +
+
+ +
+
+ +
+
+ +
+
- Address not available in PIMS -

+ +
+ > + test description +

diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertyInformation.test.tsx.snap b/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertyInformation.test.tsx.snap index d2a9b55a88..c89df20aa1 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertyInformation.test.tsx.snap +++ b/source/frontend/src/features/leases/detail/LeasePages/details/__snapshots__/PropertyInformation.test.tsx.snap @@ -68,8 +68,8 @@ exports[`PropertyInformation component renders a complete lease as expected 1`] > @@ -212,8 +212,8 @@ exports[`PropertyInformation component renders minimally as expected 1`] = ` > diff --git a/source/frontend/src/features/leases/detail/LeasePages/details/columns.tsx b/source/frontend/src/features/leases/detail/LeasePages/details/columns.tsx index f018072ddf..5d70d31b16 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/details/columns.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/details/columns.tsx @@ -1,17 +1,17 @@ import { CellProps } from 'react-table'; import { ColumnWithProps, DateCell } from '@/components/Table'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; -import Api_TypeCode from '@/models/api/TypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; import { stringToFragment } from '@/utils'; -export const leaseTermColumns: ColumnWithProps[] = [ +export const leaseTermColumns: ColumnWithProps[] = [ { Header: 'Term ID', accessor: 'id', align: 'left', sortable: false, - Cell: ({ cell }: CellProps) => + Cell: ({ cell }: CellProps) => stringToFragment(cell.row.index === 0 ? 'initial term' : `renewal ${cell.row.index}`), }, { @@ -40,7 +40,9 @@ export const leaseTermColumns: ColumnWithProps[] = [ accessor: 'statusTypeCode', align: 'left', sortable: false, - Cell: ({ cell: { value } }: CellProps | null>) => + Cell: ({ + cell: { value }, + }: CellProps | null>) => stringToFragment(value?.description ?? ''), }, ]; diff --git a/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.test.tsx index 9ce348503e..b499d8c004 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.test.tsx @@ -11,8 +11,8 @@ import { act } from 'react-test-renderer'; import { IAddLeaseContainerProps } from '@/features/leases/add/AddLeaseContainer'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { mockLookups } from '@/mocks/lookups.mock'; -import { defaultApiLease } from '@/models/api/Lease'; -import { Api_PropertyImprovement } from '@/models/api/PropertyImprovement'; +import { ApiGen_Concepts_PropertyImprovement } from '@/models/api/generated/ApiGen_Concepts_PropertyImprovement'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { fillInput, renderAsync } from '@/utils/test-utils'; @@ -34,14 +34,16 @@ const SaveButton = () => { describe('Add Improvements container component', () => { const setup = async ( renderOptions: RenderOptions & - Partial & { improvements?: Api_PropertyImprovement[] } = {}, + Partial & { + improvements?: ApiGen_Concepts_PropertyImprovement[]; + } = {}, ) => { // render component under test const component = await renderAsync( { { propertyImprovementTypeCode: { id: 'COMMBLDG' }, address: 'test address 1', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, { propertyImprovementTypeCode: { id: 'OTHER' }, address: 'test address 2', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, { propertyImprovementTypeCode: { id: 'RTA' }, address: 'test address 3', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, ], }); @@ -162,15 +164,15 @@ describe('Add Improvements container component', () => { { propertyImprovementTypeCode: { id: 'COMMBLDG' }, address: 'test address 1', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, { propertyImprovementTypeCode: { id: 'OTHER' }, address: 'test address 2', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, { propertyImprovementTypeCode: { id: 'RTA' }, address: 'test address 3', - } as Api_PropertyImprovement, + } as ApiGen_Concepts_PropertyImprovement, ], }); @@ -180,4 +182,4 @@ describe('Add Improvements container component', () => { }); const expectedFormData = - '[{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"COMMBLDG"},"improvementDescription":"","structureSize":"structure 1","address":"address 1"},{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"RTA"},"improvementDescription":"","structureSize":"structure 2","address":"address 2"},{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"OTHER"},"improvementDescription":"","structureSize":"structure 3","address":"address 3"}]'; + '[{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"COMMBLDG","description":null,"displayOrder":null,"isDisabled":false},"improvementDescription":"","structureSize":"structure 1","address":"address 1","appCreateTimestamp":"1970-01-01T00:00:00","appLastUpdateTimestamp":"1970-01-01T00:00:00","appLastUpdateUserid":null,"appCreateUserid":null,"appLastUpdateUserGuid":null,"appCreateUserGuid":null,"rowVersion":null},{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"RTA","description":null,"displayOrder":null,"isDisabled":false},"improvementDescription":"","structureSize":"structure 2","address":"address 2","appCreateTimestamp":"1970-01-01T00:00:00","appLastUpdateTimestamp":"1970-01-01T00:00:00","appLastUpdateUserid":null,"appCreateUserid":null,"appLastUpdateUserGuid":null,"appCreateUserGuid":null,"rowVersion":null},{"id":null,"leaseId":1,"lease":null,"propertyImprovementTypeCode":{"id":"OTHER","description":null,"displayOrder":null,"isDisabled":false},"improvementDescription":"","structureSize":"structure 3","address":"address 3","appCreateTimestamp":"1970-01-01T00:00:00","appLastUpdateTimestamp":"1970-01-01T00:00:00","appLastUpdateUserid":null,"appCreateUserid":null,"appLastUpdateUserGuid":null,"appCreateUserGuid":null,"rowVersion":null}]'; diff --git a/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.tsx index 9d6899829f..61c9ecb7b6 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/improvements/AddImprovementsContainer.tsx @@ -6,7 +6,7 @@ import * as API from '@/constants/API'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { usePropertyImprovementRepository } from '@/hooks/repositories/usePropertyImprovementRepository'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_PropertyImprovement } from '@/models/api/PropertyImprovement'; +import { ApiGen_Concepts_PropertyImprovement } from '@/models/api/generated/ApiGen_Concepts_PropertyImprovement'; import { ILookupCode } from '@/store/slices/lookupCodes'; import AddImprovementsForm from './AddImprovementsForm'; @@ -15,7 +15,7 @@ import { ILeaseImprovementForm, ILeaseImprovementsForm } from './models'; interface IAddImprovementsContainerProps { formikRef: React.RefObject>; onEdit?: (isEditing: boolean) => void; - improvements: Api_PropertyImprovement[]; + improvements: ApiGen_Concepts_PropertyImprovement[]; loading: boolean; onSuccess: () => void; } diff --git a/source/frontend/src/features/leases/detail/LeasePages/improvements/Improvements.tsx b/source/frontend/src/features/leases/detail/LeasePages/improvements/Improvements.tsx index 6470e9aec7..4f4edb25d0 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/improvements/Improvements.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/improvements/Improvements.tsx @@ -1,14 +1,14 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_PropertyImprovement } from '@/models/api/PropertyImprovement'; +import { ApiGen_Concepts_PropertyImprovement } from '@/models/api/generated/ApiGen_Concepts_PropertyImprovement'; import Improvement from './components/Improvement/Improvement'; import { ILeaseImprovementForm } from './models'; import * as Styled from './styles'; export interface IImprovementsProps { - improvements: Api_PropertyImprovement[]; + improvements: ApiGen_Concepts_PropertyImprovement[]; loading: boolean; } @@ -18,7 +18,7 @@ export const Improvements: React.FunctionComponent = ({ }) => { const { getByType } = useLookupCodeHelpers(); const improvementTypeCodes = getByType(API.PROPERTY_IMPROVEMENT_TYPES); - const formImprovements = improvements.map((improvement: Api_PropertyImprovement) => + const formImprovements = improvements.map((improvement: ApiGen_Concepts_PropertyImprovement) => ILeaseImprovementForm.fromApi(improvement), ); diff --git a/source/frontend/src/features/leases/detail/LeasePages/improvements/ImprovementsContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/improvements/ImprovementsContainer.test.tsx index bdc97b86ec..2308bb05c8 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/improvements/ImprovementsContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/improvements/ImprovementsContainer.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { mockLookups } from '@/mocks/lookups.mock'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { renderAsync, RenderOptions } from '@/utils/test-utils'; @@ -30,7 +30,7 @@ describe('Improvements Container component', () => { i.insuranceType.displayOrder) ?? []; + const insuranceList = orderBy(insurances, i => i.insuranceType?.displayOrder) ?? []; const leaseId = lease?.id; useEffect(() => { leaseId && getInsurances(leaseId); @@ -44,8 +45,8 @@ const InsuranceContainer: React.FunctionComponent { - if (leaseId !== undefined && leaseId !== null) { + async (insurances: ApiGen_Concepts_Insurance[]) => { + if (isValidId(leaseId)) { const updatedInsurance = await updateInsurances(leaseId, insurances); if (updatedInsurance) { leaseId && (await getInsurances(leaseId)); @@ -63,19 +64,15 @@ const InsuranceContainer: React.FunctionComponent )} - {isEditing && - leaseId !== null && - leaseId !== undefined && - !loading && - hasClaim(Claims.LEASE_EDIT) && ( - - )} + {isEditing && isValidId(leaseId) && !loading && hasClaim(Claims.LEASE_EDIT) && ( + + )} { const setup = ( renderOptions: RenderOptions & { - insuranceList: Api_Insurance[]; + insuranceList: ApiGen_Concepts_Insurance[]; insuranceTypes: ILookupCode[]; } = { store: storeState, @@ -68,7 +68,7 @@ describe('Lease Insurance', () => { }); it('Insurance count is correct', () => { - const testInsurance: Api_Insurance = { ...getMockInsurance() }; + const testInsurance: ApiGen_Concepts_Insurance = { ...getMockInsurance() }; testInsurance.insuranceType = TypeCodeUtils.createFromLookup(mockInsuranceTypeCar); const result = setup({ diff --git a/source/frontend/src/features/leases/detail/LeasePages/insurance/details/Insurance.tsx b/source/frontend/src/features/leases/detail/LeasePages/insurance/details/Insurance.tsx index f6fb5cfe37..688dcb98c5 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/insurance/details/Insurance.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/insurance/details/Insurance.tsx @@ -1,14 +1,14 @@ import React, { useMemo } from 'react'; import { Section } from '@/components/common/Section/Section'; -import { Api_Insurance } from '@/models/api/Insurance'; +import { ApiGen_Concepts_Insurance } from '@/models/api/generated/ApiGen_Concepts_Insurance'; import { ILookupCode } from '@/store/slices/lookupCodes'; import Policy from './Policy'; import { InsuranceTypeList } from './styles'; export interface InsuranceDetailsViewProps { - insuranceList: Api_Insurance[]; + insuranceList: ApiGen_Concepts_Insurance[]; insuranceTypes: ILookupCode[]; } @@ -20,8 +20,8 @@ const InsuranceDetailsView: React.FunctionComponent< !!insuranceList?.length ? insuranceList.sort((a, b) => { return ( - insuranceTypes.findIndex(i => i.id === a.insuranceType.displayOrder) - - insuranceTypes.findIndex(i => i.id === b.insuranceType.displayOrder) + insuranceTypes.findIndex(i => i.id === a.insuranceType?.displayOrder) - + insuranceTypes.findIndex(i => i.id === b.insuranceType?.displayOrder) ); }) : [], @@ -31,10 +31,10 @@ const InsuranceDetailsView: React.FunctionComponent<
- {sortedInsuranceList.map((insurance: Api_Insurance, index: number) => ( + {sortedInsuranceList.map((insurance: ApiGen_Concepts_Insurance, index: number) => (
  • - {insurance.insuranceType.description} - {insurance.insuranceType.id === 'OTHER' && insurance.otherInsuranceType + {insurance.insuranceType?.description} + {insurance.insuranceType?.id === 'OTHER' && insurance.otherInsuranceType ? `: ${insurance.otherInsuranceType}` : ''}
  • @@ -42,7 +42,7 @@ const InsuranceDetailsView: React.FunctionComponent<
    - {sortedInsuranceList.map((insurance: Api_Insurance, index: number) => ( + {sortedInsuranceList.map((insurance: ApiGen_Concepts_Insurance, index: number) => (
    > = ({ expiryDate: prettyFormatDate(insurance.expiryDate), coverageDescription: insurance.coverageDescription || '', otherInsuranceType: insurance.otherInsuranceType ?? undefined, - insuranceType: insurance.insuranceType.description, + insuranceType: insurance.insuranceType?.description ?? undefined, }; return (
    diff --git a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.test.tsx index e3f1696815..7f3b37ecbd 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.test.tsx @@ -3,8 +3,9 @@ import { Formik } from 'formik'; import { createMemoryHistory } from 'history'; import { noop } from 'lodash'; -import { TypeCodeUtils } from '@/interfaces'; -import { Api_Insurance } from '@/models/api/Insurance'; +import { TypeCodeUtils } from '@/interfaces/ITypeCode'; +import { ApiGen_Concepts_Insurance } from '@/models/api/generated/ApiGen_Concepts_Insurance'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { ILookupCode } from '@/store/slices/lookupCodes'; import { act, render, RenderOptions, RenderResult } from '@/utils/test-utils'; @@ -25,7 +26,7 @@ export const mockInsuranceTypeCar: ILookupCode = { displayOrder: 2, }; -const mockInsuranceHome: Api_Insurance = { +const mockInsuranceHome: ApiGen_Concepts_Insurance = { id: 123459, leaseId: 1, insuranceType: TypeCodeUtils.createFromLookup(mockInsuranceTypeHome), @@ -34,7 +35,7 @@ const mockInsuranceHome: Api_Insurance = { coverageLimit: 777, expiryDate: '2022-01-01', isInsuranceInPlace: true, - rowVersion: 0, + ...getEmptyBaseAudit(), }; const defaultProps: InsuranceEditContainerProps = { @@ -97,7 +98,7 @@ describe('Edit Lease Insurance', () => { }); it('Updates form lists when clicked', async () => { - const testInsuranceCar: Api_Insurance = { + const testInsuranceCar: ApiGen_Concepts_Insurance = { ...mockInsuranceHome, coverageLimit: 888, coverageDescription: 'test description for a test insurance car', diff --git a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.tsx index 2d9f939337..37002b1c58 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/EditInsuranceContainer.tsx @@ -3,9 +3,10 @@ import React from 'react'; import { Form } from '@/components/common/form/Form'; import { FormSectionClear } from '@/components/common/form/styles'; -import { Api_Insurance } from '@/models/api/Insurance'; +import { ApiGen_Concepts_Insurance } from '@/models/api/generated/ApiGen_Concepts_Insurance'; import { ILookupCode } from '@/store/slices/lookupCodes/interfaces'; import { withNameSpace } from '@/utils/formUtils'; +import { exists } from '@/utils/utils'; import InsuranceForm from './InsuranceForm'; import { InsuranceYupSchema } from './InsuranceYupSchema'; @@ -13,9 +14,9 @@ import { FormInsurance, IUpdateFormInsurance } from './models'; export interface InsuranceEditContainerProps { leaseId: number; - insuranceList: Api_Insurance[]; + insuranceList: ApiGen_Concepts_Insurance[]; insuranceTypes: ILookupCode[]; - onSave: (insurances: Api_Insurance[]) => Promise; + onSave: (insurances: ApiGen_Concepts_Insurance[]) => Promise; formikRef: React.RefObject>; } @@ -24,7 +25,7 @@ const InsuranceEditContainer: React.FunctionComponent< > = ({ leaseId, insuranceList, insuranceTypes, onSave, formikRef }) => { const handleOnChange = (e: any, codeType: any, arrayHelpers: any) => { if (formikRef.current) { - let found = initialInsurances.findIndex(x => x.insuranceType.id === codeType.id); + let found = initialInsurances.findIndex(x => x.insuranceType?.id === codeType.id); if (e.target.checked) { arrayHelpers.push(codeType.id); @@ -38,7 +39,7 @@ const InsuranceEditContainer: React.FunctionComponent< }; const initialInsurances = insuranceTypes.map(x => { - let foundInsurance = insuranceList.find(i => i.insuranceType.id === x.id); + let foundInsurance = insuranceList.find(i => i.insuranceType?.id === x.id); if (foundInsurance) { return FormInsurance.createFromModel(foundInsurance); } else { @@ -46,7 +47,7 @@ const InsuranceEditContainer: React.FunctionComponent< } }); - const initialTypes = insuranceList.map(x => x.insuranceType.id); + const initialTypes = insuranceList.map(x => x?.insuranceType?.id).filter(exists); const initialValues: IUpdateFormInsurance = { insurances: initialInsurances, @@ -59,7 +60,9 @@ const InsuranceEditContainer: React.FunctionComponent< validationSchema={InsuranceYupSchema} onSubmit={(values: IUpdateFormInsurance) => { return onSave( - values.insurances.filter(i => i.isShown).map(x => x.toInterfaceModel()), + values.insurances + .filter(i => i.isShown) + .map(x => x.toInterfaceModel()), ); }} innerRef={formikRef} diff --git a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/InsuranceForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/InsuranceForm.tsx index 9d9850a431..e9bd3bd602 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/InsuranceForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/InsuranceForm.tsx @@ -4,7 +4,7 @@ import { Col, Row } from 'react-bootstrap'; import { FastCurrencyInput, FastDatePicker, Input, TextArea } from '@/components/common/form'; import { RadioGroup } from '@/components/common/form/RadioGroup'; import { FormSection } from '@/components/common/form/styles'; -import ITypeCode from '@/interfaces/ITypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; import { withNameSpace } from '@/utils/formUtils'; import { FormInsurance } from './models'; @@ -18,7 +18,7 @@ const InsuranceForm: React.FunctionComponent { const formikProps = useFormikContext(); - const insuranceType: ITypeCode = getIn( + const insuranceType: ApiGen_Base_CodeType = getIn( formikProps.values, `${withNameSpace(nameSpace, 'insuranceType')}`, ); diff --git a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/models.ts b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/models.ts index 807ecd0e07..d4f1d39492 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/models.ts +++ b/source/frontend/src/features/leases/detail/LeasePages/insurance/edit/models.ts @@ -1,8 +1,10 @@ -import { TypeCodeUtils } from '@/interfaces'; -import ITypeCode from '@/interfaces/ITypeCode'; -import { Api_Insurance } from '@/models/api/Insurance'; +import { TypeCodeUtils } from '@/interfaces/ITypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_Insurance } from '@/models/api/generated/ApiGen_Concepts_Insurance'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { ILookupCode } from '@/store/slices/lookupCodes'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; +import { isValidIsoDateTime } from '@/utils'; import { numberFieldToRequiredNumber } from '@/utils/formUtils'; export interface IUpdateFormInsurance { @@ -17,7 +19,7 @@ export class FormInsurance { public id: number | null = null; public leaseId: NumberFieldValue = ''; - public insuranceType!: ITypeCode; + public insuranceType: ApiGen_Base_CodeType | null = null; public otherInsuranceType?: string; public coverageDescription?: string; public coverageLimit?: NumberFieldValue; @@ -37,7 +39,7 @@ export class FormInsurance { return model; } - public static createFromModel(baseModel: Api_Insurance): FormInsurance { + public static createFromModel(baseModel: ApiGen_Concepts_Insurance): FormInsurance { let model = new FormInsurance(); model.id = baseModel.id; model.leaseId = baseModel.leaseId; @@ -45,7 +47,7 @@ export class FormInsurance { model.otherInsuranceType = baseModel.otherInsuranceType ?? undefined; model.coverageDescription = baseModel.coverageDescription ?? undefined; model.coverageLimit = baseModel.coverageLimit || ''; - model.expiryDate = baseModel.expiryDate ?? undefined; + model.expiryDate = isValidIsoDateTime(baseModel.expiryDate) ? baseModel.expiryDate : undefined; model.isInsuranceInPlaceRadio = baseModel.isInsuranceInPlace === true ? 'yes' : 'no'; model.isNew = false; model.isShown = true; @@ -53,7 +55,7 @@ export class FormInsurance { return model; } - public toInterfaceModel(): Api_Insurance { + public toInterfaceModel(): ApiGen_Concepts_Insurance { return { id: this.id ?? null, leaseId: numberFieldToRequiredNumber(this.leaseId), @@ -61,9 +63,9 @@ export class FormInsurance { otherInsuranceType: this.otherInsuranceType ?? null, coverageDescription: this.coverageDescription ?? null, coverageLimit: this.coverageLimit === '' ? null : this.coverageLimit ?? null, - expiryDate: this.expiryDate === '' ? null : this.expiryDate ?? null, + expiryDate: !isValidIsoDateTime(this.expiryDate) ? null : this.expiryDate ?? null, isInsuranceInPlace: this.isInsuranceInPlaceRadio === 'yes' ? true : false, - rowVersion: this.rowVersion, + ...getEmptyBaseAudit(this.rowVersion), }; } @@ -71,7 +73,7 @@ export class FormInsurance { return ( this.isInsuranceInPlaceRadio === other.isInsuranceInPlaceRadio && this.id === other.id && - this.insuranceType.id === other.insuranceType.id && + this.insuranceType?.id === other.insuranceType?.id && this.otherInsuranceType === other.otherInsuranceType && this.coverageDescription === other.coverageDescription && this.coverageLimit === other.coverageLimit && diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/TermPaymentsContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/TermPaymentsContainer.tsx index ad5577c236..3ab49d23fb 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/TermPaymentsContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/TermPaymentsContainer.tsx @@ -13,9 +13,10 @@ import { LeasePageProps } from '@/features/mapSideBar/lease/LeaseContainer'; import { useLeasePaymentRepository } from '@/hooks/repositories/useLeasePaymentRepository'; import { useLeaseTermRepository } from '@/hooks/repositories/useLeaseTermRepository'; import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect'; -import { defaultApiLease } from '@/models/api/Lease'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; +import { getEmptyLease } from '@/models/defaultInitializers'; import { SystemConstants, useSystemConstants } from '@/store/slices/systemConstants'; +import { exists, isValidId, isValidIsoDateTime } from '@/utils'; import { useDeleteTermsPayments } from './hooks/useDeleteTermsPayments'; import PaymentModal from './modal/payment/PaymentModal'; @@ -35,7 +36,7 @@ export const TermPaymentsContainer: React.FunctionComponent< const [editPaymentModalValues, setEditPaymentModalValues] = useState< FormLeasePayment | undefined >(undefined); - const [terms, setTerms] = useState([]); + const [terms, setTerms] = useState([]); const { updateLeaseTerm, addLeaseTerm, getLeaseTerms, deleteLeaseTerm } = useLeaseTermRepository(); @@ -58,7 +59,7 @@ export const TermPaymentsContainer: React.FunctionComponent< [getLeaseTermsFunc], ); useDeepCompareEffect(() => { - if (!!leaseId) { + if (isValidId(leaseId)) { refreshLeaseTerms(leaseId); } }, [refreshLeaseTerms, leaseId]); @@ -78,10 +79,11 @@ export const TermPaymentsContainer: React.FunctionComponent< */ const onSaveTerm = useCallback( async (values: FormLeaseTerm) => { - const updatedTerm = values.id + const updatedTerm = isValidId(values.id) ? await updateLeaseTerm.execute(FormLeaseTerm.toApi(values, gstDecimal)) : await addLeaseTerm.execute(FormLeaseTerm.toApi(values, gstDecimal)); - if (!!updatedTerm?.id && leaseId) { + + if (isValidId(updatedTerm?.id) && isValidId(leaseId)) { const response = await getLeaseTerms.execute(leaseId); setTerms(response ?? []); setEditModalValues(undefined); @@ -97,11 +99,11 @@ export const TermPaymentsContainer: React.FunctionComponent< */ const onSavePayment = useCallback( async (values: FormLeasePayment) => { - if (leaseId) { + if (isValidId(leaseId)) { const updatedLeasePayment = values.id ? await updateLeasePayment.execute(leaseId, FormLeasePayment.toApi(values)) : await addLeasePayment.execute(leaseId, FormLeasePayment.toApi(values)); - if (!!updatedLeasePayment?.id) { + if (isValidId(updatedLeasePayment?.id)) { const response = await getLeaseTerms.execute(leaseId); setTerms(response ?? []); setEditPaymentModalValues(undefined); @@ -115,7 +117,10 @@ export const TermPaymentsContainer: React.FunctionComponent< const onEdit = useCallback( (values: FormLeaseTerm) => { if (lease?.terms?.length === 0) { - values = { ...values, startDate: lease?.startDate ?? '' }; + values = { + ...values, + startDate: isValidIsoDateTime(lease?.startDate) ? lease.startDate : '', + }; } setEditModalValues(values); }, @@ -127,7 +132,7 @@ export const TermPaymentsContainer: React.FunctionComponent< }, []); const onGenerate = () => { - if (lease) { + if (exists(lease)) { generateH1005a(lease); } }; @@ -150,8 +155,12 @@ export const TermPaymentsContainer: React.FunctionComponent< useEffect(() => { setModalContent({ variant: 'info', - headerIcon: !editModalValues?.id ? : , - title: !editModalValues?.id ? 'Add a Term' : 'Edit a Term', + headerIcon: !isValidId(editModalValues?.id) ? ( + + ) : ( + + ), + title: !isValidId(editModalValues?.id) ? 'Add a Term' : 'Edit a Term', okButtonText: 'Yes', cancelButtonText: 'No', handleCancel: onCancelTerm, @@ -182,7 +191,7 @@ export const TermPaymentsContainer: React.FunctionComponent< onGenerate={onGenerate} isReceivable={lease?.paymentReceivableType?.id === 'RCVBL'} lease={LeaseFormModel.fromApi({ - ...defaultApiLease, + ...getEmptyLease(), terms: terms, type: lease?.type ?? null, })} diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/TermsPaymentsContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/TermsPaymentsContainer.test.tsx index c7ca3f70df..7e0e2244e2 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/TermsPaymentsContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/TermsPaymentsContainer.test.tsx @@ -12,8 +12,9 @@ import { LeaseFormModel } from '@/features/leases/models'; import { LeasePageProps } from '@/features/mapSideBar/lease/LeaseContainer'; import { useLeaseTermRepository } from '@/hooks/repositories/useLeaseTermRepository'; import { mockLookups } from '@/mocks/lookups.mock'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes/lookupCodesSlice'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, fillInput, @@ -49,7 +50,7 @@ const defaultLeaseWithTermsPayments: LeaseFormModel = { terms: [ { ...defaultFormLeaseTerm, - statusTypeCode: { id: LeaseTermStatusTypes.EXERCISED }, + statusTypeCode: toTypeCodeNullable(LeaseTermStatusTypes.EXERCISED), payments: [{ ...defaultTestFormLeasePayment }], }, ], @@ -85,7 +86,7 @@ describe('TermsPaymentsContainer component', () => { { it('renders with data as expected', async () => { const { component } = await setup({ claims: [Claims.LEASE_EDIT], - initialValues: { ...defaultApiLease, terms: [defaultFormLeaseTerm] }, + initialValues: { ...defaultApiLease(), terms: [defaultFormLeaseTerm] }, }); expect(component.asFragment()).toMatchSnapshot(); @@ -167,7 +168,7 @@ describe('TermsPaymentsContainer component', () => { const { component: { findAllByTitle, getByText }, } = await setup({ - initialValues: { ...defaultApiLease, terms: [{ ...defaultFormLeaseTerm, id: 1 }] }, + initialValues: { ...defaultApiLease(), terms: [{ ...defaultFormLeaseTerm, id: 1 }] }, claims: [Claims.LEASE_EDIT], }); mockAxios.onPut().reply(200, { id: 1 }); @@ -188,7 +189,7 @@ describe('TermsPaymentsContainer component', () => { component: { queryByTitle }, } = await setup({ initialValues: { - ...defaultApiLease, + ...defaultApiLease(), terms: [{ ...defaultFormLeaseTerm, id: 1, payments: [{ id: 1 }] }], }, claims: [Claims.LEASE_DELETE], @@ -207,7 +208,7 @@ describe('TermsPaymentsContainer component', () => { component: { findAllByTitle, getByText }, } = await setup({ initialValues: { - ...defaultApiLease, + ...defaultApiLease(), terms: [ { ...defaultFormLeaseTerm, id: 1 }, { ...defaultFormLeaseTerm, id: 1 }, @@ -230,7 +231,7 @@ describe('TermsPaymentsContainer component', () => { component: { findAllByTitle }, } = await setup({ initialValues: { - ...defaultApiLease, + ...defaultApiLease(), terms: [{ ...defaultFormLeaseTerm, id: 1 }], }, claims: [Claims.LEASE_EDIT], @@ -252,7 +253,7 @@ describe('TermsPaymentsContainer component', () => { component: { findAllByTitle, getByText }, } = await setup({ initialValues: { - ...defaultApiLease, + ...defaultApiLease(), terms: [{ ...defaultFormLeaseTerm, id: 1 }], }, claims: [Claims.LEASE_EDIT], diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/hooks/useDeleteTermsPayments.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/hooks/useDeleteTermsPayments.tsx index f29282debc..01717d56b3 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/hooks/useDeleteTermsPayments.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/hooks/useDeleteTermsPayments.tsx @@ -4,12 +4,14 @@ import { useCallback, useContext, useState } from 'react'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { useLeasePaymentRepository } from '@/hooks/repositories/useLeasePaymentRepository'; import { IResponseWrapper } from '@/hooks/util/useApiRequestWrapper'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; import { FormLeasePayment, FormLeaseTerm } from '../models'; export const useDeleteTermsPayments = ( - deleteLeaseTerm: IResponseWrapper<(term: Api_LeaseTerm) => Promise>>, + deleteLeaseTerm: IResponseWrapper< + (term: ApiGen_Concepts_LeaseTerm) => Promise> + >, getLeaseTerms: (leaseId: number) => Promise, onSuccess: () => void, ) => { diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentForm.tsx index c3e86a2dc2..d1c0307b29 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentForm.tsx @@ -1,6 +1,7 @@ import { Formik, FormikProps } from 'formik'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { defaultFormLeasePayment, FormLeasePayment, FormLeaseTerm } from '../../models'; import { isActualGstEligible } from '../../TermPaymentsContainer'; @@ -12,7 +13,7 @@ export interface IPaymentFormProps { onSave: (values: FormLeasePayment) => void; initialValues?: FormLeasePayment; isReceived?: boolean; - terms: Api_LeaseTerm[]; + terms: ApiGen_Concepts_LeaseTerm[]; } /** @@ -46,7 +47,7 @@ export const PaymentForm: React.FunctionComponent diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.test.tsx index 8ce1a8d032..487a6976a3 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.test.tsx @@ -80,6 +80,9 @@ describe('PaymentModal component', () => { amountGst: '', leasePaymentMethodType: { id: 'CHEQ', + displayOrder: null, + description: null, + isDisabled: false, }, }); }); diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.tsx index 97d2c7bfe6..fafe2f53a6 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/payment/PaymentModal.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useRef } from 'react'; import GenericModal from '@/components/common/GenericModal'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; import { FormLeasePayment } from '../../models'; import { PaymentForm } from './PaymentForm'; @@ -11,7 +11,7 @@ import { PaymentForm } from './PaymentForm'; export interface IPaymentModalProps { initialValues?: FormLeasePayment; displayModal?: boolean; - terms: Api_LeaseTerm[]; + terms: ApiGen_Concepts_LeaseTerm[]; onCancel: () => void; onSave: (values: FormLeasePayment) => void; } diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/term/TermForm.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/term/TermForm.tsx index 5dbd8d0b49..ff8872428b 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/modal/term/TermForm.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/modal/term/TermForm.tsx @@ -5,7 +5,8 @@ import { Check, FastCurrencyInput, FastDatePicker, Input, Select } from '@/compo import { LeaseTermStatusTypes } from '@/constants'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { defaultFormLeaseTerm, FormLeaseTerm } from '../../models'; import { StyledFormBody } from '../../styles'; @@ -15,7 +16,7 @@ export interface ITermFormProps { formikRef: React.Ref>; onSave: (values: FormLeaseTerm) => void; initialValues?: FormLeaseTerm; - lease: Api_Lease | undefined; + lease: ApiGen_Concepts_Lease | undefined; } /** @@ -46,7 +47,7 @@ export const TermForm: React.FunctionComponent {formikProps => ( diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/models.ts b/source/frontend/src/features/leases/detail/LeasePages/payment/models.ts index e0e56b0790..5a014f05f2 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/models.ts +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/models.ts @@ -1,20 +1,21 @@ -import { defaultTypeCode } from '@/interfaces'; -import { Api_LeasePayment } from '@/models/api/LeasePayment'; -import { Api_LeaseTerm } from '@/models/api/LeaseTerm'; -import Api_TypeCode from '@/models/api/TypeCode'; +import { defaultTypeCode } from '@/interfaces/ITypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_LeaseTerm } from '@/models/api/generated/ApiGen_Concepts_LeaseTerm'; +import { ApiGen_Concepts_Payment } from '@/models/api/generated/ApiGen_Concepts_Payment'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; -import { stringToUndefined, toRequiredTypeCode } from '@/utils/formUtils'; +import { isValidIsoDateTime } from '@/utils'; +import { stringToNumber, stringToNumberOrNull } from '@/utils/formUtils'; export class FormLeaseTerm { id: number | null = null; leaseId: number | null = null; - statusTypeCode: Api_TypeCode | null = null; - leasePmtFreqTypeCode: Api_TypeCode | null = null; + statusTypeCode: ApiGen_Base_CodeType | null = null; + leasePmtFreqTypeCode: ApiGen_Base_CodeType | null = null; startDate: string = ''; effectiveDateHist: string | null = null; expiryDate: string = ''; renewalDate: string = ''; - endDateHist: string = ''; paymentAmount: NumberFieldValue = ''; gstAmount: NumberFieldValue = ''; paymentDueDateStr: string = ''; @@ -24,15 +25,18 @@ export class FormLeaseTerm { payments: FormLeasePayment[] = []; rowVersion?: number; - public static toApi(formLeaseTerm: FormLeaseTerm, gstConstant?: number): Api_LeaseTerm { + public static toApi( + formLeaseTerm: FormLeaseTerm, + gstConstant?: number, + ): ApiGen_Concepts_LeaseTerm { return { ...formLeaseTerm, leaseId: formLeaseTerm.leaseId ?? 0, - startDate: stringToUndefined(formLeaseTerm.startDate), + startDate: isValidIsoDateTime(formLeaseTerm.startDate) ? formLeaseTerm.startDate : null, renewalDate: null, - expiryDate: stringToUndefined(formLeaseTerm.expiryDate), - paymentAmount: stringToUndefined(formLeaseTerm.paymentAmount), - gstAmount: stringToUndefined( + expiryDate: isValidIsoDateTime(formLeaseTerm.expiryDate) ? formLeaseTerm.expiryDate : null, + paymentAmount: stringToNumberOrNull(formLeaseTerm.paymentAmount), + gstAmount: stringToNumberOrNull( formLeaseTerm.isGstEligible && gstConstant !== undefined ? (formLeaseTerm.paymentAmount as number) * (gstConstant / 100) : null, @@ -42,26 +46,30 @@ export class FormLeaseTerm { : null, statusTypeCode: formLeaseTerm.statusTypeCode?.id ? formLeaseTerm.statusTypeCode : null, payments: formLeaseTerm.payments.map(payment => FormLeasePayment.toApi(payment)), - isGstEligible: formLeaseTerm.isGstEligible ?? null, - isTermExercised: formLeaseTerm.isTermExercised ?? null, + isGstEligible: formLeaseTerm.isGstEligible ?? false, + isTermExercised: formLeaseTerm.isTermExercised ?? false, + ...getEmptyBaseAudit(formLeaseTerm.rowVersion), }; } - public static fromApi(apiLeaseTerm: Api_LeaseTerm): FormLeaseTerm { + public static fromApi(apiLeaseTerm: ApiGen_Concepts_LeaseTerm): FormLeaseTerm { return { ...apiLeaseTerm, - expiryDate: apiLeaseTerm.expiryDate ?? '', - renewalDate: apiLeaseTerm.renewalDate ?? '', - endDateHist: apiLeaseTerm.endDateHist ?? '', + startDate: isValidIsoDateTime(apiLeaseTerm.startDate) ? apiLeaseTerm.startDate : '', + expiryDate: isValidIsoDateTime(apiLeaseTerm.expiryDate) ? apiLeaseTerm.expiryDate : '', + renewalDate: isValidIsoDateTime(apiLeaseTerm.renewalDate) ? apiLeaseTerm.renewalDate : '', paymentAmount: apiLeaseTerm.paymentAmount ?? '', gstAmount: apiLeaseTerm.gstAmount ?? '', paymentDueDateStr: apiLeaseTerm.paymentDueDateStr ?? '', paymentNote: apiLeaseTerm.paymentNote ?? '', - payments: apiLeaseTerm.payments.map((payment: Api_LeasePayment) => - FormLeasePayment.fromApi(payment), - ), + payments: + apiLeaseTerm.payments?.map((payment: ApiGen_Concepts_Payment) => + FormLeasePayment.fromApi(payment), + ) ?? [], isGstEligible: apiLeaseTerm.isGstEligible ?? undefined, isTermExercised: apiLeaseTerm.isTermExercised ?? undefined, + effectiveDateHist: null, + rowVersion: apiLeaseTerm.rowVersion ?? undefined, }; } } @@ -72,7 +80,6 @@ export const defaultFormLeaseTerm: FormLeaseTerm = { startDate: '', expiryDate: '', renewalDate: '', - endDateHist: '', paymentAmount: '', gstAmount: '', paymentDueDateStr: '', @@ -80,46 +87,48 @@ export const defaultFormLeaseTerm: FormLeaseTerm = { isGstEligible: false, isTermExercised: false, effectiveDateHist: '', - statusTypeCode: defaultTypeCode, - leasePmtFreqTypeCode: defaultTypeCode, + statusTypeCode: defaultTypeCode(), + leasePmtFreqTypeCode: defaultTypeCode(), payments: [], }; export class FormLeasePayment { id?: number; leaseTermId: number = 0; - leasePaymentMethodType?: Api_TypeCode; + leasePaymentMethodType: ApiGen_Base_CodeType | null = null; receivedDate: string = ''; note?: string; - leasePaymentStatusTypeCode?: Api_TypeCode; + leasePaymentStatusTypeCode?: ApiGen_Base_CodeType; amountPreTax: NumberFieldValue = ''; amountGst: NumberFieldValue = ''; amountPst: NumberFieldValue = ''; amountTotal: NumberFieldValue = ''; rowVersion?: number; - public static toApi(formLeasePayment: FormLeasePayment): Api_LeasePayment { + public static toApi(formLeasePayment: FormLeasePayment): ApiGen_Concepts_Payment { return { ...formLeasePayment, id: formLeasePayment.id ?? null, - amountPreTax: stringToUndefined(formLeasePayment.amountPreTax), - amountGst: stringToUndefined(formLeasePayment.amountGst), - amountPst: stringToUndefined(formLeasePayment.amountPst), - amountTotal: stringToUndefined(formLeasePayment.amountTotal), + amountPreTax: stringToNumber(formLeasePayment.amountPreTax), + amountGst: stringToNumber(formLeasePayment.amountGst), + amountPst: stringToNumber(formLeasePayment.amountPst), + amountTotal: stringToNumber(formLeasePayment.amountTotal), leasePaymentStatusTypeCode: formLeasePayment.leasePaymentStatusTypeCode?.id ? formLeasePayment.leasePaymentStatusTypeCode : null, - leasePaymentMethodType: toRequiredTypeCode(formLeasePayment.leasePaymentMethodType), + leasePaymentMethodType: formLeasePayment.leasePaymentMethodType, note: formLeasePayment.note ?? null, - rowVersion: formLeasePayment.rowVersion ?? undefined, + ...getEmptyBaseAudit(formLeasePayment.rowVersion), }; } - public static fromApi(apiLeasePayment: Api_LeasePayment): FormLeasePayment { + public static fromApi(apiLeasePayment: ApiGen_Concepts_Payment): FormLeasePayment { const leasePayment = new FormLeasePayment(); leasePayment.id = apiLeasePayment.id ?? undefined; leasePayment.leaseTermId = apiLeasePayment.leaseTermId; - leasePayment.leasePaymentMethodType = apiLeasePayment.leasePaymentMethodType; - leasePayment.receivedDate = apiLeasePayment.receivedDate; + leasePayment.leasePaymentMethodType = apiLeasePayment.leasePaymentMethodType ?? null; + leasePayment.receivedDate = isValidIsoDateTime(apiLeasePayment.receivedDate) + ? apiLeasePayment.receivedDate + : ''; leasePayment.amountPreTax = apiLeasePayment.amountPreTax; leasePayment.amountPst = apiLeasePayment.amountPst ?? ''; leasePayment.amountGst = apiLeasePayment.amountGst ?? ''; @@ -127,7 +136,7 @@ export class FormLeasePayment { leasePayment.note = apiLeasePayment.note ?? undefined; leasePayment.leasePaymentStatusTypeCode = apiLeasePayment.leasePaymentStatusTypeCode ?? undefined; - leasePayment.rowVersion = apiLeasePayment.rowVersion; + leasePayment.rowVersion = apiLeasePayment.rowVersion ?? undefined; return leasePayment; } } @@ -135,12 +144,12 @@ export class FormLeasePayment { export const defaultFormLeasePayment: FormLeasePayment = { id: 0, leaseTermId: 0, - leasePaymentMethodType: defaultTypeCode, + leasePaymentMethodType: defaultTypeCode(), receivedDate: '', amountPreTax: '', amountPst: '', amountGst: '', amountTotal: '', note: '', - leasePaymentStatusTypeCode: defaultTypeCode, + leasePaymentStatusTypeCode: defaultTypeCode(), }; diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/PaymentsForm.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/PaymentsForm.test.tsx index 30d639a1cf..9e33b23e6c 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/PaymentsForm.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/PaymentsForm.test.tsx @@ -6,6 +6,7 @@ import { noop } from 'lodash'; import { Claims, LeaseTermStatusTypes } from '@/constants'; import { LeaseFormModel } from '@/features/leases/models'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, fillInput, renderAsync, RenderOptions, screen, userEvent } from '@/utils/test-utils'; import { getAllByRole as getAllByRoleBase } from '@/utils/test-utils'; @@ -17,8 +18,8 @@ const history = createMemoryHistory(); const mockAxios = new MockAdapter(axios); export const defaultTestFormLeasePayment: FormLeasePayment = { ...defaultFormLeasePayment, - leasePaymentMethodType: { id: 'Cheque' }, - leasePaymentStatusTypeCode: { id: 'Paid' }, + leasePaymentMethodType: toTypeCodeNullable('Cheque'), + leasePaymentStatusTypeCode: toTypeCodeNullable('Paid') ?? undefined, amountTotal: 1200, amountGst: 100, amountPreTax: 1100, @@ -32,7 +33,7 @@ const defaultLeaseWithTermsPayments: LeaseFormModel = { terms: [ { ...defaultFormLeaseTerm, - statusTypeCode: { id: LeaseTermStatusTypes.EXERCISED }, + statusTypeCode: toTypeCodeNullable(LeaseTermStatusTypes.EXERCISED), payments: [{ ...defaultTestFormLeasePayment }], }, ], diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/paymentsColumns.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/paymentsColumns.tsx index 3b4cda2fe8..73675373d4 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/paymentsColumns.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/table/payments/paymentsColumns.tsx @@ -11,7 +11,7 @@ import TooltipIcon from '@/components/common/TooltipIcon'; import { ColumnWithProps, renderDate, renderMoney, renderTypeCode } from '@/components/Table'; import { Claims } from '@/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_LeasePayment } from '@/models/api/LeasePayment'; +import { ApiGen_Concepts_Payment } from '@/models/api/generated/ApiGen_Concepts_Payment'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; import { formatMoney, stringToFragment } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; @@ -69,7 +69,7 @@ export const getActualsColumns = ({ maxWidth: 70, accessor: 'receivedDate', Cell: renderDate, - Footer: ({ properties }: { properties: Api_LeasePayment[] }) => ( + Footer: ({ properties }: { properties: ApiGen_Concepts_Payment[] }) => ( Payment Summary @@ -81,7 +81,7 @@ export const getActualsColumns = ({ align: 'left', maxWidth: 60, Cell: renderTypeCode, - Footer: ({ properties }: { properties: Api_LeasePayment[] }) => ( + Footer: ({ properties }: { properties: ApiGen_Concepts_Payment[] }) => ( <>({properties?.length}) payments ), }, @@ -98,7 +98,7 @@ export const getActualsColumns = ({ accessor: 'amountPreTax', align: 'right', Cell: renderMoney, - Footer: ({ properties }: { properties: Api_LeasePayment[] }) => + Footer: ({ properties }: { properties: ApiGen_Concepts_Payment[] }) => formatMoney(properties.reduce((total, current) => total + current.amountPreTax, 0)), }, { @@ -117,7 +117,7 @@ export const getActualsColumns = ({ Cell: ({ value, row }: CellProps) => { return stringToFragment(isGstEligible ? formatMoney(value) : '-'); }, - Footer: ({ properties }: { properties: Api_LeasePayment[] }) => + Footer: ({ properties }: { properties: ApiGen_Concepts_Payment[] }) => isGstEligible ? formatMoney(properties.reduce((total, current) => total + (current?.amountGst ?? 0), 0)) : '-', @@ -135,7 +135,7 @@ export const getActualsColumns = ({ accessor: 'amountTotal', align: 'right', Cell: renderMoney, - Footer: ({ properties }: { properties: Api_LeasePayment[] }) => + Footer: ({ properties }: { properties: ApiGen_Concepts_Payment[] }) => formatMoney(properties.reduce((total, current) => total + (current?.amountTotal ?? 0), 0)), }, { diff --git a/source/frontend/src/features/leases/detail/LeasePages/payment/table/terms/TermsForm.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/payment/table/terms/TermsForm.test.tsx index 8349d4ce0b..d23a98690e 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/payment/table/terms/TermsForm.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/payment/table/terms/TermsForm.test.tsx @@ -82,7 +82,12 @@ describe('TermsForm component', () => { terms: [ { ...defaultTestFormLeaseTerm, - leasePmtFreqTypeCode: { id: frequency, description: frequency }, + leasePmtFreqTypeCode: { + id: frequency, + description: frequency, + displayOrder: null, + isDisabled: false, + }, isGstEligible: true, gstAmount: 50, }, @@ -140,7 +145,12 @@ describe('TermsForm component', () => { { ...defaultTestFormLeaseTerm, expiryDate: undefined as any, - leasePmtFreqTypeCode: { id: 'MONTHLY', description: 'MONTHLY' }, + leasePmtFreqTypeCode: { + id: 'MONTHLY', + description: 'MONTHLY', + displayOrder: null, + isDisabled: false, + }, }, ], }, @@ -159,7 +169,12 @@ describe('TermsForm component', () => { terms: [ { ...defaultTestFormLeaseTerm, - leasePmtFreqTypeCode: { id: 'MONTHLY', description: 'MONTHLY' }, + leasePmtFreqTypeCode: { + id: 'MONTHLY', + description: 'MONTHLY', + displayOrder: null, + isDisabled: false, + }, }, ], }, @@ -180,7 +195,12 @@ describe('TermsForm component', () => { terms: [ { ...defaultTestFormLeaseTerm, - leasePmtFreqTypeCode: { id: 'MONTHLY', description: 'MONTHLY' }, + leasePmtFreqTypeCode: { + id: 'MONTHLY', + description: 'MONTHLY', + displayOrder: null, + isDisabled: false, + }, paymentAmount: undefined as any, }, ], @@ -202,7 +222,12 @@ describe('TermsForm component', () => { terms: [ { ...defaultTestFormLeaseTerm, - leasePmtFreqTypeCode: { id: 'MONTHLY', description: 'MONTHLY' }, + leasePmtFreqTypeCode: { + id: 'MONTHLY', + description: 'MONTHLY', + displayOrder: null, + isDisabled: false, + }, isGstEligible: false, }, ], @@ -225,7 +250,12 @@ describe('TermsForm component', () => { terms: [ { ...defaultTestFormLeaseTerm, - leasePmtFreqTypeCode: { id: 'MONTHLY', description: 'MONTHLY' }, + leasePmtFreqTypeCode: { + id: 'MONTHLY', + description: 'MONTHLY', + displayOrder: null, + isDisabled: false, + }, isTermExercised: false, }, ], @@ -330,7 +360,12 @@ describe('TermsForm component', () => { { ...defaultTestFormLeaseTerm, isTermExercised: true, - statusTypeCode: { id: LeaseTermStatusTypes.EXERCISED }, + statusTypeCode: { + id: LeaseTermStatusTypes.EXERCISED, + description: null, + displayOrder: null, + isDisabled: false, + }, payments: [] as FormLeasePayment[], }, ], diff --git a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx index c42306ad03..41db38e535 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.test.tsx @@ -4,8 +4,9 @@ import noop from 'lodash/noop'; import { LeaseContextProvider } from '@/features/leases/context/LeaseContext'; import { usePropertyLeaseRepository } from '@/hooks/repositories/usePropertyLeaseRepository'; import { getMockApiProperty } from '@/mocks/properties.mock'; -import { Api_Lease, defaultApiLease } from '@/models/api/Lease'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { prettyFormatDate } from '@/utils'; import { render, RenderOptions, RenderResult, waitFor } from '@/utils/test-utils'; @@ -17,10 +18,12 @@ usePropertyLeaseRepository as jest.MockedFunction { - const setup = (renderOptions: RenderOptions & { lease?: Api_Lease } = {}): RenderResult => { + const setup = ( + renderOptions: RenderOptions & { lease?: ApiGen_Concepts_Lease } = {}, + ): RenderResult => { // render component under test const result = render( - + , { @@ -45,7 +48,7 @@ describe('Lease Surplus Declaration', () => { }); const result = setup({ lease: { - ...defaultApiLease, + ...defaultApiLease(), }, }); expect(result.asFragment()).toMatchSnapshot(); @@ -63,10 +66,15 @@ describe('Lease Surplus Declaration', () => { response: [ { property: getMockApiProperty(), - lease: null, - leaseId: 0, + file: null, + fileId: 0, leaseArea: null, areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, }, ], }, @@ -80,8 +88,8 @@ describe('Lease Surplus Declaration', () => { }); it('Default type value is unknown', async () => { - const testProperty: Api_Property = getMockApiProperty(); - testProperty.surplusDeclarationType = undefined; + const testProperty: ApiGen_Concepts_Property = getMockApiProperty(); + testProperty.surplusDeclarationType = null; ( usePropertyLeaseRepository as jest.MockedFunction ).mockReturnValue({ @@ -91,7 +99,18 @@ describe('Lease Surplus Declaration', () => { status: undefined, loading: false, response: [ - { property: testProperty, lease: null, leaseId: 0, leaseArea: null, areaUnitType: null }, + { + property: testProperty, + file: null, + fileId: 0, + leaseArea: null, + areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, + }, ], }, }); @@ -105,9 +124,14 @@ describe('Lease Surplus Declaration', () => { }); it('Values are displayed', async () => { - const testProperty: Api_Property = { ...getMockApiProperty(), pid: 1 }; + const testProperty: ApiGen_Concepts_Property = { ...getMockApiProperty(), pid: 1 }; testProperty.surplusDeclarationComment = 'Test Comment'; - testProperty.surplusDeclarationType = { id: 'YES', isDisabled: false, description: 'Yes' }; + testProperty.surplusDeclarationType = { + id: 'YES', + isDisabled: false, + description: 'Yes', + displayOrder: null, + }; testProperty.surplusDeclarationDate = '2021-01-01'; ( usePropertyLeaseRepository as jest.MockedFunction @@ -118,7 +142,18 @@ describe('Lease Surplus Declaration', () => { status: undefined, loading: false, response: [ - { property: testProperty, lease: null, leaseId: 0, leaseArea: null, areaUnitType: null }, + { + property: testProperty, + file: null, + fileId: 0, + leaseArea: null, + areaUnitType: null, + displayOrder: null, + id: 0, + propertyId: 0, + propertyName: null, + rowVersion: null, + }, ], }, }); diff --git a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.tsx b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.tsx index 2516b7a45b..728178876e 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/surplus/Surplus.tsx @@ -6,7 +6,7 @@ import { ColumnWithProps, Table } from '@/components/Table'; import { PidCell } from '@/components/Table/PidCell'; import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { usePropertyLeaseRepository } from '@/hooks/repositories/usePropertyLeaseRepository'; -import { prettyFormatDate, stringToFragment } from '@/utils'; +import { isValidIsoDateTime, prettyFormatDate, stringToFragment } from '@/utils'; interface IDeclaration { id?: number; @@ -69,7 +69,9 @@ const Surplus: React.FunctionComponent> = () => identifier: x?.property?.pid?.toString() ?? '', comments: x?.property?.surplusDeclarationComment || '', declarationType: x?.property?.surplusDeclarationType?.description || 'Unknown', - date: x?.property?.surplusDeclarationDate || '', + date: isValidIsoDateTime(x?.property?.surplusDeclarationDate) + ? x.property!.surplusDeclarationDate + : '', }; }); diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.test.tsx index c5d48eff77..9260bd0efd 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.test.tsx @@ -10,12 +10,16 @@ import { LeaseFormModel } from '@/features/leases/models'; import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useLeaseTenantRepository } from '@/hooks/repositories/useLeaseTenantRepository'; import { + getEmptyPerson, getMockContactOrganizationWithMultiplePeople, getMockContactOrganizationWithOnePerson, } from '@/mocks/contacts.mock'; import { getMockApiLease } from '@/mocks/lease.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_Lease, defaultApiLease } from '@/models/api/Lease'; +import { getEmptyOrganization } from '@/mocks/organization.mock'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; +import { defaultApiLease, getEmptyBaseAudit } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { mockKeycloak, renderAsync } from '@/utils/test-utils'; @@ -31,7 +35,7 @@ jest.mock('@/features/leases/hooks/useUpdateLease'); jest.mock('@/hooks/repositories/useLeaseTenantRepository'); const getPersonConcept = jest.fn(); -const updateTenants = jest.fn().mockResolvedValue({ ...defaultApiLease, id: 1 }); +const updateTenants = jest.fn().mockResolvedValue({ ...defaultApiLease(), id: 1 }); const onEdit = jest.fn(); const onSuccess = jest.fn(); @@ -52,7 +56,7 @@ const View = (props: IAddLeaseTenantFormProps & IPrimaryContactWarningModalProps }; const getLeaseTenantsObj = { - execute: jest.fn().mockResolvedValue(defaultApiLease.tenants), + execute: jest.fn().mockResolvedValue(defaultApiLease().tenants), loading: false, error: undefined, response: [], @@ -60,11 +64,11 @@ const getLeaseTenantsObj = { describe('AddLeaseTenantContainer component', () => { const setup = async ( - renderOptions: RenderOptions & { lease?: Api_Lease; tenants?: FormTenant[] } = {}, + renderOptions: RenderOptions & { lease?: ApiGen_Concepts_Lease; tenants?: FormTenant[] } = {}, ) => { // render component under test const component = await renderAsync( - + { viewProps.setSelectedTenants([ { ...getMockContactOrganizationWithOnePerson(), - organization: { organizationPersons: [{ person: {} }] }, + organization: { + ...getEmptyOrganization(), + organizationPersons: [ + { person: getEmptyPerson(), organizationId: 1, personId: 2, rowVersion: null }, + ], + }, }, ]); expect(getPersonConcept).not.toHaveBeenCalled(); @@ -212,15 +221,19 @@ describe('AddLeaseTenantContainer component', () => { await waitFor(async () => { expect(updateTenants).toHaveBeenCalledTimes(1); expect(onEdit).toHaveBeenCalledWith(false); - expect(updateTenants.mock.calls[0][1][0]).toStrictEqual({ + expect(updateTenants.mock.calls[0][1][0]).toStrictEqual({ personId: 1, - organizationId: undefined, - lessorType: undefined, - tenantTypeCode: undefined, - primaryContactId: undefined, - note: undefined, - rowVersion: undefined, + person: null, + organizationId: null, + organization: null, + lessorType: null, + tenantTypeCode: null, + primaryContactId: null, + note: null, leaseId: 0, + leaseTenantId: null, + primaryContact: null, + ...getEmptyBaseAudit(), }); }); }); diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.tsx index 25a84fc814..9b7972a68f 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantContainer.tsx @@ -8,9 +8,10 @@ import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useLeaseTenantRepository } from '@/hooks/repositories/useLeaseTenantRepository'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; import { IContactSearchResult } from '@/interfaces'; -import { Api_Lease } from '@/models/api/Lease'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists, isValidId } from '@/utils/utils'; import { IAddLeaseTenantFormProps } from './AddLeaseTenantForm'; import { FormTenant } from './models'; @@ -50,9 +51,9 @@ export const AddLeaseTenantContainer: React.FunctionComponent< const tenantFunc = async () => { const tenants = await getLeaseTenants(leaseId ?? 0); if (tenants !== undefined) { - setTenants(tenants.map((t: Api_LeaseTenant) => new FormTenant(t))); + setTenants(tenants.map((t: ApiGen_Concepts_LeaseTenant) => new FormTenant(t))); setSelectedContacts( - tenants.map((t: Api_LeaseTenant) => + tenants.map((t: ApiGen_Concepts_LeaseTenant) => FormTenant.toContactSearchResult(new FormTenant(t)), ) || [], ); @@ -101,12 +102,12 @@ export const AddLeaseTenantContainer: React.FunctionComponent< setTenants([...formTenants, ...matchingExistingTenants]); }; - const submit = async (leaseToUpdate: Api_Lease) => { - if (leaseToUpdate?.id) { + const submit = async (leaseToUpdate: ApiGen_Concepts_Lease) => { + if (isValidId(leaseToUpdate.id)) { try { const updatedTenants = await updateLeaseTenants.execute( - leaseToUpdate?.id, - leaseToUpdate.tenants, + leaseToUpdate.id, + leaseToUpdate.tenants ?? [], ); if (!!updatedTenants) { formikRef?.current?.resetForm({ @@ -157,17 +158,17 @@ export const AddLeaseTenantContainer: React.FunctionComponent< // get a unique list of all tenant organization person-ids that are associated to organization tenants. // in the case of a duplicate organization person, prefers tenants that have the person field non-null. const getTenantOrganizationPersonList = (tenants?: IContactSearchResult[]) => { - const personList: { person?: Api_Person; personId: number }[] = []; + const personList: { person?: ApiGen_Concepts_Person; personId: number }[] = []; // put any tenants that have non-null organization person first to ensure that the de-duplication logic below will maintain that value. tenants = orderBy( tenants, - t => some(t?.organization?.organizationPersons, op => op.person !== undefined), + t => some(t?.organization?.organizationPersons, op => exists(op.person)), 'desc', ); tenants?.forEach(tenant => tenant?.organization?.organizationPersons?.forEach(op => { - if (op.personId !== undefined && !find(personList, p => p.personId === op.personId)) { - personList.push({ person: op?.person, personId: op?.personId }); + if (isValidId(op.personId) && !find(personList, p => p.personId === op.personId)) { + personList.push({ person: op?.person ?? undefined, personId: op?.personId }); } }), ); diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantForm.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantForm.test.tsx index c95a191d67..9ddef09f97 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantForm.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/AddLeaseTenantForm.test.tsx @@ -3,12 +3,14 @@ import { createMemoryHistory } from 'history'; import React from 'react'; import { Claims } from '@/constants/claims'; -import { IPagedItems } from '@/interfaces'; +import { IContactSearchResult, IPagedItems } from '@/interfaces'; import { + getEmptyPerson, getMockContactOrganizationWithOnePerson, getMockContactPerson, } from '@/mocks/contacts.mock'; import { mockLookups } from '@/mocks/index.mock'; +import { getEmptyOrganization } from '@/mocks/organization.mock'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { mockKeycloak, renderAsync, RenderOptions, userEvent } from '@/utils/test-utils'; @@ -159,9 +161,9 @@ describe('AddLeaseTenantForm component', () => { }); it('displays no contacts available if organization has no contacts', async () => { - const organization = { + const organization: IContactSearchResult = { ...getMockContactOrganizationWithOnePerson(), - organization: { organizationPersons: [] }, + organization: { ...getEmptyOrganization(), organizationPersons: [] }, }; await setup({ @@ -174,9 +176,9 @@ describe('AddLeaseTenantForm component', () => { }); it('displays no contacts available if organization has no contacts', async () => { - const organization = { + const organization: IContactSearchResult = { ...getMockContactOrganizationWithOnePerson(), - organization: { organizationPersons: [] }, + organization: { ...getEmptyOrganization(), organizationPersons: [] }, }; await setup({ @@ -189,9 +191,9 @@ describe('AddLeaseTenantForm component', () => { }); it('displays no contacts available if organization has no contacts', async () => { - const organization = { + const organization: IContactSearchResult = { ...getMockContactOrganizationWithOnePerson(), - organization: { organizationPersons: [] }, + organization: { ...getEmptyOrganization(), organizationPersons: [] }, }; await setup({ @@ -204,16 +206,16 @@ describe('AddLeaseTenantForm component', () => { }); it('displays the primary contact if there is only one', async () => { - const organization = { + const organization: IContactSearchResult = { ...getMockContactOrganizationWithOnePerson(), organization: { + ...getEmptyOrganization(), organizationPersons: [ { personId: 3, organizationId: 3, - isDisabled: false, rowVersion: 1, - person: { firstName: 'test', surname: 'testerson' }, + person: { ...getEmptyPerson(), firstName: 'test', surname: 'testerson' }, }, ], }, @@ -229,23 +231,22 @@ describe('AddLeaseTenantForm component', () => { }); it('displays a list if there are multiple', async () => { - const organization = { + const organization: IContactSearchResult = { ...getMockContactOrganizationWithOnePerson(), organization: { + ...getEmptyOrganization(), organizationPersons: [ { personId: 3, organizationId: 3, - isDisabled: false, rowVersion: 1, - person: { firstName: 'test', surname: 'testerson' }, + person: { ...getEmptyPerson(), firstName: 'test', surname: 'testerson' }, }, { personId: 2, organizationId: 3, - isDisabled: false, rowVersion: 1, - person: { firstName: 'second', surname: 'testerson' }, + person: { ...getEmptyPerson(), firstName: 'second', surname: 'testerson' }, }, ], }, diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/PrimaryContactWarningModal.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/PrimaryContactWarningModal.test.tsx index 1e7a77bb8f..3af60fb09b 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/PrimaryContactWarningModal.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/PrimaryContactWarningModal.test.tsx @@ -2,9 +2,9 @@ import { createMemoryHistory } from 'history'; import { noop } from 'lodash'; import { LeaseFormModel } from '@/features/leases/models'; -import { mockApiPerson, mockOrganization } from '@/mocks/filterData.mock'; -import { getMockApiLease } from '@/mocks/lease.mock'; -import { defaultApiLease } from '@/models/api/Lease'; +import { mockApiOrganization, mockApiPerson } from '@/mocks/filterData.mock'; +import { getEmptyLeaseTenant, getMockApiLease } from '@/mocks/lease.mock'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { render, RenderOptions, screen, userEvent } from '@/utils/test-utils'; import { FormTenant } from './models'; @@ -35,10 +35,10 @@ describe('PrimaryContactWarningModal component', () => { it('renders as expected', () => { const { component } = setup({ tenants: LeaseFormModel.fromApi({ - ...defaultApiLease, + ...defaultApiLease(), tenants: [ - { leaseId: 1, person: mockApiPerson }, - { leaseId: 1, organization: mockOrganization }, + { ...getEmptyLeaseTenant(), leaseId: 1, person: mockApiPerson }, + { ...getEmptyLeaseTenant(), leaseId: 1, organization: mockApiOrganization }, ], }).tenants, }); @@ -49,10 +49,10 @@ describe('PrimaryContactWarningModal component', () => { const { component } = setup({ saveCallback: saveCallback, tenants: LeaseFormModel.fromApi({ - ...defaultApiLease, + ...defaultApiLease(), tenants: [ - { leaseId: 1, person: mockApiPerson }, - { leaseId: 1, person: mockApiPerson }, + { ...getEmptyLeaseTenant(), leaseId: 1, person: mockApiPerson }, + { ...getEmptyLeaseTenant(), leaseId: 1, person: mockApiPerson }, ], }).tenants, }); @@ -69,9 +69,9 @@ describe('PrimaryContactWarningModal component', () => { ...getMockApiLease(), tenants: [ { - ...getMockApiLease().tenants[0], - primaryContactId: undefined, - primaryContact: undefined, + ...getMockApiLease().tenants![0], + primaryContactId: null, + primaryContact: null, }, ], }).tenants, diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantContainer.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantContainer.tsx index decb288a6d..9daedf9b8a 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantContainer.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantContainer.tsx @@ -7,7 +7,7 @@ import { LeaseStateContext } from '@/features/leases/context/LeaseContext'; import { LeaseFormModel } from '@/features/leases/models'; import { LeasePageProps } from '@/features/mapSideBar/lease/LeaseContainer'; import { useLeaseTenantRepository } from '@/hooks/repositories/useLeaseTenantRepository'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; import AddLeaseTenantContainer from './AddLeaseTenantContainer'; import AddLeaseTenantForm from './AddLeaseTenantForm'; @@ -28,7 +28,7 @@ const TenantContainer: React.FunctionComponent new FormTenant(t)) ?? []; + const formTenants = tenants?.map((t: ApiGen_Concepts_LeaseTenant) => new FormTenant(t)) ?? []; return !!isEditing ? ( diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantOrganizationContactInfo.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantOrganizationContactInfo.tsx index 7f672677e2..dcc4f1db6f 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantOrganizationContactInfo.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/TenantOrganizationContactInfo.tsx @@ -28,7 +28,7 @@ export const TenantOrganizationContactInfo: React.FunctionComponent< let primaryContact = tenant?.initialPrimaryContact; if (primaryContact?.id !== tenant?.primaryContactId) { primaryContact = tenant?.primaryContactId - ? getPrimaryContact(tenant?.primaryContactId, tenant) + ? getPrimaryContact(tenant?.primaryContactId, tenant) ?? undefined : undefined; } const primaryContactName = formatApiPersonNames(primaryContact); diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/ViewTenantForm.test.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/ViewTenantForm.test.tsx index ae833ee06a..e8d9746fa3 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/ViewTenantForm.test.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/ViewTenantForm.test.tsx @@ -1,9 +1,10 @@ import { createMemoryHistory } from 'history'; import { LeaseContextProvider } from '@/features/leases/context/LeaseContext'; -import { mockApiPerson, mockOrganization } from '@/mocks/filterData.mock'; -import { getMockApiLease } from '@/mocks/lease.mock'; -import { Api_Lease, defaultApiLease } from '@/models/api/Lease'; +import { mockApiOrganization, mockApiPerson, mockOrganization } from '@/mocks/filterData.mock'; +import { getEmptyLeaseTenant, getMockApiLease } from '@/mocks/lease.mock'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { render, RenderOptions } from '@/utils/test-utils'; import { FormTenant } from './models'; @@ -13,12 +14,14 @@ const history = createMemoryHistory(); describe('Tenant component', () => { const setup = ( - renderOptions: RenderOptions & ITenantProps & { lease?: Api_Lease } = { tenants: [] }, + renderOptions: RenderOptions & ITenantProps & { lease?: ApiGen_Concepts_Lease } = { + tenants: [], + }, ) => { // render component under test const component = render( , @@ -35,10 +38,10 @@ describe('Tenant component', () => { it('renders as expected', () => { const { component } = setup({ lease: { - ...defaultApiLease, + ...defaultApiLease(), tenants: [ - { leaseId: 1, person: mockApiPerson }, - { leaseId: 1, organization: mockOrganization }, + { ...getEmptyLeaseTenant(), leaseId: 1, person: mockApiPerson }, + { ...getEmptyLeaseTenant(), leaseId: 1, organization: mockApiOrganization }, ], }, tenants: [], @@ -48,11 +51,15 @@ describe('Tenant component', () => { it('renders one Person Tenant section per person', () => { const { component } = setup({ lease: { - ...defaultApiLease, + ...defaultApiLease(), }, tenants: [ { leaseId: 1, personId: mockApiPerson.id, note: 'person note' }, - { leaseId: 1, organizationId: mockOrganization.id, note: 'organization id' }, + { + leaseId: 1, + organizationId: mockOrganization.id, + note: 'organization id', + }, ], }); const { getAllByText } = component; @@ -65,10 +72,10 @@ describe('Tenant component', () => { it.skip('renders one notes section per tenant note', () => { const { component } = setup({ lease: { - ...defaultApiLease, + ...defaultApiLease(), tenants: [ - { leaseId: 1, person: mockApiPerson }, - { leaseId: 1, organization: mockOrganization }, + { ...getEmptyLeaseTenant(), leaseId: 1, person: mockApiPerson }, + { ...getEmptyLeaseTenant(), leaseId: 1, organization: mockApiOrganization }, ], }, tenants: [ @@ -84,7 +91,7 @@ describe('Tenant component', () => { it('renders assignee section', () => { const { component } = setup({ - lease: { ...defaultApiLease, tenants: [] }, + lease: { ...defaultApiLease(), tenants: [] }, tenants: [], }); const { getAllByText } = component; @@ -95,7 +102,7 @@ describe('Tenant component', () => { it('renders representative section', () => { const { component } = setup({ - lease: { ...defaultApiLease, tenants: [] }, + lease: { ...defaultApiLease(), tenants: [] }, tenants: [], }); const { getAllByText } = component; @@ -106,7 +113,7 @@ describe('Tenant component', () => { it('renders property manager section', () => { const { component } = setup({ - lease: { ...defaultApiLease, tenants: [] }, + lease: { ...defaultApiLease(), tenants: [] }, tenants: [], }); const { getAllByText } = component; @@ -116,7 +123,7 @@ describe('Tenant component', () => { }); it('renders unknown section', () => { const { component } = setup({ - lease: { ...defaultApiLease, tenants: [] }, + lease: { ...defaultApiLease(), tenants: [] }, tenants: [], }); const { getAllByText } = component; diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/columns.tsx b/source/frontend/src/features/leases/detail/LeasePages/tenant/columns.tsx index 20e1fc3bc3..386d17c76c 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/columns.tsx +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/columns.tsx @@ -7,6 +7,7 @@ import { ReactComponent as Inactive } from '@/assets/images/inactive.svg'; import { Select, SelectOption } from '@/components/common/form'; import { ColumnWithProps } from '@/components/Table'; import { getPrimaryContact } from '@/features/contacts/contactUtils'; +import { isValidId } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { FormTenant } from './models'; @@ -30,7 +31,7 @@ const getColumns = (tenantTypes: SelectOption[]): ColumnWithProps[] width: 20, maxWidth: 20, Cell: (props: CellProps) => - props.row.original.personId !== undefined ? ( + isValidId(props.row.original.personId) ? ( ) : ( @@ -61,7 +62,7 @@ const getColumns = (tenantTypes: SelectOption[]): ColumnWithProps[] let primaryContact = original.initialPrimaryContact; if (original.primaryContactId !== original.initialPrimaryContact?.id) { primaryContact = original.primaryContactId - ? getPrimaryContact(original.primaryContactId, original) + ? getPrimaryContact(original.primaryContactId, original) ?? undefined : undefined; } const primaryContactOptions: SelectOption[] = @@ -69,7 +70,7 @@ const getColumns = (tenantTypes: SelectOption[]): ColumnWithProps[] label: formatApiPersonNames(person), value: person?.id ?? 0, })) ?? []; - if (!!props?.row?.original?.personId) { + if (isValidId(props?.row?.original?.personId)) { return

    Not applicable

    ; } else if (persons?.length && persons?.length > 1) { return ( diff --git a/source/frontend/src/features/leases/detail/LeasePages/tenant/models.ts b/source/frontend/src/features/leases/detail/LeasePages/tenant/models.ts index 5a8a9f6441..7cd36ec3a1 100644 --- a/source/frontend/src/features/leases/detail/LeasePages/tenant/models.ts +++ b/source/frontend/src/features/leases/detail/LeasePages/tenant/models.ts @@ -4,14 +4,16 @@ import { getDefaultContact, } from '@/features/contacts/contactUtils'; import { IAddress, IContactSearchResult } from '@/interfaces'; -import ITypeCode from '@/interfaces/ITypeCode'; -import { Api_Address } from '@/models/api/Address'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; -import { Api_OrganizationPerson } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { getPreferredContactMethodValue } from '@/utils/contactMethodUtil'; -import { fromTypeCode, toTypeCode } from '@/utils/formUtils'; +import { fromTypeCode, toTypeCode, toTypeCodeNullable } from '@/utils/formUtils'; import { formatApiPersonNames } from '@/utils/personUtils'; +import { exists, isValidId } from '@/utils/utils'; export class FormAddress { public readonly country?: string; @@ -22,14 +24,14 @@ export class FormAddress { public readonly postal?: string; public readonly province?: string; - constructor(baseModel?: Api_Address) { - this.province = baseModel?.province?.description; - this.country = baseModel?.country?.description; - this.streetAddress1 = baseModel?.streetAddress1; - this.streetAddress2 = baseModel?.streetAddress2; - this.streetAddress3 = baseModel?.streetAddress3; - this.municipality = baseModel?.municipality; - this.postal = baseModel?.postal; + constructor(baseModel: ApiGen_Concepts_Address | null) { + this.province = baseModel?.province?.description ?? undefined; + this.country = baseModel?.country?.description ?? undefined; + this.streetAddress1 = baseModel?.streetAddress1 ?? undefined; + this.streetAddress2 = baseModel?.streetAddress2 ?? undefined; + this.streetAddress3 = baseModel?.streetAddress3 ?? undefined; + this.municipality = baseModel?.municipality ?? undefined; + this.postal = baseModel?.postal ?? undefined; } } @@ -48,10 +50,10 @@ export class FormTenant { public readonly landline?: string; public readonly mobile?: string; public readonly isDisabled?: boolean; - public readonly organizationPersons?: Api_OrganizationPerson[]; + public readonly organizationPersons?: ApiGen_Concepts_OrganizationPerson[]; public readonly primaryContactId?: number; - public readonly initialPrimaryContact?: Api_Person; - public readonly lessorTypeCode?: ITypeCode; + public readonly initialPrimaryContact?: ApiGen_Concepts_Person; + public readonly lessorTypeCode?: ApiGen_Base_CodeType; public readonly tenantType?: string; public readonly original?: IContactSearchResult; public readonly provinceState?: string; @@ -69,7 +71,15 @@ export class FormTenant { organization: !!model.organizationId ? { id: model.organizationId, - organizationPersons: model.organizationPersons, + organizationPersons: model.organizationPersons ?? null, + alias: null, + comment: null, + contactMethods: null, + incorporationNumber: null, + isDisabled: false, + name: null, + organizationAddresses: null, + rowVersion: null, } : undefined, mailingAddress: model.mailingAddress?.streetAddress1, @@ -83,53 +93,58 @@ export class FormTenant { ); }; - public static toApi(model: FormTenant): Api_LeaseTenant { + public static toApi(model: FormTenant): ApiGen_Concepts_LeaseTenant { return { - personId: model.personId, - organizationId: !model.personId ? model.organizationId : undefined, - lessorType: model.lessorTypeCode, - tenantTypeCode: toTypeCode(model.tenantType), - primaryContactId: !model.personId ? model.primaryContactId : undefined, - note: model.note, - rowVersion: model.rowVersion, + personId: model.personId ?? null, + organizationId: !isValidId(model.personId) ? model.organizationId ?? null : null, + lessorType: model.lessorTypeCode ?? null, + tenantTypeCode: toTypeCodeNullable(model.tenantType), + primaryContactId: !isValidId(model.personId) ? model.primaryContactId ?? null : null, + note: model.note ?? null, leaseId: model.leaseId ?? 0, + leaseTenantId: null, + organization: null, + person: null, + primaryContact: null, + ...getEmptyBaseAudit(model.rowVersion), }; } - constructor(apiModel?: Api_LeaseTenant, selectedContactModel?: IContactSearchResult) { - if (!!apiModel) { + constructor(apiModel?: ApiGen_Concepts_LeaseTenant, selectedContactModel?: IContactSearchResult) { + if (exists(apiModel)) { // convert an api tenant to a form tenant. const tenant = apiModel.person ?? apiModel.organization; - const address = !!tenant ? getApiPersonOrOrgMailingAddress(tenant) : undefined; + const address = !!tenant ? getApiPersonOrOrgMailingAddress(tenant) : null; this.id = apiModel.lessorType?.id === 'PER' ? `P${apiModel.personId}` : `O${apiModel.organizationId}`; - this.personId = apiModel.personId; - this.summary = apiModel.person ? formatApiPersonNames(tenant) : apiModel.organization?.name; + this.personId = apiModel.personId ?? undefined; + this.summary = exists(apiModel.person) + ? formatApiPersonNames(apiModel.person) + : apiModel.organization?.name ?? undefined; this.leaseId = apiModel.leaseId; - this.rowVersion = apiModel.rowVersion; - this.email = getPreferredContactMethodValue( - tenant?.contactMethods, - ContactMethodTypes.WorkEmail, - ContactMethodTypes.PersonalEmail, - ); + this.rowVersion = apiModel.rowVersion ?? undefined; + this.email = + getPreferredContactMethodValue( + tenant?.contactMethods, + ContactMethodTypes.WorkEmail, + ContactMethodTypes.PersonalEmail, + ) ?? undefined; this.mailingAddress = new FormAddress(address); this.municipalityName = address?.municipality ?? ''; this.note = apiModel.note ?? ''; - this.organizationPersons = apiModel?.organization?.organizationPersons; - this.organizationId = apiModel.organizationId; - this.landline = getPreferredContactMethodValue( - tenant?.contactMethods, - ContactMethodTypes.WorkPhone, - ); - this.mobile = getPreferredContactMethodValue( - tenant?.contactMethods, - ContactMethodTypes.WorkMobile, - ); - this.lessorTypeCode = apiModel.lessorType; - this.tenantType = fromTypeCode(apiModel.tenantTypeCode); - this.primaryContactId = apiModel.primaryContactId; - this.initialPrimaryContact = apiModel.primaryContact; - } else if (!!selectedContactModel) { + this.organizationPersons = apiModel?.organization?.organizationPersons ?? undefined; + this.organizationId = apiModel.organizationId ?? undefined; + this.landline = + getPreferredContactMethodValue(tenant?.contactMethods, ContactMethodTypes.WorkPhone) ?? + undefined; + this.mobile = + getPreferredContactMethodValue(tenant?.contactMethods, ContactMethodTypes.WorkMobile) ?? + undefined; + this.lessorTypeCode = apiModel.lessorType ?? undefined; + this.tenantType = fromTypeCode(apiModel.tenantTypeCode) ?? undefined; + this.primaryContactId = apiModel.primaryContactId ?? undefined; + this.initialPrimaryContact = apiModel.primaryContact ?? undefined; + } else if (exists(selectedContactModel)) { // In this case, construct a tenant using a contact. const primaryContact = getDefaultContact(selectedContactModel.organization); this.id = selectedContactModel?.id; @@ -142,11 +157,12 @@ export class FormTenant { this.organizationId = selectedContactModel.organizationId; this.landline = selectedContactModel.landline; this.mobile = selectedContactModel.mobile; - this.lessorTypeCode = !!this.personId ? { id: 'PER' } : { id: 'ORG' }; + this.lessorTypeCode = isValidId(this.personId) ? toTypeCode('PER') : toTypeCode('ORG'); this.tenantType = selectedContactModel.tenantType; - this.organizationPersons = selectedContactModel?.organization?.organizationPersons; + this.organizationPersons = + selectedContactModel?.organization?.organizationPersons ?? undefined; this.primaryContactId = primaryContact?.id; - this.initialPrimaryContact = primaryContact; + this.initialPrimaryContact = primaryContact ?? undefined; this.original = selectedContactModel; this.provinceState = selectedContactModel.provinceState; } diff --git a/source/frontend/src/features/leases/detail/LeaseStatusSummary.test.tsx b/source/frontend/src/features/leases/detail/LeaseStatusSummary.test.tsx index 366c651c27..6a29014073 100644 --- a/source/frontend/src/features/leases/detail/LeaseStatusSummary.test.tsx +++ b/source/frontend/src/features/leases/detail/LeaseStatusSummary.test.tsx @@ -1,7 +1,7 @@ import { createMemoryHistory } from 'history'; import moment from 'moment'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { render, RenderOptions } from '@/utils/test-utils'; import { ILeaseStatusSummaryProps, LeaseStatusSummary } from './LeaseStatusSummary'; @@ -27,14 +27,14 @@ describe('LeaseStatusSummary component', () => { it('renders as expected when active', () => { const futureExpiry = moment().add(1, 'days'); const { component } = setup({ - lease: { ...defaultApiLease, expiryDate: futureExpiry.format('YYYY-MM-DD') }, + lease: { ...defaultApiLease(), expiryDate: futureExpiry.format('YYYY-MM-DD') }, }); expect(component.asFragment()).toMatchSnapshot(); }); it('renders as expected when inactive', () => { const pastExpiry = moment().subtract(1, 'days'); const { component } = setup({ - lease: { ...defaultApiLease, expiryDate: pastExpiry.format('YYYY-MM-DD') }, + lease: { ...defaultApiLease(), expiryDate: pastExpiry.format('YYYY-MM-DD') }, }); expect(component.asFragment()).toMatchSnapshot(); }); @@ -42,7 +42,7 @@ describe('LeaseStatusSummary component', () => { it('displays the lFileNo when provided', () => { const { component: { getByText }, - } = setup({ lease: { ...defaultApiLease, lFileNo: '111-222-333' } }); + } = setup({ lease: { ...defaultApiLease(), lFileNo: '111-222-333' } }); expect(getByText('111-222-333')).toBeVisible(); }); }); diff --git a/source/frontend/src/features/leases/detail/LeaseStatusSummary.tsx b/source/frontend/src/features/leases/detail/LeaseStatusSummary.tsx index 842c8449dc..5a93bda2fd 100644 --- a/source/frontend/src/features/leases/detail/LeaseStatusSummary.tsx +++ b/source/frontend/src/features/leases/detail/LeaseStatusSummary.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import styled from 'styled-components'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; export interface ILeaseStatusSummaryProps { - lease?: Api_Lease; + lease?: ApiGen_Concepts_Lease; } /** @@ -15,8 +15,8 @@ export const LeaseStatusSummary: React.FunctionComponent< React.PropsWithChildren > = ({ lease }) => { return !!lease ? ( - - {lease?.statusType?.description} + + {lease?.fileStatusTypeCode?.description} {lease?.lFileNo ?? ''} ) : ( diff --git a/source/frontend/src/features/leases/detail/StackedTenantFields.test.tsx b/source/frontend/src/features/leases/detail/StackedTenantFields.test.tsx index b6e9f5eacb..3ae99068ab 100644 --- a/source/frontend/src/features/leases/detail/StackedTenantFields.test.tsx +++ b/source/frontend/src/features/leases/detail/StackedTenantFields.test.tsx @@ -1,5 +1,7 @@ import { createMemoryHistory } from 'history'; +import { getEmptyPerson } from '@/mocks/contacts.mock'; +import { getEmptyLeaseTenant } from '@/mocks/lease.mock'; import { render, RenderOptions } from '@/utils/test-utils'; import StackedPidTenantFields, { IStackedTenantFieldsProps } from './StackedTenantFields'; @@ -30,8 +32,10 @@ describe('StackedPidTenantFields component', () => { const { component } = setup({ tenants: [ { + ...getEmptyLeaseTenant(), leaseId: 1, - person: { firstName: 'First', surname: 'Last' }, + personId: 1, + person: { ...getEmptyPerson(), id: 1, firstName: 'First', surname: 'Last' }, }, ], }); @@ -42,7 +46,19 @@ describe('StackedPidTenantFields component', () => { const { component: { getByText }, } = setup({ - tenants: [{ leaseId: 1, person: { firstName: 'tenantFirst', surname: 'tenantSurname' } }], + tenants: [ + { + ...getEmptyLeaseTenant(), + leaseId: 1, + personId: 1, + person: { + ...getEmptyPerson(), + id: 1, + firstName: 'tenantFirst', + surname: 'tenantSurname', + }, + }, + ], }); expect(getByText('tenantFirst tenantSurname')).toBeVisible(); }); diff --git a/source/frontend/src/features/leases/detail/StackedTenantFields.tsx b/source/frontend/src/features/leases/detail/StackedTenantFields.tsx index 35b78d2a48..15b8b57287 100644 --- a/source/frontend/src/features/leases/detail/StackedTenantFields.tsx +++ b/source/frontend/src/features/leases/detail/StackedTenantFields.tsx @@ -3,11 +3,12 @@ import styled from 'styled-components'; import OverflowTip from '@/components/common/OverflowTip'; import { InlineFlexDiv } from '@/components/common/styles'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; import { getAllNames } from '../leaseUtils'; + export interface IStackedTenantFieldsProps { - tenants?: Api_LeaseTenant[]; + tenants?: ApiGen_Concepts_LeaseTenant[]; } /** diff --git a/source/frontend/src/features/leases/hooks/useAddLease.test.tsx b/source/frontend/src/features/leases/hooks/useAddLease.test.tsx index 06f24d4633..d44f7daf6a 100644 --- a/source/frontend/src/features/leases/hooks/useAddLease.test.tsx +++ b/source/frontend/src/features/leases/hooks/useAddLease.test.tsx @@ -7,7 +7,7 @@ import configureMockStore, { MockStoreEnhanced } from 'redux-mock-store'; import thunk from 'redux-thunk'; import * as MOCK from '@/mocks/data.mock'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { useAddLease } from './useAddLease'; @@ -40,21 +40,21 @@ describe('useAddLease functions', () => { }); describe('addLease', () => { it('Request successful, dispatches success with correct response', async () => { - mockAxios.onPost().reply(200, defaultApiLease); + mockAxios.onPost().reply(200, defaultApiLease()); const { addLease } = setup(); - const leaseResponse = await addLease.execute(defaultApiLease, []); + const leaseResponse = await addLease.execute(defaultApiLease(), []); expect(find(currentStore.getActions(), { type: 'loading-bar/SHOW' })).toBeDefined(); expect(find(currentStore.getActions(), { type: 'network/logError' })).toBeUndefined(); - expect(leaseResponse).toEqual(defaultApiLease); + expect(leaseResponse).toEqual(defaultApiLease()); }); it('Request failure, dispatches error with correct response', async () => { mockAxios.onPost().reply(400, MOCK.ERROR); const { addLease } = setup(); - expect(async () => await addLease.execute(defaultApiLease, [])).rejects.toThrow(); + expect(async () => await addLease.execute(defaultApiLease(), [])).rejects.toThrow(); }); }); }); diff --git a/source/frontend/src/features/leases/hooks/useAddLease.ts b/source/frontend/src/features/leases/hooks/useAddLease.ts index 44030a66d5..4ec4933e44 100644 --- a/source/frontend/src/features/leases/hooks/useAddLease.ts +++ b/source/frontend/src/features/leases/hooks/useAddLease.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { useApiLeases } from '@/hooks/pims-api/useApiLeases'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; /** @@ -14,12 +14,12 @@ export const useAddLease = () => { const addLease = useApiRequestWrapper< ( - lease: Api_Lease, + lease: ApiGen_Concepts_Lease, userOverrideCodes: UserOverrideCode[], - ) => Promise> + ) => Promise> >({ requestFunction: useCallback( - async (lease: Api_Lease, userOverrideCodes: UserOverrideCode[] = []) => + async (lease: ApiGen_Concepts_Lease, userOverrideCodes: UserOverrideCode[] = []) => await postLease(lease, userOverrideCodes), [postLease], ), diff --git a/source/frontend/src/features/leases/hooks/useLeaseDetail.ts b/source/frontend/src/features/leases/hooks/useLeaseDetail.ts index 5ff359984a..06a0583f62 100644 --- a/source/frontend/src/features/leases/hooks/useLeaseDetail.ts +++ b/source/frontend/src/features/leases/hooks/useLeaseDetail.ts @@ -8,7 +8,7 @@ import { useLeaseTermRepository } from '@/hooks/repositories/useLeaseTermReposit import { usePropertyLeaseRepository } from '@/hooks/repositories/usePropertyLeaseRepository'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { useAxiosErrorHandler } from '@/utils'; import { LeaseStateContext } from './../context/LeaseContext'; @@ -55,10 +55,10 @@ export function useLeaseDetail(leaseId?: number) { leaseTermsPromise, ]); if (!!lease) { - const mergedLeases: Api_Lease = { + const mergedLeases: ApiGen_Concepts_Lease = { ...lease, tenants: leaseTenants ?? [], - properties: propertyLeases ?? [], + fileProperties: propertyLeases ?? [], terms: leaseTerms ?? [], }; setLease(mergedLeases); diff --git a/source/frontend/src/features/leases/hooks/useUpdateLease.test.tsx b/source/frontend/src/features/leases/hooks/useUpdateLease.test.tsx index af77137898..9ae01b6844 100644 --- a/source/frontend/src/features/leases/hooks/useUpdateLease.test.tsx +++ b/source/frontend/src/features/leases/hooks/useUpdateLease.test.tsx @@ -7,7 +7,7 @@ import configureMockStore, { MockStoreEnhanced } from 'redux-mock-store'; import thunk from 'redux-thunk'; import * as MOCK from '@/mocks/data.mock'; -import { defaultApiLease } from '@/models/api/Lease'; +import { defaultApiLease } from '@/models/defaultInitializers'; import { useUpdateLease } from './useUpdateLease'; @@ -33,7 +33,7 @@ const setup = (values?: any) => { const { result } = renderHook(useUpdateLease, { wrapper: getWrapper(getStore(values)) }); return result.current; }; -const defaultLeaseWithId = { ...defaultApiLease, id: 1 }; +const defaultLeaseWithId = { ...defaultApiLease(), id: 1 }; describe('useUpdateLease functions', () => { afterAll(() => { diff --git a/source/frontend/src/features/leases/hooks/useUpdateLease.ts b/source/frontend/src/features/leases/hooks/useUpdateLease.ts index a72b905d02..68ea39225c 100644 --- a/source/frontend/src/features/leases/hooks/useUpdateLease.ts +++ b/source/frontend/src/features/leases/hooks/useUpdateLease.ts @@ -3,7 +3,7 @@ import { useCallback } from 'react'; import { useApiLeases } from '@/hooks/pims-api/useApiLeases'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; /** @@ -15,12 +15,12 @@ export const useUpdateLease = () => { const updateApiLease = useApiRequestWrapper< ( - lease: Api_Lease, + lease: ApiGen_Concepts_Lease, userOverrideCodes: UserOverrideCode[], - ) => Promise> + ) => Promise> >({ requestFunction: useCallback( - async (lease: Api_Lease, userOverrideCodes: UserOverrideCode[] = []) => + async (lease: ApiGen_Concepts_Lease, userOverrideCodes: UserOverrideCode[] = []) => await putApiLease(lease, userOverrideCodes), [putApiLease], ), diff --git a/source/frontend/src/features/leases/leaseUtils.ts b/source/frontend/src/features/leases/leaseUtils.ts index db6e324744..2a0fedb51c 100644 --- a/source/frontend/src/features/leases/leaseUtils.ts +++ b/source/frontend/src/features/leases/leaseUtils.ts @@ -1,13 +1,14 @@ -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; +import { isValidId } from '@/utils'; import { formatNames } from '@/utils/personUtils'; /** * return all of the person tenant names and organization tenant names of this lease * @param lease */ -export const getAllNames = (tenants: Api_LeaseTenant[]): string[] => { - const persons = tenants?.filter(t => t.personId !== null).map(p => p.person) ?? []; - const organizations = tenants?.filter(t => t.personId === null).map(p => p.organization) ?? []; +export const getAllNames = (tenants: ApiGen_Concepts_LeaseTenant[]): string[] => { + const persons = tenants?.filter(t => isValidId(t.personId)).map(p => p.person) ?? []; + const organizations = tenants?.filter(t => !isValidId(t.personId)).map(p => p.organization) ?? []; const allNames = persons ?.map(p => formatNames([p?.firstName, p?.middleNames, p?.surname])) diff --git a/source/frontend/src/features/leases/models.ts b/source/frontend/src/features/leases/models.ts index e135e0f3bb..e77905d18a 100644 --- a/source/frontend/src/features/leases/models.ts +++ b/source/frontend/src/features/leases/models.ts @@ -1,23 +1,25 @@ import { IMapProperty } from '@/components/propertySelector/models'; import { PropertyAreaUnitTypes } from '@/constants/index'; import { IAutocompletePrediction } from '@/interfaces'; -import { Api_Lease, Api_LeaseConsultation } from '@/models/api/Lease'; -import { Api_Project } from '@/models/api/Project'; -import { Api_PropertyLease } from '@/models/api/PropertyLease'; +import { ApiGen_Concepts_ConsultationLease } from '@/models/api/generated/ApiGen_Concepts_ConsultationLease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; +import { ApiGen_Concepts_PropertyLease } from '@/models/api/generated/ApiGen_Concepts_PropertyLease'; +import { EpochIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { ILookupCode } from '@/store/slices/lookupCodes/interfaces/ILookupCode'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; +import { exists, isValidId, isValidIsoDateTime, isValidString } from '@/utils'; import { emptyStringtoNullable, fromTypeCode, stringToNull, stringToUndefined, - toTypeCode, + toTypeCodeNullable, } from '@/utils/formUtils'; import { PropertyForm } from '../mapSideBar/shared/models'; import { FormLeaseDeposit } from './detail/LeasePages/deposits/models/FormLeaseDeposit'; import { FormLeaseDepositReturn } from './detail/LeasePages/deposits/models/FormLeaseDepositReturn'; -import { FormInsurance } from './detail/LeasePages/insurance/edit/models'; import { FormLeaseTerm } from './detail/LeasePages/payment/models'; import { FormTenant } from './detail/LeasePages/tenant/models'; @@ -57,7 +59,6 @@ export class LeaseFormModel { hasPhysicalLicense?: boolean; hasDigitalLicense?: boolean; project?: IAutocompletePrediction; - apiProject: Api_Project | null = null; tenantNotes: string[] = []; properties: FormLeaseProperty[] = []; consultations: FormLeaseConsultation[] = []; @@ -65,18 +66,17 @@ export class LeaseFormModel { securityDepositReturns: FormLeaseDepositReturn[] = []; terms: FormLeaseTerm[] = []; tenants: FormTenant[] = []; - insurances: FormInsurance[] = []; rowVersion: number = 0; - static fromApi(apiModel?: Api_Lease): LeaseFormModel { + static fromApi(apiModel?: ApiGen_Concepts_Lease): LeaseFormModel { const leaseDetail = new LeaseFormModel(); leaseDetail.id = apiModel?.id ?? undefined; leaseDetail.lFileNo = apiModel?.lFileNo || ''; leaseDetail.psFileNo = apiModel?.psFileNo || ''; leaseDetail.tfaFileNumber = apiModel?.tfaFileNumber || ''; - leaseDetail.expiryDate = apiModel?.expiryDate || ''; - leaseDetail.startDate = apiModel?.startDate || ''; + leaseDetail.expiryDate = isValidIsoDateTime(apiModel?.expiryDate) ? apiModel!.expiryDate : ''; + leaseDetail.startDate = isValidIsoDateTime(apiModel?.startDate) ? apiModel!.startDate : ''; leaseDetail.responsibilityEffectiveDate = apiModel?.responsibilityEffectiveDate || ''; leaseDetail.amount = parseFloat(apiModel?.amount?.toString() ?? '') || 0.0; leaseDetail.paymentReceivableTypeCode = fromTypeCode(apiModel?.paymentReceivableType) || ''; @@ -84,7 +84,7 @@ export class LeaseFormModel { leaseDetail.purposeTypeCode = fromTypeCode(apiModel?.purposeType) || ''; leaseDetail.responsibilityTypeCode = fromTypeCode(apiModel?.responsibilityType) || ''; leaseDetail.initiatorTypeCode = fromTypeCode(apiModel?.initiatorType) || ''; - leaseDetail.statusTypeCode = fromTypeCode(apiModel?.statusType) || ''; + leaseDetail.statusTypeCode = fromTypeCode(apiModel?.fileStatusTypeCode) || ''; leaseDetail.leaseTypeCode = fromTypeCode(apiModel?.type) || ''; leaseDetail.regionId = fromTypeCode(apiModel?.region)?.toString() || ''; leaseDetail.programTypeCode = fromTypeCode(apiModel?.programType) || ''; @@ -94,7 +94,7 @@ export class LeaseFormModel { leaseDetail.motiName = apiModel?.motiName || ''; leaseDetail.hasDigitalLicense = apiModel?.hasDigitalLicense ?? undefined; leaseDetail.hasPhysicalLicense = apiModel?.hasPhysicalLicense ?? undefined; - leaseDetail.properties = apiModel?.properties?.map(p => FormLeaseProperty.fromApi(p)) ?? []; + leaseDetail.properties = apiModel?.fileProperties?.map(p => FormLeaseProperty.fromApi(p)) ?? []; leaseDetail.isResidential = apiModel?.isResidential || false; leaseDetail.isCommercialBuilding = apiModel?.isCommercialBuilding || false; leaseDetail.isOtherImprovement = apiModel?.isOtherImprovement || false; @@ -107,67 +107,68 @@ export class LeaseFormModel { leaseDetail.project = !!apiModel?.project ? { id: apiModel?.project?.id || 0, text: apiModel?.project?.description || '' } : undefined; - leaseDetail.apiProject = apiModel?.project || null; const sortedConsultations = apiModel?.consultations?.sort( (a, b) => (a.consultationType?.displayOrder || 0) - (b.consultationType?.displayOrder || 0), ); leaseDetail.consultations = sortedConsultations?.map(c => FormLeaseConsultation.fromApi(c)) || []; - leaseDetail.securityDeposits = - apiModel?.securityDeposits?.map(d => FormLeaseDeposit.fromApi(d)) || []; leaseDetail.terms = apiModel?.terms?.map(t => FormLeaseTerm.fromApi(t)) || []; leaseDetail.tenants = apiModel?.tenants?.map(t => new FormTenant(t)) || []; - leaseDetail.insurances = apiModel?.insurances?.map(i => FormInsurance.createFromModel(i)) || []; return leaseDetail; } - public static toApi(formLease: LeaseFormModel): Api_Lease { + public static toApi(formLease: LeaseFormModel): ApiGen_Concepts_Lease { return { - id: formLease.id, + id: formLease.id ?? 0, lFileNo: stringToNull(formLease.lFileNo), psFileNo: stringToNull(formLease.psFileNo), tfaFileNumber: stringToNull(formLease.tfaFileNumber), - expiryDate: stringToNull(formLease.expiryDate), - startDate: formLease.startDate, - responsibilityEffectiveDate: stringToNull(formLease.responsibilityEffectiveDate), + expiryDate: isValidIsoDateTime(formLease.expiryDate) ? formLease.expiryDate : null, + startDate: isValidIsoDateTime(formLease.startDate) ? formLease.startDate : EpochIsoDateTime, + responsibilityEffectiveDate: isValidIsoDateTime(formLease.responsibilityEffectiveDate) + ? formLease.responsibilityEffectiveDate + : null, amount: parseFloat(formLease.amount?.toString() ?? '') || 0.0, - paymentReceivableType: toTypeCode(formLease.paymentReceivableTypeCode) ?? null, + paymentReceivableType: toTypeCodeNullable(formLease.paymentReceivableTypeCode) ?? null, categoryType: formLease.categoryTypeCode - ? toTypeCode(formLease.categoryTypeCode) ?? null + ? toTypeCodeNullable(formLease.categoryTypeCode) ?? null : null, - purposeType: toTypeCode(formLease.purposeTypeCode) ?? null, - responsibilityType: toTypeCode(formLease.responsibilityTypeCode) ?? null, - initiatorType: toTypeCode(formLease.initiatorTypeCode) ?? null, - statusType: toTypeCode(formLease.statusTypeCode) ?? null, - type: toTypeCode(formLease.leaseTypeCode) ?? null, - region: toTypeCode(Number(formLease.regionId)) ?? null, - programType: toTypeCode(formLease.programTypeCode) ?? null, + purposeType: toTypeCodeNullable(formLease.purposeTypeCode) ?? null, + responsibilityType: toTypeCodeNullable(formLease.responsibilityTypeCode) ?? null, + initiatorType: toTypeCodeNullable(formLease.initiatorTypeCode) ?? null, + fileStatusTypeCode: toTypeCodeNullable(formLease.statusTypeCode) ?? null, + type: toTypeCodeNullable(formLease.leaseTypeCode) ?? null, + region: toTypeCodeNullable(Number(formLease.regionId)) ?? null, + programType: toTypeCodeNullable(formLease.programTypeCode) ?? null, note: stringToNull(formLease.note), returnNotes: formLease.returnNotes, documentationReference: stringToNull(formLease.documentationReference), motiName: formLease.motiName, - hasDigitalLicense: formLease.hasDigitalLicense, - hasPhysicalLicense: formLease.hasPhysicalLicense, - properties: formLease.properties?.map(p => FormLeaseProperty.toApi(p)) ?? [], + hasDigitalLicense: formLease.hasDigitalLicense ?? null, + hasPhysicalLicense: formLease.hasPhysicalLicense ?? null, + fileProperties: formLease.properties?.map(p => FormLeaseProperty.toApi(p)), isResidential: formLease.isResidential, isCommercialBuilding: formLease.isCommercialBuilding, isOtherImprovement: formLease.isOtherImprovement, description: stringToNull(formLease.description), - rowVersion: formLease.rowVersion > 0 ? formLease.rowVersion : undefined, otherCategoryType: stringToNull(formLease.otherCategoryTypeDescription), otherProgramType: stringToNull(formLease.otherProgramTypeDescription), otherPurposeType: stringToNull(formLease.otherPurposeTypeDescription), otherType: stringToNull(formLease.otherLeaseTypeDescription), - project: - formLease.project?.id !== undefined && formLease.project?.id !== 0 - ? ({ id: formLease.project?.id } as any) - : undefined, + project: isValidId(formLease.project?.id) ? ({ id: formLease.project?.id } as any) : null, consultations: formLease.consultations.map(x => x.toApi()), tenants: formLease.tenants.map(t => FormTenant.toApi(t)), terms: formLease.terms.map(t => FormLeaseTerm.toApi(t)), - insurances: formLease.insurances.map(i => i.toInterfaceModel()), + fileName: null, + fileNumber: null, + hasDigitalFile: formLease.hasDigitalLicense ?? false, + hasPhysicalFile: formLease.hasPhysicalLicense ?? false, + isExpired: false, + programName: null, + renewalCount: 0, + ...getEmptyBaseAudit(formLease.rowVersion), }; } @@ -176,7 +177,7 @@ export class LeaseFormModel { .map(property => { return property.property; }) - .filter((item): item is PropertyForm => !!item); + .filter(exists); } } @@ -195,11 +196,11 @@ export class FormLeaseProperty { this.areaUnitTypeCode = PropertyAreaUnitTypes.Meter; } - public static fromApi(apiPropertyLease: Api_PropertyLease): FormLeaseProperty { - const model = new FormLeaseProperty(apiPropertyLease.lease?.id); + public static fromApi(apiPropertyLease: ApiGen_Concepts_PropertyLease): FormLeaseProperty { + const model = new FormLeaseProperty(apiPropertyLease.file?.id); model.property = PropertyForm.fromApi(apiPropertyLease); model.id = apiPropertyLease.id; - model.rowVersion = apiPropertyLease.rowVersion; + model.rowVersion = apiPropertyLease.rowVersion ?? undefined; model.name = apiPropertyLease.propertyName ?? undefined; model.landArea = apiPropertyLease.leaseArea?.toString() || '0'; model.areaUnitTypeCode = apiPropertyLease.areaUnitType?.id || PropertyAreaUnitTypes.Meter; @@ -212,20 +213,25 @@ export class FormLeaseProperty { return model; } - public static toApi(formLeaseProperty: FormLeaseProperty): Api_PropertyLease { - const numberLeaseArea: number | undefined = stringToUndefined(formLeaseProperty.landArea); + public static toApi(formLeaseProperty: FormLeaseProperty): ApiGen_Concepts_PropertyLease { + const landArea = stringToUndefined(formLeaseProperty.landArea); + const numberLeaseArea: number | undefined = isValidString(landArea) + ? Number(landArea) + : undefined; return { - id: formLeaseProperty.id, - leaseId: formLeaseProperty.leaseId, - lease: null, - rowVersion: formLeaseProperty.rowVersion, - property: formLeaseProperty.property?.toApi(), - propertyName: formLeaseProperty.name ?? undefined, + id: formLeaseProperty.id ?? 0, + fileId: formLeaseProperty.leaseId ?? 0, + file: null, + property: formLeaseProperty.property?.toApi() ?? null, + propertyId: formLeaseProperty.property?.id ?? 0, + propertyName: formLeaseProperty.name ?? null, leaseArea: numberLeaseArea ?? null, areaUnitType: numberLeaseArea !== undefined - ? toTypeCode(formLeaseProperty.areaUnitTypeCode) ?? null + ? toTypeCodeNullable(formLeaseProperty.areaUnitTypeCode) ?? null : null, + displayOrder: null, + ...getEmptyBaseAudit(formLeaseProperty.rowVersion), }; } } @@ -240,7 +246,7 @@ export class FormLeaseConsultation { public parentLeaseId: number = 0; public rowVersion: number | undefined = undefined; - static fromApi(apiModel: Api_LeaseConsultation): FormLeaseConsultation { + static fromApi(apiModel: ApiGen_Concepts_ConsultationLease): FormLeaseConsultation { const model = new FormLeaseConsultation(); model.id = apiModel.id || 0; model.consultationType = fromTypeCode(apiModel.consultationType) || ''; @@ -265,24 +271,24 @@ export class FormLeaseConsultation { return model; } - public toApi(): Api_LeaseConsultation { + public toApi(): ApiGen_Concepts_ConsultationLease { return { id: this.id, - consultationType: toTypeCode(this.consultationType) || null, - consultationStatusType: toTypeCode(this.consultationStatusType) || null, + consultationType: toTypeCodeNullable(this.consultationType) || null, + consultationStatusType: toTypeCodeNullable(this.consultationStatusType) || null, parentLeaseId: this.parentLeaseId, otherDescription: emptyStringtoNullable(this.consultationTypeOtherDescription), - rowVersion: this.rowVersion, + ...getEmptyBaseAudit(this.rowVersion), }; } } export const getDefaultFormLease: () => LeaseFormModel = () => LeaseFormModel.fromApi({ - properties: [], + fileProperties: [], tenants: [], - startDate: '', - expiryDate: '', + startDate: EpochIsoDateTime, + expiryDate: EpochIsoDateTime, lFileNo: '', tfaFileNumber: '', psFileNo: '', @@ -292,10 +298,10 @@ export const getDefaultFormLease: () => LeaseFormModel = () => isCommercialBuilding: false, isOtherImprovement: false, returnNotes: '', - hasDigitalLicense: undefined, - hasPhysicalLicense: undefined, - statusType: { id: 'DRAFT' }, - paymentReceivableType: { id: 'RCVBL' }, + hasDigitalLicense: null, + hasPhysicalLicense: null, + fileStatusTypeCode: toTypeCodeNullable('DRAFT'), + paymentReceivableType: toTypeCodeNullable('RCVBL'), categoryType: null, purposeType: null, programType: null, @@ -309,5 +315,18 @@ export const getDefaultFormLease: () => LeaseFormModel = () => otherProgramType: null, consultations: [], terms: [], - insurances: [], + id: 0, + programName: null, + documentationReference: null, + note: null, + description: null, + renewalCount: 0, + responsibilityEffectiveDate: null, + hasPhysicalFile: false, + hasDigitalFile: false, + isExpired: false, + project: null, + ...getEmptyBaseAudit(), + fileName: null, + fileNumber: null, }); diff --git a/source/frontend/src/features/leases/shared/propertyPicker/LeasePropertySelector.tsx b/source/frontend/src/features/leases/shared/propertyPicker/LeasePropertySelector.tsx index 8afdeae555..694a53ea00 100644 --- a/source/frontend/src/features/leases/shared/propertyPicker/LeasePropertySelector.tsx +++ b/source/frontend/src/features/leases/shared/propertyPicker/LeasePropertySelector.tsx @@ -15,6 +15,7 @@ import { useProperties } from '@/hooks/repositories/useProperties'; import useDeepCompareEffect from '@/hooks/util/useDeepCompareEffect'; import useDeepCompareMemo from '@/hooks/util/useDeepCompareMemo'; import { IProperty } from '@/interfaces'; +import { isValidId } from '@/utils'; import { FormLeaseProperty, LeaseFormModel } from '../../models'; import SelectedPropertyHeaderRow from './selectedPropertyList/SelectedPropertyHeaderRow'; @@ -109,24 +110,24 @@ export const LeasePropertySelector: React.FunctionComponent 0) { - formProperty.property.apiId = result[0].id; + formProperty.property!.apiId = result[0].id; } } } newFormProperties.push(formProperty); - if (formProperty.property?.apiId === undefined) { + if (isValidId(formProperty.property?.apiId)) { needsWarning = needsWarning || true; } else { needsWarning = needsWarning || false; diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx index 3a7d7ed6a5..cb50767bf1 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionContainer.tsx @@ -18,9 +18,10 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useQuery } from '@/hooks/use-query'; import useApiUserOverride from '@/hooks/useApiUserOverride'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; -import { Api_File } from '@/models/api/File'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_File } from '@/models/api/generated/ApiGen_Concepts_File'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; -import { stripTrailingSlash } from '@/utils'; +import { exists, stripTrailingSlash } from '@/utils'; import { SideBarContext } from '../context/sidebarContext'; import { FileTabType } from '../shared/detail/FileTabs'; @@ -125,7 +126,7 @@ export const AcquisitionContainer: React.FunctionComponent { var retrieved = await retrieveAcquisitionFile(acquisitionFileId); - if (retrieved === undefined) { + if (!exists(retrieved)) { return; } @@ -135,12 +136,10 @@ export const AcquisitionContainer: React.FunctionComponent { if ( - lastUpdatedBy === undefined || + !exists(lastUpdatedBy) || acquisitionFileId !== lastUpdatedBy?.parentId || staleLastUpdatedBy ) { @@ -170,7 +169,7 @@ export const AcquisitionContainer: React.FunctionComponent { - if (acquisitionFile === undefined || acquisitionFileId !== acquisitionFile.id || staleFile) { + if (!exists(acquisitionFile) || acquisitionFileId !== acquisitionFile.id || staleFile) { fetchAcquisitionFile(); } }, [acquisitionFile, fetchAcquisitionFile, acquisitionFileId, staleFile]); @@ -262,12 +261,19 @@ export const AcquisitionContainer: React.FunctionComponent => { + const onUpdateProperties = ( + file: ApiGen_Concepts_File, + ): Promise => { // The backend does not update the product or project so its safe to send nulls even if there might be data for those fields. return withUserOverride((userOverrideCodes: UserOverrideCode[]) => { return updateAcquisitionProperties .execute( - { ...file, productId: null, projectId: null, fileChecklistItems: [] }, + { + ...(file as ApiGen_Concepts_AcquisitionFile), + productId: null, + projectId: null, + fileChecklistItems: [], + }, userOverrideCodes, ) .then(response => { diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx index 0d23a7a4bc..8a6089c9c2 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.test.tsx @@ -10,6 +10,7 @@ import { mockAcquisitionFileOwnersResponse, mockAcquisitionFileResponse, } from '@/mocks/acquisitionFiles.mock'; +import { mockApiPerson } from '@/mocks/filterData.mock'; import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock'; import { mockLastUpdatedBy } from '@/mocks/lastUpdatedBy.mock'; import { mockLookups } from '@/mocks/lookups.mock'; @@ -29,6 +30,17 @@ import AcquisitionView, { IAcquisitionViewProps } from './AcquisitionView'; jest.mock('@react-keycloak/web'); jest.mock('@/components/common/mapFSM/MapStateMachineContext'); +jest.mock('@/hooks/repositories/useComposedProperties', () => { + return { + useComposedProperties: jest.fn().mockResolvedValue({ apiWrapper: { response: {} } }), + PROPERTY_TYPES: {}, + }; +}); +jest.mock('@/features/mapSideBar/hooks/usePropertyDetails', () => { + return { + usePropertyDetails: jest.fn(), + }; +}); const onClose = jest.fn(); const onSave = jest.fn(); @@ -129,6 +141,16 @@ describe('AcquisitionView component', () => { rest.get('/api/acquisitionfiles/:id/owners', (req, res, ctx) => res(ctx.delay(500), ctx.status(200), ctx.json(mockAcquisitionFileOwnersResponse())), ), + rest.get('/api/persons/concept/:id', (req, res, ctx) => + res(ctx.delay(500), ctx.status(200), ctx.json(mockApiPerson)), + ), + rest.get('/api/acquisitionfiles/:id/properties', (req, res, ctx) => + res( + ctx.delay(500), + ctx.status(200), + ctx.json(mockAcquisitionFileResponse().fileProperties), + ), + ), rest.get('/api/acquisitionfiles/:id/interestholders', (req, res, ctx) => res(ctx.delay(500), ctx.status(200), ctx.json(getMockApiInterestHolders())), ), diff --git a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx index 5806c5b0a3..bdf64f0617 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/AcquisitionView.tsx @@ -17,8 +17,8 @@ import { FileTypes } from '@/constants'; import FileLayout from '@/features/mapSideBar/layout/FileLayout'; import MapSideBarLayout from '@/features/mapSideBar/layout/MapSideBarLayout'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_File } from '@/models/api/File'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_File } from '@/models/api/generated/ApiGen_Concepts_File'; import { stripTrailingSlash } from '@/utils'; import { getFilePropertyName } from '@/utils/mapPropertyUtils'; @@ -41,7 +41,7 @@ export interface IAcquisitionViewProps { onShowPropertySelector: () => void; onSuccess: () => void; onCancelConfirm: () => void; - onUpdateProperties: (file: Api_File) => Promise; + onUpdateProperties: (file: ApiGen_Concepts_File) => Promise; canRemove: (propertyId: number) => Promise; isEditing: boolean; setIsEditing: (value: boolean) => void; @@ -73,9 +73,9 @@ export const AcquisitionView: React.FunctionComponent = ( const history = useHistory(); const match = useRouteMatch(); const { file, lastUpdatedBy } = useContext(SideBarContext); - const acquisitionFile: Api_AcquisitionFile = { + const acquisitionFile: ApiGen_Concepts_AcquisitionFile = { ...file, - } as Api_AcquisitionFile; + } as ApiGen_Concepts_AcquisitionFile; // match for property menu routes - eg /property/1/ltsa const fileMatch = matchPath>(location.pathname, `${match.path}/:tab`); diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx index db248bcd55..e7f4076723 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.test.tsx @@ -9,7 +9,7 @@ import { useUserInfoRepository } from '@/hooks/repositories/useUserInfoRepositor import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; import { mapMachineBaseMock } from '@/mocks/mapFSM.mock'; -import { Api_User } from '@/models/api/User'; +import { ApiGen_Concepts_User } from '@/models/api/generated/ApiGen_Concepts_User'; import { emptyRegion } from '@/models/layers/motRegionalBoundary'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, renderAsync, RenderOptions, screen, userEvent } from '@/utils/test-utils'; @@ -57,7 +57,7 @@ jest.mock('@/hooks/repositories/useUserInfoRepository'); region: { id: 2 }, }, ], - } as Api_User, + } as ApiGen_Concepts_User, }); // Mock API service calls diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.tsx index cd0d5a09d3..40adfef019 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionContainer.tsx @@ -8,7 +8,7 @@ import { ReactComponent as RealEstateAgent } from '@/assets/images/real-estate-a import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext'; import MapSideBarLayout from '@/features/mapSideBar/layout/MapSideBarLayout'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { featuresetToMapProperty } from '@/utils/mapPropertyUtils'; import { PropertyForm } from '../../shared/models'; @@ -67,7 +67,7 @@ export const AddAcquisitionContainer: React.FC = }; // navigate to read-only view after file has been created - const onSuccess = async (acqFile: Api_AcquisitionFile) => { + const onSuccess = async (acqFile: ApiGen_Concepts_AcquisitionFile) => { if (acqFile.fileProperties?.find(ap => !ap.property?.address && !ap.property?.id)) { toast.warn( 'Address could not be retrieved for this property, it will have to be provided manually in property details tab', diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionForm.tsx index b69ea3d81b..645e9b0f6d 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/add/AddAcquisitionForm.tsx @@ -23,9 +23,10 @@ import { useOrganizationRepository } from '@/features/contacts/repositories/useO import { useProjectProvider } from '@/hooks/repositories/useProjectProvider'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; import { IAutocompletePrediction } from '@/interfaces/IAutocomplete'; -import { Api_OrganizationPerson } from '@/models/api/Organization'; -import { Api_Product } from '@/models/api/Project'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; +import { ApiGen_Concepts_Product } from '@/models/api/generated/ApiGen_Concepts_Product'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { isValidId, isValidString } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { AcquisitionFormModal } from '../common/modals/AcquisitionFormModal'; @@ -113,9 +114,9 @@ const AddAcquisitionDetailSubForm: React.FC<{ showDiffMinistryRegionModal, setShowDiffMinistryRegionModal, }) => { - const [projectProducts, setProjectProducts] = React.useState( - undefined, - ); + const [projectProducts, setProjectProducts] = React.useState< + ApiGen_Concepts_Product[] | undefined + >(undefined); const { values, setFieldValue } = formikProps; @@ -129,7 +130,7 @@ const AddAcquisitionDetailSubForm: React.FC<{ const onMinistryProjectSelected = async (param: IAutocompletePrediction[]) => { if (param.length > 0) { - if (param[0].id !== undefined) { + if (isValidId(param[0].id)) { const result = await retrieveProjectProducts(param[0].id); if (result !== undefined) { setProjectProducts(result); @@ -162,7 +163,7 @@ const AddAcquisitionDetailSubForm: React.FC<{ }, [orgPersons, setFieldValue]); const primaryContacts: SelectOption[] = - orgPersons?.map((orgPerson: Api_OrganizationPerson) => { + orgPersons?.map((orgPerson: ApiGen_Concepts_OrganizationPerson) => { return { label: `${formatApiPersonNames(orgPerson.person)}`, value: orgPerson.personId ?? ' ', @@ -204,7 +205,7 @@ const AddAcquisitionDetailSubForm: React.FC<{ const selectedValue = [].slice .call(e.target.selectedOptions) .map((option: HTMLOptionElement & number) => option.value)[0]; - if (!!selectedValue && selectedValue !== 'OTHER') { + if (isValidString(selectedValue) && selectedValue !== 'OTHER') { formikProps.setFieldValue('fundingTypeOtherDescription', ''); } }} @@ -321,7 +322,7 @@ const AddAcquisitionDetailSubForm: React.FC<{ /> { diff --git a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts index 10f775f352..2feab97b19 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/add/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/add/models.ts @@ -1,13 +1,11 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; import { IAutocompletePrediction } from '@/interfaces'; -import { - Api_AcquisitionFile, - Api_AcquisitionFileOwner, - Api_AcquisitionFileProperty, - Api_AcquisitionFileTeam, -} from '@/models/api/AcquisitionFile'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { fromTypeCode, stringToNull, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_AcquisitionFileProperty } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileProperty'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { fromTypeCode, stringToNumberOrNull, toTypeCodeNullable } from '@/utils/formUtils'; +import { exists, isValidId, isValidIsoDateTime } from '@/utils/utils'; import { PropertyForm } from '../../shared/models'; import { ChecklistItemFormModel } from '../../shared/tabs/checklist/update/models'; @@ -47,72 +45,79 @@ export class AcquisitionForm implements WithAcquisitionTeam, WithAcquisitionOwne ); totalAllowableCompensation: number | '' = ''; - toApi(): Api_AcquisitionFile { + toApi(): ApiGen_Concepts_AcquisitionFile { return { - id: this.id, - fileName: this.fileName, - rowVersion: this.rowVersion, - assignedDate: this.assignedDate, - deliveryDate: this.deliveryDate, - totalAllowableCompensation: stringToNull(this.totalAllowableCompensation), - legacyFileNumber: this.legacyFileNumber, - fileStatusTypeCode: toTypeCode(this.acquisitionFileStatusType), - acquisitionPhysFileStatusTypeCode: toTypeCode(this.acquisitionPhysFileStatusType), - acquisitionTypeCode: toTypeCode(this.acquisitionType), - regionCode: toTypeCode(Number(this.region)), - projectId: this.project?.id !== undefined && this.project?.id !== 0 ? this.project?.id : null, + id: this.id ?? 0, + fileName: this.fileName ?? null, + assignedDate: isValidIsoDateTime(this.assignedDate) ? this.assignedDate : null, + deliveryDate: isValidIsoDateTime(this.deliveryDate) ? this.deliveryDate : null, + totalAllowableCompensation: stringToNumberOrNull(this.totalAllowableCompensation), + legacyFileNumber: this.legacyFileNumber ?? null, + fileStatusTypeCode: toTypeCodeNullable(this.acquisitionFileStatusType), + acquisitionPhysFileStatusTypeCode: toTypeCodeNullable(this.acquisitionPhysFileStatusType), + acquisitionTypeCode: toTypeCodeNullable(this.acquisitionType), + regionCode: toTypeCodeNullable(Number(this.region)), + projectId: isValidId(this.project?.id) ? this.project!.id : null, productId: this.product !== '' ? Number(this.product) : null, - fundingTypeCode: toTypeCode(this.fundingTypeCode), + fundingTypeCode: toTypeCodeNullable(this.fundingTypeCode), fundingOther: this.fundingTypeOtherDescription, // ACQ file properties - fileProperties: this.properties.map(ap => { - return { - id: ap.id, - propertyName: ap.name, - displayOrder: ap.displayOrder, - rowVersion: ap.rowVersion, - property: ap.toApi(), - propertyId: ap.apiId, - acquisitionFile: { id: this.id }, - }; - }), + fileProperties: this.properties.map(ap => ({ + id: ap.id ?? 0, + propertyName: ap.name ?? null, + displayOrder: ap.displayOrder ?? null, + rowVersion: ap.rowVersion ?? null, + property: ap.toApi(), + propertyId: ap.apiId ?? 0, + fileId: this.id ?? 0, + acquisitionFile: null, + file: null, + })), acquisitionFileOwners: this.owners .filter(x => !x.isEmpty()) - .map(x => x.toApi()), + .map(x => x.toApi()), acquisitionTeam: this.team .filter(x => !!x.contact && !!x.contactTypeCode) .map(x => x.toApi(this.id || 0)) - .filter((x): x is Api_AcquisitionFileTeam => x !== null), + .filter(exists), acquisitionFileInterestHolders: [ InterestHolderForm.toApi(this.ownerSolicitor, []), InterestHolderForm.toApi(this.ownerRepresentative, []), - ].filter((x): x is Api_InterestHolder => x !== null), + ].filter(exists), fileChecklistItems: this.fileCheckList.map(x => x.toApi()), + completionDate: null, + compensationRequisitions: null, + fileNo: 0, + fileNumber: null, + legacyStakeholders: null, + product: null, + project: null, + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_AcquisitionFile): AcquisitionForm { + static fromApi(model: ApiGen_Concepts_AcquisitionFile): AcquisitionForm { const newForm = new AcquisitionForm(); newForm.id = model.id; newForm.fileName = model.fileName || ''; - newForm.rowVersion = model.rowVersion; - newForm.assignedDate = model.assignedDate; - newForm.deliveryDate = model.deliveryDate; + newForm.rowVersion = model.rowVersion ?? undefined; + newForm.assignedDate = model.assignedDate ?? undefined; + newForm.deliveryDate = model.deliveryDate ?? undefined; newForm.totalAllowableCompensation = model.totalAllowableCompensation || ''; - newForm.legacyFileNumber = model.legacyFileNumber; - newForm.acquisitionFileStatusType = fromTypeCode(model.fileStatusTypeCode); - newForm.acquisitionPhysFileStatusType = fromTypeCode(model.acquisitionPhysFileStatusTypeCode); - newForm.acquisitionType = fromTypeCode(model.acquisitionTypeCode); + newForm.legacyFileNumber = model.legacyFileNumber ?? undefined; + newForm.acquisitionFileStatusType = fromTypeCode(model.fileStatusTypeCode) ?? undefined; + newForm.acquisitionPhysFileStatusType = + fromTypeCode(model.acquisitionPhysFileStatusTypeCode) ?? undefined; + newForm.acquisitionType = fromTypeCode(model.acquisitionTypeCode) ?? undefined; newForm.region = fromTypeCode(model.regionCode)?.toString(); // ACQ file properties newForm.properties = model.fileProperties?.map(x => PropertyForm.fromApi(x)) || []; newForm.product = model.product?.id?.toString() ?? ''; - newForm.fundingTypeCode = model.fundingTypeCode?.id; + newForm.fundingTypeCode = model.fundingTypeCode?.id ?? undefined; newForm.fundingTypeOtherDescription = model.fundingOther || ''; - newForm.project = - model.project !== undefined - ? { id: model.project?.id || 0, text: model.project?.description || '' } - : undefined; + newForm.project = exists(model.project) + ? { id: model.project.id || 0, text: model.project.description || '' } + : undefined; const interestHolders = model.acquisitionFileInterestHolders?.map(x => InterestHolderForm.fromApi(x, x.interestHolderType?.id as InterestHolderType), diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.test.tsx index c0ca84a3b2..4d8896cfdb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.test.tsx @@ -89,9 +89,9 @@ describe('AcquisitionHeader component', () => { it('renders the Product label when null', async () => { const { getByText, getByTestId } = setup({ acquisitionFile: { - ...mockAcquisitionFileResponse, + ...mockAcquisitionFileResponse(), totalAllowableCompensation: 0, - product: undefined, + product: null, productId: null, projectId: null, fileChecklistItems: [], diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.tsx index 9059f8d138..45a510bda6 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/AcquisitionHeader.tsx @@ -4,13 +4,13 @@ import styled from 'styled-components'; import { HeaderField } from '@/components/common/HeaderField/HeaderField'; import { UserNameTooltip } from '@/components/common/UserNameTooltip'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { Api_LastUpdatedBy } from '@/models/api/File'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { prettyFormatUTCDate } from '@/utils'; import { formatMinistryProject } from '@/utils/formUtils'; export interface IAcquisitionHeaderProps { - acquisitionFile?: Api_AcquisitionFile; + acquisitionFile?: ApiGen_Concepts_AcquisitionFile; lastUpdatedBy: Api_LastUpdatedBy | null; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/GenerateFormContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/GenerateFormContainer.tsx index 1f4b31c4d0..2f4a8d1b44 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/GenerateFormContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/GenerateFormContainer.tsx @@ -7,6 +7,7 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { useModalManagement } from '@/hooks/useModalManagement'; import { Api_GenerateOwner } from '@/models/generate/GenerateOwner'; +import { exists } from '@/utils/utils'; import { IGenerateFormViewProps } from './GenerateFormView'; import { useGenerateH0443 } from './hooks/useGenerateH0443'; @@ -55,8 +56,8 @@ const GenerateFormContainer: React.FunctionComponent< interestHoldersFetchCall, ]); - if (fileOwners) { - fileOwners?.map(owner => + if (exists(fileOwners)) { + fileOwners.map(owner => generateRecipientsList.push( new LetterRecipientModel(owner.id!, 'OWNR', new Api_GenerateOwner(owner), owner), ), diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.test.tsx index a5c05da90d..4335f80d65 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.test.tsx @@ -9,14 +9,14 @@ import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { mockAgreementsResponse } from '@/mocks/agreements.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { useGenerateAgreement } from './useGenerateAgreement'; const generateFn = jest.fn(); -const getAcquisitionFileFn = jest.fn(); -const getAcquisitionFileProperties = jest.fn(); +const getAcquisitionFileFn = jest.fn(); +const getAcquisitionFileProperties = jest.fn(); const getPersonConceptFn = jest.fn().mockResolvedValue({}); const getOrganizationConceptFn = jest.fn().mockResolvedValue({}); @@ -48,7 +48,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; @@ -71,7 +74,7 @@ describe('useGenerateAgreement functions', () => { }); }); it('makes requests to expected api endpoints if a team member is a property coordinator with person', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -80,6 +83,12 @@ describe('useGenerateAgreement functions', () => { personId: 1, teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + organizationId: null, }, ], }; @@ -94,7 +103,7 @@ describe('useGenerateAgreement functions', () => { }); it('makes requests to expected api endpoints if a team member is a negotiating agent with person', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -103,6 +112,12 @@ describe('useGenerateAgreement functions', () => { personId: 1, teamProfileTypeCode: 'NEGOTAGENT', rowVersion: 2, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + organizationId: null, }, ], }; @@ -117,7 +132,7 @@ describe('useGenerateAgreement functions', () => { }); it('makes requests to expected api endpoints if a team member is a property coordinator with org', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -126,6 +141,12 @@ describe('useGenerateAgreement functions', () => { organizationId: 1, teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + personId: null, }, ], }; @@ -140,7 +161,7 @@ describe('useGenerateAgreement functions', () => { }); it('makes requests to expected api endpoints if a team member is a negotiating agent with organization', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -149,6 +170,12 @@ describe('useGenerateAgreement functions', () => { organizationId: 1, teamProfileTypeCode: 'NEGOTAGENT', rowVersion: 2, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + personId: null, }, ], }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.ts b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.ts index 5ae78f1d0f..6e61171d54 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateAgreement.ts @@ -4,19 +4,21 @@ import { useDocumentGenerationRepository } from '@/features/documents/hooks/useD import { FormTemplateTypes } from '@/features/mapSideBar/shared/content/models'; import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; -import { Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { AgreementTypes, Api_Agreement } from '@/models/api/Agreement'; +import { ApiGen_CodeTypes_AgreementTypes } from '@/models/api/generated/ApiGen_CodeTypes_AgreementTypes'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_Organization } from '@/models/api/Organization'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_Agreement } from '@/models/api/generated/ApiGen_Concepts_Agreement'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; import { Api_GenerateAcquisitionFile } from '@/models/generate/acquisition/GenerateAcquisitionFile'; import { Api_GenerateAgreement } from '@/models/generate/GenerateAgreement'; +import { exists } from '@/utils/utils'; export const useGenerateAgreement = () => { const { getPersonConcept, getOrganizationConcept } = useApiContacts(); const { getAcquisitionFile, getAcquisitionProperties } = useAcquisitionProvider(); const { generateDocumentDownloadWrappedRequest: generate } = useDocumentGenerationRepository(); - const generateAgreement = async (agreement: Api_Agreement) => { - if (agreement?.agreementType?.id === undefined) { + const generateAgreement = async (agreement: ApiGen_Concepts_Agreement) => { + if (!exists(agreement?.agreementType?.id)) { throw Error('user must choose agreement type in order to generate a document'); } const file = await getAcquisitionFile.execute(agreement.acquisitionFileId); @@ -24,7 +26,7 @@ export const useGenerateAgreement = () => { if (!file) { throw Error('Acquisition file not found'); } - file.fileProperties = properties; + file.fileProperties = properties ?? null; const coordinator = file.acquisitionTeam?.find( team => team.teamProfileTypeCode === 'PROPCOORD', @@ -96,7 +98,7 @@ export const useGenerateAgreement = () => { }); const agreementData = new Api_GenerateAgreement(agreement, fileData); const generatedFile = await generate({ - templateType: getTemplateTypeFromAgreementType(agreement.agreementType.id), + templateType: getTemplateTypeFromAgreementType(agreement.agreementType!.id), templateData: agreementData, convertToType: null, }); @@ -113,22 +115,25 @@ export const useGenerateAgreement = () => { * @param agreementType * @returns */ -const getTemplateTypeFromAgreementType = (agreementType: string) => { +const getTemplateTypeFromAgreementType = (agreementType: string | null) => { switch (agreementType) { - case AgreementTypes.H179A: + case ApiGen_CodeTypes_AgreementTypes.H179A: return FormTemplateTypes.H179A; - case AgreementTypes.H179P: + case ApiGen_CodeTypes_AgreementTypes.H179P: return FormTemplateTypes.H179P; - case AgreementTypes.H179T: + case ApiGen_CodeTypes_AgreementTypes.H179T: return FormTemplateTypes.H179T; - case AgreementTypes.H0074: + case ApiGen_CodeTypes_AgreementTypes.H0074: return FormTemplateTypes.H0074; default: throw Error(`Unable to find form type for agreement type: ${agreementType}`); } }; -const setOrganization = (team: Api_AcquisitionFileTeam, organization: Api_Organization) => { +const setOrganization = ( + team: ApiGen_Concepts_AcquisitionFileTeam, + organization: ApiGen_Concepts_Organization, +) => { if (!!team) { team.organization = organization; team.primaryContact = diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.test.tsx index 4eb1d50b0c..1acdfec0e7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.test.tsx @@ -10,9 +10,9 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockContactOrganizationWithOnePerson } from '@/mocks/contacts.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { ExpropriationForm1Model } from '../../../tabs/expropriation/models'; import { useGenerateExpropriationForm1 } from './useGenerateExpropriationForm1'; @@ -20,8 +20,11 @@ import { useGenerateExpropriationForm1 } from './useGenerateExpropriationForm1'; const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getAcquisitionFileFn = jest.fn, any[]>(); -const getAcquisitionFilePropertiesFn = jest.fn, any[]>(); +const getAcquisitionFileFn = jest.fn, any[]>(); +const getAcquisitionFilePropertiesFn = jest.fn< + Promise, + any[] +>(); const getPersonConceptFn = jest.fn(); const getOrganizationConceptFn = jest.fn(); const getInterestHoldersFn = jest.fn(); @@ -59,7 +62,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.tsx index d0bf32263d..12c22c5fa0 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm1.tsx @@ -11,6 +11,7 @@ import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHol import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; import { Api_GenerateAcquisitionFile } from '@/models/generate/acquisition/GenerateAcquisitionFile'; import { Api_GenerateExpropriationForm1 } from '@/models/generate/acquisition/GenerateExpropriationForm1'; +import { isValidId } from '@/utils'; export const useGenerateExpropriationForm1 = () => { const { getOrganizationConcept, getPersonConcept } = useApiContacts(); @@ -35,7 +36,7 @@ export const useGenerateExpropriationForm1 = () => { if (!file) { throw Error('Acquisition file not found'); } - file.fileProperties = properties; + file.fileProperties = properties ?? null; // fetch primary contact information for organizations within interest holders if (interestHolders) { @@ -61,7 +62,7 @@ export const useGenerateExpropriationForm1 = () => { }); const filePropertyIds = new Set( - formModel.impactedProperties.map(fp => fp?.id).filter((p): p is number => !!p), + formModel.impactedProperties.map(fp => fp?.id).filter(isValidId), ); const selectedProperties = properties?.filter(fp => filePropertyIds.has(Number(fp.id))); diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.test.tsx index 03d265bc81..377a2d8a2d 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.test.tsx @@ -10,9 +10,9 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockContactOrganizationWithOnePerson } from '@/mocks/contacts.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { ExpropriationForm5Model } from '../../../tabs/expropriation/models'; import { useGenerateExpropriationForm5 } from './useGenerateExpropriationForm5'; @@ -20,8 +20,11 @@ import { useGenerateExpropriationForm5 } from './useGenerateExpropriationForm5'; const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getAcquisitionFileFn = jest.fn, any[]>(); -const getAcquisitionFilePropertiesFn = jest.fn, any[]>(); +const getAcquisitionFileFn = jest.fn, any[]>(); +const getAcquisitionFilePropertiesFn = jest.fn< + Promise, + any[] +>(); const getOrganizationConceptFn = jest.fn(); const getInterestHoldersFn = jest.fn(); @@ -57,7 +60,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.tsx index ab84791086..c0a7a9794a 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm5.tsx @@ -10,6 +10,7 @@ import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHol import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; import { Api_GenerateAcquisitionFile } from '@/models/generate/acquisition/GenerateAcquisitionFile'; import { Api_GenerateExpropriationForm5 } from '@/models/generate/acquisition/GenerateExpropriationForm5'; +import { isValidId } from '@/utils'; export const useGenerateExpropriationForm5 = () => { const { getOrganizationConcept } = useApiContacts(); @@ -34,7 +35,7 @@ export const useGenerateExpropriationForm5 = () => { if (!file) { throw Error('Acquisition file not found'); } - file.fileProperties = properties; + file.fileProperties = properties ?? null; const fileData = new Api_GenerateAcquisitionFile({ file: file, @@ -42,7 +43,7 @@ export const useGenerateExpropriationForm5 = () => { }); const filePropertyIds = new Set( - formModel.impactedProperties.map(fp => fp?.id).filter((p): p is number => !!p), + formModel.impactedProperties.map(fp => fp?.id).filter(isValidId), ); const selectedProperties = properties?.filter(fp => filePropertyIds.has(Number(fp.id))); diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm8.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm8.test.tsx index 030654b336..9ba784ed0e 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm8.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm8.test.tsx @@ -6,8 +6,8 @@ import thunk from 'redux-thunk'; import { useDocumentGenerationRepository } from '@/features/documents/hooks/useDocumentGenerationRepository'; import { useForm8Repository } from '@/hooks/repositories/useForm8Repository'; import { mockGetExpropriationPaymentApi } from '@/mocks/ExpropriationPayment.mock'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; import { useGenerateExpropriationForm8 } from './useGenerateExpropriationForm8'; @@ -15,7 +15,10 @@ const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getExpropriationPaymentApi = jest.fn, any[]>(); +const getExpropriationPaymentApi = jest.fn< + Promise, + any[] +>(); jest.mock('@/hooks/repositories/useForm8Repository'); (useForm8Repository as jest.Mock).mockImplementation(() => ({ @@ -40,7 +43,7 @@ const getWrapper = const setup = (params?: { storeValues?: any; - expropriationPaymentResponse?: Api_ExpropriationPayment; + expropriationPaymentResponse?: ApiGen_Concepts_ExpropriationPayment; }) => { var expropriationPaymentResponse = mockGetExpropriationPaymentApi(); if (params?.expropriationPaymentResponse !== undefined) { diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.test.tsx index 2eceec02a4..bbf64257c0 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.test.tsx @@ -10,9 +10,9 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockContactOrganizationWithOnePerson } from '@/mocks/contacts.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { ExpropriationForm9Model } from '../../../tabs/expropriation/models'; import { useGenerateExpropriationForm9 } from './useGenerateExpropriationForm9'; @@ -20,8 +20,11 @@ import { useGenerateExpropriationForm9 } from './useGenerateExpropriationForm9'; const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getAcquisitionFileFn = jest.fn, any[]>(); -const getAcquisitionFilePropertiesFn = jest.fn, any[]>(); +const getAcquisitionFileFn = jest.fn, any[]>(); +const getAcquisitionFilePropertiesFn = jest.fn< + Promise, + any[] +>(); const getOrganizationConceptFn = jest.fn(); const getInterestHoldersFn = jest.fn(); @@ -57,7 +60,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.tsx index 7c2e7e3f16..fe90dec612 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateExpropriationForm9.tsx @@ -10,6 +10,7 @@ import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHol import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; import { Api_GenerateAcquisitionFile } from '@/models/generate/acquisition/GenerateAcquisitionFile'; import { Api_GenerateExpropriationForm9 } from '@/models/generate/acquisition/GenerateExpropriationForm9'; +import { isValidId } from '@/utils'; export const useGenerateExpropriationForm9 = () => { const { getOrganizationConcept } = useApiContacts(); @@ -34,7 +35,7 @@ export const useGenerateExpropriationForm9 = () => { if (!file) { throw Error('Acquisition file not found'); } - file.fileProperties = properties; + file.fileProperties = properties ?? null; const fileData = new Api_GenerateAcquisitionFile({ file: file, @@ -42,7 +43,7 @@ export const useGenerateExpropriationForm9 = () => { }); const filePropertyIds = new Set( - formModel.impactedProperties.map(fp => fp?.id).filter((p): p is number => !!p), + formModel.impactedProperties.map(fp => fp?.id).filter(isValidId), ); const selectedProperties = properties?.filter(fp => filePropertyIds.has(Number(fp.id))); diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx index a576c0cd1e..8a79aebc79 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.test.tsx @@ -11,14 +11,14 @@ import { useProperties } from '@/hooks/repositories/useProperties'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockPerson } from '@/mocks/contacts.mock'; import { getMockOrganization } from '@/mocks/organization.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; import { useGenerateH0443 } from './useGenerateH0443'; -const getPropertiesFn = jest.fn(); +const getPropertiesFn = jest.fn(); const generateFn = jest.fn(); -const getAcquisitionFileFn = jest.fn(); +const getAcquisitionFileFn = jest.fn(); const getPersonConceptFn = jest.fn(); const getOrganizationConceptFn = jest.fn(); @@ -54,7 +54,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; @@ -80,7 +83,7 @@ describe('useGenerateH0443 functions', () => { const organizationPersonMock = getMockPerson({ id: 3, firstName: 'JONH', surname: 'Doe' }); getPersonConceptFn.mockResolvedValue(Promise.resolve({ data: organizationPersonMock })); - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -89,6 +92,12 @@ describe('useGenerateH0443 functions', () => { personId: 1, teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, + person: null, + organizationId: null, + organization: null, + primaryContactId: null, + primaryContact: null, + teamProfileType: null, }, { id: 2, @@ -96,6 +105,12 @@ describe('useGenerateH0443 functions', () => { personId: 2, teamProfileTypeCode: 'PROPAGENT', rowVersion: 2, + person: null, + organizationId: null, + organization: null, + primaryContactId: null, + primaryContact: null, + teamProfileType: null, }, ], }; @@ -114,7 +129,7 @@ describe('useGenerateH0443 functions', () => { const organizationMock = getMockOrganization(); getOrganizationConceptFn.mockResolvedValue({ data: organizationMock }); - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -124,6 +139,11 @@ describe('useGenerateH0443 functions', () => { rowVersion: 2, organizationId: 100, primaryContactId: 3, + person: null, + organization: null, + primaryContact: null, + teamProfileType: null, + personId: null, }, ], }; @@ -143,7 +163,7 @@ describe('useGenerateH0443 functions', () => { const organizationMock = getMockOrganization(); getOrganizationConceptFn.mockResolvedValue(Promise.resolve({ data: organizationMock })); - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -152,6 +172,12 @@ describe('useGenerateH0443 functions', () => { teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, organizationId: 100, + person: null, + organization: null, + primaryContactId: null, + primaryContact: null, + teamProfileType: null, + personId: null, }, ], }; @@ -170,7 +196,7 @@ describe('useGenerateH0443 functions', () => { getOrganizationConceptFn.mockResolvedValue(Promise.resolve({ data: organizationMock })); getPersonConceptFn.mockResolvedValue(Promise.resolve({ data: organizationPersonMock })); - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -180,6 +206,11 @@ describe('useGenerateH0443 functions', () => { organizationId: 2, primaryContactId: 3, rowVersion: 2, + person: null, + organization: null, + primaryContact: null, + teamProfileType: null, + personId: null, }, ], }; @@ -198,7 +229,7 @@ describe('useGenerateH0443 functions', () => { getOrganizationConceptFn.mockResolvedValue(Promise.resolve({ data: organizationMock })); getPersonConceptFn.mockResolvedValue(Promise.resolve({ data: organizationPersonMock })); - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -207,6 +238,12 @@ describe('useGenerateH0443 functions', () => { teamProfileTypeCode: 'PROPAGENT', organizationId: 2, rowVersion: 2, + person: null, + organization: null, + primaryContactId: null, + primaryContact: null, + teamProfileType: null, + personId: null, }, ], }; @@ -220,14 +257,28 @@ describe('useGenerateH0443 functions', () => { }); it('makes requests to expected api endpoints if there are properties', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), fileProperties: [ { propertyId: 1, + displayOrder: null, + file: null, + fileId: 0, + id: 0, + property: null, + propertyName: null, + rowVersion: null, }, { propertyId: 2, + displayOrder: null, + file: null, + fileId: 0, + id: 0, + property: null, + propertyName: null, + rowVersion: null, }, ], }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.ts b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.ts index 628e3879c6..611ae1226a 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH0443.ts @@ -4,11 +4,12 @@ import { useDocumentGenerationRepository } from '@/features/documents/hooks/useD import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useProperties } from '@/hooks/repositories/useProperties'; -import { Api_AcquisitionFileOwner } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; import { Api_GenerateOwner } from '@/models/generate/GenerateOwner'; import { Api_GeneratePerson } from '@/models/generate/GeneratePerson'; import { Api_GenerateProperty } from '@/models/generate/GenerateProperty'; +import { exists, isValidId } from '@/utils'; export const useGenerateH0443 = () => { const { getPersonConcept, getOrganizationConcept } = useApiContacts(); @@ -37,11 +38,11 @@ export const useGenerateH0443 = () => { // Retrieve Properties const filePropertiesIds = - file.fileProperties?.map(fp => fp.propertyId).filter((p): p is number => !!p) || []; + file.fileProperties?.map(fp => fp.propertyId).filter(isValidId) || []; const properties = await getMultipleProperties(filePropertiesIds); - const owners: Api_AcquisitionFileOwner[] = - file.acquisitionFileOwners?.filter((x): x is Api_AcquisitionFileOwner => !!x) || []; + const owners: ApiGen_Concepts_AcquisitionFileOwner[] = + file.acquisitionFileOwners?.filter(exists) || []; const contactOwner = owners.find(x => x.isPrimaryContact === true); const h0443Data: H0443Data = { @@ -119,7 +120,7 @@ export const useGenerateH0443 = () => { return generateLetter; }; -function getOwnerName(owner: Api_AcquisitionFileOwner): string { +function getOwnerName(owner: ApiGen_Concepts_AcquisitionFileOwner): string { if (owner.isOrganization) { var corpName: string = owner.lastNameAndCorpName || ''; if (owner.incorporationNumber) { diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.test.tsx index c3b2753bd1..bb22af0771 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.test.tsx @@ -13,16 +13,16 @@ import { usePropertyLeaseRepository } from '@/hooks/repositories/usePropertyLeas import { useSecurityDepositRepository } from '@/hooks/repositories/useSecurityDepositRepository'; import { getMockDeposits } from '@/mocks/deposits.mock'; import { getMockApiLease } from '@/mocks/lease.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; import { useGenerateH1005a } from './useGenerateH1005a'; const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getLeaseTenantsFn = jest.fn, any[]>(); +const getLeaseTenantsFn = jest.fn, any[]>(); const getSecurityDepositsFn = jest.fn(); const getInsurancesFn = jest.fn(); const getPropertyLeasesFn = jest.fn(); @@ -75,7 +75,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { const { result } = renderHook(useGenerateH1005a, { wrapper: getWrapper(getStore(params?.storeValues)), }); diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.ts b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.ts index f8b26f4f83..67f05747d7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH1005a.ts @@ -9,11 +9,11 @@ import { usePropertyLeaseRepository } from '@/hooks/repositories/usePropertyLeas import { useSecurityDepositRepository } from '@/hooks/repositories/useSecurityDepositRepository'; import { useApiRequestWrapper } from '@/hooks/util/useApiRequestWrapper'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { Api_GenerateLease } from '@/models/generate/lease/GenerateLease'; -import { useAxiosErrorHandler } from '@/utils'; +import { exists, useAxiosErrorHandler } from '@/utils'; -export const useGenerateH1005a = (lease?: Api_Lease) => { +export const useGenerateH1005a = (lease?: ApiGen_Concepts_Lease) => { const { generateDocumentDownloadWrappedRequest: generate } = useDocumentGenerationRepository(); const { getInsurances: { execute: getInsurances }, @@ -42,7 +42,7 @@ export const useGenerateH1005a = (lease?: Api_Lease) => { onError: useAxiosErrorHandler('Failed to load lease, reload this page to try again.'), }); - const generateH1005a = async (lease: Api_Lease) => { + const generateH1005a = async (lease: ApiGen_Concepts_Lease) => { if (lease?.id) { const updatedLeasePromise = getLease(lease.id); const insurancesPromise = getInsurances(lease.id); @@ -59,8 +59,10 @@ export const useGenerateH1005a = (lease?: Api_Lease) => { termsPromise, propertyLeasesPromise, ]); - if (updatedLease === null || updatedLease === undefined) + if (!exists(updatedLease)) { throw new Error('Failed to load lease, reload this page to try again.'); + } + const leaseData = new Api_GenerateLease( updatedLease, insurances ?? [], diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.test.tsx index 3633bd6af3..402faaa144 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.test.tsx @@ -13,17 +13,17 @@ import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHol import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockApiDefaultCompensation } from '@/mocks/compensations.mock'; import { emptyApiInterestHolder } from '@/mocks/interestHolder.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; import { useGenerateH120 } from './useGenerateH120'; const generateFn = jest .fn() .mockResolvedValue({ status: ApiGen_CodeTypes_ExternalResponseStatus.Success, payload: {} }); -const getAcquisitionFileFn = jest.fn, any[]>(); +const getAcquisitionFileFn = jest.fn, any[]>(); const getAcquisitionPropertiesFn = jest.fn(); const getAcquisitionCompReqH120s = jest.fn(); const getH120sCategoryFn = jest.fn(); @@ -76,7 +76,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; @@ -106,7 +109,7 @@ describe('useGenerateH120 functions', () => { }); it('makes request to get person concept for compensation payee', async () => { - const apiCompensation: Api_CompensationRequisition = { + const apiCompensation: ApiGen_Concepts_CompensationRequisition = { ...getMockApiDefaultCompensation(), acquisitionFileTeam: { id: 101, @@ -114,6 +117,12 @@ describe('useGenerateH120 functions', () => { personId: 8, teamProfileTypeCode: 'MOTILAWYER', rowVersion: 1, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + organizationId: null, }, }; const generate = setup(); @@ -122,7 +131,7 @@ describe('useGenerateH120 functions', () => { }); it('makes request to get organization concept for compensation payee', async () => { - const apiCompensation: Api_CompensationRequisition = { + const apiCompensation: ApiGen_Concepts_CompensationRequisition = { ...getMockApiDefaultCompensation(), acquisitionFileTeam: { id: 101, @@ -130,6 +139,12 @@ describe('useGenerateH120 functions', () => { organizationId: 8, teamProfileTypeCode: 'MOTILAWYER', rowVersion: 1, + organization: null, + person: null, + personId: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, }, }; const generate = setup(); @@ -138,11 +153,11 @@ describe('useGenerateH120 functions', () => { }); it('searches interest holder array for compensation payee ', async () => { - const apiCompensationWithInterestHolder: Api_CompensationRequisition = { + const apiCompensationWithInterestHolder: ApiGen_Concepts_CompensationRequisition = { ...getMockApiDefaultCompensation(), interestHolderId: 14, }; - const apiInterestHolder: Api_InterestHolder = { + const apiInterestHolder: ApiGen_Concepts_InterestHolder = { ...emptyApiInterestHolder, interestHolderId: 14, acquisitionFileId: 2, @@ -156,6 +171,9 @@ describe('useGenerateH120 functions', () => { personAddresses: [], contactMethods: [], rowVersion: 1, + comment: null, + middleNames: null, + preferredName: null, }, rowVersion: 1, }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.ts b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.ts index e7c4ab14e3..1f679065b7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120.ts @@ -9,8 +9,8 @@ import { useAdminBoundaryMapLayer } from '@/hooks/repositories/mapLayer/useAdmin import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useH120CategoryRepository } from '@/hooks/repositories/useH120CategoryRepository'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { Api_GenerateAcquisitionFile } from '@/models/generate/acquisition/GenerateAcquisitionFile'; import { Api_GenerateCompensation } from '@/models/generate/acquisition/GenerateCompensation'; import { Api_GenerateH120Property } from '@/models/generate/acquisition/GenerateH120Property'; @@ -37,7 +37,7 @@ export const useGenerateH120 = () => { return layerData?.properties.ED_NAME ?? ''; }; - const generateCompensation = async (compensation: Api_CompensationRequisition) => { + const generateCompensation = async (compensation: ApiGen_Concepts_CompensationRequisition) => { if (!compensation?.id) { throw Error( 'user must choose a valid compensation requisition in order to generate a document', @@ -83,7 +83,7 @@ export const useGenerateH120 = () => { if (!file) { throw Error('Acquisition file not found'); } - file.fileProperties = properties; + file.fileProperties = properties ?? null; // Add ELECTORAL_DISTRICT info to each property (from map layer request) const fileData = new Api_GenerateAcquisitionFile({ @@ -104,9 +104,10 @@ export const useGenerateH120 = () => { // Populate payee information if (compensation?.acquisitionFileTeam) { if (compensation?.acquisitionFileTeam?.personId) { - compensation.acquisitionFileTeam.person = acquisitionFileTeamPerson?.data; + compensation.acquisitionFileTeam.person = acquisitionFileTeamPerson?.data ?? null; } else if (compensation?.acquisitionFileTeam?.organizationId) { - compensation.acquisitionFileTeam.organization = acquisitionFileTeamOrganization?.data; + compensation.acquisitionFileTeam.organization = + acquisitionFileTeamOrganization?.data ?? null; } } else if (compensation?.interestHolderId) { const matchedInterestHolder = diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.test.tsx index 94c3f6d96a..6482b964f1 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.test.tsx @@ -8,12 +8,12 @@ import { useDocumentGenerationRepository } from '@/features/documents/hooks/useD import { useApiContacts } from '@/hooks/pims-api/useApiContacts'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { useGenerateLetter } from '../hooks/useGenerateLetter'; const generateFn = jest.fn(); -const getAcquisitionFileFn = jest.fn(); +const getAcquisitionFileFn = jest.fn(); const getPersonConceptFn = jest.fn(); const getOrganizationConceptFn = jest.fn(); @@ -44,7 +44,10 @@ const getWrapper = ({ children }: any) => {children}; -const setup = (params?: { storeValues?: any; acquisitionResponse?: Api_AcquisitionFile }) => { +const setup = (params?: { + storeValues?: any; + acquisitionResponse?: ApiGen_Concepts_AcquisitionFile; +}) => { var acquisitionResponse = mockAcquisitionFileResponse(); if (params?.acquisitionResponse !== undefined) { acquisitionResponse = params.acquisitionResponse; @@ -67,7 +70,7 @@ describe('useGenerateLetter functions', () => { }); }); it('makes requests to expected api endpoints if a team member is a property coordinator with person', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -76,6 +79,12 @@ describe('useGenerateLetter functions', () => { personId: 1, teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, + organization: null, + organizationId: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, }, ], }; @@ -89,7 +98,7 @@ describe('useGenerateLetter functions', () => { }); it('makes requests to expected api endpoints if a team member is a property coordinator with org', async () => { - const responseWithTeam: Api_AcquisitionFile = { + const responseWithTeam: ApiGen_Concepts_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ { @@ -98,6 +107,12 @@ describe('useGenerateLetter functions', () => { organizationId: 1, teamProfileTypeCode: 'PROPCOORD', rowVersion: 2, + organization: null, + person: null, + primaryContact: null, + primaryContactId: null, + teamProfileType: null, + personId: null, }, ], }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.ts b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.ts index a1e2d073c1..0f8130e7d4 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateLetter.ts @@ -6,6 +6,7 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { ApiGen_CodeTypes_ExternalResponseStatus } from '@/models/api/generated/ApiGen_CodeTypes_ExternalResponseStatus'; import { Api_GenerateLetter } from '@/models/generate/GenerateLetter'; import { Api_GenerateOwner } from '@/models/generate/GenerateOwner'; +import { isValidId } from '@/utils'; export const useGenerateLetter = () => { const { getPersonConcept, getOrganizationConcept } = useApiContacts(); @@ -24,11 +25,11 @@ export const useGenerateLetter = () => { const coordinator = file.acquisitionTeam?.find( team => team.teamProfileTypeCode === 'PROPCOORD', ); - if (!!coordinator?.personId) { - coordinator.person = (await getPersonConcept(coordinator?.personId))?.data; - } else if (!!coordinator?.organizationId) { - coordinator.organization = ( - await getOrganizationConcept(coordinator?.organizationId) + if (isValidId(coordinator?.personId)) { + coordinator!.person = (await getPersonConcept(coordinator!.personId))?.data; + } else if (isValidId(coordinator?.organizationId)) { + coordinator!.organization = ( + await getOrganizationConcept(coordinator!.organizationId) )?.data; } const letterData = new Api_GenerateLetter(file, coordinator); diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/modals/models/LetterRecipientModel.tsx b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/modals/models/LetterRecipientModel.tsx index adf289fd05..0d917eea0c 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/modals/models/LetterRecipientModel.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/common/GenerateForm/modals/models/LetterRecipientModel.tsx @@ -1,5 +1,6 @@ -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; import { Api_GenerateOwner } from '@/models/generate/GenerateOwner'; import { formatApiPersonNames } from '@/utils/personUtils'; @@ -9,13 +10,21 @@ export class LetterRecipientModel { readonly id: string; interestType: RecipientType; generateModel: Api_GenerateOwner; - conceptModel: Api_Person | Api_InterestHolder | null; + conceptModel: + | ApiGen_Concepts_Person + | ApiGen_Concepts_InterestHolder + | ApiGen_Concepts_AcquisitionFileOwner + | null; constructor( conceptId: number, type: RecipientType, model: Api_GenerateOwner, - conceptModel: Api_Person | Api_InterestHolder | null = null, + conceptModel: + | ApiGen_Concepts_Person + | ApiGen_Concepts_InterestHolder + | ApiGen_Concepts_AcquisitionFileOwner + | null = null, ) { this.id = `${type}${conceptId}`; this.interestType = type; @@ -49,7 +58,7 @@ export class LetterRecipientModel { if (this.interestType === 'OWNR') { return this.generateModel.owner_string; } else { - const model = this.conceptModel as Api_InterestHolder; + const model = this.conceptModel as ApiGen_Concepts_InterestHolder; return model.personId ? formatApiPersonNames(model.person) : model.organization?.name ?? ''; } } @@ -59,7 +68,7 @@ export class LetterRecipientModel { if (this.interestType === 'OWNR') { return null; } else { - const model = this.conceptModel as Api_InterestHolder; + const model = this.conceptModel as ApiGen_Concepts_InterestHolder; paramString = model.personId ? `P${model.personId}` : `O${model.organizationId}`; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/common/models.ts b/source/frontend/src/features/mapSideBar/acquisition/common/models.ts index 9b29bc358b..452bc23b52 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/common/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/common/models.ts @@ -1,10 +1,19 @@ import { isEmpty, isNumber } from 'lodash'; import { fromApiOrganization, fromApiPerson, IContactSearchResult } from '@/interfaces'; -import { Api_AcquisitionFileOwner, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { Api_Address } from '@/models/api/Address'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { NumberFieldValue } from '@/typings/NumberFieldValue'; -import { fromTypeCode, stringToBoolean, stringToUndefined, toTypeCode } from '@/utils/formUtils'; +import { + fromTypeCode, + stringToBoolean, + stringToNull, + toTypeCodeConcept, + toTypeCodeNullable, +} from '@/utils/formUtils'; +import { exists, isValidId } from '@/utils/utils'; export interface WithAcquisitionTeam { team: AcquisitionTeamFormModel[]; @@ -27,37 +36,38 @@ export class AcquisitionTeamFormModel { this.contact = contact; } - toApi(acquisitionFileId: number): Api_AcquisitionFileTeam | null { + toApi(acquisitionFileId: number): ApiGen_Concepts_AcquisitionFileTeam | null { const personId = this.contact?.personId ?? null; const organizationId = !personId ? this.contact?.organizationId ?? null : null; - if (personId === null && organizationId === null) { + if (!isValidId(personId) && !isValidId(organizationId)) { return null; } return { - id: this.id, - rowVersion: this.rowVersion, + id: this.id ?? 0, acquisitionFileId: acquisitionFileId, - personId: personId ?? undefined, - person: undefined, - organizationId: organizationId ?? undefined, - organization: undefined, + personId: personId ?? null, + person: null, + organizationId: organizationId ?? null, + organization: null, primaryContactId: !!this.primaryContactId && isNumber(+this.primaryContactId) ? Number(this.primaryContactId) - : undefined, - teamProfileType: toTypeCode(this.contactTypeCode), + : null, + teamProfileType: toTypeCodeNullable(this.contactTypeCode), teamProfileTypeCode: this.contactTypeCode, + primaryContact: null, + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_AcquisitionFileTeam | null): AcquisitionTeamFormModel { - const contact: IContactSearchResult | undefined = - model?.person !== undefined && model?.person !== null - ? fromApiPerson(model.person) - : model?.organization !== undefined && model?.organization !== null - ? fromApiOrganization(model.organization) - : undefined; + static fromApi(model: ApiGen_Concepts_AcquisitionFileTeam | null): AcquisitionTeamFormModel { + // todo:the method 'exists' here should allow the compiler to validate the child property. this works correctly in typescropt 5.3 + + const contact: IContactSearchResult | undefined = exists(model?.person) + ? fromApiPerson(model!.person) + : exists(model?.organization) + ? fromApiOrganization(model!.organization) + : undefined; const newForm = new AcquisitionTeamFormModel( fromTypeCode(model?.teamProfileType) || '', @@ -107,11 +117,10 @@ export class AcquisitionOwnerFormModel { } } - toApi(): Api_AcquisitionFileOwner { + toApi(): ApiGen_Concepts_AcquisitionFileOwner { return { - id: this.id, - rowVersion: this.rowVersion, - acquisitionFileId: this.acquisitionFileId, + id: this.id ?? 0, + acquisitionFileId: this.acquisitionFileId ?? 0, isPrimaryContact: stringToBoolean(this.isPrimaryContact), isOrganization: this.isOrganization === 'true' ? true : false, lastNameAndCorpName: @@ -140,13 +149,14 @@ export class AcquisitionOwnerFormModel { contactPhoneNum: this.contactPhoneNumber.trim() === '' ? null : this.contactPhoneNumber.trim(), address: OwnerAddressFormModel.toApi(this.address), + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_AcquisitionFileOwner): AcquisitionOwnerFormModel { + static fromApi(model: ApiGen_Concepts_AcquisitionFileOwner): AcquisitionOwnerFormModel { const newForm = new AcquisitionOwnerFormModel(); newForm.id = model.id; - newForm.rowVersion = model.rowVersion; + newForm.rowVersion = model.rowVersion ?? undefined; newForm.acquisitionFileId = model.acquisitionFileId; newForm.isPrimaryContact = model.isPrimaryContact ? 'true' : ''; newForm.isOrganization = model.isOrganization ? 'true' : 'false'; @@ -193,23 +203,23 @@ export class OwnerAddressFormModel { return 1; } - static fromApi(apiAddress: Api_Address): OwnerAddressFormModel { + static fromApi(apiAddress: ApiGen_Concepts_Address): OwnerAddressFormModel { const model = new OwnerAddressFormModel(); - model.id = apiAddress.id; - model.rowVersion = apiAddress.rowVersion; - model.streetAddress1 = apiAddress.streetAddress1; - model.streetAddress2 = apiAddress.streetAddress2; - model.streetAddress3 = apiAddress.streetAddress3; - model.municipality = apiAddress.municipality; - model.postal = apiAddress.postal; - model.provinceId = apiAddress.provinceStateId; - model.countryId = apiAddress.countryId; - model.countryOther = apiAddress.countryOther; + model.id = apiAddress.id ?? undefined; + model.rowVersion = apiAddress.rowVersion ?? undefined; + model.streetAddress1 = apiAddress.streetAddress1 ?? undefined; + model.streetAddress2 = apiAddress.streetAddress2 ?? undefined; + model.streetAddress3 = apiAddress.streetAddress3 ?? undefined; + model.municipality = apiAddress.municipality ?? undefined; + model.postal = apiAddress.postal ?? undefined; + model.provinceId = apiAddress.provinceStateId ?? undefined; + model.countryId = apiAddress.countryId ?? undefined; + model.countryOther = apiAddress.countryOther ?? undefined; return model; } - static toApi(model: OwnerAddressFormModel | undefined): Api_Address | null { + static toApi(model: OwnerAddressFormModel | undefined): ApiGen_Concepts_Address | null { if ( !model || (isEmpty(model.streetAddress1) && isEmpty(model.municipality) && isEmpty(model.postal)) @@ -218,18 +228,23 @@ export class OwnerAddressFormModel { } return { - id: model.id, - rowVersion: model.rowVersion, - streetAddress1: model.streetAddress1, - streetAddress2: stringToUndefined(model.streetAddress2), - streetAddress3: stringToUndefined(model.streetAddress3), - municipality: model.municipality, - postal: model.postal, - provinceStateId: isEmpty(model.provinceId) ? undefined : Number(model.provinceId), - province: isEmpty(model.provinceId) ? undefined : toTypeCode(Number(model.provinceId)), + id: model.id ?? null, + rowVersion: model.rowVersion ?? null, + streetAddress1: model.streetAddress1 ?? null, + streetAddress2: stringToNull(model.streetAddress2), + streetAddress3: stringToNull(model.streetAddress3), + municipality: model.municipality ?? null, + postal: model.postal ?? null, + provinceStateId: isEmpty(model.provinceId) ? null : Number(model.provinceId), + province: isEmpty(model.provinceId) ? null : toTypeCodeConcept(Number(model.provinceId)), countryId: Number(model.countryId), - country: toTypeCode(Number(model.countryId)), - countryOther: model.countryOther, + country: toTypeCodeConcept(Number(model.countryId)), + countryOther: model.countryOther ?? null, + comment: null, + district: null, + latitude: null, + longitude: null, + region: null, }; } } diff --git a/source/frontend/src/features/mapSideBar/acquisition/hooks/useAddAcquisitionFormManagement.ts b/source/frontend/src/features/mapSideBar/acquisition/hooks/useAddAcquisitionFormManagement.ts index 019da3abd4..0b80916708 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/hooks/useAddAcquisitionFormManagement.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/hooks/useAddAcquisitionFormManagement.ts @@ -8,15 +8,16 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import useApiUserOverride from '@/hooks/useApiUserOverride'; import { useInitialMapSelectorProperties } from '@/hooks/useInitialMapSelectorProperties'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { exists, isValidId } from '@/utils'; import { AddAcquisitionFileYupSchema } from '../add/AddAcquisitionFileYupSchema'; import { AcquisitionForm } from '../add/models'; export interface IUseAddAcquisitionFormManagementProps { /** Optional - callback to execute after acquisition file has been added to the datastore */ - onSuccess?: (acquisitionFile: Api_AcquisitionFile) => Promise; + onSuccess?: (acquisitionFile: ApiGen_Concepts_AcquisitionFile) => Promise; initialForm?: AcquisitionForm; selectedFeature: LocationFeatureDataset | null; formikRef: React.RefObject>; @@ -41,7 +42,7 @@ export function useAddAcquisitionFormManagement(props: IUseAddAcquisitionFormMan try { const acquisitionFile = values.toApi(); const response = await addAcquisitionFile.execute(acquisitionFile, userOverrideCodes); - if (!!response?.id) { + if (exists(response) && isValidId(response?.id)) { if (typeof onSuccess === 'function') { await onSuccess(response); } diff --git a/source/frontend/src/features/mapSideBar/acquisition/models/DetailAcquisitionFileOwner.ts b/source/frontend/src/features/mapSideBar/acquisition/models/DetailAcquisitionFileOwner.ts index 135e23a226..8fe035e762 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/models/DetailAcquisitionFileOwner.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/models/DetailAcquisitionFileOwner.ts @@ -1,5 +1,6 @@ -import { Api_AcquisitionFileOwner } from '@/models/api/AcquisitionFile'; -import { Api_Address } from '@/models/api/Address'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_Address } from '@/models/api/generated/ApiGen_Concepts_Address'; +import { exists } from '@/utils/utils'; export class DetailAcquisitionFileOwner { isPrimary?: boolean; @@ -9,7 +10,7 @@ export class DetailAcquisitionFileOwner { ownerContactEmail?: string; ownerContactPhone?: string; - static fromApi(owner: Api_AcquisitionFileOwner): DetailAcquisitionFileOwner { + static fromApi(owner: ApiGen_Concepts_AcquisitionFileOwner): DetailAcquisitionFileOwner { return { isPrimary: owner.isPrimaryContact, ownerName: getOwnerDisplayName(owner), @@ -21,7 +22,7 @@ export class DetailAcquisitionFileOwner { } } -const getOwnerDisplayName = (owner: Api_AcquisitionFileOwner): string => { +const getOwnerDisplayName = (owner: ApiGen_Concepts_AcquisitionFileOwner): string => { let ownerDisplayName = ''; if (owner.isOrganization) { let regNumber = owner.registrationNumber ? `Reg#:${owner.registrationNumber}` : null; @@ -43,8 +44,8 @@ const getOwnerDisplayName = (owner: Api_AcquisitionFileOwner): string => { return ownerDisplayName; }; -const getFormattedAddress = (address?: Api_Address | null): string => { - if (address === null || address === undefined) { +const getFormattedAddress = (address?: ApiGen_Concepts_Address | null): string => { + if (!exists(address)) { return ''; } @@ -90,5 +91,5 @@ const concatValues = ( nameParts: Array, separator: string = ' ', ): string => { - return nameParts.filter(n => n !== null && n !== undefined && n.trim() !== '').join(separator); + return nameParts.filter(n => exists(n) && n.trim() !== '').join(separator); }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/models/PayeeOptionModel.ts b/source/frontend/src/features/mapSideBar/acquisition/models/PayeeOptionModel.ts index 9df9d141cf..d2e312fd25 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/models/PayeeOptionModel.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/models/PayeeOptionModel.ts @@ -1,8 +1,10 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; -import { Api_AcquisitionFileOwner, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { isNullOrWhitespace } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { exists, isNullOrWhitespace } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { PayeeType } from './PayeeTypeModel'; @@ -28,7 +30,7 @@ export class PayeeOption { this.payeeType = payeeType; } - public static fromApi(apiModel: Api_CompensationRequisition): string { + public static fromApi(apiModel: ApiGen_Concepts_CompensationRequisition): string { if (apiModel.acquisitionOwnerId) { return PayeeOption.generateKey(apiModel.acquisitionOwnerId, PayeeType.Owner); } @@ -58,8 +60,11 @@ export class PayeeOption { return ''; } - public static toApi(payeeKey: string, options: PayeeOption[]): Api_CompensationRequisition { - const compensationModel: Api_CompensationRequisition = { + public static toApi( + payeeKey: string, + options: PayeeOption[], + ): ApiGen_Concepts_CompensationRequisition { + const compensationModel: ApiGen_Concepts_CompensationRequisition = { isPaymentInTrust: null, gstNumber: null, acquisitionOwnerId: null, @@ -91,6 +96,7 @@ export class PayeeOption { finalizedDate: null, specialInstruction: null, detailedRemarks: null, + ...getEmptyBaseAudit(), }; if (isNullOrWhitespace(payeeKey)) { @@ -128,7 +134,7 @@ export class PayeeOption { } } - public static createOwner(model: Api_AcquisitionFileOwner): PayeeOption { + public static createOwner(model: ApiGen_Concepts_AcquisitionFileOwner): PayeeOption { let name = model.isOrganization ? `${model.lastNameAndCorpName}, Inc. No. ${model.incorporationNumber} (OR Reg. No. ${model.registrationNumber})` : [model.givenName, model.lastNameAndCorpName, model.otherName].filter(x => !!x).join(' '); @@ -141,7 +147,7 @@ export class PayeeOption { ); } - public static createOwnerSolicitor(model: Api_InterestHolder): PayeeOption { + public static createOwnerSolicitor(model: ApiGen_Concepts_InterestHolder): PayeeOption { let name = ''; if (model.person) { name = formatApiPersonNames(model.person); @@ -157,7 +163,7 @@ export class PayeeOption { ); } - public static createOwnerRepresentative(model: Api_InterestHolder): PayeeOption { + public static createOwnerRepresentative(model: ApiGen_Concepts_InterestHolder): PayeeOption { let name = formatApiPersonNames(model.person); return new PayeeOption( model.interestHolderId || 0, @@ -168,7 +174,7 @@ export class PayeeOption { ); } - public static createTeamMember(model: Api_AcquisitionFileTeam): PayeeOption { + public static createTeamMember(model: ApiGen_Concepts_AcquisitionFileTeam): PayeeOption { let name = ''; if (model.person) { name = formatApiPersonNames(model.person); @@ -184,7 +190,7 @@ export class PayeeOption { ); } - public static createInterestHolder(model: Api_InterestHolder): PayeeOption { + public static createInterestHolder(model: ApiGen_Concepts_InterestHolder): PayeeOption { if (model.interestHolderType?.id === InterestHolderType.OWNER_SOLICITOR) { return this.createOwnerSolicitor(model); } else if (model.interestHolderType?.id === InterestHolderType.OWNER_REPRESENTATIVE) { @@ -200,9 +206,12 @@ export class PayeeOption { // The interest holders should always have a property const typeDescription = - model.interestHolderProperties.length > 0 - ? model.interestHolderProperties[0].propertyInterestTypes[0]?.description - : 'ERROR: Missing interest type'; + exists(model.interestHolderProperties) && model.interestHolderProperties.length > 0 + ? exists(model.interestHolderProperties[0].propertyInterestTypes) && + model.interestHolderProperties[0].propertyInterestTypes.length > 0 + ? model.interestHolderProperties[0].propertyInterestTypes[0].description + : 'ERROR: Missing interest type' + : 'ERROR: Missing interest holder'; return new PayeeOption( model.interestHolderId || 0, @@ -213,7 +222,7 @@ export class PayeeOption { ); } - public static createLegacyPayee(model: Api_CompensationRequisition): PayeeOption { + public static createLegacyPayee(model: ApiGen_Concepts_CompensationRequisition): PayeeOption { return new PayeeOption( model.id || 0, model.legacyPayee || '', diff --git a/source/frontend/src/features/mapSideBar/acquisition/router/AcquisitionRouter.tsx b/source/frontend/src/features/mapSideBar/acquisition/router/AcquisitionRouter.tsx index b1fb5f1e5f..f8721df67a 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/router/AcquisitionRouter.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/router/AcquisitionRouter.tsx @@ -5,8 +5,8 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; import Claims from '@/constants/claims'; import { InventoryTabNames } from '@/features/mapSideBar/property/InventoryTabs'; import { FileTabType } from '@/features/mapSideBar/shared/detail/FileTabs'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { stripTrailingSlash } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { exists, stripTrailingSlash } from '@/utils'; import AppRoute from '@/utils/AppRoute'; import { UpdateChecklistForm } from '../../shared/tabs/checklist/update/UpdateChecklistForm'; @@ -24,7 +24,7 @@ import { UpdateStakeHolderForm } from '../tabs/stakeholders/update/UpdateStakeHo export interface IAcquisitionRouterProps { formikRef: React.Ref>; - acquisitionFile?: Api_AcquisitionFile; + acquisitionFile?: ApiGen_Concepts_AcquisitionFile; isEditing: boolean; setIsEditing: (value: boolean) => void; defaultFileTab: FileTabType; @@ -35,7 +35,7 @@ export interface IAcquisitionRouterProps { export const AcquisitionRouter: React.FC = props => { const { path, url } = useRouteMatch(); - if (props.acquisitionFile === undefined || props.acquisitionFile === null) { + if (!exists(props.acquisitionFile)) { return null; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.test.tsx index b3991a425e..99b0b9cef3 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.test.tsx @@ -103,6 +103,7 @@ describe('AcquisitionFileTabs component', () => { id: 'SECTN3', description: 'Section 3 Agreement', isDisabled: false, + displayOrder: null, }; const { queryByText } = setup({ @@ -120,6 +121,7 @@ describe('AcquisitionFileTabs component', () => { id: 'SECTN6', description: 'Section 6 Expropriation', isDisabled: false, + displayOrder: null, }; const { queryByText } = setup({ diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.tsx index 86a8c02132..47e527e058 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/AcquisitionFileTabs.tsx @@ -1,14 +1,15 @@ import React, { useContext } from 'react'; import { useHistory, useLocation, useParams } from 'react-router-dom'; +import { EnumAcquisitionFileType } from '@/constants/acquisitionFileType'; import * as API from '@/constants/API'; import { Claims } from '@/constants/claims'; import { NoteTypes } from '@/constants/noteTypes'; import { FileTabs, FileTabType, TabFileView } from '@/features/mapSideBar/shared/detail/FileTabs'; import NoteListView from '@/features/notes/list/NoteListView'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_AcquisitionFile, EnumAcquisitionFileType } from '@/models/api/AcquisitionFile'; import { ApiGen_CodeTypes_DocumentRelationType } from '@/models/api/generated/ApiGen_CodeTypes_DocumentRelationType'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { SideBarContext } from '../../context/sidebarContext'; import { ChecklistView } from '../../shared/tabs/checklist/detail/ChecklistView'; @@ -24,7 +25,7 @@ import StakeHolderContainer from './stakeholders/detail/StakeHolderContainer'; import StakeHolderView from './stakeholders/detail/StakeHolderView'; export interface IAcquisitionFileTabsProps { - acquisitionFile?: Api_AcquisitionFile; + acquisitionFile?: ApiGen_Concepts_AcquisitionFile; defaultTab: FileTabType; setIsEditing: (value: boolean) => void; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementContainer.tsx index b29b277021..efee20afd7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementContainer.tsx @@ -3,6 +3,7 @@ import { useCallback, useContext, useEffect } from 'react'; import { SideBarContext } from '@/features/mapSideBar/context/sidebarContext'; import { useAgreementProvider } from '@/hooks/repositories/useAgreementProvider'; +import { isValidId } from '@/utils'; import { useGenerateAgreement } from '../../../common/GenerateForm/hooks/useGenerateAgreement'; import { IAgreementViewProps } from './AgreementView'; @@ -22,7 +23,7 @@ export const AgreementContainer: React.FunctionComponent< const generateAgreement = useGenerateAgreement(); const { file, fileLoading } = useContext(SideBarContext); - if (!!file && file?.id === undefined && fileLoading === false) { + if (!isValidId(file?.id) && fileLoading === false) { throw new Error('Unable to determine id of current file.'); } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementView.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementView.tsx index 452e4d082a..15a0f8072a 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/detail/AgreementView.tsx @@ -11,16 +11,17 @@ import { StyledEditWrapper, StyledSummarySection } from '@/components/common/Sec import { StyledAddButton } from '@/components/common/styles'; import Claims from '@/constants/claims'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { AgreementStatusTypes, Api_Agreement } from '@/models/api/Agreement'; +import { ApiGen_CodeTypes_AgreementStatusTypes } from '@/models/api/generated/ApiGen_CodeTypes_AgreementStatusTypes'; +import { ApiGen_Concepts_Agreement } from '@/models/api/generated/ApiGen_Concepts_Agreement'; import { formatMoney, prettyFormatDate } from '@/utils'; import { StyledSectionSubheader } from '../styles'; export interface IAgreementViewProps { loading: boolean; - agreements: Api_Agreement[]; + agreements: ApiGen_Concepts_Agreement[]; onEdit: () => void; - onGenerate: (agreement: Api_Agreement) => void; + onGenerate: (agreement: ApiGen_Concepts_Agreement) => void; } export const AgreementView: React.FunctionComponent = ({ @@ -75,7 +76,8 @@ export const AgreementView: React.FunctionComponent = ({ {agreement.agreementStatusType?.description ?? ''} - {agreement.agreementStatusType?.id === AgreementStatusTypes.CANCELLED && ( + {agreement.agreementStatusType?.id === + ApiGen_CodeTypes_AgreementStatusTypes.CANCELLED && ( {agreement.cancellationNote ?? ''} diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/AgreementSubForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/AgreementSubForm.tsx index 7fe5293573..4ebd3b0f41 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/AgreementSubForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/AgreementSubForm.tsx @@ -13,7 +13,7 @@ import { SectionField, StyledFieldLabel } from '@/components/common/Section/Sect import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; import { useModalContext } from '@/hooks/useModalContext'; -import { AgreementStatusTypes } from '@/models/api/Agreement'; +import { ApiGen_CodeTypes_AgreementStatusTypes } from '@/models/api/generated/ApiGen_CodeTypes_AgreementStatusTypes'; import { ILookupCode } from '@/store/slices/lookupCodes'; import { mapLookupCode } from '@/utils'; import { withNameSpace } from '@/utils/formUtils'; @@ -46,7 +46,7 @@ export const AgreementSubForm: React.FunctionComponent = const setFieldValue = formikProps.setFieldValue; useEffect(() => { if ( - agreement.agreementStatusTypeCode !== AgreementStatusTypes.CANCELLED && + agreement.agreementStatusTypeCode !== ApiGen_CodeTypes_AgreementStatusTypes.CANCELLED && !!agreement.cancellationNote ) { setModalContent({ @@ -59,7 +59,7 @@ export const AgreementSubForm: React.FunctionComponent = handleCancel: () => { setFieldValue( withNameSpace(nameSpace, 'agreementStatusTypeCode'), - AgreementStatusTypes.CANCELLED, + ApiGen_CodeTypes_AgreementStatusTypes.CANCELLED, ); setDisplayModal(false); }, @@ -82,7 +82,7 @@ export const AgreementSubForm: React.FunctionComponent = disabled={isDisabled} /> - {agreement.agreementStatusTypeCode === AgreementStatusTypes.CANCELLED && ( + {agreement.agreementStatusTypeCode === ApiGen_CodeTypes_AgreementStatusTypes.CANCELLED && ( diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsContainer.test.tsx index 3c68e3a068..8118336682 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsContainer.test.tsx @@ -3,7 +3,7 @@ import { createRef } from 'react'; import { useAgreementProvider } from '@/hooks/repositories/useAgreementProvider'; import { mockAgreementsResponse } from '@/mocks/agreements.mock'; import { mockLookups } from '@/mocks/index.mock'; -import { Api_Agreement } from '@/models/api/Agreement'; +import { ApiGen_Concepts_Agreement } from '@/models/api/generated/ApiGen_Concepts_Agreement'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, render, RenderOptions } from '@/utils/test-utils'; @@ -83,7 +83,7 @@ describe('UpdateAgreementsContainer component', () => { it('makes request to update the agreements and returns the response', async () => { setup(); mockUpdateAgreements.mockResolvedValue(mockAgreementsResponse()); - let updatedAgreements: Api_Agreement[] | undefined; + let updatedAgreements: ApiGen_Concepts_Agreement[] | undefined; const testAgreementForm = new AgreementsFormModel(testAcquisitionFileId); const testAgreementItem = new SingleAgreementFormModel(); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsForm.tsx index 8165015691..a93e73c190 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/UpdateAgreementsForm.tsx @@ -9,8 +9,8 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { Section } from '@/components/common/Section/Section'; import TooltipIcon from '@/components/common/TooltipIcon'; import { getDeleteModalProps, useModalContext } from '@/hooks/useModalContext'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_Agreement } from '@/models/api/Agreement'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Agreement } from '@/models/api/generated/ApiGen_Concepts_Agreement'; import { ILookupCode } from '@/store/slices/lookupCodes'; import { cannotEditMessage } from '../../../common/constants'; @@ -21,11 +21,13 @@ import { UpdateAgreementsYupSchema } from './UpdateAgreementsYupSchema'; export interface IUpdateAgreementsFormProps { isLoading: boolean; - acquistionFile: Api_AcquisitionFile | undefined; + acquistionFile: ApiGen_Concepts_AcquisitionFile | undefined; formikRef: React.Ref>; initialValues: AgreementsFormModel; agreementTypes: ILookupCode[]; - onSave: (apiAcquisitionFile: AgreementsFormModel) => Promise; + onSave: ( + apiAcquisitionFile: AgreementsFormModel, + ) => Promise; } export const UpdateAgreementsForm: React.FC = ({ diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/models.ts index 81f37418ad..6c0db3b06f 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/agreement/update/models.ts @@ -1,6 +1,8 @@ -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_Agreement } from '@/models/api/Agreement'; -import { stringToUndefined, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_Agreement } from '@/models/api/generated/ApiGen_Concepts_Agreement'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { isValidIsoDateTime } from '@/utils'; +import { stringToNull, stringToNumberOrNull, toTypeCodeNullable } from '@/utils/formUtils'; export class SingleAgreementFormModel { public agreementId: number = 0; @@ -26,12 +28,12 @@ export class SingleAgreementFormModel { public rowVersion: number | null = null; - static fromApi(apiModel: Api_Agreement): SingleAgreementFormModel { + static fromApi(apiModel: ApiGen_Concepts_Agreement): SingleAgreementFormModel { const agreement = new SingleAgreementFormModel(); agreement.agreementId = apiModel.agreementId; - agreement.agreementTypeCode = apiModel.agreementType.id || ''; - agreement.agreementTypeDescription = apiModel.agreementType.description || ''; + agreement.agreementTypeCode = apiModel.agreementType?.id || ''; + agreement.agreementTypeDescription = apiModel.agreementType?.description || ''; agreement.agreementDate = apiModel.agreementDate || ''; agreement.agreementStatusTypeCode = apiModel.agreementStatusType?.id || ''; agreement.agreementStatusTypeDescription = apiModel.agreementStatusType?.description || ''; @@ -53,27 +55,38 @@ export class SingleAgreementFormModel { return agreement; } - public toApi(acquisitionFileId: number): Api_Agreement { + public toApi(acquisitionFileId: number): ApiGen_Concepts_Agreement { return { agreementId: this.agreementId, acquisitionFileId: acquisitionFileId, - agreementType: toTypeCode(this.agreementTypeCode) || {}, - agreementDate: stringToUndefined(this.agreementDate), - agreementStatusType: toTypeCode(this.agreementStatusTypeCode) || {}, - completionDate: stringToUndefined(this.completionDate), - terminationDate: stringToUndefined(this.terminationDate), - commencementDate: stringToUndefined(this.commencementDate), - possessionDate: stringToUndefined(this.possessionDate), + agreementType: toTypeCodeNullable(this.agreementTypeCode) || { + id: null, + description: null, + displayOrder: null, + isDisabled: false, + }, + agreementDate: isValidIsoDateTime(this.agreementDate) ? this.agreementDate : null, + agreementStatusType: toTypeCodeNullable(this.agreementStatusTypeCode) || { + id: null, + description: null, + displayOrder: null, + isDisabled: false, + }, + completionDate: isValidIsoDateTime(this.completionDate) ? this.completionDate : null, + terminationDate: isValidIsoDateTime(this.terminationDate) ? this.terminationDate : null, + commencementDate: isValidIsoDateTime(this.commencementDate) ? this.commencementDate : null, + possessionDate: isValidIsoDateTime(this.possessionDate) ? this.possessionDate : null, depositAmount: this.depositAmount !== '' ? Number(this.depositAmount) : null, - noLaterThanDays: stringToUndefined(this.noLaterThanDays), - purchasePrice: stringToUndefined(this.purchasePrice), - legalSurveyPlanNum: stringToUndefined(this.legalSurveyPlanNum), - offerDate: stringToUndefined(this.offerDate), - expiryDateTime: stringToUndefined(this.expiryDateTime), - signedDate: stringToUndefined(this.signedDate), - inspectionDate: stringToUndefined(this.inspectionDate), - rowVersion: this.rowVersion ?? undefined, - cancellationNote: stringToUndefined(this.cancellationNote), + noLaterThanDays: stringToNumberOrNull(this.noLaterThanDays), + purchasePrice: stringToNumberOrNull(this.purchasePrice), + legalSurveyPlanNum: stringToNull(this.legalSurveyPlanNum), + offerDate: isValidIsoDateTime(this.offerDate) ? this.offerDate : null, + expiryDateTime: isValidIsoDateTime(this.expiryDateTime) ? this.expiryDateTime : null, + signedDate: isValidIsoDateTime(this.signedDate) ? this.signedDate : null, + inspectionDate: isValidIsoDateTime(this.inspectionDate) ? this.inspectionDate : null, + cancellationNote: stringToNull(this.cancellationNote), + isDraft: null, + ...getEmptyBaseAudit(this.rowVersion), }; } } @@ -86,16 +99,19 @@ export class AgreementsFormModel { this.acquisitionFileId = acquisitionFileId; } - static fromApi(acquisitionFileId: number, agreements: Api_Agreement[]): AgreementsFormModel { + static fromApi( + acquisitionFileId: number, + agreements: ApiGen_Concepts_Agreement[], + ): AgreementsFormModel { const newFormModel = new AgreementsFormModel(acquisitionFileId); agreements.forEach(x => newFormModel.agreements.push(SingleAgreementFormModel.fromApi(x))); return newFormModel; } - public toApi(): Api_Agreement[] { + public toApi(): ApiGen_Concepts_Agreement[] { return this.agreements.map(x => x.toApi(this.acquisitionFileId)); } } -export const isAcquisitionFile = (file: unknown): file is Api_AcquisitionFile => +export const isAcquisitionFile = (file: unknown): file is ApiGen_Concepts_AcquisitionFile => !!file && Object.prototype.hasOwnProperty.call(file, 'acquisitionTypeCode'); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.test.tsx index f3a59353de..442c24f721 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.test.tsx @@ -1,3 +1,4 @@ +// eslint-disable-next-line simple-import-sort/imports import { createRef } from 'react'; import { IUpdateChecklistFormProps } from '@/features/mapSideBar/shared/tabs/checklist/update/UpdateChecklistForm'; @@ -7,12 +8,12 @@ import { mockFileChecklistResponse, } from '@/mocks/acquisitionFiles.mock'; import { mockLookups } from '@/mocks/index.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_FileWithChecklist } from '@/models/api/File'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, createAxiosError, render, RenderOptions, screen } from '@/utils/test-utils'; import { UpdateAcquisitionChecklistContainer } from './UpdateAcquisitionChecklistContainer'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileWithChecklist } from '@/models/api/generated/ApiGen_Concepts_FileWithChecklist'; // mock API service calls jest.mock('@/hooks/repositories/useAcquisitionProvider'); @@ -37,7 +38,7 @@ const TestView: React.FC = props => { }; describe('UpdateAcquisitionChecklist container', () => { - let acquisitionFile: Api_AcquisitionFile | undefined = undefined; + let acquisitionFile: ApiGen_Concepts_AcquisitionFile | undefined = undefined; const onSuccess = jest.fn(); const setup = (renderOptions: RenderOptions = {}) => { @@ -82,9 +83,9 @@ describe('UpdateAcquisitionChecklist container', () => { setup(); mockUpdateAcquisitionChecklist.mockResolvedValue(mockFileChecklistResponse()); - let updatedChecklist: Api_FileWithChecklist | undefined; + let updatedChecklist: ApiGen_Concepts_FileWithChecklist | undefined; await act(async () => { - updatedChecklist = await viewProps?.onSave({} as Api_FileWithChecklist); + updatedChecklist = await viewProps?.onSave({} as ApiGen_Concepts_FileWithChecklist); }); expect(mockUpdateAcquisitionChecklist).toHaveBeenCalled(); @@ -95,7 +96,7 @@ describe('UpdateAcquisitionChecklist container', () => { setup(); await act(async () => { - viewProps?.onSuccess({} as Api_AcquisitionFile); + viewProps?.onSuccess({} as ApiGen_Concepts_AcquisitionFile); }); expect(onSuccess).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.tsx index de4fbbd3c8..dd3310f7fb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/checklist/update/UpdateAcquisitionChecklistContainer.tsx @@ -9,12 +9,12 @@ import { IUpdateChecklistFormProps } from '@/features/mapSideBar/shared/tabs/che import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_FileWithChecklist } from '@/models/api/File'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileWithChecklist } from '@/models/api/generated/ApiGen_Concepts_FileWithChecklist'; export interface IAcquisitionChecklistContainerProps { formikRef: React.Ref>; - acquisitionFile?: Api_AcquisitionFile; + acquisitionFile?: ApiGen_Concepts_AcquisitionFile; onSuccess: () => void; View: React.FC; } @@ -37,11 +37,11 @@ export const UpdateAcquisitionChecklistContainer: React.FC { + const saveChecklist = async (apiAcquisitionFile: ApiGen_Concepts_FileWithChecklist) => { return updateAcquisitionChecklist(apiAcquisitionFile); }; - const onUpdateSuccess = async (apiAcquisitionFile: Api_FileWithChecklist) => { + const onUpdateSuccess = async (apiAcquisitionFile: ApiGen_Concepts_FileWithChecklist) => { onSuccess && onSuccess(); }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/CompensationRequisitionTrayContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/CompensationRequisitionTrayContainer.tsx index aa2c6cdec7..ccb1851c84 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/CompensationRequisitionTrayContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/CompensationRequisitionTrayContainer.tsx @@ -3,9 +3,10 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { SideBarContext } from '@/features/mapSideBar/context/sidebarContext'; import { useProjectProvider } from '@/hooks/repositories/useProjectProvider'; import { useCompensationRequisitionRepository } from '@/hooks/repositories/useRequisitionCompensationRepository'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { SystemConstants, useSystemConstants } from '@/store/slices/systemConstants'; +import { isValidId } from '@/utils'; import { CompensationRequisitionTrayViewProps } from './CompensationRequisitionTrayView'; @@ -30,7 +31,7 @@ export const CompensationRequisitionTrayContainer: React.FunctionComponent< } = useProjectProvider(); const [loadedCompensation, setLoadedCompensation] = useState< - Api_CompensationRequisition | undefined + ApiGen_Concepts_CompensationRequisition | undefined >(); const clientConstant = getSystemConstant(SystemConstants.CLIENT); @@ -42,7 +43,7 @@ export const CompensationRequisitionTrayContainer: React.FunctionComponent< } = useCompensationRequisitionRepository(); const fetchCompensationReq = useCallback(async () => { - if (!!compensationRequisitionId) { + if (isValidId(compensationRequisitionId)) { const compensationReq = await getCompensationRequisition(compensationRequisitionId); if (compensationReq) { setLoadedCompensation(compensationReq); @@ -74,7 +75,9 @@ export const CompensationRequisitionTrayContainer: React.FunctionComponent< return loadedCompensation ? ( void; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.test.tsx index 311896e7a1..4b54bd982d 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.test.tsx @@ -5,10 +5,11 @@ import { mockApiAcquisitionFileTeamPerson, } from '@/mocks/acquisitionFiles.mock'; import { getMockApiDefaultCompensation } from '@/mocks/compensations.mock'; +import { getEmptyPerson } from '@/mocks/contacts.mock'; import { emptyApiInterestHolder } from '@/mocks/interestHolder.mock'; -import { getMockOrganization } from '@/mocks/organization.mock'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { Api_Person } from '@/models/api/Person'; +import { getEmptyOrganization, getMockOrganization } from '@/mocks/organization.mock'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; import { render, RenderOptions, waitForEffects } from '@/utils/test-utils'; import { @@ -65,7 +66,7 @@ describe('Compensation Detail View container', () => { id: 1, firstName: 'first', surname: 'last', - } as Api_Person, + } as ApiGen_Concepts_Person, }); getOrganizationConceptFn.mockResolvedValue({ data: getMockOrganization(), @@ -94,7 +95,7 @@ describe('Compensation Detail View container', () => { id: 1, firstName: 'first', surname: 'last', - } as Api_Person); + } as ApiGen_Concepts_Person); }); it('makes request to get organization concept for acquisition team payee', async () => { @@ -115,12 +116,12 @@ describe('Compensation Detail View container', () => { }); it('makes request to get person concept for interest holder payee', async () => { - const ihPerson: Api_InterestHolder = { + const ihPerson: ApiGen_Concepts_InterestHolder = { ...emptyApiInterestHolder, interestHolderId: 1, acquisitionFileId: 2, personId: 1, - person: { id: 1, firstName: 'first', surname: 'last' }, + person: { ...getEmptyPerson(), id: 1, firstName: 'first', surname: 'last' }, }; setup({ props: { @@ -138,16 +139,16 @@ describe('Compensation Detail View container', () => { id: 1, firstName: 'first', surname: 'last', - } as Api_Person); + } as ApiGen_Concepts_Person); }); it('makes request to get organization concept for interest holder payee', async () => { - const ihOrg: Api_InterestHolder = { + const ihOrg: ApiGen_Concepts_InterestHolder = { ...emptyApiInterestHolder, interestHolderId: 1, acquisitionFileId: 2, organizationId: 100, - organization: { id: 100, name: 'ABC Inc' }, + organization: { ...getEmptyOrganization(), id: 100, name: 'ABC Inc' }, }; setup({ diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.tsx index d0d3c5986e..077e982070 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailContainer.tsx @@ -6,16 +6,17 @@ import { useOrganizationRepository } from '@/features/contacts/repositories/useO import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository'; import { useGenerateH120 } from '@/features/mapSideBar/acquisition/common/GenerateForm/hooks/useGenerateH120'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_Organization } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists, isValidId } from '@/utils'; import { CompensationRequisitionDetailViewProps } from './CompensationRequisitionDetailView'; export interface CompensationRequisitionDetailContainerProps { - compensation: Api_CompensationRequisition; - acquisitionFile: Api_AcquisitionFile; + compensation: ApiGen_Concepts_CompensationRequisition; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; clientConstant: string; loading: boolean; setEditMode: (editMode: boolean) => void; @@ -27,8 +28,10 @@ export const CompensationRequisitionDetailContainer: React.FunctionComponent< > = ({ compensation, setEditMode, View, clientConstant, acquisitionFile, loading }) => { const onGenerate = useGenerateH120(); - const [payeePerson, setPayeePerson] = useState(); - const [payeeOrganization, setPayeeOrganization] = useState(); + const [payeePerson, setPayeePerson] = useState(); + const [payeeOrganization, setPayeeOrganization] = useState< + ApiGen_Concepts_Organization | undefined + >(); const { getPersonDetail: { execute: getPerson, loading: loadingPerson }, @@ -39,28 +42,25 @@ export const CompensationRequisitionDetailContainer: React.FunctionComponent< } = useOrganizationRepository(); const fetchCompensationPayee = useCallback(async () => { - if (compensation.id !== null) { + if (isValidId(compensation.id)) { try { - if (!!compensation.acquisitionFileTeam) { - if (!!compensation.acquisitionFileTeam.personId) { + if (exists(compensation.acquisitionFileTeam)) { + if (isValidId(compensation.acquisitionFileTeam.personId)) { const person = await getPerson(compensation.acquisitionFileTeam.personId); setPayeePerson(person); } - if (!!compensation.acquisitionFileTeam.organizationId) { + if (isValidId(compensation.acquisitionFileTeam.organizationId)) { const organization = await getOrganization( compensation.acquisitionFileTeam.organizationId, ); setPayeeOrganization(organization); } } else if (!!compensation.interestHolder) { - if ( - compensation.interestHolder.personId !== undefined && - compensation.interestHolder.personId !== null - ) { + if (isValidId(compensation.interestHolder.personId)) { const person = await getPerson(compensation.interestHolder.personId); setPayeePerson(person); } - if (!!compensation.interestHolder.organizationId) { + if (isValidId(compensation.interestHolder.organizationId)) { const organization = await getOrganization(compensation.interestHolder.organizationId); setPayeeOrganization(organization); } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.test.tsx index 13d8e79fa2..d9aa184218 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.test.tsx @@ -8,7 +8,8 @@ import { emptyCompensationFinancial, getMockApiDefaultCompensation, } from '@/mocks/compensations.mock'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, render, RenderOptions, userEvent, waitFor } from '@/utils/test-utils'; import CompensationRequisitionDetailView, { @@ -127,7 +128,7 @@ describe('Compensation Detail View Component', () => { props: { acquisitionFile: { ...acquistionFile, - fileStatusTypeCode: { id: AcquisitionStatus.Active }, + fileStatusTypeCode: toTypeCodeNullable(AcquisitionStatus.Active), }, compensation: { ...mockFinalCompensation, isDraft: true }, }, @@ -163,7 +164,7 @@ describe('Compensation Detail View Component', () => { props: { acquisitionFile: { ...acquistionFile, - fileStatusTypeCode: { id: AcquisitionStatus.Complete }, + fileStatusTypeCode: toTypeCodeNullable(AcquisitionStatus.Complete), }, compensation: { ...mockFinalCompensation, isDraft: false }, }, @@ -187,7 +188,7 @@ describe('Compensation Detail View Component', () => { }); it('displays the compensation finalized date', async () => { - const mockFinalCompensation: Api_CompensationRequisition = { + const mockFinalCompensation: ApiGen_Concepts_CompensationRequisition = { ...getMockApiDefaultCompensation(), isDraft: false, finalizedDate: '2024-06-12T18:00:00', diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.tsx index 25efc947da..b4b517e43d 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/CompensationRequisitionDetailView.tsx @@ -14,11 +14,11 @@ import { StyledAddButton } from '@/components/common/styles'; import TooltipIcon from '@/components/common/TooltipIcon'; import { Claims, Roles } from '@/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_Organization } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; -import { formatMoney, prettyFormatDate } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { exists, formatMoney, prettyFormatDate } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { cannotEditMessage } from '../../../common/constants'; @@ -26,14 +26,14 @@ import { DetailAcquisitionFileOwner } from '../../../models/DetailAcquisitionFil import StatusUpdateSolver from '../../fileDetails/detail/statusUpdateSolver'; export interface CompensationRequisitionDetailViewProps { - acquisitionFile: Api_AcquisitionFile; - compensation: Api_CompensationRequisition; - compensationContactPerson: Api_Person | undefined; - compensationContactOrganization: Api_Organization | undefined; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; + compensation: ApiGen_Concepts_CompensationRequisition; + compensationContactPerson: ApiGen_Concepts_Person | undefined; + compensationContactOrganization: ApiGen_Concepts_Organization | undefined; clientConstant: string; loading: boolean; setEditMode: (editMode: boolean) => void; - onGenerate: (compensation: Api_CompensationRequisition) => void; + onGenerate: (compensation: ApiGen_Concepts_CompensationRequisition) => void; } interface PayeeViewDetails { @@ -59,10 +59,9 @@ export const CompensationRequisitionDetailView: React.FunctionComponent< const { hasClaim, hasRole } = useKeycloakWrapper(); const [payeeDetails, setPayeeDetails] = useState(null); - const projectName = - compensation?.alternateProject !== undefined - ? compensation?.alternateProject?.code + ' - ' + compensation?.alternateProject?.description - : ''; + const projectName = exists(compensation?.alternateProject) + ? compensation?.alternateProject?.code + ' - ' + compensation?.alternateProject?.description + : ''; useEffect(() => { if (!compensation) { @@ -116,15 +115,15 @@ export const CompensationRequisitionDetailView: React.FunctionComponent< }, [compensation, compensationContactOrganization, compensationContactPerson]); const compPretaxAmount = compensation?.financials - .map(f => f.pretaxAmount ?? 0) + ?.map(f => f.pretaxAmount ?? 0) .reduce((prev, next) => prev + next, 0); const compTaxAmount = compensation?.financials - .map(f => f.taxAmount ?? 0) + ?.map(f => f.taxAmount ?? 0) .reduce((prev, next) => prev + next, 0); const compTotalAmount = compensation?.financials - .map(f => f.totalAmount ?? 0) + ?.map(f => f.totalAmount ?? 0) .reduce((prev, next) => prev + next, 0); const acqFileProject = acquisitionFile?.project; @@ -324,7 +323,7 @@ export const CompensationRequisitionDetailView: React.FunctionComponent<
    - {compensation.financials.map((item, index) => ( + {compensation.financials?.map((item, index) => ( <> diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/__snapshots__/CompensationRequisitionDetailView.test.tsx.snap b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/__snapshots__/CompensationRequisitionDetailView.test.tsx.snap index 6a50a168d5..c261d5cd82 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/__snapshots__/CompensationRequisitionDetailView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/detail/__snapshots__/CompensationRequisitionDetailView.test.tsx.snap @@ -466,9 +466,7 @@ exports[`Compensation Detail View Component renders as expected 1`] = `
    - undefined - undefined -
    + />
    >; } @@ -48,9 +49,9 @@ export const CompensationListContainer: React.FunctionComponent< totalAllowableCompensation: number | null, ): Promise => { if (file) { - const updatedFile = { + const updatedFile: ApiGen_Concepts_AcquisitionFile = { ...file, - totalAllowableCompensation: totalAllowableCompensation ?? undefined, + totalAllowableCompensation: totalAllowableCompensation ?? null, }; try { const response = await putAcquisitionFile(updatedFile, []); @@ -76,7 +77,7 @@ export const CompensationListContainer: React.FunctionComponent< }; const onAddCompensationRequisition = (fileId: number) => { - const defaultCompensationRequisition: Api_CompensationRequisition = { + const defaultCompensationRequisition: ApiGen_Concepts_CompensationRequisition = { id: null, acquisitionFileId: fileId, alternateProjectId: null, @@ -108,6 +109,7 @@ export const CompensationListContainer: React.FunctionComponent< legacyPayee: null, isPaymentInTrust: null, gstNumber: null, + ...getEmptyBaseAudit(), }; postAcquisitionCompensationRequisition(fileId, defaultCompensationRequisition).then( diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.test.tsx index 74f0814eaf..2779886455 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.test.tsx @@ -8,8 +8,9 @@ import { getMockApiCompensationList, } from '@/mocks/compensations.mock'; import { mockAcquisitionFileResponse, mockLookups } from '@/mocks/index.mock'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, render, RenderOptions, userEvent, waitFor } from '@/utils/test-utils'; import CompensationListView, { ICompensationListViewProps } from './CompensationListView'; @@ -72,7 +73,7 @@ describe('compensation list view', () => { }); it('displays the calculated total for the entire file excluding drafts', async () => { - const mockList: Api_CompensationRequisition[] = [ + const mockList: ApiGen_Concepts_CompensationRequisition[] = [ { ...emptyCompensationRequisition, isDraft: true, @@ -116,7 +117,7 @@ describe('compensation list view', () => { const { findAllByTitle } = setup({ acquisitionFile: { ...mockAcquisitionFileResponse(), - fileStatusTypeCode: { id: AcquisitionStatus.Active }, + fileStatusTypeCode: toTypeCodeNullable(AcquisitionStatus.Active), }, compensations: compensations, claims: [Claims.COMPENSATION_REQUISITION_DELETE], diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.tsx index 721b7e2d3c..b32494df25 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationListView.tsx @@ -9,17 +9,17 @@ import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; import { SectionListHeader } from '@/components/common/SectionListHeader'; import Claims from '@/constants/claims'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationFinancial } from '@/models/api/CompensationFinancial'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationFinancial } from '@/models/api/generated/ApiGen_Concepts_CompensationFinancial'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { formatMoney } from '@/utils'; import StatusUpdateSolver from '../../fileDetails/detail/statusUpdateSolver'; import { CompensationResults } from './CompensationResults'; export interface ICompensationListViewProps { - acquisitionFile: Api_AcquisitionFile; - compensations: Api_CompensationRequisition[]; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; + compensations: ApiGen_Concepts_CompensationRequisition[]; onAdd: () => void; onDelete: (compensationId: number) => void; onUpdateTotalCompensation: (totalAllowableCompensation: number | null) => Promise; @@ -37,10 +37,10 @@ export const CompensationListView: React.FunctionComponent !x.isDraft) - .reduce((fileTotal: number, current: Api_CompensationRequisition) => { + .reduce((fileTotal: number, current: ApiGen_Concepts_CompensationRequisition) => { const compensationTotal = current.financials?.reduce( - (financialTotal: number, financial: Api_CompensationFinancial) => { + (financialTotal: number, financial: ApiGen_Concepts_CompensationFinancial) => { return financialTotal + (financial.totalAmount || 0); }, 0, @@ -50,10 +50,10 @@ export const CompensationListView: React.FunctionComponent x.isDraft) - .reduce((fileTotal: number, current: Api_CompensationRequisition) => { + .reduce((fileTotal: number, current: ApiGen_Concepts_CompensationRequisition) => { const compensationTotal = current.financials?.reduce( - (financialTotal: number, financial: Api_CompensationFinancial) => { + (financialTotal: number, financial: ApiGen_Concepts_CompensationFinancial) => { return financialTotal + (financial.totalAmount || 0); }, 0, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationResults.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationResults.tsx index fc048120c2..a464ab10ec 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationResults.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/CompensationResults.tsx @@ -1,11 +1,11 @@ import { Table } from '@/components/Table'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import StatusUpdateSolver from '../../fileDetails/detail/statusUpdateSolver'; import { createCompensationTableColumns } from './columns'; export interface ICompensationResultProps { - results: Api_CompensationRequisition[]; + results: ApiGen_Concepts_CompensationRequisition[]; statusSolver: StatusUpdateSolver; onShow: (compensationId: number) => void; onDelete: (compensationId: number) => void; @@ -17,7 +17,7 @@ export function CompensationResults(props: ICompensationResultProps) { const columns = createCompensationTableColumns(props.statusSolver, props.onShow, props.onDelete); return ( - + name="AcquisitionCompensationTable" manualSortBy={false} lockPageSize={true} diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/columns.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/columns.tsx index 9c8abc71d6..1817746207 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/columns.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/list/columns.tsx @@ -9,8 +9,8 @@ import { InlineFlexDiv } from '@/components/common/styles'; import { ColumnWithProps } from '@/components/Table'; import Claims from '@/constants/claims'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_CompensationFinancial } from '@/models/api/CompensationFinancial'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_CompensationFinancial } from '@/models/api/generated/ApiGen_Concepts_CompensationFinancial'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { formatMoney, prettyFormatDate, stringToFragment } from '@/utils'; import StatusUpdateSolver from '../../fileDetails/detail/statusUpdateSolver'; @@ -20,14 +20,14 @@ export function createCompensationTableColumns( onShow: (compensationId: number) => void, onDelete: (compensationId: number) => void, ) { - const columns: ColumnWithProps[] = [ + const columns: ColumnWithProps[] = [ { Header: 'Final Date', align: 'left', sortable: false, minWidth: 40, maxWidth: 40, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return stringToFragment(prettyFormatDate(cellProps.row.original.finalizedDate)); }, }, @@ -38,7 +38,7 @@ export function createCompensationTableColumns( sortable: false, minWidth: 40, maxWidth: 40, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { const { hasClaim } = useKeycloakWrapper(); return hasClaim(Claims.COMPENSATION_REQUISITION_VIEW) ? ( ) => { + Cell: (cellProps: CellProps) => { const totalAmount = cellProps.row.original.financials?.reduce( - (total: number, method: Api_CompensationFinancial) => { + (total: number, method: ApiGen_Concepts_CompensationFinancial) => { return total + (method.totalAmount ?? 0); }, 0, @@ -75,7 +75,7 @@ export function createCompensationTableColumns( sortable: false, minWidth: 20, maxWidth: 20, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return cellProps.row.original.isDraft ? stringToFragment('Draft') : Final; }, }, @@ -85,7 +85,7 @@ export function createCompensationTableColumns( sortable: false, width: 20, maxWidth: 20, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { const { hasClaim } = useKeycloakWrapper(); return ( diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/CompensationRequisitionYupSchema.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/CompensationRequisitionYupSchema.ts index 7a9f68e245..d4a0948edf 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/CompensationRequisitionYupSchema.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/CompensationRequisitionYupSchema.ts @@ -1,4 +1,6 @@ import * as yup from 'yup'; + +import { exists } from '@/utils'; /* eslint-disable no-template-curly-in-string */ export const CompensationRequisitionYupSchema = yup.object().shape({ @@ -29,7 +31,7 @@ export const CompensationRequisitionYupSchema = yup.object().shape({ isGstRequired: yup.string(), pretaxAmount: yup .number() - .transform(value => (isNaN(value) || value === null || value === undefined ? 0 : value)) + .transform(value => (isNaN(value) || !exists(value) ? 0 : value)) .required('Amount is required'), taxAmount: yup.number().when('isGstRequired', { is: 'true', diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.test.tsx index d7204107a1..c09da479bb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.test.tsx @@ -8,7 +8,9 @@ import { } from '@/mocks/acquisitionFiles.mock'; import { getMockApiDefaultCompensation } from '@/mocks/compensations.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { ApiGen_Concepts_FinancialCodeTypes } from '@/models/api/generated/ApiGen_Concepts_FinancialCodeTypes'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { systemConstantsSlice } from '@/store/slices/systemConstants/systemConstantsSlice'; import { act, render, RenderOptions, waitFor, waitForEffects } from '@/utils/test-utils'; @@ -204,22 +206,26 @@ describe('UpdateCompensationRequisition Container component', () => { }); it('filters expired financial codes when updating', async () => { - const expiredFinancialCodes: Api_FinancialCode[] = [ + const expiredFinancialCodes: ApiGen_Concepts_FinancialCode[] = [ { id: 1, - type: 'expired', + type: ApiGen_Concepts_FinancialCodeTypes.Responsibility, code: '1', description: '1', effectiveDate: moment().add(-2, 'days').format('YYYY-MM-DD'), expiryDate: moment().add(-1, 'days').format('YYYY-MM-DD'), + displayOrder: null, + ...getEmptyBaseAudit(), }, { id: 2, - type: 'non-expired', + type: ApiGen_Concepts_FinancialCodeTypes.WorkActivity, code: '2', description: '2', effectiveDate: moment().add(-2, 'days').format('YYYY-MM-DD'), expiryDate: moment().add(1, 'days').format('YYYY-MM-DD'), + displayOrder: null, + ...getEmptyBaseAudit(), }, ]; mockGetApi.execute = jest.fn().mockResolvedValue(expiredFinancialCodes); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.tsx index f3bb995b24..c583b8d925 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionContainer.tsx @@ -7,16 +7,18 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import { useFinancialCodeRepository } from '@/hooks/repositories/useFinancialCodeRepository'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { useCompensationRequisitionRepository } from '@/hooks/repositories/useRequisitionCompensationRepository'; -import { Api_AcquisitionFile, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; import { SystemConstants, useSystemConstants } from '@/store/slices/systemConstants'; +import { exists } from '@/utils'; import { CompensationRequisitionFormModel } from './models'; import { CompensationRequisitionFormProps } from './UpdateCompensationRequisitionForm'; export interface UpdateCompensationRequisitionContainerProps { - compensation: Api_CompensationRequisition; - acquisitionFile: Api_AcquisitionFile; + compensation: ApiGen_Concepts_CompensationRequisition; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onSuccess: () => void; onCancel: () => void; View: React.FC; @@ -109,7 +111,8 @@ const UpdateCompensationRequisitionContainer: React.FC< const teamMemberOptions: PayeeOption[] = acquisitionFile.acquisitionTeam ?.filter( - (x): x is Api_AcquisitionFileTeam => !!x && x.teamProfileTypeCode === 'MOTILAWYER', + (x): x is ApiGen_Concepts_AcquisitionFileTeam => + exists(x) && x.teamProfileTypeCode === 'MOTILAWYER', ) .map(x => PayeeOption.createTeamMember(x)) || []; options.push(...teamMemberOptions); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.test.tsx index af23b76f44..d06b72e3ef 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.test.tsx @@ -11,7 +11,7 @@ import { getMockApiDefaultCompensation, } from '@/mocks/compensations.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_AcquisitionFileOwner } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, @@ -45,7 +45,7 @@ const defaultCompensation = new CompensationRequisitionFormModel( '', ); -const getPayeeOptions = (owners: Api_AcquisitionFileOwner[]): PayeeOption[] => { +const getPayeeOptions = (owners: ApiGen_Concepts_AcquisitionFileOwner[]): PayeeOption[] => { const options: PayeeOption[] = []; const ownersOptions: PayeeOption[] = owners.map(x => PayeeOption.createOwner(x)); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx index 8f5277bcf0..f30f2deec7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/UpdateCompensationRequisitionForm.tsx @@ -24,8 +24,9 @@ import { PayeeOption } from '@/features/mapSideBar/acquisition/models/PayeeOptio import SidebarFooter from '@/features/mapSideBar/shared/SidebarFooter'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; import { IAutocompletePrediction } from '@/interfaces/IAutocomplete'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { isValidId } from '@/utils'; import { prettyFormatDate } from '@/utils/dateUtils'; import { withNameSpace } from '@/utils/formUtils'; @@ -35,7 +36,7 @@ import { CompensationRequisitionFormModel } from './models'; export interface CompensationRequisitionFormProps { isLoading: boolean; - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; payeeOptions: PayeeOption[]; initialValues: CompensationRequisitionFormModel; gstConstant: number; @@ -45,7 +46,7 @@ export interface CompensationRequisitionFormProps { yearlyFinancialOptions: SelectOption[]; onSave: ( compensation: CompensationRequisitionFormModel, - ) => Promise; + ) => Promise; onCancel: () => void; showAltProjectError: boolean; setShowAltProjectError: React.Dispatch>; @@ -125,7 +126,7 @@ const UpdateCompensationRequisitionForm: React.FC { if (param.length > 0) { - if (param[0].id !== undefined && acquisitionFile.projectId === param[0].id) { + if (isValidId(param[0].id) && acquisitionFile.projectId === param[0].id) { setShowAltProjectError(true); } } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/models.ts index 9f448566cd..419a716650 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/compensation/update/models.ts @@ -3,10 +3,12 @@ import isNumber from 'lodash/isNumber'; import { SelectOption } from '@/components/common/form'; import { PayeeOption } from '@/features/mapSideBar/acquisition/models/PayeeOptionModel'; import { IAutocompletePrediction } from '@/interfaces/IAutocomplete'; -import { Api_CompensationFinancial } from '@/models/api/CompensationFinancial'; -import { Api_CompensationRequisition } from '@/models/api/CompensationRequisition'; -import { Api_FinancialCode } from '@/models/api/FinancialCode'; -import { booleanToString, stringToBoolean, stringToUndefined } from '@/utils/formUtils'; +import { ApiGen_Concepts_CompensationFinancial } from '@/models/api/generated/ApiGen_Concepts_CompensationFinancial'; +import { ApiGen_Concepts_CompensationRequisition } from '@/models/api/generated/ApiGen_Concepts_CompensationRequisition'; +import { ApiGen_Concepts_FinancialCode } from '@/models/api/generated/ApiGen_Concepts_FinancialCode'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { isValidId, isValidIsoDateTime } from '@/utils'; +import { booleanToString, stringToBoolean, stringToNull } from '@/utils/formUtils'; export class CompensationRequisitionFormModel { id: number | null; @@ -14,11 +16,11 @@ export class CompensationRequisitionFormModel { status: string = ''; fiscalYear: string = ''; stob: SelectOption | null = null; - yearlyFinancial: Api_FinancialCode | null = null; + yearlyFinancial: ApiGen_Concepts_FinancialCode | null = null; serviceLine: SelectOption | null = null; - chartOfAccounts: Api_FinancialCode | null = null; + chartOfAccounts: ApiGen_Concepts_FinancialCode | null = null; responsibilityCentre: SelectOption | null = null; - responsibility: Api_FinancialCode | null = null; + responsibility: ApiGen_Concepts_FinancialCode | null = null; readonly finalizedDate: string; agreementDateTime: string = ''; expropriationNoticeServedDateTime: string = ''; @@ -36,22 +38,19 @@ export class CompensationRequisitionFormModel { this.id = id; this.acquisitionFileId = acquisitionFileId; this.payee = new AcquisitionPayeeFormModel(); - this.finalizedDate = finalizedDate; + this.finalizedDate = isValidIsoDateTime(finalizedDate) ? finalizedDate : ''; } - toApi(payeeOptions: PayeeOption[]): Api_CompensationRequisition { + toApi(payeeOptions: PayeeOption[]): ApiGen_Concepts_CompensationRequisition { let modelWithPayeeInformation = this.payee.toApi(payeeOptions); return { ...modelWithPayeeInformation, id: this.id, acquisitionFileId: this.acquisitionFileId, - alternateProjectId: - this.alternateProject?.id !== undefined && this.alternateProject?.id !== 0 - ? this.alternateProject?.id - : null, + alternateProjectId: isValidId(this.alternateProject?.id) ? this.alternateProject!.id : null, isDraft: this.status === 'draft' ? true : false, - fiscalYear: stringToUndefined(this.fiscalYear), + fiscalYear: stringToNull(this.fiscalYear), yearlyFinancialId: !!this.stob?.value && isNumber(this.stob?.value) ? Number(this.stob?.value) : null, yearlyFinancial: null, @@ -65,23 +64,31 @@ export class CompensationRequisitionFormModel { ? Number(this.responsibilityCentre?.value) : null, responsibility: null, - agreementDate: stringToUndefined(this.agreementDateTime), - finalizedDate: stringToUndefined(this.finalizedDate), - expropriationNoticeServedDate: stringToUndefined(this.expropriationNoticeServedDateTime), - expropriationVestingDate: stringToUndefined(this.expropriationVestingDateTime), - advancedPaymentServedDate: stringToUndefined(this.advancedPaymentServedDate), - generationDate: stringToUndefined(this.generationDatetTime), - specialInstruction: stringToUndefined(this.specialInstruction), - detailedRemarks: stringToUndefined(this.detailedRemarks), + agreementDate: isValidIsoDateTime(this.agreementDateTime) ? this.agreementDateTime : null, + finalizedDate: isValidIsoDateTime(this.finalizedDate) ? this.finalizedDate : null, + expropriationNoticeServedDate: isValidIsoDateTime(this.expropriationNoticeServedDateTime) + ? this.expropriationNoticeServedDateTime + : null, + expropriationVestingDate: isValidIsoDateTime(this.expropriationVestingDateTime) + ? this.expropriationVestingDateTime + : null, + advancedPaymentServedDate: isValidIsoDateTime(this.advancedPaymentServedDate) + ? this.advancedPaymentServedDate + : null, + generationDate: isValidIsoDateTime(this.generationDatetTime) + ? this.generationDatetTime + : null, + specialInstruction: stringToNull(this.specialInstruction), + detailedRemarks: stringToNull(this.detailedRemarks), financials: this.financials .filter(x => !x.isEmpty()) - .map(x => x.toApi()), - rowVersion: this.rowVersion ?? undefined, + .map(x => x.toApi()), + rowVersion: this.rowVersion ?? null, }; } static fromApi( - apiModel: Api_CompensationRequisition, + apiModel: ApiGen_Concepts_CompensationRequisition, yearlyFinancialOptions: SelectOption[] = [], chartOfAccountOptions: SelectOption[] = [], responsibilityCentreOptions: SelectOption[] = [], @@ -132,17 +139,16 @@ export class CompensationRequisitionFormModel { compensation.rowVersion = apiModel.rowVersion ?? null; - const payeePretaxAmount = apiModel?.financials - .map(f => f.pretaxAmount ?? 0) - .reduce((prev, next) => prev + next, 0); + const payeePretaxAmount = + apiModel?.financials?.map(f => f.pretaxAmount ?? 0).reduce((prev, next) => prev + next, 0) ?? + 0; - const payeeTaxAmount = apiModel?.financials - .map(f => f.taxAmount ?? 0) - .reduce((prev, next) => prev + next, 0); + const payeeTaxAmount = + apiModel?.financials?.map(f => f.taxAmount ?? 0).reduce((prev, next) => prev + next, 0) ?? 0; - const payeeTotalAmount = apiModel?.financials - .map(f => f.totalAmount ?? 0) - .reduce((prev, next) => prev + next, 0); + const payeeTotalAmount = + apiModel?.financials?.map(f => f.totalAmount ?? 0).reduce((prev, next) => prev + next, 0) ?? + 0; compensation.payee = AcquisitionPayeeFormModel.fromApi(apiModel); compensation.payee.pretaxAmount = payeePretaxAmount; @@ -158,7 +164,7 @@ export class FinancialActivityFormModel { readonly _compensationRequisitionId: number; financialActivityCodeId: SelectOption | null = null; - financialActivityCode: Api_FinancialCode | null = null; + financialActivityCode: ApiGen_Concepts_FinancialCode | null = null; pretaxAmount: number = 0; isGstRequired: string = ''; taxAmount: number = 0; @@ -175,26 +181,27 @@ export class FinancialActivityFormModel { return this.financialActivityCode === null && this.pretaxAmount === 0; } - toApi(): Api_CompensationFinancial { + toApi(): ApiGen_Concepts_CompensationFinancial { return { id: this._id, compensationId: this._compensationRequisitionId, financialActivityCodeId: !!this.financialActivityCodeId?.value && isNumber(this.financialActivityCodeId?.value) ? Number(this.financialActivityCodeId?.value) - : null, + : 0, financialActivityCode: null, pretaxAmount: this.pretaxAmount, isGstRequired: stringToBoolean(this.isGstRequired), taxAmount: this.taxAmount, totalAmount: this.totalAmount, - rowVersion: this.rowVersion ?? null, isDisabled: stringToBoolean(this.isDisabled), + h120CategoryId: null, + ...getEmptyBaseAudit(this.rowVersion), }; } static fromApi( - model: Api_CompensationFinancial, + model: ApiGen_Concepts_CompensationFinancial, financialActivityOptions: SelectOption[] = [], ): FinancialActivityFormModel { const newForm = new FinancialActivityFormModel(model.id, model.compensationId); @@ -224,7 +231,7 @@ export class AcquisitionPayeeFormModel { taxAmount: number = 0; totalAmount: number = 0; - static fromApi(apiModel: Api_CompensationRequisition): AcquisitionPayeeFormModel { + static fromApi(apiModel: ApiGen_Concepts_CompensationRequisition): AcquisitionPayeeFormModel { const payeeModel = new AcquisitionPayeeFormModel(); payeeModel.payeeKey = PayeeOption.fromApi(apiModel); @@ -235,15 +242,15 @@ export class AcquisitionPayeeFormModel { return payeeModel; } - toApi(payeeOptions: PayeeOption[]): Api_CompensationRequisition { - let modelWithPayeeInformation: Api_CompensationRequisition = PayeeOption.toApi( + toApi(payeeOptions: PayeeOption[]): ApiGen_Concepts_CompensationRequisition { + let modelWithPayeeInformation: ApiGen_Concepts_CompensationRequisition = PayeeOption.toApi( this.payeeKey, payeeOptions, ); return { ...modelWithPayeeInformation, - legacyPayee: stringToUndefined(this.legacyPayee), + legacyPayee: stringToNull(this.legacyPayee), isPaymentInTrust: stringToBoolean(this.isPaymentInTrust), gstNumber: this.gstNumber, }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/ExpropriationTabContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/ExpropriationTabContainer.tsx index 6657ddd327..e1a11d9ac8 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/ExpropriationTabContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/ExpropriationTabContainer.tsx @@ -3,13 +3,14 @@ import { useCallback, useContext, useEffect, useState } from 'react'; import { SideBarContext } from '@/features/mapSideBar/context/sidebarContext'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useForm8Repository } from '@/hooks/repositories/useForm8Repository'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; +import { isValidId } from '@/utils/utils'; import { IExpropriationTabContainerViewProps } from './ExpropriationTabContainerView'; export interface IExpropriationTabContainerProps { - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; View: React.FunctionComponent; } @@ -17,7 +18,7 @@ export const ExpropriationTabContainer: React.FunctionComponent< React.PropsWithChildren > = ({ View, acquisitionFile }) => { const { fileLoading, setStaleLastUpdatedBy } = useContext(SideBarContext); - const [form8s, setForm8s] = useState([]); + const [form8s, setForm8s] = useState([]); const { getAcquisitionFileForm8s: { execute: getAcquisitionFileForm8s, loading: loadingForm8s }, @@ -49,11 +50,11 @@ export const ExpropriationTabContainer: React.FunctionComponent< } }; - if (!!acquisitionFile && acquisitionFile?.id === undefined && fileLoading === false) { + if (!isValidId(acquisitionFile?.id) && fileLoading === false) { throw new Error('Unable to determine id of current file.'); } - return !!acquisitionFile?.id ? ( + return isValidId(acquisitionFile?.id) ? ( void; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form1/ExpropriationForm1.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form1/ExpropriationForm1.tsx index 5d379df0c7..15fdd55b00 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form1/ExpropriationForm1.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form1/ExpropriationForm1.tsx @@ -11,14 +11,14 @@ import FormItem from '@/components/common/form/FormItem'; import { SectionField } from '@/components/common/Section/SectionField'; import { RestrictContactType } from '@/components/contact/ContactManagerView/ContactFilterComponent/ContactFilterComponent'; import FilePropertiesTable from '@/components/filePropertiesTable/FilePropertiesTable'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; import { ExpropriationForm1Model } from '../models'; import { ExpropriationForm1YupSchema } from './ExpropriationForm1YupSchema'; export interface IExpropriationForm1Props { - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onGenerate: (acquisitionFileId: number, values: ExpropriationForm1Model) => Promise; onError?: (e: Error) => void; } @@ -81,7 +81,7 @@ export const ExpropriationForm1: React.FC = ({ disabledSelection={false} fileProperties={acquisitionFile.fileProperties ?? []} selectedFileProperties={formikProps.values.impactedProperties} - setSelectedFileProperties={(fileProperties: Api_PropertyFile[]) => { + setSelectedFileProperties={(fileProperties: ApiGen_Concepts_FileProperty[]) => { formikProps.setFieldValue('impactedProperties', fileProperties); }} > diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form5/ExpropriationForm5.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form5/ExpropriationForm5.tsx index f8d67ccb07..4b60ef96b3 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form5/ExpropriationForm5.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form5/ExpropriationForm5.tsx @@ -10,14 +10,14 @@ import FormItem from '@/components/common/form/FormItem'; import { SectionField } from '@/components/common/Section/SectionField'; import { RestrictContactType } from '@/components/contact/ContactManagerView/ContactFilterComponent/ContactFilterComponent'; import FilePropertiesTable from '@/components/filePropertiesTable/FilePropertiesTable'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; import { ExpropriationForm5Model } from '../models'; import { ExpropriationForm5YupSchema } from './ExpropriationForm5YupSchema'; export interface IExpropriationForm5Props { - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onGenerate: (acquisitionFileId: number, values: ExpropriationForm5Model) => Promise; onError?: (e: Error) => void; } @@ -80,7 +80,7 @@ export const ExpropriationForm5: React.FC = ({ disabledSelection={false} fileProperties={acquisitionFile.fileProperties ?? []} selectedFileProperties={formikProps.values.impactedProperties} - setSelectedFileProperties={(fileProperties: Api_PropertyFile[]) => { + setSelectedFileProperties={(fileProperties: ApiGen_Concepts_FileProperty[]) => { formikProps.setFieldValue('impactedProperties', fileProperties); }} > diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/UpdateForm8Form.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/UpdateForm8Form.tsx index 14a61fd8ef..3b25dd1c98 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/UpdateForm8Form.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/UpdateForm8Form.tsx @@ -10,7 +10,7 @@ import { RestrictContactType } from '@/components/contact/ContactManagerView/Con import { PayeeOption } from '@/features/mapSideBar/acquisition/models/PayeeOptionModel'; import SidebarFooter from '@/features/mapSideBar/shared/SidebarFooter'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; import Form8PaymentItemsSubForm from './Form8PaymentItemsSubForm'; import { Form8FormModel } from './models/Form8FormModel'; @@ -20,7 +20,9 @@ export interface IForm8FormProps { initialValues: Form8FormModel | null; gstConstant: number; payeeOptions: PayeeOption[]; - onSave: (form8: Api_ExpropriationPayment) => Promise; + onSave: ( + form8: ApiGen_Concepts_ExpropriationPayment, + ) => Promise; onCancel: () => void; onSuccess: () => void; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.test.tsx index 37e0f517aa..4cade898bf 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.test.tsx @@ -6,7 +6,7 @@ import { mockAcquisitionFileOwnersResponse } from '@/mocks/acquisitionFiles.mock import { mockGetExpropriationPaymentApi } from '@/mocks/ExpropriationPayment.mock'; import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { systemConstantsSlice } from '@/store/slices/systemConstants/systemConstantsSlice'; import { act, render, RenderOptions } from '@/utils/test-utils'; @@ -110,9 +110,9 @@ describe('Add Form8 Container component', () => { mockGetFileOwnersApi.execute.mockReturnValue(mockFileOwnersResponse); mockPostApi.execute.mockReturnValue(mockGetExpropriationPaymentApi()); - let createdForm8: Api_ExpropriationPayment | undefined; + let createdForm8: ApiGen_Concepts_ExpropriationPayment | undefined; await act(async () => { - createdForm8 = await viewProps?.onSave({} as Api_ExpropriationPayment); + createdForm8 = await viewProps?.onSave({} as ApiGen_Concepts_ExpropriationPayment); }); expect(mockPostApi.execute).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.tsx index ea29daa1cd..50d44b6769 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/add/AddForm8Container.tsx @@ -5,7 +5,7 @@ import LoadingBackdrop from '@/components/common/LoadingBackdrop'; import { PayeeOption } from '@/features/mapSideBar/acquisition/models/PayeeOptionModel'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; import { SystemConstants, useSystemConstants } from '@/store/slices/systemConstants'; import { Form8FormModel } from '../models/Form8FormModel'; @@ -69,7 +69,7 @@ export const AddForm8Container: React.FunctionComponent< ); }, [acquisitionFileId, retrieveAcquisitionOwners, fetchInterestHolders]); - const handleSave = async (form8: Api_ExpropriationPayment) => { + const handleSave = async (form8: ApiGen_Concepts_ExpropriationPayment) => { return postAcquisitionForm8(acquisitionFileId, form8); }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationForm8Details.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationForm8Details.tsx index cc9210122a..fe16b6ae9e 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationForm8Details.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationForm8Details.tsx @@ -13,8 +13,8 @@ import { Claims } from '@/constants'; import { DetailAcquisitionFileOwner } from '@/features/mapSideBar/acquisition/models/DetailAcquisitionFileOwner'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; import { getDeleteModalProps, useModalContext } from '@/hooks/useModalContext'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; import { formatMoney } from '@/utils/numberFormatUtils'; import { formatApiPersonNames } from '@/utils/personUtils'; @@ -22,7 +22,7 @@ import ExpropriationPaymentItemsTable from './ExpropriationPaymentItemsTable'; export interface IExpropriationForm8DetailsProps { form8Index: number; - form8: Api_ExpropriationPayment; + form8: ApiGen_Concepts_ExpropriationPayment; acquisitionFileNumber: string; onDelete: (form8Id: number) => void; onGenerate: (form8Id: number, acquisitionFileNumber: string) => void; @@ -201,7 +201,9 @@ const StyledForm8Summary = styled.div` font-weight: 600; `; -const getInterestHolderLink = (interestHolder: Api_InterestHolder | null): string | null => { +const getInterestHolderLink = ( + interestHolder: ApiGen_Concepts_InterestHolder | null, +): string | null => { if (!interestHolder) { return null; } @@ -213,7 +215,9 @@ const getInterestHolderLink = (interestHolder: Api_InterestHolder | null): strin return 'O' + interestHolder.organization?.id; }; -const getInterestHolderDisplayName = (interestHolder: Api_InterestHolder | null): string | null => { +const getInterestHolderDisplayName = ( + interestHolder: ApiGen_Concepts_InterestHolder | null, +): string | null => { if (!interestHolder) { return null; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationPaymentItemsTable.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationPaymentItemsTable.tsx index 4d4e4e55eb..8704b1b5e6 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationPaymentItemsTable.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/details/ExpropriationPaymentItemsTable.tsx @@ -2,21 +2,21 @@ import * as React from 'react'; import { CellProps } from 'react-table'; import { ColumnWithProps, Table } from '@/components/Table'; -import { Api_ExpropriationPaymentItem } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPaymentItem } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPaymentItem'; import { stringToFragment } from '@/utils/columnUtils'; import { formatMoney } from '@/utils/numberFormatUtils'; export interface IExpropriationPaymentItemsTableProps { - paymentItems: Api_ExpropriationPaymentItem[]; + paymentItems: ApiGen_Concepts_ExpropriationPaymentItem[]; } -const columns: ColumnWithProps[] = [ +const columns: ColumnWithProps[] = [ { Header: 'Item', align: 'left', sortable: false, width: 40, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return stringToFragment(cellProps.row.original.paymentItemType?.description); }, }, @@ -26,7 +26,7 @@ const columns: ColumnWithProps[] = [ sortable: false, minWidth: 20, maxWidth: 20, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return stringToFragment(formatMoney(cellProps.row.original.pretaxAmount)); }, }, @@ -36,7 +36,7 @@ const columns: ColumnWithProps[] = [ sortable: false, minWidth: 20, maxWidth: 20, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return stringToFragment(formatMoney(cellProps.row.original.taxAmount)); }, }, @@ -46,7 +46,7 @@ const columns: ColumnWithProps[] = [ sortable: false, minWidth: 20, maxWidth: 20, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return stringToFragment(formatMoney(cellProps.row.original.totalAmount)); }, }, @@ -57,7 +57,7 @@ const ExpropriationPaymentItemsTable: React.FunctionComponent< > = ({ paymentItems }) => { return ( <> - + name="paymentItems" columns={columns} hideToolbar diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormModel.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormModel.ts index e327d9349b..0b472d8223 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormModel.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormModel.ts @@ -2,11 +2,10 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; import { PayeeOption } from '@/features/mapSideBar/acquisition/models/PayeeOptionModel'; import { PayeeType } from '@/features/mapSideBar/acquisition/models/PayeeTypeModel'; import { fromApiOrganization } from '@/interfaces'; -import { - Api_ExpropriationPayment, - Api_ExpropriationPaymentItem, -} from '@/models/api/ExpropriationPayment'; -import { booleanToString, stringToBoolean, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPaymentItem } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPaymentItem'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { booleanToString, stringToBoolean, toTypeCodeNullable } from '@/utils/formUtils'; import { isNullOrWhitespace } from '@/utils/utils'; import { ExpropriationAuthorityFormModel } from '../../models'; @@ -29,8 +28,8 @@ export class Form8FormModel { this.acquisitionFileId = acquisitionFileId; } - toApi(payeeOptions: PayeeOption[]): Api_ExpropriationPayment { - const expropriationPaymentApi = { + toApi(payeeOptions: PayeeOption[]): ApiGen_Concepts_ExpropriationPayment { + const expropriationPaymentApi: ApiGen_Concepts_ExpropriationPayment = { id: this.id, acquisitionFileId: this.acquisitionFileId, acquisitionOwnerId: this.acquisitionOwnerId, @@ -41,10 +40,10 @@ export class Form8FormModel { expropriatingAuthority: null, description: this.description, isDisabled: this.isDisabled, - rowVersion: this.rowVersion, paymentItems: this.paymentItems .filter(x => !x.isEmpty()) - .map(x => x.toApi()), + .map(x => x.toApi()), + ...getEmptyBaseAudit(this.rowVersion), }; if (isNullOrWhitespace(this.payeeKey)) { @@ -73,7 +72,7 @@ export class Form8FormModel { return expropriationPaymentApi; } - static fromApi(model: Api_ExpropriationPayment): Form8FormModel { + static fromApi(model: ApiGen_Concepts_ExpropriationPayment): Form8FormModel { const newForm = new Form8FormModel(model.id, model.acquisitionFileId); newForm.acquisitionOwnerId = model.acquisitionOwnerId; @@ -114,7 +113,7 @@ export class Form8PaymentItemModel { return this.paymentItemTypeCode === '' && this.pretaxAmount === 0; } - static fromApi(model: Api_ExpropriationPaymentItem): Form8PaymentItemModel { + static fromApi(model: ApiGen_Concepts_ExpropriationPaymentItem): Form8PaymentItemModel { const newPaymentItem = new Form8PaymentItemModel( model.id ?? null, model.expropriationPaymentId, @@ -131,23 +130,23 @@ export class Form8PaymentItemModel { return newPaymentItem; } - toApi(): Api_ExpropriationPaymentItem { + toApi(): ApiGen_Concepts_ExpropriationPaymentItem { return { id: this.id, expropriationPaymentId: this.expropriationPaymentId, paymentItemTypeCode: this.paymentItemTypeCode ?? null, - paymentItemType: toTypeCode(this.paymentItemTypeCode) ?? null, + paymentItemType: toTypeCodeNullable(this.paymentItemTypeCode) ?? null, isGstRequired: stringToBoolean(this.isGstRequired), pretaxAmount: this.pretaxAmount, taxAmount: this.taxAmount, totalAmount: this.totalAmount, - rowVersion: this.rowVersion ?? null, isDisabled: this.isDisabled, + ...getEmptyBaseAudit(this.rowVersion), }; } } -const getPayeeKey = (form8Api: Api_ExpropriationPayment): string => { +const getPayeeKey = (form8Api: ApiGen_Concepts_ExpropriationPayment): string => { if (form8Api.acquisitionOwnerId) { return PayeeOption.generateKey(form8Api.acquisitionOwnerId, PayeeType.Owner); } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormYupSchema.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormYupSchema.tsx index 9659906e65..63deecc0ff 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormYupSchema.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/models/Form8FormYupSchema.tsx @@ -1,4 +1,6 @@ import * as yup from 'yup'; + +import { exists } from '@/utils/utils'; /* eslint-disable no-template-curly-in-string */ declare module 'yup' { @@ -26,9 +28,7 @@ export const Form8FormModelYupSchema = yup.object().shape({ .of( yup.object().shape({ paymentItemTypeCode: yup.string().required('Type is required'), - pretaxAmount: yup - .number() - .transform(value => (isNaN(value) || value === null || value === undefined ? 0 : value)), + pretaxAmount: yup.number().transform(value => (isNaN(value) || !exists(value) ? 0 : value)), }), ) .unique( diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/update/UpdateForm8Container.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/update/UpdateForm8Container.tsx index d08069a033..641ef02e95 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/update/UpdateForm8Container.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form8/update/UpdateForm8Container.tsx @@ -7,7 +7,7 @@ import { FileTabType } from '@/features/mapSideBar/shared/detail/FileTabs'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { useForm8Repository } from '@/hooks/repositories/useForm8Repository'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; -import { Api_ExpropriationPayment } from '@/models/api/ExpropriationPayment'; +import { ApiGen_Concepts_ExpropriationPayment } from '@/models/api/generated/ApiGen_Concepts_ExpropriationPayment'; import { SystemConstants, useSystemConstants } from '@/store/slices/systemConstants'; import { Form8FormModel } from '../models/Form8FormModel'; @@ -82,7 +82,7 @@ export const UpdateForm8Container: React.FunctionComponent< } }, [fetchInterestHolders, form8Id, getForm8, retrieveAcquisitionOwners]); - const handleSave = async (form8: Api_ExpropriationPayment) => { + const handleSave = async (form8: ApiGen_Concepts_ExpropriationPayment) => { return updateForm8(form8); }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form9/ExpropriationForm9.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form9/ExpropriationForm9.tsx index 472f1fb7ab..4d3cb38b4c 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form9/ExpropriationForm9.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/form9/ExpropriationForm9.tsx @@ -11,14 +11,14 @@ import FormItem from '@/components/common/form/FormItem'; import { SectionField } from '@/components/common/Section/SectionField'; import { RestrictContactType } from '@/components/contact/ContactManagerView/ContactFilterComponent/ContactFilterComponent'; import FilePropertiesTable from '@/components/filePropertiesTable/FilePropertiesTable'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; import { ExpropriationForm9Model } from '../models'; import { ExpropriationForm9YupSchema } from './ExpropriationForm9YupSchema'; export interface IExpropriationForm9Props { - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onGenerate: (acquisitionFileId: number, values: ExpropriationForm9Model) => Promise; onError?: (e: Error) => void; } @@ -81,7 +81,7 @@ export const ExpropriationForm9: React.FC = ({ disabledSelection={false} fileProperties={acquisitionFile.fileProperties ?? []} selectedFileProperties={formikProps.values.impactedProperties} - setSelectedFileProperties={(fileProperties: Api_PropertyFile[]) => { + setSelectedFileProperties={(fileProperties: ApiGen_Concepts_FileProperty[]) => { formikProps.setFieldValue('impactedProperties', fileProperties); }} > diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/models.ts index b51df1b977..6aa62c8e7e 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/expropriation/models.ts @@ -1,10 +1,10 @@ import { IContactSearchResult } from '@/interfaces'; -import { Api_AcquisitionFileProperty } from '@/models/api/AcquisitionFile'; -import { Api_Organization } from '@/models/api/Organization'; +import { ApiGen_Concepts_AcquisitionFileProperty } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileProperty'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; export class ExpropriationAuthorityFormModel { organizationId: number | null = null; - organization: Api_Organization | null = null; + organization: ApiGen_Concepts_Organization | null = null; contact: IContactSearchResult | null = null; } @@ -13,16 +13,16 @@ class ExpropriationBaseModel { } export class ExpropriationForm1Model extends ExpropriationBaseModel { - impactedProperties: Api_AcquisitionFileProperty[] = []; + impactedProperties: ApiGen_Concepts_AcquisitionFileProperty[] = []; landInterest: string = ''; purpose: string = ''; } export class ExpropriationForm5Model extends ExpropriationBaseModel { - impactedProperties: Api_AcquisitionFileProperty[] = []; + impactedProperties: ApiGen_Concepts_AcquisitionFileProperty[] = []; } export class ExpropriationForm9Model extends ExpropriationBaseModel { - impactedProperties: Api_AcquisitionFileProperty[] = []; + impactedProperties: ApiGen_Concepts_AcquisitionFileProperty[] = []; registeredPlanNumbers: string = ''; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryContainer.tsx index b364442b48..c2e4293347 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryContainer.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; -import { Api_AcquisitionFileOwner } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; export interface IAcquisitionOwnersContainerProps { acquisitionFileId: number; @@ -9,16 +9,16 @@ export interface IAcquisitionOwnersContainerProps { } export interface IAcquisitionOwnersSummaryViewProps { - ownersList?: Api_AcquisitionFileOwner[]; + ownersList?: ApiGen_Concepts_AcquisitionFileOwner[]; isLoading: boolean; } const AcquisitionOwnersSummaryContainer: React.FunctionComponent< React.PropsWithChildren > = ({ acquisitionFileId, View }) => { - const [ownersDetails, setOwnersDetails] = useState( - undefined, - ); + const [ownersDetails, setOwnersDetails] = useState< + ApiGen_Concepts_AcquisitionFileOwner[] | undefined + >(undefined); const { getAcquisitionOwners: { diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryView.test.tsx index e8b0365ca0..4f05f9512b 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionOwnersSummaryView.test.tsx @@ -2,7 +2,7 @@ import { mockAcquisitionFileOwnersResponse, mockAcquisitionFileResponse, } from '@/mocks/acquisitionFiles.mock'; -import { Api_AcquisitionFileOwner } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; import { render, RenderOptions } from '@/utils/test-utils'; import { IAcquisitionOwnersSummaryViewProps } from './AcquisitionOwnersSummaryContainer'; @@ -13,7 +13,7 @@ jest.mock('@react-keycloak/web'); const mockAcquisitionFile = mockAcquisitionFileResponse(1); const mockViewProps: IAcquisitionOwnersSummaryViewProps = { - ownersList: mockAcquisitionFile.acquisitionFileOwners, + ownersList: mockAcquisitionFile.acquisitionFileOwners || [], isLoading: false, }; @@ -86,7 +86,10 @@ describe('Acquisition File Owners View component', () => { }); it('Display the Organization Owner data with Incorporation and NO Registration Number', () => { - const ownerTest = { ...ownerCorporate, registrationNumber: null } as Api_AcquisitionFileOwner; + const ownerTest = { + ...ownerCorporate, + registrationNumber: null, + } as ApiGen_Concepts_AcquisitionFileOwner; const { getByText, getIsPrymaryContactRadioButton } = setup({ props: { ownersList: [ownerTest], isLoading: false }, }); @@ -103,7 +106,10 @@ describe('Acquisition File Owners View component', () => { }); it('Display the Organization Owner data with NO Incorporation and Registration Number', () => { - const ownerTest = { ...ownerCorporate, incorporationNumber: null } as Api_AcquisitionFileOwner; + const ownerTest = { + ...ownerCorporate, + incorporationNumber: null, + } as ApiGen_Concepts_AcquisitionFileOwner; const { getByText, getIsPrymaryContactRadioButton } = setup({ props: { ownersList: [ownerTest], isLoading: false }, }); @@ -127,7 +133,7 @@ describe('Acquisition File Owners View component', () => { country: { code: 'OTHER', description: 'Other' }, countryOther: 'test name', }, - } as Api_AcquisitionFileOwner; + } as ApiGen_Concepts_AcquisitionFileOwner; const { getByText } = setup({ props: { ownersList: [ownerTest], isLoading: false }, }); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.test.tsx index 41fcd29418..2430d65dcb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.test.tsx @@ -5,7 +5,10 @@ import { mockAcquisitionFileOwnersResponse, mockAcquisitionFileResponse, } from '@/mocks/acquisitionFiles.mock'; -import { Api_Person } from '@/models/api/Person'; +import { getEmptyPerson } from '@/mocks/contacts.mock'; +import { getEmptyOrganization } from '@/mocks/organization.mock'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, cleanup, render, RenderOptions, userEvent, waitForEffects } from '@/utils/test-utils'; import AcquisitionSummaryView, { IAcquisitionSummaryViewProps } from './AcquisitionSummaryView'; @@ -51,7 +54,7 @@ describe('AcquisitionSummaryView component', () => { firstName: 'Foo', middleNames: 'Bar', surname: 'Baz', - } as Api_Person, + } as ApiGen_Concepts_Person, }); getAcquisitionFileOwnersFn.mockResolvedValue({ data: mockAcquisitionFileOwnersResponse(1), @@ -100,7 +103,7 @@ describe('AcquisitionSummaryView component', () => { { acquisitionFile: { ...mockAcquisitionFileResponse(), - fileStatusTypeCode: { id: 'COMPLETE' }, + fileStatusTypeCode: toTypeCodeNullable('COMPLETE'), }, }, { claims: [Claims.ACQUISITION_EDIT] }, @@ -147,6 +150,7 @@ describe('AcquisitionSummaryView component', () => { id: 1, acquisitionFileId: 1, person: { + ...getEmptyPerson(), id: 1, surname: 'Smith', firstName: 'Bob', @@ -160,8 +164,15 @@ describe('AcquisitionSummaryView component', () => { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + organization: null, + organizationId: null, + personId: null, + primaryContact: null, + primaryContactId: null, + teamProfileTypeCode: null, }, ], }, @@ -185,6 +196,7 @@ describe('AcquisitionSummaryView component', () => { acquisitionFileId: 1, organizationId: 1, organization: { + ...getEmptyOrganization(), id: 1, name: 'Test Organization', alias: 'ABC Inc', @@ -195,8 +207,15 @@ describe('AcquisitionSummaryView component', () => { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + + person: null, + personId: null, + primaryContact: null, + primaryContactId: null, + teamProfileTypeCode: null, }, ], }, @@ -222,6 +241,7 @@ describe('AcquisitionSummaryView component', () => { acquisitionFileId: 1, organizationId: 1, organization: { + ...getEmptyOrganization(), id: 1, name: 'Test Organization', alias: 'ABC Inc', @@ -238,13 +258,20 @@ describe('AcquisitionSummaryView component', () => { personAddresses: [], contactMethods: [], rowVersion: 2, + comment: null, + isDisabled: false, + preferredName: null, }, teamProfileType: { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + person: null, + personId: null, + teamProfileTypeCode: null, }, ], }, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.tsx index faf9cb5870..028f751061 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/AcquisitionSummaryView.tsx @@ -12,8 +12,8 @@ import { Claims, Roles } from '@/constants'; import { InterestHolderType } from '@/constants/interestHolderTypes'; import { usePersonRepository } from '@/features/contacts/repositories/usePersonRepository'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { prettyFormatDate } from '@/utils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { exists, prettyFormatDate } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { cannotEditMessage } from '../../../common/constants'; @@ -23,7 +23,7 @@ import { DetailAcquisitionFile } from './models'; import StatusUpdateSolver from './statusUpdateSolver'; export interface IAcquisitionSummaryViewProps { - acquisitionFile?: Api_AcquisitionFile; + acquisitionFile?: ApiGen_Concepts_AcquisitionFile; onEdit: () => void; } @@ -36,15 +36,13 @@ const AcquisitionSummaryView: React.FC = ({ const { hasRole } = useKeycloakWrapper(); - const projectName = - acquisitionFile?.project !== undefined - ? acquisitionFile?.project?.code + ' - ' + acquisitionFile?.project?.description - : ''; + const projectName = exists(acquisitionFile?.project) + ? acquisitionFile?.project?.code + ' - ' + acquisitionFile?.project?.description + : ''; - const productName = - acquisitionFile?.product !== undefined - ? acquisitionFile?.product?.code + ' ' + acquisitionFile?.product?.description - : ''; + const productName = exists(acquisitionFile?.product) + ? acquisitionFile?.product?.code + ' ' + acquisitionFile?.product?.description + : ''; const ownerSolicitor = acquisitionFile?.acquisitionFileInterestHolders?.find( x => x.interestHolderType?.id === InterestHolderType.OWNER_SOLICITOR, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/__snapshots__/AcquisitionOwnersSummaryView.test.tsx.snap b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/__snapshots__/AcquisitionOwnersSummaryView.test.tsx.snap index d64b7e7fb0..be05277574 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/__snapshots__/AcquisitionOwnersSummaryView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/__snapshots__/AcquisitionOwnersSummaryView.test.tsx.snap @@ -146,8 +146,8 @@ exports[`Acquisition File Owners View component Renders Component as expected 1` 456 Souris Street PO Box 250 A Hoot and a holler from the A&W -North Podunk, IH8 B0B - +North Podunk, British Columbia, IH8 B0B +Canada
    @@ -317,8 +317,8 @@ North Podunk, IH8 B0B 123 Main Street PO Box 123 Next to the Dairy Queen -East Podunk, I4M B0B - +East Podunk, British Columbia, I4M B0B +Canada diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/models.ts index b63bb29c9c..6fd1e5f855 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/models.ts @@ -1,4 +1,5 @@ -import { Api_AcquisitionFile, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileTeam } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileTeam'; import { formatApiPersonNames } from '@/utils/personUtils'; export class DetailAcquisitionFile { @@ -12,17 +13,17 @@ export class DetailAcquisitionFile { regionDescription?: string; acquisitionTeam: DetailAcquisitionFileTeam[] = []; - static fromApi(model?: Api_AcquisitionFile): DetailAcquisitionFile { + static fromApi(model?: ApiGen_Concepts_AcquisitionFile): DetailAcquisitionFile { const detail = new DetailAcquisitionFile(); - detail.fileName = model?.fileName; - detail.legacyFileNumber = model?.legacyFileNumber; - detail.assignedDate = model?.assignedDate; - detail.deliveryDate = model?.deliveryDate; - detail.completionDate = model?.completionDate; + detail.fileName = model?.fileName ?? undefined; + detail.legacyFileNumber = model?.legacyFileNumber ?? undefined; + detail.assignedDate = model?.assignedDate ?? undefined; + detail.deliveryDate = model?.deliveryDate ?? undefined; + detail.completionDate = model?.completionDate ?? undefined; detail.acquisitionPhysFileStatusTypeDescription = - model?.acquisitionPhysFileStatusTypeCode?.description; - detail.acquisitionTypeDescription = model?.acquisitionTypeCode?.description; - detail.regionDescription = model?.regionCode?.description; + model?.acquisitionPhysFileStatusTypeCode?.description ?? undefined; + detail.acquisitionTypeDescription = model?.acquisitionTypeCode?.description ?? undefined; + detail.regionDescription = model?.regionCode?.description ?? undefined; detail.acquisitionTeam = model?.acquisitionTeam?.map(x => DetailAcquisitionFileTeam.fromApi(x)) || []; @@ -38,12 +39,12 @@ export class DetailAcquisitionFileTeam { primaryContactName?: string; teamProfileTypeCodeDescription?: string; - static fromApi(model: Api_AcquisitionFileTeam): DetailAcquisitionFileTeam { + static fromApi(model: ApiGen_Concepts_AcquisitionFileTeam): DetailAcquisitionFileTeam { const teamDetail = new DetailAcquisitionFileTeam(); teamDetail.personId = model?.person?.id; teamDetail.organizationId = model?.organization?.id; teamDetail.primaryContactId = model?.primaryContact?.id; - teamDetail.teamProfileTypeCodeDescription = model?.teamProfileType?.description; + teamDetail.teamProfileTypeCodeDescription = model?.teamProfileType?.description ?? undefined; teamDetail.teamName = model?.person ? formatApiPersonNames(model?.person) diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/statusUpdateSolver.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/statusUpdateSolver.ts index 45bc7ac30c..21208a2b29 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/statusUpdateSolver.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/detail/statusUpdateSolver.ts @@ -1,11 +1,11 @@ import { AcquisitionStatus } from '@/constants/acquisitionFileStatus'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { AgreementStatusTypes } from '@/models/api/Agreement'; +import { ApiGen_CodeTypes_AgreementStatusTypes } from '@/models/api/generated/ApiGen_CodeTypes_AgreementStatusTypes'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; class StatusUpdateSolver { - private readonly acquisitionFile: Api_AcquisitionFile | null; + private readonly acquisitionFile: ApiGen_Concepts_AcquisitionFile | null; - constructor(apiModel: Api_AcquisitionFile | undefined | null) { + constructor(apiModel: ApiGen_Concepts_AcquisitionFile | undefined | null) { this.acquisitionFile = apiModel ?? null; } @@ -111,7 +111,7 @@ class StatusUpdateSolver { case AcquisitionStatus.Closed: case AcquisitionStatus.Complete: case AcquisitionStatus.Hold: - canEdit = agreementStatusCode !== AgreementStatusTypes.FINAL ?? true; + canEdit = agreementStatusCode !== ApiGen_CodeTypes_AgreementStatusTypes.FINAL ?? true; break; default: canEdit = false; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.test.tsx index 390256e460..5410b92a87 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.test.tsx @@ -3,7 +3,7 @@ import { createRef } from 'react'; import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvider'; import { mockAcquisitionFileResponse, mockLookups } from '@/mocks/index.mock'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { @@ -52,7 +52,7 @@ const TestView: React.FC = props => { }; describe('UpdateAcquisition container', () => { - let acquisitionFile: Api_AcquisitionFile; + let acquisitionFile: ApiGen_Concepts_AcquisitionFile; const onSuccess = jest.fn(); const setup = (renderOptions: RenderOptions = {}) => { diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.tsx index 56e1580560..776edea8d4 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionContainer.tsx @@ -8,15 +8,16 @@ import { useAcquisitionProvider } from '@/hooks/repositories/useAcquisitionProvi import useApiUserOverride from '@/hooks/useApiUserOverride'; import { useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { isValidId } from '@/utils'; import { UpdateAcquisitionSummaryFormModel } from './models'; import { UpdateAcquisitionFileYupSchema } from './UpdateAcquisitionFileYupSchema'; import { IUpdateAcquisitionFormProps } from './UpdateAcquisitionForm'; export interface IUpdateAcquisitionContainerProps { - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onSuccess: () => void; View: React.FC; } @@ -44,7 +45,7 @@ export const UpdateAcquisitionContainer = React.forwardRef< } = useAcquisitionProvider(); const withUserOverride = useApiUserOverride< - (userOverrideCodes: UserOverrideCode[]) => Promise + (userOverrideCodes: UserOverrideCode[]) => Promise >('Failed to update Acquisition File'); const handleSubmit = async ( @@ -56,7 +57,7 @@ export const UpdateAcquisitionContainer = React.forwardRef< const acquisitionFile = values.toApi(); const response = await updateAcquisitionFile(acquisitionFile, userOverrideCodes); - if (!!response?.id) { + if (isValidId(response?.id)) { if (acquisitionFile.fileProperties?.find(ap => !ap.property?.address && !ap.property?.id)) { toast.warn( 'Address could not be retrieved for this property, it will have to be provided manually in property details tab', diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionForm.tsx index 6ccb7f4d2e..f97ad5a416 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/fileDetails/update/UpdateAcquisitionForm.tsx @@ -23,8 +23,9 @@ import { useOrganizationRepository } from '@/features/contacts/repositories/useO import { useProjectProvider } from '@/hooks/repositories/useProjectProvider'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; import { IAutocompletePrediction } from '@/interfaces'; -import { Api_OrganizationPerson } from '@/models/api/Organization'; -import { Api_Product } from '@/models/api/Project'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; +import { ApiGen_Concepts_Product } from '@/models/api/generated/ApiGen_Concepts_Product'; +import { isValidId, isValidString } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import UpdateAcquisitionOwnersSubForm from '../../../common/update/acquisitionOwners/UpdateAcquisitionOwnersSubForm'; @@ -74,9 +75,9 @@ const AcquisitionDetailSubForm: React.FC<{ values: { fileStatusTypeCode }, } = formikProps; - const [projectProducts, setProjectProducts] = React.useState( - undefined, - ); + const [projectProducts, setProjectProducts] = React.useState< + ApiGen_Concepts_Product[] | undefined + >(undefined); const { retrieveProjectProducts } = useProjectProvider(); const { getOptionsByType } = useLookupCodeHelpers(); const regionTypes = getOptionsByType(API.REGION_TYPES); @@ -89,7 +90,7 @@ const AcquisitionDetailSubForm: React.FC<{ const onMinistryProjectSelected = React.useCallback( async (param: IAutocompletePrediction[]) => { if (param.length > 0) { - if (param[0].id !== undefined) { + if (isValidId(param[0].id)) { const result = await retrieveProjectProducts(param[0].id); if (result !== undefined) { setProjectProducts(result); @@ -110,7 +111,7 @@ const AcquisitionDetailSubForm: React.FC<{ // clear the associated 'Completion Date' field if the corresponding File Status has its value changed from COMPLETE to something else. React.useEffect(() => { - if (!!fileStatusTypeCode && fileStatusTypeCode !== 'COMPLT') { + if (isValidString(fileStatusTypeCode) && fileStatusTypeCode !== 'COMPLT') { setFieldValue('completionDate', ''); } }, [fileStatusTypeCode, setFieldValue]); @@ -137,7 +138,7 @@ const AcquisitionDetailSubForm: React.FC<{ }, [orgPersons, setFieldValue]); const primaryContacts: SelectOption[] = - orgPersons?.map((orgPerson: Api_OrganizationPerson) => { + orgPersons?.map((orgPerson: ApiGen_Concepts_OrganizationPerson) => { return { label: `${formatApiPersonNames(orgPerson.person)}`, value: orgPerson.personId ?? ' ', @@ -202,7 +203,7 @@ const AcquisitionDetailSubForm: React.FC<{ const selectedValue = [].slice .call(e.target.selectedOptions) .map((option: HTMLOptionElement & number) => option.value)[0]; - if (!!selectedValue && selectedValue !== 'OTHER') { + if (isValidString(selectedValue) && selectedValue !== 'OTHER') { formikProps.setFieldValue('fundingTypeOtherDescription', ''); } }} 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 8de5811441..098d7a6c7f 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 @@ -1,13 +1,12 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; import { ChecklistItemFormModel } from '@/features/mapSideBar/shared/tabs/checklist/update/models'; import { IAutocompletePrediction } from '@/interfaces'; -import { - Api_AcquisitionFile, - Api_AcquisitionFileOwner, - Api_AcquisitionFileTeam, -} from '@/models/api/AcquisitionFile'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { fromTypeCode, stringToUndefined, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFileOwner } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFileOwner'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { fromTypeCode, toTypeCodeNullable } from '@/utils/formUtils'; +import { exists, isValidId, isValidIsoDateTime } from '@/utils/utils'; import { AcquisitionOwnerFormModel, @@ -48,68 +47,74 @@ export class UpdateAcquisitionSummaryFormModel ownerRepresentative: InterestHolderForm = new InterestHolderForm( InterestHolderType.OWNER_REPRESENTATIVE, ); - otherInterestHolders: Api_InterestHolder[] = []; + otherInterestHolders: ApiGen_Concepts_InterestHolder[] = []; legacyStakeholders: string[] = []; - toApi(): Api_AcquisitionFile { + toApi(): ApiGen_Concepts_AcquisitionFile { return { - id: this.id, - fileNo: this.fileNo, - fileNumber: this.fileNumber, - legacyFileNumber: this.legacyFileNumber, - fileName: this.fileName, - rowVersion: this.rowVersion, - assignedDate: stringToUndefined(this.assignedDate), - deliveryDate: stringToUndefined(this.deliveryDate), - completionDate: stringToUndefined(this.completionDate), - fileStatusTypeCode: toTypeCode(this.fileStatusTypeCode), - acquisitionPhysFileStatusTypeCode: toTypeCode(this.acquisitionPhysFileStatusType), - acquisitionTypeCode: toTypeCode(this.acquisitionType), - regionCode: toTypeCode(Number(this.region)), - projectId: this.project?.id !== undefined && this.project?.id !== 0 ? this.project?.id : null, + id: this.id || 0, + fileNo: this.fileNo ?? 0, + fileNumber: this.fileNumber ?? null, + legacyFileNumber: this.legacyFileNumber ?? null, + fileName: this.fileName ?? null, + assignedDate: isValidIsoDateTime(this.assignedDate) ? this.assignedDate : null, + deliveryDate: isValidIsoDateTime(this.deliveryDate) ? this.deliveryDate : null, + completionDate: isValidIsoDateTime(this.completionDate) ? this.completionDate : null, + fileStatusTypeCode: toTypeCodeNullable(this.fileStatusTypeCode), + acquisitionPhysFileStatusTypeCode: toTypeCodeNullable(this.acquisitionPhysFileStatusType), + acquisitionTypeCode: toTypeCodeNullable(this.acquisitionType), + regionCode: toTypeCodeNullable(Number(this.region)), + projectId: isValidId(this.project?.id) ? this.project!.id : null, productId: this.product !== '' ? Number(this.product) : null, - fundingTypeCode: toTypeCode(this.fundingTypeCode), + fundingTypeCode: toTypeCodeNullable(this.fundingTypeCode), fundingOther: this.fundingTypeOtherDescription, acquisitionFileOwners: this.owners .filter(x => !x.isEmpty()) - .map(x => x.toApi()), + .map(x => x.toApi()), acquisitionTeam: this.team .filter(x => !!x.contact && !!x.contactTypeCode) .map(x => x.toApi(this.id || 0)) - .filter((x): x is Api_AcquisitionFileTeam => x !== null), + .filter(exists), acquisitionFileInterestHolders: [ ...this.otherInterestHolders, InterestHolderForm.toApi(this.ownerSolicitor, []), InterestHolderForm.toApi(this.ownerRepresentative, []), - ].filter((x): x is Api_InterestHolder => x !== null), + ].filter(exists), fileChecklistItems: this.fileChecklist.map(x => x.toApi()), + compensationRequisitions: null, + fileProperties: [], + legacyStakeholders: null, + product: null, + project: null, + totalAllowableCompensation: null, + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_AcquisitionFile): UpdateAcquisitionSummaryFormModel { + static fromApi(model: ApiGen_Concepts_AcquisitionFile): UpdateAcquisitionSummaryFormModel { const newForm = new UpdateAcquisitionSummaryFormModel(); newForm.id = model.id; newForm.fileNo = model.fileNo; - newForm.fileNumber = model.fileNumber; - newForm.legacyFileNumber = model.legacyFileNumber; + newForm.fileNumber = model.fileNumber ?? undefined; + newForm.legacyFileNumber = model.legacyFileNumber ?? undefined; newForm.fileName = model.fileName || ''; - newForm.rowVersion = model.rowVersion; - newForm.assignedDate = model.assignedDate; - newForm.deliveryDate = model.deliveryDate; + newForm.rowVersion = model.rowVersion ?? undefined; + newForm.assignedDate = model.assignedDate ?? undefined; + newForm.deliveryDate = model.deliveryDate ?? undefined; newForm.completionDate = model.completionDate ?? ''; - newForm.fileStatusTypeCode = fromTypeCode(model.fileStatusTypeCode); - newForm.acquisitionPhysFileStatusType = fromTypeCode(model.acquisitionPhysFileStatusTypeCode); - newForm.acquisitionType = fromTypeCode(model.acquisitionTypeCode); + newForm.fileStatusTypeCode = fromTypeCode(model.fileStatusTypeCode) ?? undefined; + newForm.acquisitionPhysFileStatusType = + fromTypeCode(model.acquisitionPhysFileStatusTypeCode) ?? undefined; + newForm.acquisitionType = fromTypeCode(model.acquisitionTypeCode) ?? undefined; newForm.region = fromTypeCode(model.regionCode)?.toString(); newForm.team = model.acquisitionTeam?.map(x => AcquisitionTeamFormModel.fromApi(x)) || []; newForm.owners = model.acquisitionFileOwners?.map(x => AcquisitionOwnerFormModel.fromApi(x)) || []; - newForm.fundingTypeCode = model.fundingTypeCode?.id; + newForm.fundingTypeCode = model.fundingTypeCode?.id ?? undefined; newForm.fundingTypeOtherDescription = model.fundingOther || ''; - newForm.project = - model.project !== undefined - ? { id: model.project?.id || 0, text: model.project?.description || '' } - : undefined; + newForm.project = exists(model.project) + ? { id: model.project?.id || 0, text: model.project?.description || '' } + : undefined; newForm.product = model.product?.id?.toString() ?? ''; const interestHolders = model.acquisitionFileInterestHolders?.map(x => diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/PropertyInterestHoldersViewTable.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/PropertyInterestHoldersViewTable.tsx index 2220f60aec..3e78ea0081 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/PropertyInterestHoldersViewTable.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/PropertyInterestHoldersViewTable.tsx @@ -3,8 +3,8 @@ import { CellProps } from 'react-table'; import { StyledLink } from '@/components/maps/leaflet/LayerPopup/styles'; import { ColumnWithProps, Table } from '@/components/Table'; -import { Api_InterestHolderProperty } from '@/models/api/InterestHolder'; -import { Api_Person } from '@/models/api/Person'; +import { ApiGen_Concepts_InterestHolderProperty } from '@/models/api/generated/ApiGen_Concepts_InterestHolderProperty'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; import { formatApiPersonNames } from '@/utils/personUtils'; import { InterestHolderViewForm, InterestHolderViewRow } from '../update/models'; @@ -22,7 +22,7 @@ const getColumnsByProperty = ( align: 'left', minWidth: 60, maxWidth: 60, - Cell: (cellProps: CellProps) => { + Cell: (cellProps: CellProps) => { return ( ) => { + Cell: (cellProps: CellProps) => { return cellProps.row.original?.primaryContact ? ( ) => { + Cell: ( + cellProps: CellProps, + ) => { const propertyInterestType = cellProps.row.original.interestHolderType?.description ?? ''; return <>{propertyInterestType}; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.test.tsx index afbae117c8..c10e45cfdb 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.test.tsx @@ -5,7 +5,7 @@ import { forwardRef } from 'react'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { render, RenderOptions } from '@/utils/test-utils'; @@ -19,7 +19,7 @@ const storeState = { const mockGetApi = { error: undefined, - response: [] as Api_InterestHolder[], + response: [] as ApiGen_Concepts_InterestHolder[], execute: jest.fn().mockResolvedValue(getMockApiInterestHolders()), loading: false, }; diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.tsx index cfc628da6f..9db32da4c5 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderContainer.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { useEffect } from 'react'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { IStakeHolderViewProps } from './StakeHolderView'; export interface IStakeHolderContainerProps { View: React.FC; onEdit: () => void; - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; } export const StakeHolderContainer: React.FunctionComponent = ({ diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.test.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.test.tsx index f62d1e8d45..2bbea7bce7 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.test.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.test.tsx @@ -5,6 +5,7 @@ import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock'; import { mockLookups } from '@/mocks/lookups.mock'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { render, RenderOptions } from '@/utils/test-utils'; +import { exists } from '@/utils/utils'; import { InterestHolderViewForm, InterestHolderViewRow } from '../update/models'; import StakeholderOrganizer from './stakeholderOrganizer'; @@ -55,10 +56,12 @@ describe('StakeHolderView component', () => { const groupedInterestProperties = getMockApiInterestHolders() .flatMap(i => i.interestHolderProperties) + .filter(exists) .map(i => InterestHolderViewForm.fromApi(i)); const groupedNonInterestProperties = getMockApiInterestHolders() .flatMap(i => i.interestHolderProperties) + .filter(exists) .map(i => InterestHolderViewForm.fromApi(i)); organizerMock.getInterestProperties.mockReturnValue(groupedInterestProperties); @@ -95,7 +98,7 @@ describe('StakeHolderView component', () => { it('displays table with grouped property values', () => { const model = InterestHolderViewForm.fromApi( - getMockApiInterestHolders()[0].interestHolderProperties[0], + getMockApiInterestHolders()![0].interestHolderProperties![0], ); organizerMock.getInterestProperties.mockReturnValue([model]); @@ -110,10 +113,10 @@ describe('StakeHolderView component', () => { it('displays table with grouped property values where there are multiple interest type codes', () => { const interestHolder = getMockApiInterestHolders()[0]; - const interestHolderProperty = interestHolder.interestHolderProperties[0]; + const interestHolderProperty = interestHolder.interestHolderProperties![0]; interestHolderProperty.propertyInterestTypes = [ - { id: 'O', description: 'Ordinal' }, - { id: 'R', description: 'Registered' }, + { id: 'O', description: 'Ordinal', displayOrder: null, isDisabled: false }, + { id: 'R', description: 'Registered', displayOrder: null, isDisabled: false }, ]; const model = InterestHolderViewForm.fromApi(interestHolderProperty); model.identifier = 'PID: 025-196-375'; @@ -121,10 +124,14 @@ describe('StakeHolderView component', () => { InterestHolderViewRow.fromApi(interestHolderProperty, interestHolder, { id: 'O', description: 'Ordinal', + displayOrder: null, + isDisabled: false, }), InterestHolderViewRow.fromApi(interestHolderProperty, interestHolder, { id: 'R', description: 'Registered', + displayOrder: null, + isDisabled: false, }), ]; @@ -141,7 +148,7 @@ describe('StakeHolderView component', () => { it('displays table with grouped non-interest property values', () => { const model = InterestHolderViewForm.fromApi( - getMockApiInterestHolders()[0].interestHolderProperties[0], + getMockApiInterestHolders()![0].interestHolderProperties![0], ); organizerMock.getInterestProperties.mockReturnValue([model]); diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.tsx index deb7434d18..62e4aab81e 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/StakeHolderView.tsx @@ -8,8 +8,8 @@ import { StyledEditWrapper, StyledSummarySection } from '@/components/common/Sec import { Claims } from '@/constants/index'; import { StyledNoData } from '@/features/documents/commonStyles'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; import StatusUpdateSolver from '../../fileDetails/detail/statusUpdateSolver'; import PropertyInterestHoldersViewTable from './PropertyInterestHoldersViewTable'; @@ -17,8 +17,8 @@ import StakeholderOrganizer from './stakeholderOrganizer'; export interface IStakeHolderViewProps { loading: boolean; - acquisitionFile: Api_AcquisitionFile; - interestHolders: Api_InterestHolder[] | undefined; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; + interestHolders: ApiGen_Concepts_InterestHolder[] | undefined; onEdit: () => void; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.test.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.test.ts index edc9de2b38..c6f5fc185f 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.test.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.test.ts @@ -4,7 +4,7 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { emptyApiInterestHolder, emptyInterestHolderProperty } from '@/mocks/interestHolder.mock'; import { getMockApiInterestHolders } from '@/mocks/interestHolders.mock'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; import StakeholderOrganizer from './stakeholderOrganizer'; @@ -24,13 +24,15 @@ describe('StakeholderOrganizer', () => { it('does not group interest and non-interests for the same property', async () => { const acquisitionFile = mockAcquisitionFileResponse(); - const testInterestHolders: Api_InterestHolder[] = [ + const testInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'NIP' }], + propertyInterestTypes: [ + { id: 'NIP', description: null, displayOrder: null, isDisabled: false }, + ], acquisitionFilePropertyId: 1, }, ], @@ -40,7 +42,9 @@ describe('StakeholderOrganizer', () => { interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'IP' }], + propertyInterestTypes: [ + { id: 'IP', description: null, displayOrder: null, isDisabled: false }, + ], acquisitionFilePropertyId: 1, }, ], @@ -61,28 +65,43 @@ describe('StakeholderOrganizer', () => { it('does not group interest holders for different properties interest types', async () => { const acquisitionFile = mockAcquisitionFileResponse(); - const testInterestHolders: Api_InterestHolder[] = [ + const testInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, personId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, + interestHolderProperties: [ { ...emptyInterestHolderProperty, acquisitionFilePropertyId: 1, - propertyInterestTypes: [{ id: 'test_interest_1' }], + propertyInterestTypes: [ + { id: 'test_interest_1', description: null, displayOrder: null, isDisabled: false }, + ], }, ], }, { ...emptyApiInterestHolder, personId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, interestHolderProperties: [ { ...emptyInterestHolderProperty, acquisitionFilePropertyId: 2, - propertyInterestTypes: [{ id: 'test_interest_2' }], + propertyInterestTypes: [ + { id: 'test_interest_2', description: null, displayOrder: null, isDisabled: false }, + ], }, ], }, @@ -100,16 +119,24 @@ describe('StakeholderOrganizer', () => { it('it separates non-interest and interest payees even if they are for the same interest holder property', async () => { const acquisitionFile = mockAcquisitionFileResponse(); - const testInterestHolders: Api_InterestHolder[] = [ + const testInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, personId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, interestHolderProperties: [ { ...emptyInterestHolderProperty, acquisitionFilePropertyId: 1, - propertyInterestTypes: [{ id: 'test_interest_1' }, { id: 'NIP' }], + propertyInterestTypes: [ + { id: 'test_interest_1', description: null, displayOrder: null, isDisabled: false }, + { id: 'NIP', description: null, displayOrder: null, isDisabled: false }, + ], }, ], }, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.ts index b995c39a79..07d24ae609 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/detail/stakeholderOrganizer.ts @@ -1,16 +1,18 @@ -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_InterestHolder, Api_InterestHolderProperty } from '@/models/api/InterestHolder'; -import Api_TypeCode from '@/models/api/TypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { ApiGen_Concepts_InterestHolderProperty } from '@/models/api/generated/ApiGen_Concepts_InterestHolderProperty'; +import { exists } from '@/utils'; import { InterestHolderViewForm, InterestHolderViewRow } from '../update/models'; class StakeholderOrganizer { - private readonly acquisitionFile: Api_AcquisitionFile; - private readonly interestHolders: Api_InterestHolder[] | undefined; + private readonly acquisitionFile: ApiGen_Concepts_AcquisitionFile; + private readonly interestHolders: ApiGen_Concepts_InterestHolder[] | undefined; constructor( - acquisitionFile: Api_AcquisitionFile, - interestHolders: Api_InterestHolder[] | undefined, + acquisitionFile: ApiGen_Concepts_AcquisitionFile, + interestHolders: ApiGen_Concepts_InterestHolder[] | undefined, ) { this.acquisitionFile = acquisitionFile; this.interestHolders = interestHolders; @@ -18,13 +20,18 @@ class StakeholderOrganizer { getInterestProperties() { const allInterestProperties = - this.interestHolders?.flatMap(interestHolder => interestHolder.interestHolderProperties) ?? - []; + this.interestHolders + ?.flatMap(interestHolder => interestHolder.interestHolderProperties) + .filter(exists) ?? []; const interestProperties = allInterestProperties - .filter(ip => ip.propertyInterestTypes.some(pit => pit?.id !== 'NIP')) - .map(ip => { - const filteredTypes = ip.propertyInterestTypes.filter(pit => pit?.id !== 'NIP'); + .filter( + ip => + ip.propertyInterestTypes != null && + ip.propertyInterestTypes.some(pit => pit?.id !== 'NIP'), + ) + .map(ip => { + const filteredTypes = ip.propertyInterestTypes?.filter(pit => pit?.id !== 'NIP') ?? null; return { ...ip, propertyInterestTypes: filteredTypes }; }); @@ -33,22 +40,23 @@ class StakeholderOrganizer { getNonInterestProperties() { const allInterestProperties = - this.interestHolders?.flatMap(interestHolder => interestHolder.interestHolderProperties) ?? - []; + this.interestHolders + ?.flatMap(interestHolder => interestHolder.interestHolderProperties) + .filter(exists) ?? []; const nonInterestProperties = allInterestProperties - .filter(ip => ip.propertyInterestTypes.some(pit => pit?.id === 'NIP')) - .map(ip => { - const filteredTypes = ip.propertyInterestTypes.filter(pit => pit?.id === 'NIP'); + .filter(ip => ip.propertyInterestTypes?.some(pit => pit?.id === 'NIP')) + .map(ip => { + const filteredTypes = ip.propertyInterestTypes?.filter(pit => pit?.id === 'NIP') ?? null; return { ...ip, propertyInterestTypes: filteredTypes }; }); return this.generateFormFromProperties(nonInterestProperties); } - private generateFormFromProperties(interestProperties: Api_InterestHolderProperty[]) { + private generateFormFromProperties(interestProperties: ApiGen_Concepts_InterestHolderProperty[]) { const groupedInterestProperties: InterestHolderViewForm[] = []; - interestProperties.forEach((interestHolderProperty: Api_InterestHolderProperty) => { + interestProperties.forEach((interestHolderProperty: ApiGen_Concepts_InterestHolderProperty) => { const matchingGroup = groupedInterestProperties.find( gip => gip.id === interestHolderProperty.acquisitionFilePropertyId, ); @@ -64,13 +72,13 @@ class StakeholderOrganizer { if (!matchingGroup) { const newGroup = InterestHolderViewForm.fromApi(interestHolderProperty); - newGroup.groupedPropertyInterests = interestHolderProperty.propertyInterestTypes.map( - (itc: Api_TypeCode) => + newGroup.groupedPropertyInterests = + interestHolderProperty.propertyInterestTypes?.map((itc: ApiGen_Base_CodeType) => InterestHolderViewRow.fromApi(interestHolderProperty, interestHolder, itc), - ); + ) ?? []; groupedInterestProperties.push(newGroup); } else { - interestHolderProperty.propertyInterestTypes.forEach((itc: Api_TypeCode) => + interestHolderProperty.propertyInterestTypes?.forEach((itc: ApiGen_Base_CodeType) => matchingGroup.groupedPropertyInterests.push( InterestHolderViewRow.fromApi(interestHolderProperty, interestHolder, itc), ), diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/InterestHolderSubForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/InterestHolderSubForm.tsx index face37b4ca..2834b1cb34 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/InterestHolderSubForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/InterestHolderSubForm.tsx @@ -13,9 +13,10 @@ import FilePropertiesTable from '@/components/filePropertiesTable/FileProperties import * as API from '@/constants/API'; import { useOrganizationRepository } from '@/features/contacts/repositories/useOrganizationRepository'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_OrganizationPerson } from '@/models/api/Organization'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; +import { ApiGen_Concepts_OrganizationPerson } from '@/models/api/generated/ApiGen_Concepts_OrganizationPerson'; +import { exists } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; import { InterestHolderForm, StakeHolderForm } from './models'; @@ -24,7 +25,7 @@ export interface IInterestHolderProps { index: number; errors: FormikErrors; arrayHelpers: FieldArrayRenderProps; - file: Api_AcquisitionFile; + file: ApiGen_Concepts_AcquisitionFile; interestHolder: InterestHolderForm; } @@ -62,7 +63,7 @@ export const InterestHolderSubForm: React.FunctionComponent { + orgPersons?.map((orgPerson: ApiGen_Concepts_OrganizationPerson) => { return { label: `${formatApiPersonNames(orgPerson.person)}`, value: orgPerson.personId ?? '', @@ -135,9 +136,9 @@ export const InterestHolderSubForm: React.FunctionComponent file.fileProperties?.find(fp => fp.id === ip.acquisitionFilePropertyId)) - .filter((fp): fp is Api_PropertyFile => !!fp) ?? [] + .filter(exists) ?? [] } - setSelectedFileProperties={(fileProperties: Api_PropertyFile[]) => { + setSelectedFileProperties={(fileProperties: ApiGen_Concepts_FileProperty[]) => { const interestHolderProperties = fileProperties.map(fileProperty => { const matchingProperty = interestHolder.impactedProperties.find( ip => ip.acquisitionFilePropertyId === fileProperty.id, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderContainer.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderContainer.tsx index 45e7ed3b64..28b6d3f9ec 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderContainer.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderContainer.tsx @@ -7,7 +7,7 @@ import { toast } from 'react-toastify'; import { InterestHolderType } from '@/constants/interestHolderTypes'; import { useInterestHolderRepository } from '@/hooks/repositories/useInterestHolderRepository'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; import { StakeHolderForm } from './models'; import { IUpdateStakeHolderFormProps } from './UpdateStakeHolderForm'; @@ -15,7 +15,7 @@ import { IUpdateStakeHolderFormProps } from './UpdateStakeHolderForm'; export interface IUpdateStakeHolderContainerProps { View: React.FC; formikRef: React.Ref>; - acquisitionFile: Api_AcquisitionFile; + acquisitionFile: ApiGen_Concepts_AcquisitionFile; onSuccess: () => void; } diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderForm.tsx b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderForm.tsx index 2ab99d979e..f63ae06377 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderForm.tsx +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/UpdateStakeHolderForm.tsx @@ -14,9 +14,10 @@ import { StyledSummarySection } from '@/components/common/Section/SectionStyles' import FilePropertiesTable from '@/components/filePropertiesTable/FilePropertiesTable'; import { StyledLink } from '@/components/maps/leaflet/LayerPopup/styles'; import { InterestHolderType } from '@/constants/interestHolderTypes'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; -import { Api_PropertyFile } from '@/models/api/PropertyFile'; +import { ApiGen_Concepts_AcquisitionFile } from '@/models/api/generated/ApiGen_Concepts_AcquisitionFile'; +import { ApiGen_Concepts_FileProperty } from '@/models/api/generated/ApiGen_Concepts_FileProperty'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { exists } from '@/utils'; import { InterestHolderSubForm } from './InterestHolderSubForm'; import { InterestHolderForm, StakeHolderForm } from './models'; @@ -24,11 +25,11 @@ import { UpdateStakeHolderYupSchema } from './UpdateStakeHolderYupSchema'; export interface IUpdateStakeHolderFormProps { formikRef: React.Ref>; - file: Api_AcquisitionFile; + file: ApiGen_Concepts_AcquisitionFile; onSubmit: ( interestHolders: StakeHolderForm, formikHelpers: FormikHelpers, - ) => Promise; + ) => Promise; interestHolders: StakeHolderForm; loading: boolean; } @@ -180,9 +181,11 @@ export const UpdateStakeHolderForm: React.FunctionComponent fp.id === ip.acquisitionFilePropertyId, ), ) - .filter((fp): fp is Api_PropertyFile => !!fp) ?? [] + .filter(exists) ?? [] } - setSelectedFileProperties={(fileProperties: Api_PropertyFile[]) => { + setSelectedFileProperties={( + fileProperties: ApiGen_Concepts_FileProperty[], + ) => { const interestHolderProperties = fileProperties.map(fileProperty => { const matchingProperty = interestHolder.impactedProperties.find( ip => ip.acquisitionFilePropertyId === fileProperty.id, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.test.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.test.ts index 3988716a95..951309d894 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.test.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.test.ts @@ -1,6 +1,7 @@ import { InterestHolderType } from '@/constants/interestHolderTypes'; import { emptyApiInterestHolder, emptyInterestHolderProperty } from '@/mocks/interestHolder.mock'; -import { Api_InterestHolder } from '@/models/api/InterestHolder'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { InterestHolderForm, StakeHolderForm } from './models'; @@ -21,19 +22,23 @@ const emptyInterestHolderForm: InterestHolderForm = { describe('Interest Holder model tests', () => { it('StakeHolderForm fromApi splits a single InterestHolder into interests and non-interests', () => { - const apiInterestHolders: Api_InterestHolder = { + const apiInterestHolders: ApiGen_Concepts_InterestHolder = { ...emptyApiInterestHolder, interestHolderId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: toTypeCodeNullable(InterestHolderType.INTEREST_HOLDER), interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'NIP' }], + propertyInterestTypes: [ + { id: 'NIP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'IP' }], + propertyInterestTypes: [ + { id: 'IP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], @@ -44,19 +49,28 @@ describe('Interest Holder model tests', () => { }); it('StakeHolderForm fromApi splits a single InterestHolder into multiple if an interest holder has multiple properties of different types', () => { - const apiInterestHolders: Api_InterestHolder = { + const apiInterestHolders: ApiGen_Concepts_InterestHolder = { ...emptyApiInterestHolder, interestHolderId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'PI' }], + propertyInterestTypes: [ + { id: 'PI', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'IP' }], + propertyInterestTypes: [ + { id: 'IP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], @@ -66,16 +80,23 @@ describe('Interest Holder model tests', () => { }); it('StakeHolderForm fromApi does not combine multiple stakeholders even if they have the same type', () => { - const apiInterestHolders: Api_InterestHolder[] = [ + const apiInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, interestHolderId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, personId: 2, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip' }], + propertyInterestTypes: [ + { id: 'ip', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], @@ -83,12 +104,19 @@ describe('Interest Holder model tests', () => { { ...emptyApiInterestHolder, interestHolderId: 2, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, personId: 1, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip' }], + propertyInterestTypes: [ + { id: 'ip', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 2, }, ], @@ -99,21 +127,30 @@ describe('Interest Holder model tests', () => { }); it('StakeHolderForm fromApi does not split an interest holder with multiple properties if the properties have the same type', () => { - const apiInterestHolders: Api_InterestHolder[] = [ + const apiInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, interestHolderId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, personId: 2, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip' }], + propertyInterestTypes: [ + { id: 'ip', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip' }], + propertyInterestTypes: [ + { id: 'ip', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], @@ -124,21 +161,30 @@ describe('Interest Holder model tests', () => { }); it('StakeHolderForm fromApi does split an interest holder with multiple properties if the properties have different types', () => { - const apiInterestHolders: Api_InterestHolder[] = [ + const apiInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, interestHolderId: 1, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, personId: 2, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip' }], + propertyInterestTypes: [ + { id: 'ip', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'ip2' }], + propertyInterestTypes: [ + { id: 'ip2', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], @@ -149,37 +195,55 @@ describe('Interest Holder model tests', () => { }); it('StakeHolderForm fromApi splits multiple InterestHolders into interests and non-interests', () => { - const apiInterestHolders: Api_InterestHolder[] = [ + const apiInterestHolders: ApiGen_Concepts_InterestHolder[] = [ { ...emptyApiInterestHolder, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, interestHolderId: 1, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'NIP' }], + propertyInterestTypes: [ + { id: 'NIP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'IP' }], + propertyInterestTypes: [ + { id: 'IP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 1, }, ], }, { ...emptyApiInterestHolder, - interestHolderType: { id: InterestHolderType.INTEREST_HOLDER }, + interestHolderType: { + id: InterestHolderType.INTEREST_HOLDER, + description: null, + displayOrder: null, + isDisabled: false, + }, interestHolderId: 2, interestHolderProperties: [ { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'NIP' }], + propertyInterestTypes: [ + { id: 'NIP', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 2, }, { ...emptyInterestHolderProperty, - propertyInterestTypes: [{ id: 'PI' }], + propertyInterestTypes: [ + { id: 'PI', description: null, displayOrder: null, isDisabled: false }, + ], interestHolderId: 2, }, ], @@ -329,11 +393,16 @@ describe('Interest Holder model tests', () => { const apiInterestHolders = StakeHolderForm.toApi(model); expect(apiInterestHolders).toHaveLength(1); expect(apiInterestHolders[0].interestHolderProperties).toHaveLength(1); - expect(apiInterestHolders[0].interestHolderProperties[0].propertyInterestTypes).toHaveLength(2); + expect(apiInterestHolders[0].interestHolderProperties![0].propertyInterestTypes).toHaveLength( + 2, + ); }); it('InterestHolderForm sets contact based on api response', () => { - const apiInterestHolder: Api_InterestHolder = { ...emptyApiInterestHolder, personId: 1 }; + const apiInterestHolder: ApiGen_Concepts_InterestHolder = { + ...emptyApiInterestHolder, + personId: 1, + }; const interestHolderModel = InterestHolderForm.fromApi( apiInterestHolder, diff --git a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.ts b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.ts index 205f34d59e..2af7c2af67 100644 --- a/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.ts +++ b/source/frontend/src/features/mapSideBar/acquisition/tabs/stakeholders/update/models.ts @@ -2,17 +2,21 @@ import { chain, uniqBy } from 'lodash'; import { InterestHolderType } from '@/constants/interestHolderTypes'; import { IContactSearchResult } from '@/interfaces'; -import { Api_InterestHolder, Api_InterestHolderProperty } from '@/models/api/InterestHolder'; -import { Api_Organization } from '@/models/api/Organization'; -import { Api_Person } from '@/models/api/Person'; -import Api_TypeCode from '@/models/api/TypeCode'; +import { ApiGen_Base_CodeType } from '@/models/api/generated/ApiGen_Base_CodeType'; +import { ApiGen_Concepts_InterestHolder } from '@/models/api/generated/ApiGen_Concepts_InterestHolder'; +import { ApiGen_Concepts_InterestHolderProperty } from '@/models/api/generated/ApiGen_Concepts_InterestHolderProperty'; +import { ApiGen_Concepts_Organization } from '@/models/api/generated/ApiGen_Concepts_Organization'; +import { ApiGen_Concepts_Person } from '@/models/api/generated/ApiGen_Concepts_Person'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { getFilePropertyName } from '@/utils/mapPropertyUtils'; +import { exists } from '@/utils/utils'; export class StakeHolderForm { public interestHolders: InterestHolderForm[] = []; public nonInterestPayees: InterestHolderForm[] = []; - static fromApi(apiModel: Api_InterestHolder[]): StakeHolderForm { + static fromApi(apiModel: ApiGen_Concepts_InterestHolder[]): StakeHolderForm { const stakeHolderForm = new StakeHolderForm(); const interestHolderModels = apiModel.filter( @@ -20,8 +24,8 @@ export class StakeHolderForm { ); const interestHoldersByType: InterestHolderForm[] = []; - interestHolderModels.forEach((ih: Api_InterestHolder) => { - ih.interestHolderProperties.forEach((ihp: Api_InterestHolderProperty) => { + interestHolderModels.forEach((ih: ApiGen_Concepts_InterestHolder) => { + ih.interestHolderProperties?.forEach((ihp: ApiGen_Concepts_InterestHolderProperty) => { if (ihp.propertyInterestTypes !== null) { ihp.propertyInterestTypes.forEach(pit => { const formModel = InterestHolderForm.fromApi(ih, InterestHolderType.INTEREST_HOLDER); @@ -56,7 +60,7 @@ export class StakeHolderForm { return stakeHolderForm; } - static toApi(model: StakeHolderForm): Api_InterestHolder[] { + static toApi(model: StakeHolderForm): ApiGen_Concepts_InterestHolder[] { // Group by personId or organizationId, create a unique list of all impacted properties for each group, and then map back to API model. return chain(model.interestHolders.concat(model.nonInterestPayees)) @@ -90,7 +94,7 @@ export class StakeHolderForm { return apiInterestHolder; }) .value() - .filter((x): x is Api_InterestHolder => x !== null); + .filter(exists); } } @@ -99,7 +103,7 @@ export class InterestHolderPropertyFormModel { acquisitionFilePropertyId: string = ''; propertyInterestType: string = ''; - static fromApi(model: Api_InterestHolderProperty): InterestHolderPropertyFormModel[] { + static fromApi(model: ApiGen_Concepts_InterestHolderProperty): InterestHolderPropertyFormModel[] { return ( model.propertyInterestTypes ?.map(x => { @@ -109,7 +113,7 @@ export class InterestHolderPropertyFormModel { propertyInterestType: x.id || '', }; }) - .filter((x): x is InterestHolderPropertyFormModel => x !== undefined) || [] + .filter(exists) || [] ); } } @@ -134,16 +138,15 @@ export class InterestHolderForm { } static fromApi( - apiModel: Api_InterestHolder, + apiModel: ApiGen_Concepts_InterestHolder, interestTypeCode: InterestHolderType, ): InterestHolderForm { const interestHolderForm = new InterestHolderForm(interestTypeCode); interestHolderForm.interestHolderId = apiModel.interestHolderId; interestHolderForm.personId = apiModel.personId?.toString() || ''; interestHolderForm.organizationId = apiModel.organizationId?.toString() || ''; - interestHolderForm.impactedProperties = apiModel.interestHolderProperties.map(ihp => - InterestHolderPropertyForm.fromApi(ihp), - ); + interestHolderForm.impactedProperties = + apiModel.interestHolderProperties?.map(ihp => InterestHolderPropertyForm.fromApi(ihp)) || []; interestHolderForm.primaryContactId = apiModel.primaryContactId; interestHolderForm.rowVersion = apiModel.rowVersion ?? null; interestHolderForm.isDisabled = apiModel.isDisabled; @@ -167,8 +170,8 @@ export class InterestHolderForm { static toApi( model: InterestHolderForm, - properties: Api_InterestHolderProperty[], - ): Api_InterestHolder | null { + properties: ApiGen_Concepts_InterestHolderProperty[], + ): ApiGen_Concepts_InterestHolder | null { const personId = model.contact?.personId ?? null; const organizationId = !personId ? model.contact?.organizationId ?? null : null; if (personId === null && organizationId === null) { @@ -185,10 +188,10 @@ export class InterestHolderForm { isDisabled: model.isDisabled, interestHolderProperties: properties, acquisitionFileId: model.acquisitionFileId, - rowVersion: model.rowVersion ?? undefined, comment: model.comment, - interestHolderType: { id: model.interestTypeCode }, + interestHolderType: toTypeCodeNullable(model.interestTypeCode), primaryContact: null, + ...getEmptyBaseAudit(model.rowVersion), }; } } @@ -199,7 +202,7 @@ export class InterestHolderPropertyForm { acquisitionFilePropertyId: number | null = null; rowVersion: number | null = null; - static fromApi(apiModel: Api_InterestHolderProperty): InterestHolderPropertyForm { + static fromApi(apiModel: ApiGen_Concepts_InterestHolderProperty): InterestHolderPropertyForm { const interestHolderPropertyForm = new InterestHolderPropertyForm(); interestHolderPropertyForm.interestHolderPropertyId = apiModel.interestHolderPropertyId; interestHolderPropertyForm.interestHolderId = apiModel.interestHolderId; @@ -212,14 +215,14 @@ export class InterestHolderPropertyForm { static toApi( model: InterestHolderPropertyForm, interestTypeCodes: string[], - ): Api_InterestHolderProperty { + ): ApiGen_Concepts_InterestHolderProperty { return { interestHolderId: model.interestHolderId, interestHolderPropertyId: model.interestHolderPropertyId ?? null, acquisitionFilePropertyId: model.acquisitionFilePropertyId, acquisitionFileProperty: null, - rowVersion: model.rowVersion, - propertyInterestTypes: interestTypeCodes.map(itc => ({ id: itc })), + propertyInterestTypes: interestTypeCodes.map(itc => toTypeCodeNullable(itc)).filter(exists), + ...getEmptyBaseAudit(model.rowVersion), }; } } @@ -229,28 +232,28 @@ export class InterestHolderViewForm { identifier: string = ''; groupedPropertyInterests: InterestHolderViewRow[] = []; - static fromApi(apiModel: Api_InterestHolderProperty) { + static fromApi(apiModel: ApiGen_Concepts_InterestHolderProperty) { const interestHolderViewForm = new InterestHolderViewForm(); interestHolderViewForm.id = apiModel.acquisitionFileProperty?.id ?? null; interestHolderViewForm.identifier = `${ - getFilePropertyName(apiModel.acquisitionFileProperty ?? undefined).label - }: ${getFilePropertyName(apiModel.acquisitionFileProperty ?? undefined).value}`; + getFilePropertyName(apiModel.acquisitionFileProperty).label + }: ${getFilePropertyName(apiModel.acquisitionFileProperty).value}`; return interestHolderViewForm; } } export class InterestHolderViewRow { id: number | null = null; - interestHolderProperty: Api_InterestHolderProperty | null = null; - person: Api_Person | null = null; - organization: Api_Organization | null = null; - interestHolderType: Api_TypeCode | null = null; - primaryContact: Api_Person | null = null; + interestHolderProperty: ApiGen_Concepts_InterestHolderProperty | null = null; + person: ApiGen_Concepts_Person | null = null; + organization: ApiGen_Concepts_Organization | null = null; + interestHolderType: ApiGen_Base_CodeType | null = null; + primaryContact: ApiGen_Concepts_Person | null = null; static fromApi( - apiInterestHolderProperty: Api_InterestHolderProperty, - apiInterestHolder: Api_InterestHolder | undefined, - interestHolderType: Api_TypeCode, + apiInterestHolderProperty: ApiGen_Concepts_InterestHolderProperty, + apiInterestHolder: ApiGen_Concepts_InterestHolder | undefined, + interestHolderType: ApiGen_Base_CodeType, ) { const interestHolderViewRow = new InterestHolderViewRow(); interestHolderViewRow.id = apiInterestHolder?.interestHolderId ?? 0; diff --git a/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx b/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx index 58c9afd541..a67da3705c 100644 --- a/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx +++ b/source/frontend/src/features/mapSideBar/context/sidebarContext.tsx @@ -1,15 +1,16 @@ -import { LatLngLiteral } from 'leaflet'; import { findIndex } from 'lodash'; import * as React from 'react'; import { useCallback, useEffect, useState } from 'react'; import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext'; import { FileTypes } from '@/constants/fileTypes'; -import { Api_File, Api_LastUpdatedBy } from '@/models/api/File'; -import { Api_Project } from '@/models/api/Project'; +import { Api_LastUpdatedBy } from '@/models/api/File'; +import { ApiGen_Concepts_File } from '@/models/api/generated/ApiGen_Concepts_File'; +import { ApiGen_Concepts_Project } from '@/models/api/generated/ApiGen_Concepts_Project'; +import { exists } from '@/utils'; import { getLatLng } from '@/utils/mapPropertyUtils'; -export interface TypedFile extends Api_File { +export interface TypedFile extends ApiGen_Concepts_File { fileType: FileTypes; projectId?: number | null; productId?: number | null; @@ -24,8 +25,8 @@ export interface ISideBarContext { setFileLoading: (loading: boolean) => void; resetFilePropertyLocations: () => void; projectLoading: boolean; - project?: Api_Project; - setProject: (project?: Api_Project) => void; + project?: ApiGen_Concepts_Project; + setProject: (project?: ApiGen_Concepts_Project) => void; setProjectLoading: (loading: boolean) => void; getFilePropertyIndexById: (filePropertyId: number) => number; fullWidth: boolean; @@ -60,7 +61,7 @@ export const SideBarContext = React.createContext({ setFullWidth: (fullWidth: boolean) => { throw Error('setFullWidth function not defined'); }, - setProject: (project?: Api_Project) => { + setProject: (project?: ApiGen_Concepts_Project) => { throw Error('setProject function not defined'); }, projectLoading: false, @@ -80,11 +81,11 @@ export const SideBarContext = React.createContext({ export const SideBarContextProvider = (props: { children: React.ReactChild | React.ReactChild[] | React.ReactNode; file?: TypedFile; - project?: Api_Project; + project?: ApiGen_Concepts_Project; lastUpdatedBy?: Api_LastUpdatedBy; }) => { const [file, setFile] = useState(props.file); - const [project, setProject] = useState(props.project); + const [project, setProject] = useState(props.project); const [staleFile, setStaleFile] = useState(false); const [lastUpdatedBy, setLastUpdatedBy] = useState( props.lastUpdatedBy ?? null, @@ -111,7 +112,7 @@ export const SideBarContextProvider = (props: { ); const setProjectInstance = useCallback( - (project?: Api_Project) => { + (project?: ApiGen_Concepts_Project) => { setProject(project); }, [setProject], @@ -125,10 +126,10 @@ export const SideBarContextProvider = (props: { const fileProperties = file?.fileProperties; const resetFilePropertyLocations = useCallback(() => { - if (fileProperties !== undefined) { + if (exists(fileProperties)) { const propertyLocations = fileProperties .map(x => getLatLng(x.property?.location)) - .filter((x): x is LatLngLiteral => x !== undefined && x !== null); + .filter(exists); setFilePropertyLocations(propertyLocations); } else { diff --git a/source/frontend/src/features/mapSideBar/disposition/DispositionContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/DispositionContainer.tsx index 74bb578957..504f667149 100644 --- a/source/frontend/src/features/mapSideBar/disposition/DispositionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/DispositionContainer.tsx @@ -8,7 +8,7 @@ import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineCo import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import { useQuery } from '@/hooks/use-query'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; -import { stripTrailingSlash } from '@/utils'; +import { exists, isValidId, stripTrailingSlash } from '@/utils'; import { SideBarContext } from '../context/sidebarContext'; import { IDispositionViewProps } from './DispositionView'; @@ -77,7 +77,7 @@ export const DispositionContainer: React.FunctionComponent { var retrieved = await retrieveDispositionFile(dispositionFileId); - if (retrieved === undefined) { + if (!exists(retrieved)) { return; } @@ -103,8 +103,8 @@ export const DispositionContainer: React.FunctionComponent { if ( - lastUpdatedBy === undefined || - dispositionFileId !== lastUpdatedBy?.parentId || + !exists(lastUpdatedBy) || + dispositionFileId !== lastUpdatedBy.parentId || staleLastUpdatedBy ) { fetchLastUpdatedBy(); @@ -113,7 +113,7 @@ export const DispositionContainer: React.FunctionComponent { if ( - dispositionFileId === undefined || + !isValidId(dispositionFileId) || (dispositionFileId !== dispositionFile?.id && !loadingDispositionFile) || staleFile ) { @@ -222,10 +222,10 @@ export const DispositionContainer: React.FunctionComponent void; onShowPropertySelector: () => void; onSuccess: (updateProperties?: boolean) => void; - onUpdateProperties: (file: Api_File) => Promise; + onUpdateProperties: (file: ApiGen_Concepts_File) => Promise; canRemove: (propertyId: number) => Promise; isEditing: boolean; setIsEditing: (value: boolean) => void; formikRef: React.RefObject>; isFormValid: boolean; error: AxiosError | undefined; - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; } export const DispositionView: React.FunctionComponent = ({ diff --git a/source/frontend/src/features/mapSideBar/disposition/__snapshots__/DispositionView.test.tsx.snap b/source/frontend/src/features/mapSideBar/disposition/__snapshots__/DispositionView.test.tsx.snap index 66eee09df7..fa68d9a817 100644 --- a/source/frontend/src/features/mapSideBar/disposition/__snapshots__/DispositionView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/disposition/__snapshots__/DispositionView.test.tsx.snap @@ -901,7 +901,7 @@ exports[`DispositionView component renders as expected 1`] = `
    = props => { return Content Rendered; }; -describe('Add Form8 Container component', () => { +const mockCreateDispositionFile = { + error: undefined, + response: undefined, + execute: jest.fn(), + loading: false, +}; + +jest.mock('@/hooks/repositories/useDispositionProvider', () => ({ + useDispositionProvider: () => { + return { + addDispositionFileApi: mockCreateDispositionFile, + }; + }, +})); + +describe('Add Disposition Container component', () => { const setup = async ( renderOptions: RenderOptions & { props?: Partial; } = {}, ) => { + const ref = createRef>(); const component = await renderAsync( , { @@ -37,6 +56,7 @@ describe('Add Form8 Container component', () => { return { ...component, + getFormikRef: () => ref, }; }; @@ -64,4 +84,31 @@ describe('Add Form8 Container component', () => { expect(onClose).toHaveBeenCalled(); }); + + it(`triggers the modal for contractor not in team (400 - Error)`, async () => { + mockCreateDispositionFile.execute.mockRejectedValue( + createAxiosError( + 409, + `As a contractor, you must add yourself as a team member to the file in order to create or save changes`, + ), + ); + const mockDispositionValues = new DispositionFormModel(1, 'NUMBER', 1); + + const { getFormikRef, findByText } = await setup(); + const formikHelpers: Partial> = { + setSubmitting: jest.fn(), + }; + + await act(async () => { + return viewProps?.onSubmit( + mockDispositionValues, + formikHelpers as FormikHelpers, + ); + }); + + await act(async () => getFormikRef().current?.submitForm()); + + const popup = await findByText(/As a contractor, you must add yourself as a team member/i); + expect(popup).toBeVisible(); + }); }); diff --git a/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainer.tsx index b71e1edeb7..ee1d98025c 100644 --- a/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainer.tsx @@ -1,14 +1,19 @@ -import { FormikProps } from 'formik'; +import { AxiosError } from 'axios'; +import { FormikHelpers, FormikProps } from 'formik'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { useMapStateMachine } from '@/components/common/mapFSM/MapStateMachineContext'; +import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; +import useApiUserOverride from '@/hooks/useApiUserOverride'; import { useInitialMapSelectorProperties } from '@/hooks/useInitialMapSelectorProperties'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { useModalContext } from '@/hooks/useModalContext'; +import { IApiError } from '@/interfaces/IApiError'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { UserOverrideCode } from '@/models/api/UserOverrideCode'; import { featuresetToMapProperty } from '@/utils'; import { PropertyForm } from '../../shared/models'; -import useAddDispositionFormManagement from '../hooks/useAddDispositionFormManagement'; import { DispositionFormModel } from '../models/DispositionFormModel'; import { IAddDispositionContainerViewProps } from './AddDispositionContainerView'; @@ -24,6 +29,12 @@ const AddDispositionContainer: React.FC = ({ onCl const mapMachine = useMapStateMachine(); const selectedFeatureDataset = mapMachine.selectedFeatureDataset; + const { setModalContent, setDisplayModal } = useModalContext(); + + const { + addDispositionFileApi: { execute: addDispositionFileApi, loading }, + } = useDispositionProvider(); + const initialForm = useMemo(() => { const dispositionForm = new DispositionFormModel(); // support creating a new disposition file from the map popup @@ -61,25 +72,60 @@ const AddDispositionContainer: React.FC = ({ onCl formikRef.current?.submitForm(); }; - const handleSuccess = async (disposition: Api_DispositionFile) => { + const withUserOverride = useApiUserOverride< + (userOverrideCodes: UserOverrideCode[]) => Promise + >('Failed to create Disposition File'); + + const handleSuccess = async (disposition: ApiGen_Concepts_DispositionFile) => { mapMachine.refreshMapProperties(); history.replace(`/mapview/sidebar/disposition/${disposition.id}`); }; - const helper = useAddDispositionFormManagement({ - onSuccess: handleSuccess, - formikRef, - }); + const handleSubmit = async ( + values: DispositionFormModel, + formikHelpers: FormikHelpers, + userOverrideCodes: UserOverrideCode[], + ) => { + try { + const dispositionFile = values.toApi(); + const response = await addDispositionFileApi(dispositionFile, userOverrideCodes); + + if (!!response?.id) { + formikHelpers?.resetForm(); + handleSuccess(response); + } + } finally { + formikHelpers?.setSubmitting(false); + } + }; return ( , + ) => + withUserOverride( + (userOverrideCodes: UserOverrideCode[]) => + handleSubmit(values, formikHelpers, userOverrideCodes), + [], + (axiosError: AxiosError) => { + setModalContent({ + variant: 'error', + title: 'Warning', + message: axiosError?.response?.data.error, + okButtonText: 'Close', + }); + setDisplayModal(true); + }, + ) + } > ); }; diff --git a/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainerView.tsx b/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainerView.tsx index 84cc9b2dd4..4c94287f21 100644 --- a/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainerView.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/add/AddDispositionContainerView.tsx @@ -1,9 +1,8 @@ -import { FormikProps } from 'formik'; +import { FormikHelpers, FormikProps } from 'formik'; import { MdAirlineStops } from 'react-icons/md'; import styled from 'styled-components'; import LoadingBackdrop from '@/components/common/LoadingBackdrop'; -import { UserOverrideCode } from '@/models/api/UserOverrideCode'; import MapSideBarLayout from '../../layout/MapSideBarLayout'; import SidebarFooter from '../../shared/SidebarFooter'; @@ -17,8 +16,7 @@ export interface IAddDispositionContainerViewProps { displayFormInvalid: boolean; onSubmit: ( values: DispositionFormModel, - setSubmitting: (isSubmitting: boolean) => void, - userOverrides: UserOverrideCode[], + formikHelpers: FormikHelpers, ) => void | Promise; onCancel: () => void; onSave: () => void; diff --git a/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.test.tsx b/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.test.tsx index eef95eb826..e3b4d57146 100644 --- a/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.test.tsx @@ -1,7 +1,7 @@ import { mockDispositionFileResponse } from '@/mocks/dispositionFiles.mock'; import { rest, server } from '@/mocks/msw/server'; import { getUserMock } from '@/mocks/user.mock'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { prettyFormatUTCDate } from '@/utils/dateUtils'; import { render, RenderOptions } from '@/utils/test-utils'; @@ -86,9 +86,14 @@ describe('DispositionHeader component', () => { }); it('renders the file status when provided', async () => { - const testDispositionFile: Api_DispositionFile = { + const testDispositionFile: ApiGen_Concepts_DispositionFile = { ...mockDispositionFileResponse(), - fileStatusTypeCode: { id: 'TEST', description: 'mock file status' }, + fileStatusTypeCode: { + id: 'TEST', + description: 'mock file status', + displayOrder: null, + isDisabled: false, + }, }; const { getByText } = setup({ dispositionFile: testDispositionFile, lastUpdatedBy: null }); diff --git a/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.tsx b/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.tsx index f68b3b555e..450ead5fa6 100644 --- a/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/common/DispositionHeader.tsx @@ -4,12 +4,13 @@ import styled from 'styled-components'; import { HeaderField } from '@/components/common/HeaderField/HeaderField'; import { UserNameTooltip } from '@/components/common/UserNameTooltip'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; import { Api_LastUpdatedBy } from '@/models/api/File'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { prettyFormatUTCDate } from '@/utils/dateUtils'; export interface IDispositionHeaderProps { - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; + lastUpdatedBy: Api_LastUpdatedBy | null; } diff --git a/source/frontend/src/features/mapSideBar/disposition/form/DispositionForm.tsx b/source/frontend/src/features/mapSideBar/disposition/form/DispositionForm.tsx index 08dc2976ea..e4a1ff4a99 100644 --- a/source/frontend/src/features/mapSideBar/disposition/form/DispositionForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/form/DispositionForm.tsx @@ -1,4 +1,4 @@ -import { Formik, FormikProps } from 'formik'; +import { Formik, FormikHelpers, FormikProps } from 'formik'; import React from 'react'; import styled from 'styled-components'; @@ -10,7 +10,6 @@ import { Section } from '@/components/common/Section/Section'; import { SectionField } from '@/components/common/Section/SectionField'; import * as API from '@/constants/API'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; -import { UserOverrideCode } from '@/models/api/UserOverrideCode'; import { AddDispositionFormYupSchema } from '../models/AddDispositionFormYupSchema'; import { DispositionFormModel } from '../models/DispositionFormModel'; @@ -21,8 +20,7 @@ export interface IDispositionFormProps { initialValues: DispositionFormModel; onSubmit: ( values: DispositionFormModel, - setSubmitting: (isSubmitting: boolean) => void, - userOverrides: UserOverrideCode[], + formikHelpers: FormikHelpers, ) => void | Promise; } @@ -50,9 +48,7 @@ const DispositionForm = React.forwardRef, IDis innerRef={ref} initialValues={initialValues} validationSchema={AddDispositionFormYupSchema} - onSubmit={async (values, formikHelpers) => { - onSubmit(values, formikHelpers.setSubmitting, []); - }} + onSubmit={onSubmit} > {formikProps => { return ( diff --git a/source/frontend/src/features/mapSideBar/disposition/form/DispositionTeamSubForm.tsx b/source/frontend/src/features/mapSideBar/disposition/form/DispositionTeamSubForm.tsx index 641bd9ca29..f18770a85a 100644 --- a/source/frontend/src/features/mapSideBar/disposition/form/DispositionTeamSubForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/form/DispositionTeamSubForm.tsx @@ -63,7 +63,8 @@ const DispositionTeamSubForm: React.FunctionComponent< ...getDeleteModalProps(), title: 'Remove Team Member', message: 'Do you wish to remove this team member?', - okButtonText: 'Remove', + okButtonText: 'Yes', + cancelButtonText: 'No', handleOk: async () => { arrayHelpers.remove(index); setDisplayModal(false); diff --git a/source/frontend/src/features/mapSideBar/disposition/hooks/useAddDispositionFormManagement.ts b/source/frontend/src/features/mapSideBar/disposition/hooks/useAddDispositionFormManagement.ts deleted file mode 100644 index f871bd840f..0000000000 --- a/source/frontend/src/features/mapSideBar/disposition/hooks/useAddDispositionFormManagement.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { FormikProps } from 'formik'; -import { useCallback } from 'react'; - -import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; -import useApiUserOverride from '@/hooks/useApiUserOverride'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; -import { UserOverrideCode } from '@/models/api/UserOverrideCode'; - -import { DispositionFormModel } from '../models/DispositionFormModel'; - -export interface IUseAddDispositionFormmanagementProps { - formikRef: React.RefObject>; - onSuccess?: (dispositionFile: Api_DispositionFile) => Promise; -} - -const useAddDispositionFormManagement = (props: IUseAddDispositionFormmanagementProps) => { - const { addDispositionFileApi } = useDispositionProvider(); - - const { onSuccess } = props; - const withUserOverride = useApiUserOverride< - (userOverrideCodes: UserOverrideCode[]) => Promise - >('Failed to add Disposition File'); - - // save handler - const handleSubmit = useCallback( - async (values: DispositionFormModel, setSubmitting: (isSubmitting: boolean) => void) => { - return withUserOverride(async (userOverrideCodes: UserOverrideCode[]) => { - const dispositionFile = values.toApi(); - const response = await addDispositionFileApi.execute(dispositionFile, userOverrideCodes); - if (!!response?.id) { - if (typeof onSuccess === 'function') { - await onSuccess(response); - setSubmitting(false); - } - } - }); - }, - [addDispositionFileApi, onSuccess, withUserOverride], - ); - - return { - handleSubmit, - loading: addDispositionFileApi.loading, - }; -}; - -export default useAddDispositionFormManagement; diff --git a/source/frontend/src/features/mapSideBar/disposition/models/DispositionAppraisalFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/models/DispositionAppraisalFormModel.ts index c8e1a5c4b0..b08cb2d027 100644 --- a/source/frontend/src/features/mapSideBar/disposition/models/DispositionAppraisalFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/models/DispositionAppraisalFormModel.ts @@ -1,4 +1,6 @@ -import { Api_DispositionFileAppraisal } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { isValidIsoDateTime } from '@/utils'; import { emptyStringtoNullable } from '@/utils/formUtils'; export class DispositionAppraisalFormModel { @@ -28,7 +30,7 @@ export class DispositionAppraisalFormModel { ); } - static fromApi(entity: Api_DispositionFileAppraisal): DispositionAppraisalFormModel { + static fromApi(entity: ApiGen_Concepts_DispositionFileAppraisal): DispositionAppraisalFormModel { const model = new DispositionAppraisalFormModel( entity.id, entity.dispositionFileId, @@ -44,18 +46,18 @@ export class DispositionAppraisalFormModel { return model; } - toApi(): Api_DispositionFileAppraisal { + toApi(): ApiGen_Concepts_DispositionFileAppraisal { return { id: this.id, dispositionFileId: this.dispositionFileId, appraisedAmount: this.appraisedValueAmount ? parseFloat(this.appraisedValueAmount.toString()) : null, - appraisalDate: emptyStringtoNullable(this.appraisalDate), + appraisalDate: isValidIsoDateTime(this.appraisalDate) ? this.appraisalDate : null, bcaValueAmount: this.bcaValueAmount ? parseFloat(this.bcaValueAmount.toString()) : null, bcaRollYear: emptyStringtoNullable(this.bcaRollYear), listPriceAmount: this.listPriceAmount ? parseFloat(this.listPriceAmount.toString()) : null, - rowVersion: this.rowVersion ?? 0, + ...getEmptyBaseAudit(this.rowVersion), }; } } diff --git a/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts index e51211e768..b6f8fbb19b 100644 --- a/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/models/DispositionFormModel.ts @@ -1,14 +1,8 @@ -import { - Api_DispositionFile, - Api_DispositionFileProperty, - Api_DispositionFileTeam, -} from '@/models/api/DispositionFile'; -import { - emptyStringtoNullable, - fromTypeCode, - toTypeCode, - toTypeCodeNullable, -} from '@/utils/formUtils'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { ApiGen_Concepts_DispositionFileProperty } from '@/models/api/generated/ApiGen_Concepts_DispositionFileProperty'; +import { getEmptyBaseAudit } from '@/models/defaultInitializers'; +import { emptyStringtoNullable, fromTypeCode, toTypeCodeNullable } from '@/utils/formUtils'; +import { exists, isValidIsoDateTime } from '@/utils/utils'; import { PropertyForm } from '../../shared/models'; import { ChecklistItemFormModel } from '../../shared/tabs/checklist/update/models'; @@ -53,62 +47,61 @@ export class DispositionFormModel implements WithDispositionTeam { this.dispositionStatusTypeCode = dispositionStatus; } - toApi(): Api_DispositionFile { + toApi(): ApiGen_Concepts_DispositionFile { return { - id: this.id ?? undefined, - fileName: this.fileName ?? undefined, - fileNumber: this.fileNumber ?? undefined, - fileStatusTypeCode: toTypeCode(this.fileStatusTypeCode), + id: this.id ?? 0, + fileName: this.fileName ?? null, + fileNumber: this.fileNumber ?? null, + fileStatusTypeCode: toTypeCodeNullable(this.fileStatusTypeCode), fileReference: emptyStringtoNullable(this.referenceNumber), - assignedDate: this.assignedDate, - completionDate: this.completionDate, + assignedDate: isValidIsoDateTime(this.assignedDate) ? this.assignedDate : this.assignedDate, + completionDate: isValidIsoDateTime(this.completionDate) + ? this.completionDate + : this.completionDate, dispositionTypeCode: toTypeCodeNullable(this.dispositionTypeCode), dispositionTypeOther: this.dispositionTypeOther ? this.dispositionTypeOther : null, dispositionStatusTypeCode: toTypeCodeNullable(this.dispositionStatusTypeCode), - initiatingBranchTypeCode: toTypeCode(this.initiatingBranchTypeCode), - physicalFileStatusTypeCode: toTypeCode(this.physicalFileStatusTypeCode), + initiatingBranchTypeCode: toTypeCodeNullable(this.initiatingBranchTypeCode), + physicalFileStatusTypeCode: toTypeCodeNullable(this.physicalFileStatusTypeCode), fundingTypeCode: toTypeCodeNullable(this.fundingTypeCode), - initiatingDocumentTypeCode: toTypeCode(this.initiatingDocumentTypeCode), + initiatingDocumentTypeCode: toTypeCodeNullable(this.initiatingDocumentTypeCode), initiatingDocumentTypeOther: this.initiatingDocumentTypeOther ? this.initiatingDocumentTypeOther : null, - initiatingDocumentDate: this.initiatingDocumentDate, + initiatingDocumentDate: isValidIsoDateTime(this.initiatingDocumentDate) + ? this.initiatingDocumentDate + : null, regionCode: toTypeCodeNullable(Number(this.regionCode)), dispositionTeam: this.team .filter(x => !!x.contact && !!x.teamProfileTypeCode) .map(x => x.toApi(this.id || 0)) - .filter((x): x is Api_DispositionFileTeam => x !== null), - fileProperties: this.fileProperties.map(ap => { - return { - id: ap.id, - propertyName: ap.name, - displayOrder: ap.displayOrder, - rowVersion: ap.rowVersion, - property: ap.toApi(), - propertyId: ap.apiId, - acquisitionFile: { id: this.id }, - }; - }), + .filter(exists), + fileProperties: this.fileProperties.map(ap => ({ + id: ap.id ?? 0, + propertyName: ap.name ?? null, + displayOrder: ap.displayOrder ?? null, + rowVersion: ap.rowVersion ?? null, + property: ap.toApi(), + propertyId: ap.apiId ?? 0, + file: null, + fileId: 0, + })), dispositionOffers: this.offers.map(x => x.toApi()), dispositionSale: this.sale ? this.sale.toApi() : null, - project: null, - projectId: null, - product: null, - productId: null, dispositionAppraisal: this.appraisal ? this.appraisal.toApi() : null, fileChecklistItems: this.fileChecklist.map(x => x.toApi()), - rowVersion: this.rowVersion ?? 0, + ...getEmptyBaseAudit(this.rowVersion), }; } - static fromApi(model: Api_DispositionFile): DispositionFormModel { + static fromApi(model: ApiGen_Concepts_DispositionFile): DispositionFormModel { const dispositionForm = new DispositionFormModel( model.id, model.fileNumber, model.rowVersion, - model.fileStatusTypeCode?.id, - model.dispositionStatusTypeCode?.id, + model.fileStatusTypeCode?.id ?? undefined, + model.dispositionStatusTypeCode?.id ?? undefined, ); dispositionForm.fundingTypeCode = fromTypeCode(model.fundingTypeCode) ?? ''; diff --git a/source/frontend/src/features/mapSideBar/disposition/models/DispositionSaleFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/models/DispositionSaleFormModel.ts index 3717a095b2..b7bfc04439 100644 --- a/source/frontend/src/features/mapSideBar/disposition/models/DispositionSaleFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/models/DispositionSaleFormModel.ts @@ -1,5 +1,6 @@ import { ApiGen_Concepts_DispositionFileSale } from '@/models/api/generated/ApiGen_Concepts_DispositionFileSale'; import { ApiGen_Concepts_DispositionSalePurchaser } from '@/models/api/generated/ApiGen_Concepts_DispositionSalePurchaser'; +import { exists, isValidIsoDateTime } from '@/utils'; import { emptyStringtoNullable, stringToBoolean } from '@/utils/formUtils'; import { DispositionSaleContactModel, WithSalePurchasers } from './DispositionSaleContactModel'; @@ -73,8 +74,12 @@ export class DispositionSaleFormModel implements WithSalePurchasers { return { id: this.id, dispositionFileId: this.dispositionFileId, - finalConditionRemovalDate: emptyStringtoNullable(this.finalConditionRemovalDate), - saleCompletionDate: emptyStringtoNullable(this.saleCompletionDate), + finalConditionRemovalDate: isValidIsoDateTime(this.finalConditionRemovalDate) + ? this.finalConditionRemovalDate + : null, + saleCompletionDate: isValidIsoDateTime(this.saleCompletionDate) + ? this.finalConditionRemovalDate + : null, saleFiscalYear: emptyStringtoNullable(this.saleFiscalYear), finalSaleAmount: this.finalSaleAmount ? parseFloat(this.finalSaleAmount.toString()) : null, realtorCommissionAmount: this.realtorCommissionAmount @@ -104,7 +109,7 @@ export class DispositionSaleFormModel implements WithSalePurchasers { export const calculateNetProceedsBeforeSppAmount = ( apiModel: ApiGen_Concepts_DispositionFileSale | null, ): number | null => { - return apiModel == null + return !exists(apiModel) ? 0 : (apiModel.finalSaleAmount ?? 0) - ((apiModel.realtorCommissionAmount ?? 0) + @@ -116,7 +121,7 @@ export const calculateNetProceedsBeforeSppAmount = ( export const calculateNetProceedsAfterSppAmount = ( apiModel: ApiGen_Concepts_DispositionFileSale | null, ): number | null => { - return apiModel == null + return !exists(apiModel) ? 0 : (apiModel.finalSaleAmount ?? 0) - ((apiModel.realtorCommissionAmount ?? 0) + diff --git a/source/frontend/src/features/mapSideBar/disposition/models/DispositionTeamSubFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/models/DispositionTeamSubFormModel.ts index 94e0874dda..da2cb0dc68 100644 --- a/source/frontend/src/features/mapSideBar/disposition/models/DispositionTeamSubFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/models/DispositionTeamSubFormModel.ts @@ -5,8 +5,9 @@ import { fromApiPerson, IContactSearchResult, } from '@/interfaces/IContactSearchResult'; -import { Api_DispositionFileTeam } from '@/models/api/DispositionFile'; -import { fromTypeCode, toTypeCode } from '@/utils/formUtils'; +import { ApiGen_Concepts_DispositionFileTeam } from '@/models/api/generated/ApiGen_Concepts_DispositionFileTeam'; +import { fromTypeCode, toTypeCodeNullable } from '@/utils/formUtils'; +import { exists, isValidId } from '@/utils/utils'; export interface WithDispositionTeam { team: DispositionTeamSubFormModel[]; @@ -27,10 +28,10 @@ export class DispositionTeamSubFormModel { this.contact = contact; } - toApi(dispositionFileId: number): Api_DispositionFileTeam | null { + toApi(dispositionFileId: number): ApiGen_Concepts_DispositionFileTeam | null { const personId = this.contact?.personId ?? null; const organizationId = !personId ? this.contact?.organizationId ?? null : null; - if (personId === null && organizationId === null) { + if (!isValidId(personId) && !isValidId(organizationId)) { return null; } @@ -38,26 +39,27 @@ export class DispositionTeamSubFormModel { id: this.id ?? 0, rowVersion: this.rowVersion ?? 0, dispositionFileId: dispositionFileId, - personId: personId ?? undefined, - person: undefined, - organizationId: organizationId ?? undefined, - organization: undefined, + personId: personId ?? null, + person: null, + organizationId: organizationId ?? null, + organization: null, primaryContactId: !!this.primaryContactId && isNumber(+this.primaryContactId) ? Number(this.primaryContactId) - : undefined, - teamProfileType: toTypeCode(this.teamProfileTypeCode), + : null, + teamProfileType: toTypeCodeNullable(this.teamProfileTypeCode), teamProfileTypeCode: this.teamProfileTypeCode, + primaryContact: null, }; } - static fromApi(model: Api_DispositionFileTeam | null): DispositionTeamSubFormModel { - const contact: IContactSearchResult | undefined = - model?.person !== undefined && model?.person !== null - ? fromApiPerson(model.person) - : model?.organization !== undefined && model?.organization !== null - ? fromApiOrganization(model.organization) - : undefined; + static fromApi(model: ApiGen_Concepts_DispositionFileTeam | null): DispositionTeamSubFormModel { + // todo:the method 'exists' here should allow the compiler to validate the child property. this works correctly in typescropt 5.3 + + const contact: IContactSearchResult | undefined = exists(model?.person) + ? fromApiPerson(model!.person) + : exists(model?.organization) + ? fromApiOrganization(model!.organization) + : undefined; const newForm = new DispositionTeamSubFormModel(model?.id ?? 0, model?.rowVersion, contact); newForm.teamProfileTypeCode = fromTypeCode(model?.teamProfileType) ?? ''; diff --git a/source/frontend/src/features/mapSideBar/disposition/router/DispositionRouter.tsx b/source/frontend/src/features/mapSideBar/disposition/router/DispositionRouter.tsx index c9991118f0..f08265dff6 100644 --- a/source/frontend/src/features/mapSideBar/disposition/router/DispositionRouter.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/router/DispositionRouter.tsx @@ -5,8 +5,8 @@ import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom'; import Claims from '@/constants/claims'; import { InventoryTabNames } from '@/features/mapSideBar/property/InventoryTabs'; import { FileTabType } from '@/features/mapSideBar/shared/detail/FileTabs'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; -import { stripTrailingSlash } from '@/utils'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { exists, stripTrailingSlash } from '@/utils'; import AppRoute from '@/utils/AppRoute'; import { UpdateChecklistForm } from '../../shared/tabs/checklist/update/UpdateChecklistForm'; @@ -24,7 +24,7 @@ import UpdateDispositionSaleView from '../tabs/offersAndSale/dispositionSale/upd export interface IDispositionRouterProps { formikRef: React.Ref>; - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; isEditing: boolean; setIsEditing: (value: boolean) => void; defaultFileTab: FileTabType; @@ -35,7 +35,7 @@ export interface IDispositionRouterProps { export const DispositionRouter: React.FC = props => { const { path, url } = useRouteMatch(); - if (props.dispositionFile === undefined || props.dispositionFile === null) { + if (!exists(props.dispositionFile)) { return null; } diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/DispositionFileTabs.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/DispositionFileTabs.tsx index 28c5c5df8f..ebb34422b4 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/DispositionFileTabs.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/DispositionFileTabs.tsx @@ -8,8 +8,8 @@ import { FileTabs, FileTabType, TabFileView } from '@/features/mapSideBar/shared import DocumentsTab from '@/features/mapSideBar/shared/tabs/DocumentsTab'; import NoteListView from '@/features/notes/list/NoteListView'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; import { ApiGen_CodeTypes_DocumentRelationType } from '@/models/api/generated/ApiGen_CodeTypes_DocumentRelationType'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { SideBarContext } from '../../context/sidebarContext'; import { ChecklistView } from '../../shared/tabs/checklist/detail/ChecklistView'; @@ -18,7 +18,7 @@ import OffersAndSaleContainer from './offersAndSale/OffersAndSaleContainer'; import OffersAndSaleContainerView from './offersAndSale/OffersAndSaleContainerView'; export interface IDispositionFileTabsProps { - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; defaultTab: FileTabType; setIsEditing: (value: boolean) => void; } diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap b/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap index a54addec66..e66fbed9e9 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/__snapshots__/DispositionFileTabs.test.tsx.snap @@ -516,7 +516,7 @@ exports[`DispositionFileTabs component matches snapshot 1`] = `
    = props => { }; describe('UpdateDispositionChecklist container', () => { - let dispositionFile: Api_DispositionFile | undefined = undefined; + let dispositionFile: ApiGen_Concepts_DispositionFile | undefined = undefined; const onSuccess = jest.fn(); const setup = (renderOptions: RenderOptions = {}) => { @@ -79,9 +79,9 @@ describe('UpdateDispositionChecklist container', () => { setup(); mockUpdateDispositionChecklist.mockResolvedValue(mockFileChecklistResponse()); - let updatedChecklist: Api_FileWithChecklist | undefined; + let updatedChecklist: ApiGen_Concepts_FileWithChecklist | undefined; await act(async () => { - updatedChecklist = await viewProps?.onSave({} as Api_FileWithChecklist); + updatedChecklist = await viewProps?.onSave({} as ApiGen_Concepts_FileWithChecklist); }); expect(mockUpdateDispositionChecklist).toHaveBeenCalled(); @@ -92,7 +92,7 @@ describe('UpdateDispositionChecklist container', () => { setup(); await act(async () => { - viewProps?.onSuccess({} as Api_DispositionFile); + viewProps?.onSuccess({} as ApiGen_Concepts_DispositionFile); }); expect(onSuccess).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/checklist/update/UpdateDispositionChecklistContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/checklist/update/UpdateDispositionChecklistContainer.tsx index 0ff41eee98..c449e0ebae 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/checklist/update/UpdateDispositionChecklistContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/checklist/update/UpdateDispositionChecklistContainer.tsx @@ -9,12 +9,12 @@ import { IUpdateChecklistFormProps } from '@/features/mapSideBar/shared/tabs/che import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import { useLookupCodeHelpers } from '@/hooks/useLookupCodeHelpers'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; -import { Api_FileWithChecklist } from '@/models/api/File'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { ApiGen_Concepts_FileWithChecklist } from '@/models/api/generated/ApiGen_Concepts_FileWithChecklist'; export interface IDispositionChecklistContainerProps { formikRef: React.Ref>; - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; onSuccess: () => void; View: React.FC; } @@ -37,11 +37,11 @@ export const UpdateDispositionChecklistContainer: React.FC { + const saveChecklist = async (apiDispositionFile: ApiGen_Concepts_FileWithChecklist) => { return updateDispositionChecklist(apiDispositionFile); }; - const onUpdateSuccess = async (apiDispositionFile: Api_FileWithChecklist) => { + const onUpdateSuccess = async (apiDispositionFile: ApiGen_Concepts_FileWithChecklist) => { onSuccess && onSuccess(); }; diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.test.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.test.tsx index c8e76e63de..471eb46053 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.test.tsx @@ -1,5 +1,7 @@ import Claims from '@/constants/claims'; +import { getEmptyPerson } from '@/mocks/contacts.mock'; import { mockDispositionFileResponse } from '@/mocks/dispositionFiles.mock'; +import { toTypeCodeNullable } from '@/utils/formUtils'; import { act, cleanup, render, RenderOptions, userEvent, waitForEffects } from '@/utils/test-utils'; import DispositionSummaryView, { IDispositionSummaryViewProps } from './DispositionSummaryView'; @@ -73,7 +75,7 @@ describe('DispositionSummaryView component', () => { { dispositionFile: { ...mockDispositionFileResponse(), - initiatingDocumentTypeCode: { id: 'OTHER' }, + initiatingDocumentTypeCode: toTypeCodeNullable('OTHER'), }, }, { claims: [] }, @@ -87,7 +89,7 @@ describe('DispositionSummaryView component', () => { { dispositionFile: { ...mockDispositionFileResponse(), - dispositionTypeCode: { id: 'OTHER' }, + dispositionTypeCode: toTypeCodeNullable('OTHER'), }, }, { claims: [] }, @@ -108,6 +110,7 @@ describe('DispositionSummaryView component', () => { dispositionFileId: 1, personId: 1, person: { + ...getEmptyPerson(), id: 1, surname: 'Smith', firstName: 'Bob', @@ -121,8 +124,14 @@ describe('DispositionSummaryView component', () => { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + organization: null, + organizationId: null, + primaryContact: null, + primaryContactId: null, + teamProfileTypeCode: null, }, ], }, @@ -151,13 +160,24 @@ describe('DispositionSummaryView component', () => { alias: 'ABC Inc', incorporationNumber: '1234', comment: '', + contactMethods: null, + isDisabled: false, + organizationAddresses: null, + organizationPersons: null, + rowVersion: null, }, teamProfileType: { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + person: null, + personId: null, + primaryContact: null, + primaryContactId: null, + teamProfileTypeCode: null, }, ], }, @@ -187,6 +207,11 @@ describe('DispositionSummaryView component', () => { alias: 'ABC Inc', incorporationNumber: '1234', comment: '', + contactMethods: null, + isDisabled: false, + organizationAddresses: null, + organizationPersons: null, + rowVersion: null, }, primaryContactId: 1, primaryContact: { @@ -198,13 +223,20 @@ describe('DispositionSummaryView component', () => { personAddresses: [], contactMethods: [], rowVersion: 2, + comment: null, + isDisabled: false, + preferredName: null, }, teamProfileType: { id: 'NEGOTAGENT', description: 'Negotiation agent', isDisabled: false, + displayOrder: null, }, rowVersion: 2, + person: null, + personId: null, + teamProfileTypeCode: null, }, ], }, diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx index 4135c1dd66..1271378adb 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/DispositionSummaryView.tsx @@ -8,12 +8,12 @@ import { StyledEditWrapper, StyledSummarySection } from '@/components/common/Sec import { StyledLink } from '@/components/maps/leaflet/LayerPopup/styles'; import { Claims } from '@/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { prettyFormatDate } from '@/utils'; import { formatApiPersonNames } from '@/utils/personUtils'; export interface IDispositionSummaryViewProps { - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; onEdit: () => void; } @@ -94,7 +94,7 @@ export const DispositionSummaryView: React.FunctionComponent {dispositionFile?.physicalFileStatusTypeCode?.description} - + {dispositionFile?.initiatingBranchTypeCode?.description} @@ -102,7 +102,7 @@ export const DispositionSummaryView: React.FunctionComponent
    - {dispositionFile?.dispositionTeam.map((teamMember, index) => ( + {dispositionFile?.dispositionTeam?.map((teamMember, index) => ( - initiating branch: + Initiating branch:
    = props => { }; describe('UpdateDisposition container', () => { - let dispositionFile: Api_DispositionFile; + let dispositionFile: ApiGen_Concepts_DispositionFile; const onSuccess = jest.fn(); const setup = (renderOptions: RenderOptions = {}) => { @@ -238,6 +238,28 @@ describe('UpdateDisposition container', () => { expect(onSuccess).toHaveBeenCalled(); }); + it(`displays Contactor cannot remove itself from Team`, async () => { + mockUpdateDispositionFile.mockRejectedValue( + createAxiosError( + 400, + 'test error', + { + errorCode: UserOverrideCode.PROPERTY_OF_INTEREST_TO_INVENTORY, + }, + 'ContractorNotInTeamException', + ), + ); + const { formikRef } = setup(); + + expect(formikRef.current).not.toBeNull(); + await act(async () => formikRef.current?.submitForm()); + + const popup = await screen.findByText( + /Contractors cannot remove themselves from a Disposition file./i, + ); + expect(popup).toBeVisible(); + }); + it(`displays custom 400 errors in a modal`, async () => { mockUpdateDispositionFile.mockRejectedValue( createAxiosError(400, 'test error', { diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionContainer.tsx index d8f5c96e96..70316e5b1e 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionContainer.tsx @@ -2,18 +2,20 @@ import { AxiosError } from 'axios'; import { FormikHelpers, FormikProps } from 'formik'; import React from 'react'; +import { ModalSize } from '@/components/common/GenericModal'; import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import useApiUserOverride from '@/hooks/useApiUserOverride'; import { useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFile } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; import { UserOverrideCode } from '@/models/api/UserOverrideCode'; +import { isValidId } from '@/utils'; import { DispositionFormModel } from '../../../../models/DispositionFormModel'; import { IUpdateDispositionFormProps } from './UpdateDispositionForm'; export interface IUpdateDispositionContainerProps { - dispositionFile: Api_DispositionFile; + dispositionFile: ApiGen_Concepts_DispositionFile; onSuccess: () => void; View: React.FC; } @@ -30,7 +32,7 @@ export const UpdateDispositionContainer = React.forwardRef< } = useDispositionProvider(); const withUserOverride = useApiUserOverride< - (userOverrideCodes: UserOverrideCode[]) => Promise + (userOverrideCodes: UserOverrideCode[]) => Promise >('Failed to update Disposition File'); const handleSubmit = async ( @@ -47,7 +49,7 @@ export const UpdateDispositionContainer = React.forwardRef< userOverrideCodes, ); - if (!!response?.id) { + if (isValidId(response?.id)) { formikHelpers?.resetForm(); onSuccess(); } @@ -57,6 +59,41 @@ export const UpdateDispositionContainer = React.forwardRef< } }; + const RemoveSelfContractorModalContent = (): React.ReactNode => { + return ( + <> +

    + Contractors cannot remove themselves from a Disposition file.
    + Please contact the admin at pims@gov.bc.ca +

    + + ); + }; + + const handleError = async (axiosError: AxiosError): Promise => { + switch (axiosError?.response?.data.type) { + case 'ContractorNotInTeamException': + setModalContent({ + variant: 'error', + title: 'Error', + modalSize: ModalSize.LARGE, + message: RemoveSelfContractorModalContent(), + okButtonText: 'Close', + }); + setDisplayModal(true); + break; + default: { + setModalContent({ + variant: 'warning', + title: 'Warning', + message: axiosError?.response?.data.error, + okButtonText: 'Close', + }); + setDisplayModal(true); + } + } + }; + return ( ) => { - setModalContent({ - variant: 'warning', - title: 'Warning', - message: axiosError?.response?.data.error, - okButtonText: 'Close', - }); - setDisplayModal(true); + handleError(axiosError); }, ) } diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx index 063098b087..9e65cd8805 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/fileDetails/detail/update/UpdateDispositionForm.tsx @@ -120,7 +120,7 @@ const UpdateDispositionForm: React.FC = ({ /> {formikProps.values?.dispositionTypeCode === 'OTHER' && ( - + )} @@ -137,7 +137,7 @@ const UpdateDispositionForm: React.FC = ({ /> {formikProps.values?.initiatingDocumentTypeCode === 'OTHER' && ( - + )} diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainer.tsx index d0bbd38930..8ffc68f094 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainer.tsx @@ -1,17 +1,15 @@ import { useCallback, useEffect, useState } from 'react'; import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; -import { - Api_DispositionFile, - Api_DispositionFileAppraisal, - Api_DispositionFileOffer, -} from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { ApiGen_Concepts_DispositionFileSale } from '@/models/api/generated/ApiGen_Concepts_DispositionFileSale'; import { IOffersAndSaleContainerViewProps } from './OffersAndSaleContainerView'; export interface IOffersAndSaleContainerProps { - dispositionFile?: Api_DispositionFile; + dispositionFile?: ApiGen_Concepts_DispositionFile; View: React.FC; } @@ -25,11 +23,13 @@ const OffersAndSaleContainer: React.FunctionComponent([]); + const [dispositionOffers, setDispositionOffers] = useState< + ApiGen_Concepts_DispositionFileOffer[] + >([]); const [dispositionSale, setDispositionSale] = useState(null); const [dispositionAppraisal, setdispositionAppraisal] = - useState(null); + useState(null); const fetchDispositionInformation = useCallback(async () => { if (dispositionFile?.id) { diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.test.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.test.tsx index 73cc6066d7..de5bd4f91f 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.test.tsx @@ -72,7 +72,7 @@ describe('Disposition Offer Detail View component', () => { props: { dispositionFile: mockDisposition, dispositionOffers: [] }, }); expect( - getByText(/There are no value details indicated with this disposition file/i), + getByText(/There are no sale details indicated with this disposition file/i), ).toBeVisible(); }); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.tsx index be62a824d1..9eedb21be2 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/OffersAndSaleContainerView.tsx @@ -10,14 +10,13 @@ import { SectionField } from '@/components/common/Section/SectionField'; import { SectionListHeader } from '@/components/common/SectionListHeader'; import { Claims } from '@/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; -import { - Api_DispositionFile, - Api_DispositionFileAppraisal, - Api_DispositionFileOffer, -} from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFile } from '@/models/api/generated/ApiGen_Concepts_DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { ApiGen_Concepts_DispositionFileSale } from '@/models/api/generated/ApiGen_Concepts_DispositionFileSale'; import { prettyFormatDate } from '@/utils/dateUtils'; import { formatMoney } from '@/utils/numberFormatUtils'; +import { exists } from '@/utils/utils'; import { calculateNetProceedsAfterSppAmount, @@ -28,10 +27,10 @@ import DispositionSaleContactDetails from './dispositionOffer/dispositionSaleCon export interface IOffersAndSaleContainerViewProps { loading: boolean; - dispositionFile: Api_DispositionFile; - dispositionOffers: Api_DispositionFileOffer[]; + dispositionFile: ApiGen_Concepts_DispositionFile; + dispositionOffers: ApiGen_Concepts_DispositionFileOffer[]; dispositionSale: ApiGen_Concepts_DispositionFileSale | null; - dispositionAppraisal: Api_DispositionFileAppraisal | null; + dispositionAppraisal: ApiGen_Concepts_DispositionFileAppraisal | null; onDispositionOfferDeleted: (offerId: number) => void; } @@ -109,7 +108,7 @@ const OffersAndSaleContainerView: React.FunctionComponent ) : ( -

    There are no value details indicated with this disposition file.

    +

    There are no Appraisal and Assessment details indicated with this disposition file.

    )} @@ -165,16 +164,15 @@ const OffersAndSaleContainerView: React.FunctionComponent - {dispositionSale.dispositionPurchasers && + {exists(dispositionSale.dispositionPurchasers) && dispositionSale.dispositionPurchasers.map((purchaser, index) => ( - {dispositionSale.dispositionPurchasers && - index !== dispositionSale.dispositionPurchasers?.length - 1 && ( - - )} + {index !== dispositionSale.dispositionPurchasers!.length - 1 && ( + + )} ))}
    diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/__snapshots__/OffersAndSaleContainerView.test.tsx.snap b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/__snapshots__/OffersAndSaleContainerView.test.tsx.snap index 5f3d1c9bcc..a6df7a6b7a 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/__snapshots__/OffersAndSaleContainerView.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/__snapshots__/OffersAndSaleContainerView.test.tsx.snap @@ -76,7 +76,7 @@ exports[`Disposition Offer Detail View component renders as expected 1`] = ` class="collapse show" >

    - There are no value details indicated with this disposition file. + There are no Appraisal and Assessment details indicated with this disposition file.

    diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/form/DispositionAppraisalForm.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/form/DispositionAppraisalForm.tsx index fa2b9557f2..8b3dadb2b2 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/form/DispositionAppraisalForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/form/DispositionAppraisalForm.tsx @@ -11,7 +11,7 @@ import { DispositionAppraisalFormModel } from '@/features/mapSideBar/disposition import SidebarFooter from '@/features/mapSideBar/shared/SidebarFooter'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFileAppraisal } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; import { DispositionAppraisalFormYupSchema } from './DispostionAppraisalFormYupSchema'; @@ -19,8 +19,8 @@ export interface IDispositionAppraisalFormProps { initialValues: DispositionAppraisalFormModel; loading: boolean; onSave: ( - appraisal: Api_DispositionFileAppraisal, - ) => Promise; + appraisal: ApiGen_Concepts_DispositionFileAppraisal, + ) => Promise; onCancel: () => void; onSuccess: () => void; onError: (e: AxiosError) => void; diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.test.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.test.tsx index b1e672b180..4bd0f088ba 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.test.tsx @@ -5,7 +5,7 @@ import { Claims } from '@/constants/claims'; import { DispositionAppraisalFormModel } from '@/features/mapSideBar/disposition/models/DispositionAppraisalFormModel'; import { mockDispositionAppraisalApi } from '@/mocks/dispositionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_DispositionFileAppraisal } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { render, RenderOptions, waitForEffects } from '@/utils/test-utils'; @@ -119,7 +119,7 @@ describe('Update Disposition Appraisal Container component', () => { await setup(); await waitForEffects(); - let createdAppraisal: Api_DispositionFileAppraisal | undefined; + let createdAppraisal: ApiGen_Concepts_DispositionFileAppraisal | undefined; await act(async () => { createdAppraisal = await viewProps?.onSave({ id: null, @@ -129,7 +129,7 @@ describe('Update Disposition Appraisal Container component', () => { bcaValueAmount: 350000.0, bcaRollYear: '2024', listPriceAmount: 500000.0, - } as Api_DispositionFileAppraisal); + } as ApiGen_Concepts_DispositionFileAppraisal); }); expect(mockPostAppraisalApi.execute).toHaveBeenCalled(); @@ -144,9 +144,9 @@ describe('Update Disposition Appraisal Container component', () => { await setup({ props: { dispositionFileId: 1 } }); await waitForEffects(); - let updatedAppraisal: Api_DispositionFileAppraisal | undefined; + let updatedAppraisal: ApiGen_Concepts_DispositionFileAppraisal | undefined; await act(async () => { - updatedAppraisal = await viewProps?.onSave({} as Api_DispositionFileAppraisal); + updatedAppraisal = await viewProps?.onSave({} as ApiGen_Concepts_DispositionFileAppraisal); }); expect(mockPutAppraisalApi.execute).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.tsx index f05d5d285b..614b662485 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionAppraisal/update/UpdateDispositionAppraisalContainer.tsx @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'; import { DispositionAppraisalFormModel } from '@/features/mapSideBar/disposition/models/DispositionAppraisalFormModel'; import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFileAppraisal } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileAppraisal } from '@/models/api/generated/ApiGen_Concepts_DispositionFileAppraisal'; import { IDispositionAppraisalFormProps } from '../form/DispositionAppraisalForm'; @@ -43,7 +43,7 @@ const UpdateDispositionAppraisalContainer: React.FunctionComponent< setdispositionAppraisal(dispositionAppraisalModel); }, [dispositionFileId, getDispositionAppraisal]); - const handleSave = async (appraisal: Api_DispositionFileAppraisal) => { + const handleSave = async (appraisal: ApiGen_Concepts_DispositionFileAppraisal) => { if (dispositionAppraisal?.id) { return putDispositionAppraisal(dispositionFileId, dispositionAppraisal?.id, appraisal); } else { diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.test.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.test.tsx index 87341779d1..d78727be41 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.test.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { Claims } from '@/constants/claims'; import { mockDispositionFileOfferApi } from '@/mocks/dispositionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, createAxiosError, render, RenderOptions } from '@/utils/test-utils'; @@ -89,9 +89,9 @@ describe('Add Disposition Offer Container component', () => { const offerMock = mockDispositionFileOfferApi(); mockPostApi.execute.mockReturnValue(offerMock); - let createdOffer: Api_DispositionFileOffer | undefined; + let createdOffer: ApiGen_Concepts_DispositionFileOffer | undefined; await act(async () => { - createdOffer = await viewProps?.onSave({} as Api_DispositionFileOffer); + createdOffer = await viewProps?.onSave({} as ApiGen_Concepts_DispositionFileOffer); }); expect(mockPostApi.execute).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.tsx index 905fdd0041..41f05ecd5d 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/add/AddDispositionOfferContainer.tsx @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'; import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import { useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { IDispositionOfferFormProps } from '../form/DispositionOfferForm'; import { DispositionOfferFormModel } from '../models/DispositionOfferFormModel'; @@ -31,7 +31,7 @@ const AddDispositionOfferContainer: React.FunctionComponent< } = useDispositionProvider(); const initialValues = new DispositionOfferFormModel(null, dispositionFileId); - const handleSave = async (newOffer: Api_DispositionFileOffer) => { + const handleSave = async (newOffer: ApiGen_Concepts_DispositionFileOffer) => { setOfferStatusError(false); return postDispositionOffer(dispositionFileId, newOffer); }; diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/dispositionOfferDetails/DispositionOfferDetails.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/dispositionOfferDetails/DispositionOfferDetails.tsx index 746240932c..a25000cac0 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/dispositionOfferDetails/DispositionOfferDetails.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/dispositionOfferDetails/DispositionOfferDetails.tsx @@ -8,13 +8,13 @@ import { SectionField } from '@/components/common/Section/SectionField'; import { Claims } from '@/constants'; import useKeycloakWrapper from '@/hooks/useKeycloakWrapper'; import { getDeleteModalProps, useModalContext } from '@/hooks/useModalContext'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { prettyFormatDate } from '@/utils/dateUtils'; import { formatMoney } from '@/utils/numberFormatUtils'; export interface IDispositionOfferDetailsProps { index: number; - dispositionOffer: Api_DispositionFileOffer; + dispositionOffer: ApiGen_Concepts_DispositionFileOffer; onDelete: (offerId: number) => void; } @@ -40,7 +40,7 @@ const DispositionOfferDetails: React.FunctionComponent history.push(`${match.url}/offers/${dispositionOffer.id}/update`)} /> { diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/form/DispositionOfferForm.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/form/DispositionOfferForm.tsx index 73b8e9d071..b6a08b8ab9 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/form/DispositionOfferForm.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/form/DispositionOfferForm.tsx @@ -17,7 +17,7 @@ import SidebarFooter from '@/features/mapSideBar/shared/SidebarFooter'; import useLookupCodeHelpers from '@/hooks/useLookupCodeHelpers'; import { getCancelModalProps, useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { DispositionOfferFormModel } from '../models/DispositionOfferFormModel'; import { DispositionOfferFormYupSchema } from '../models/DispositionOfferFormYupSchema'; @@ -26,7 +26,9 @@ export interface IDispositionOfferFormProps { initialValues: DispositionOfferFormModel | null; showOfferStatusError: boolean; loading: boolean; - onSave: (offer: Api_DispositionFileOffer) => Promise; + onSave: ( + offer: ApiGen_Concepts_DispositionFileOffer, + ) => Promise; onCancel: () => void; onSuccess: () => void; onError: (e: AxiosError) => void; diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/models/DispositionOfferFormModel.ts b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/models/DispositionOfferFormModel.ts index 5dd6f6291f..3550188c29 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/models/DispositionOfferFormModel.ts +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/models/DispositionOfferFormModel.ts @@ -1,4 +1,6 @@ -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; +import { EpochIsoDateTime } from '@/models/api/UtcIsoDateTime'; +import { isValidIsoDateTime } from '@/utils'; import { emptyStringtoNullable, toTypeCodeNullable } from '@/utils/formUtils'; export class DispositionOfferFormModel { @@ -15,12 +17,12 @@ export class DispositionOfferFormModel { this.dispositionFileId = dispositionFileId; } - static fromApi(entity: Api_DispositionFileOffer): DispositionOfferFormModel { + static fromApi(entity: ApiGen_Concepts_DispositionFileOffer): DispositionOfferFormModel { const model = new DispositionOfferFormModel(entity.id, entity.dispositionFileId); model.dispositionOfferStatusTypeCode = entity.dispositionOfferStatusTypeCode; model.offerName = entity.offerName; - model.offerDate = entity.offerDate; + model.offerDate = isValidIsoDateTime(entity.offerDate) ? entity.offerDate : null; model.offerExpiryDate = entity.offerExpiryDate; model.offerAmount = entity.offerAmount; model.offerNote = entity.offerNote; @@ -29,16 +31,16 @@ export class DispositionOfferFormModel { return model; } - toApi(): Api_DispositionFileOffer { + toApi(): ApiGen_Concepts_DispositionFileOffer { return { id: this.id, dispositionFileId: this.dispositionFileId, dispositionOfferStatusTypeCode: emptyStringtoNullable(this.dispositionOfferStatusTypeCode), dispositionOfferStatusType: toTypeCodeNullable(this.dispositionOfferStatusTypeCode), offerName: emptyStringtoNullable(this.offerName), - offerDate: emptyStringtoNullable(this.offerDate), - offerExpiryDate: emptyStringtoNullable(this.offerExpiryDate), - offerAmount: this.offerAmount, + offerDate: isValidIsoDateTime(this.offerDate) ? this.offerDate : EpochIsoDateTime, + offerExpiryDate: isValidIsoDateTime(this.offerExpiryDate) ? this.offerExpiryDate : null, + offerAmount: this.offerAmount ?? 0, offerNote: emptyStringtoNullable(this.offerNote), rowVersion: this.rowVersion ?? 0, }; diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.test.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.test.tsx index f58bc7ff78..cfac56f00a 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.test.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.test.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { Claims } from '@/constants/claims'; import { mockDispositionFileOfferApi } from '@/mocks/dispositionFiles.mock'; import { mockLookups } from '@/mocks/lookups.mock'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { lookupCodesSlice } from '@/store/slices/lookupCodes'; import { act, createAxiosError, render, RenderOptions, waitForEffects } from '@/utils/test-utils'; @@ -103,9 +103,9 @@ describe('Update Disposition Offer Container component', () => { await setup(); - let createdOffer: Api_DispositionFileOffer | undefined; + let createdOffer: ApiGen_Concepts_DispositionFileOffer | undefined; await act(async () => { - createdOffer = await viewProps?.onSave({} as Api_DispositionFileOffer); + createdOffer = await viewProps?.onSave({} as ApiGen_Concepts_DispositionFileOffer); }); expect(mockPutOfferApi.execute).toHaveBeenCalled(); diff --git a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.tsx b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.tsx index 611a707c53..202592c2ce 100644 --- a/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.tsx +++ b/source/frontend/src/features/mapSideBar/disposition/tabs/offersAndSale/dispositionOffer/update/UpdateDispositionOfferContainer.tsx @@ -6,7 +6,7 @@ import { toast } from 'react-toastify'; import { useDispositionProvider } from '@/hooks/repositories/useDispositionProvider'; import { useModalContext } from '@/hooks/useModalContext'; import { IApiError } from '@/interfaces/IApiError'; -import { Api_DispositionFileOffer } from '@/models/api/DispositionFile'; +import { ApiGen_Concepts_DispositionFileOffer } from '@/models/api/generated/ApiGen_Concepts_DispositionFileOffer'; import { IDispositionOfferFormProps } from '../form/DispositionOfferForm'; import { DispositionOfferFormModel } from '../models/DispositionOfferFormModel'; @@ -41,7 +41,7 @@ const UpdateDispositionOfferContainer: React.FunctionComponent< } }, [dispositionFileId, dispositionOfferId, getDispositionOffer]); - const handleSave = async (newOffer: Api_DispositionFileOffer) => { + const handleSave = async (newOffer: ApiGen_Concepts_DispositionFileOffer) => { setOfferStatusError(false); return putDispositionOffer(dispositionFileId, dispositionOfferId, newOffer); }; diff --git a/source/frontend/src/features/mapSideBar/hooks/usePropertyDetails.ts b/source/frontend/src/features/mapSideBar/hooks/usePropertyDetails.ts index a16d804e2f..ba9f55efe1 100644 --- a/source/frontend/src/features/mapSideBar/hooks/usePropertyDetails.ts +++ b/source/frontend/src/features/mapSideBar/hooks/usePropertyDetails.ts @@ -4,14 +4,17 @@ import { useAdminBoundaryMapLayer } from '@/hooks/repositories/mapLayer/useAdmin import { useIndianReserveBandMapLayer } from '@/hooks/repositories/mapLayer/useIndianReserveBandMapLayer'; import { useLegalAdminBoundariesMapLayer } from '@/hooks/repositories/mapLayer/useLegalAdminBoundariesMapLayer'; import useIsMounted from '@/hooks/util/useIsMounted'; -import { Api_Property } from '@/models/api/Property'; +import { ApiGen_Concepts_Property } from '@/models/api/generated/ApiGen_Concepts_Property'; +import { exists } from '@/utils'; import { IPropertyDetailsForm, toFormValues, } from '../property/tabs/propertyDetails/detail/PropertyDetailsTabView.helpers'; -export function usePropertyDetails(property?: Api_Property): IPropertyDetailsForm | undefined { +export function usePropertyDetails( + property?: ApiGen_Concepts_Property, +): IPropertyDetailsForm | undefined { const isMounted = useIsMounted(); const electoralService_ = useAdminBoundaryMapLayer(); const findElectoralDistrict = electoralService_.findElectoralDistrict; @@ -27,7 +30,7 @@ export function usePropertyDetails(property?: Api_Property): IPropertyDetailsFor const lat = property?.latitude; const lng = property?.longitude; const location = useMemo( - () => (lat === undefined || lng === undefined ? undefined : { lat, lng }), + () => (!exists(lat) || !exists(lng) ? undefined : { lat, lng }), [lat, lng], ); @@ -46,7 +49,7 @@ export function usePropertyDetails(property?: Api_Property): IPropertyDetailsFor async function fn() { // Query BC Geographic Warehouse layers - ONLY if lat, long have been provided! - if (location !== undefined) { + if (exists(location)) { const electoralDistrictFeature = await findElectoralDistrict(location); const alrFeature = await findOneAgriculturalReserve(location, 'GEOMETRY'); const firstNationsFeature = await findFirstNation(location, 'GEOMETRY'); diff --git a/source/frontend/src/features/mapSideBar/lease/ViewSelector.tsx b/source/frontend/src/features/mapSideBar/lease/ViewSelector.tsx index d320e847ef..d30c5fa72a 100644 --- a/source/frontend/src/features/mapSideBar/lease/ViewSelector.tsx +++ b/source/frontend/src/features/mapSideBar/lease/ViewSelector.tsx @@ -2,18 +2,18 @@ import { FormikProps } from 'formik'; import * as React from 'react'; import { LeaseFormModel } from '@/features/leases/models'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { LeaseFileTabNames } from './detail/LeaseFileTabs'; import { LeaseTabsContainer } from './detail/LeaseTabsContainer'; import { LeaseContainerState, LeasePageNames, leasePages } from './LeaseContainer'; export interface IViewSelectorProps { - lease?: Api_Lease; + lease?: ApiGen_Concepts_Lease; isEditing: boolean; setContainerState: (value: Partial) => void; refreshLease: () => void; - setLease: (lease: Api_Lease) => void; + setLease: (lease: ApiGen_Concepts_Lease) => void; activeEditForm?: LeasePageNames; activeTab?: LeaseFileTabNames; formikRef: React.RefObject>; diff --git a/source/frontend/src/features/mapSideBar/lease/common/LeaseHeader.tsx b/source/frontend/src/features/mapSideBar/lease/common/LeaseHeader.tsx index d429352ed5..0add5c2706 100644 --- a/source/frontend/src/features/mapSideBar/lease/common/LeaseHeader.tsx +++ b/source/frontend/src/features/mapSideBar/lease/common/LeaseHeader.tsx @@ -13,13 +13,13 @@ import { InlineFlexDiv } from '@/components/common/styles'; import { UserNameTooltip } from '@/components/common/UserNameTooltip'; import { LeaseHeaderAddresses } from '@/features/leases/detail/LeaseHeaderAddresses'; import { Api_LastUpdatedBy } from '@/models/api/File'; -import { Api_Lease } from '@/models/api/Lease'; +import { ApiGen_Concepts_Lease } from '@/models/api/generated/ApiGen_Concepts_Lease'; import { prettyFormatDate, prettyFormatUTCDate } from '@/utils'; import { LeaseHeaderTenants } from './LeaseHeaderTenants'; export interface ILeaseHeaderProps { - lease?: Api_Lease; + lease?: ApiGen_Concepts_Lease; lastUpdatedBy: Api_LastUpdatedBy | null; } @@ -46,7 +46,7 @@ export const LeaseHeader: React.FC = ({ lease, lastUpdatedBy } /> @@ -92,7 +92,7 @@ export const LeaseHeader: React.FC = ({ lease, lastUpdatedBy - {lease?.statusType?.description} + {lease?.fileStatusTypeCode?.description} diff --git a/source/frontend/src/features/mapSideBar/lease/common/LeaseHeaderTenants.tsx b/source/frontend/src/features/mapSideBar/lease/common/LeaseHeaderTenants.tsx index 6f17a11c3c..ae25b5f612 100644 --- a/source/frontend/src/features/mapSideBar/lease/common/LeaseHeaderTenants.tsx +++ b/source/frontend/src/features/mapSideBar/lease/common/LeaseHeaderTenants.tsx @@ -2,10 +2,10 @@ import React from 'react'; import ExpandableTextList from '@/components/common/ExpandableTextList'; import { getAllNames } from '@/features/leases/leaseUtils'; -import { Api_LeaseTenant } from '@/models/api/LeaseTenant'; +import { ApiGen_Concepts_LeaseTenant } from '@/models/api/generated/ApiGen_Concepts_LeaseTenant'; export interface ILeaseHeaderTenantsProps { - tenants?: Api_LeaseTenant[]; + tenants?: ApiGen_Concepts_LeaseTenant[]; delimiter?: React.ReactElement | string; maxCollapsedLength?: number; } diff --git a/source/frontend/src/features/mapSideBar/lease/common/__snapshots__/LeaseHeader.test.tsx.snap b/source/frontend/src/features/mapSideBar/lease/common/__snapshots__/LeaseHeader.test.tsx.snap index 7d22f14101..9efd961823 100644 --- a/source/frontend/src/features/mapSideBar/lease/common/__snapshots__/LeaseHeader.test.tsx.snap +++ b/source/frontend/src/features/mapSideBar/lease/common/__snapshots__/LeaseHeader.test.tsx.snap @@ -263,6 +263,7 @@ exports[`LeaseHeader component renders as expected when no data is provided 1`]
    + French Mouse Property Management