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 08c5f73986..ddd517f6ec 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 @@ -17,8 +17,8 @@ import { useGenerateAgreement } from './useGenerateAgreement'; const generateFn = jest.fn(); const getAcquisitionFileFn = jest.fn(); const getAcquisitionFileProperties = jest.fn(); -const getPersonConceptFn = jest.fn(); -const getOrganizationConceptFn = jest.fn(); +const getPersonConceptFn = jest.fn().mockResolvedValue({}); +const getOrganizationConceptFn = jest.fn().mockResolvedValue({}); jest.mock('@/features/documents/hooks/useDocumentGenerationRepository'); (useDocumentGenerationRepository as jest.Mock).mockImplementation(() => ({ @@ -70,7 +70,7 @@ describe('useGenerateAgreement functions', () => { expect(generateFn).toHaveBeenCalled(); }); }); - it('makes requests to expected api endpoints if a team member is a property coordinator', async () => { + it('makes requests to expected api endpoints if a team member is a property coordinator with person', async () => { const responseWithTeam: Api_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ @@ -92,4 +92,27 @@ describe('useGenerateAgreement functions', () => { expect(getAcquisitionFileProperties).toHaveBeenCalled(); }); }); + + it('makes requests to expected api endpoints if a team member is a property coordinator with org', async () => { + const responseWithTeam: Api_AcquisitionFile = { + ...mockAcquisitionFileResponse(), + acquisitionTeam: [ + { + id: 1, + acquisitionFileId: 1, + organizationId: 1, + teamProfileTypeCode: 'PROPCOORD', + rowVersion: 2, + }, + ], + }; + const generate = setup({ acquisitionResponse: responseWithTeam }); + + await act(async () => { + await generate(mockAgreementsResponse()[1]); + expect(generateFn).toHaveBeenCalled(); + expect(getOrganizationConceptFn).toHaveBeenCalled(); + expect(getAcquisitionFileProperties).toHaveBeenCalled(); + }); + }); }); 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 cc75d840e6..746cdfc696 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 @@ -38,14 +38,28 @@ export const useGenerateAgreement = () => { ); const coordinatorPromise = coordinator?.personId - ? getPersonConcept(coordinator?.personId) - : Promise.resolve(null); + ? getPersonConcept(coordinator?.personId).then(p => (coordinator.person = p?.data)) + : provincialSolicitor?.organizationId + ? getOrganizationConcept(provincialSolicitor?.organizationId).then(o => + !!coordinator ? (coordinator.organization = o?.data) : null, + ) + : Promise.resolve(); const negotiatingAgentPromise = negotiatingAgent?.personId - ? getPersonConcept(negotiatingAgent?.personId) - : Promise.resolve(null); + ? getPersonConcept(negotiatingAgent?.personId).then(p => (negotiatingAgent.person = p?.data)) + : provincialSolicitor?.organizationId + ? getOrganizationConcept(provincialSolicitor?.organizationId).then(o => + !!negotiatingAgent ? (negotiatingAgent.organization = o?.data) : null, + ) + : Promise.resolve(); const provincialSolicitorPromise = provincialSolicitor?.personId - ? getPersonConcept(provincialSolicitor?.personId) - : Promise.resolve(null); + ? getPersonConcept(provincialSolicitor?.personId).then( + p => (provincialSolicitor.person = p?.data), + ) + : provincialSolicitor?.organizationId + ? getOrganizationConcept(provincialSolicitor?.organizationId).then(o => + !!provincialSolicitor ? (provincialSolicitor.organization = o?.data) : null, + ) + : Promise.resolve(); // Owner solicitor can be either a Person or an Organization (with optional primary contact) const ownerSolicitorPersonPromise = ownerSolicitor?.personId @@ -59,21 +73,10 @@ export const useGenerateAgreement = () => { ? getPersonConcept(ownerSolicitor?.primaryContactId) : Promise.resolve(null); - const [ - coordinatorConcept, - negotiatingAgentConcept, - provincialSolicitorConcept, - ownerSolicitorPersonConcept, - ownerSolicitorOrganizationConcept, - ownerSolicitorPrimaryContactConcept, - ] = await Promise.all([ - coordinatorPromise, - negotiatingAgentPromise, - provincialSolicitorPromise, - ownerSolicitorPersonPromise, - ownerSolicitorOrganizationPromise, - ownerSolicitorPrimaryContactPromise, - ]); + await Promise.all([coordinatorPromise, negotiatingAgentPromise, provincialSolicitorPromise]); + const ownerSolicitorPersonConcept = await ownerSolicitorPersonPromise; + const ownerSolicitorOrganizationConcept = await ownerSolicitorOrganizationPromise; + const ownerSolicitorPrimaryContactConcept = await ownerSolicitorPrimaryContactPromise; if (ownerSolicitor) { ownerSolicitor.person = ownerSolicitorPersonConcept?.data ?? null; @@ -83,9 +86,9 @@ export const useGenerateAgreement = () => { const fileData = new Api_GenerateAcquisitionFile({ file, - coordinatorContact: coordinatorConcept?.data ?? null, - negotiatingAgent: negotiatingAgentConcept?.data ?? null, - provincialSolicitor: provincialSolicitorConcept?.data ?? null, + coordinatorContact: coordinator ?? null, + negotiatingAgent: negotiatingAgent ?? null, + provincialSolicitor: provincialSolicitor ?? null, ownerSolicitor: ownerSolicitor ?? null, interestHolders: [], }); 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 c7a503239e..94c3f6d96a 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 @@ -15,6 +15,7 @@ import { useGenerateLetter } from '../hooks/useGenerateLetter'; const generateFn = jest.fn(); const getAcquisitionFileFn = jest.fn(); const getPersonConceptFn = jest.fn(); +const getOrganizationConceptFn = jest.fn(); jest.mock('@/features/documents/hooks/useDocumentGenerationRepository'); (useDocumentGenerationRepository as jest.Mock).mockImplementation(() => ({ @@ -29,6 +30,7 @@ jest.mock('@/hooks/repositories/useAcquisitionProvider'); jest.mock('@/hooks/pims-api/useApiContacts'); (useApiContacts as jest.Mock).mockImplementation(() => ({ getPersonConcept: getPersonConceptFn, + getOrganizationConcept: getOrganizationConceptFn, })); let currentStore: MockStoreEnhanced; @@ -64,7 +66,7 @@ describe('useGenerateLetter functions', () => { expect(generateFn).toHaveBeenCalled(); }); }); - it('makes requests to expected api endpoints if a team member is a property coordinator', async () => { + it('makes requests to expected api endpoints if a team member is a property coordinator with person', async () => { const responseWithTeam: Api_AcquisitionFile = { ...mockAcquisitionFileResponse(), acquisitionTeam: [ @@ -85,4 +87,26 @@ describe('useGenerateLetter functions', () => { expect(getPersonConceptFn).toHaveBeenCalled(); }); }); + + it('makes requests to expected api endpoints if a team member is a property coordinator with org', async () => { + const responseWithTeam: Api_AcquisitionFile = { + ...mockAcquisitionFileResponse(), + acquisitionTeam: [ + { + id: 1, + acquisitionFileId: 1, + organizationId: 1, + teamProfileTypeCode: 'PROPCOORD', + rowVersion: 2, + }, + ], + }; + const generate = setup({ acquisitionResponse: responseWithTeam }); + + await act(async () => { + await generate(0); + expect(generateFn).toHaveBeenCalled(); + expect(getOrganizationConceptFn).toHaveBeenCalled(); + }); + }); }); 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 9de9e1cd31..342e0edf14 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 @@ -8,7 +8,7 @@ import { Api_GenerateLetter } from '@/models/generate/GenerateLetter'; import { Api_GenerateOwner } from '@/models/generate/GenerateOwner'; export const useGenerateLetter = () => { - const { getPersonConcept } = useApiContacts(); + const { getPersonConcept, getOrganizationConcept } = useApiContacts(); const { getAcquisitionFile: { execute: getAcquisitionFile }, } = useAcquisitionProvider(); @@ -24,10 +24,14 @@ export const useGenerateLetter = () => { const coordinator = file.acquisitionTeam?.find( team => team.teamProfileTypeCode === 'PROPCOORD', ); - const coordinatorPerson = !!coordinator?.personId - ? (await getPersonConcept(coordinator?.personId))?.data - : null; - const letterData = new Api_GenerateLetter(file, coordinatorPerson); + if (!!coordinator?.personId) { + coordinator.person = (await getPersonConcept(coordinator?.personId))?.data; + } else if (!!coordinator?.organizationId) { + coordinator.organization = ( + await getOrganizationConcept(coordinator?.organizationId) + )?.data; + } + const letterData = new Api_GenerateLetter(file, coordinator); letterData.owners = recipients ?? letterData.owners; const generatedFile = await generate({ templateType: FormDocumentType.LETTER, diff --git a/source/frontend/src/models/generate/GenerateLetter.ts b/source/frontend/src/models/generate/GenerateLetter.ts index 84f09491ba..73e1f79328 100644 --- a/source/frontend/src/models/generate/GenerateLetter.ts +++ b/source/frontend/src/models/generate/GenerateLetter.ts @@ -1,12 +1,14 @@ import moment from 'moment'; -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; -import { Api_Person } from '@/models/api/Person'; +import { Api_AcquisitionFile, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; import { Api_GenerateAcquisitionFile } from './acquisition/GenerateAcquisitionFile'; export class Api_GenerateLetter extends Api_GenerateAcquisitionFile { date_generated: string; - constructor(file: Api_AcquisitionFile, coordinatorContact: Api_Person | null | undefined) { + constructor( + file: Api_AcquisitionFile, + coordinatorContact: Api_AcquisitionFileTeam | null | undefined, + ) { super({ file, coordinatorContact: coordinatorContact ?? null, diff --git a/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.test.ts b/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.test.ts index 6b2f73be24..3e8f094b12 100644 --- a/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.test.ts +++ b/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.test.ts @@ -1,3 +1,5 @@ +import { AddressTypes } from '@/constants'; +import { ContactMethodTypes } from '@/constants/contactMethodType'; import { mockAcquisitionFileResponse } from '@/mocks/acquisitionFiles.mock'; import { emptyApiInterestHolder, emptyInterestHolderProperty } from '@/mocks/interestHolder.mock'; @@ -175,4 +177,181 @@ describe('GenerateFile tests', () => { }); expect(file.properties[0].interest_holders_string).toBe(''); }); + + it('generates a file with org neg agent that has org name but primary contacts contact info and address', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + negotiatingAgent: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + primaryContact: { + contactMethods: [ + { contactMethodType: { id: ContactMethodTypes.WorkEmail }, value: 'primaryworkemail' }, + { contactMethodType: { id: ContactMethodTypes.WorkPhone }, value: 'primaryworkphone' }, + ], + personAddresses: [ + { + address: { streetAddress1: 'primaryaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + }, + }); + expect(file.neg_agent?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.neg_agent?.email).toBe('primaryworkemail'); + expect(file.neg_agent?.phone).toBe('primaryworkphone'); + expect(file.neg_agent?.address?.address_string).toBe('primaryaddress'); + }); + + it('generates a file with org neg agent that has org name but null contact if primary contact empty', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + negotiatingAgent: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + }, + }); + expect(file.neg_agent?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.neg_agent?.email).toBe(''); + expect(file.neg_agent?.phone).toBe(''); + expect(file.neg_agent?.address).toBeNull(); + }); + + it('generates a file with org coord agent that has org name but primary contacts contact info', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + coordinatorContact: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + primaryContact: { + contactMethods: [ + { contactMethodType: { id: ContactMethodTypes.WorkEmail }, value: 'workemail' }, + { contactMethodType: { id: ContactMethodTypes.WorkPhone }, value: 'workphone' }, + ], + }, + }, + }); + expect(file.property_coordinator?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.property_coordinator?.email).toBe('workemail'); + expect(file.property_coordinator?.phone).toBe('workphone'); + expect(file.property_coordinator?.address?.address_string).toBe('orgaddress'); + }); + + it('generates a file with org coord agent that has org name but empty primary contact', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + coordinatorContact: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + }, + }); + expect(file.property_coordinator?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.property_coordinator?.email).toBe(''); + expect(file.property_coordinator?.phone).toBe(''); + expect(file.property_coordinator?.address?.address_string).toBe('orgaddress'); + }); + + it('generates a file with org provincial solicitor that has org address and name, primary contacts info, and separate attn field', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + provincialSolicitor: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + primaryContact: { + firstName: 'testfirst', + contactMethods: [ + { contactMethodType: { id: ContactMethodTypes.WorkEmail }, value: 'workemail' }, + { contactMethodType: { id: ContactMethodTypes.WorkPhone }, value: 'workphone' }, + ], + }, + }, + }); + expect(file.prov_solicitor?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.prov_solicitor?.email).toBe('workemail'); + expect(file.prov_solicitor?.phone).toBe('workphone'); + expect(file.prov_solicitor_attn?.full_name_string).toBe('testfirst'); + expect(file.prov_solicitor?.address?.address_string).toBe('orgaddress'); + }); + + it('generates a file with org provincial solicitor that has org address and name, and empty attn and info if primary contact empty', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + provincialSolicitor: { + acquisitionFileId: acqFile.id ?? 0, + organization: { + name: 'testOrg', + organizationAddresses: [ + { + address: { streetAddress1: 'orgaddress' }, + addressUsageType: { id: AddressTypes.Mailing }, + }, + ], + }, + }, + }); + expect(file.prov_solicitor?.full_name_string).toBe('testOrg (Inc. No. )'); + expect(file.prov_solicitor?.email).toBe(''); + expect(file.prov_solicitor?.phone).toBe(''); + expect(file.prov_solicitor_attn?.full_name_string).toBe(''); + expect(file.prov_solicitor?.address?.address_string).toBe('orgaddress'); + }); + + it('saves a list of the organization owners on the file', () => { + const acqFile = mockAcquisitionFileResponse(1, 'test', 1); + const file = new Api_GenerateAcquisitionFile({ + file: acqFile, + interestHolders: [], + }); + expect(file.organization_owners).toHaveLength(1); + expect(file.organization_owners[0].owner_string).toBe( + 'FORTIS BC, Inc. No. 9999 (OR Reg. No. 12345)', + ); + }); }); diff --git a/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.ts b/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.ts index c477f40f5c..a1e4129dd3 100644 --- a/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.ts +++ b/source/frontend/src/models/generate/acquisition/GenerateAcquisitionFile.ts @@ -1,7 +1,7 @@ -import { Api_AcquisitionFile } from '@/models/api/AcquisitionFile'; +import { Api_AcquisitionFile, Api_AcquisitionFileTeam } from '@/models/api/AcquisitionFile'; import { Api_InterestHolder, Api_InterestHolderProperty } from '@/models/api/InterestHolder'; -import { Api_Person } from '@/models/api/Person'; +import { Api_GenerateOrganization } from '../GenerateOrganization'; import { Api_GenerateOwner } from '../GenerateOwner'; import { Api_GeneratePerson } from '../GeneratePerson'; import { Api_GenerateProduct } from '../GenerateProduct'; @@ -12,16 +12,16 @@ import { Api_GenerateInterestHolder } from './GenerateInterestHolder'; export interface IApiGenerateAcquisitionFileInput { file: Api_AcquisitionFile | null; - coordinatorContact?: Api_Person | null; - negotiatingAgent?: Api_Person | null; - provincialSolicitor?: Api_Person | null; + coordinatorContact?: Api_AcquisitionFileTeam | null; + negotiatingAgent?: Api_AcquisitionFileTeam | null; + provincialSolicitor?: Api_AcquisitionFileTeam | null; ownerSolicitor?: Api_InterestHolder | null; interestHolders?: Api_InterestHolder[]; } export class Api_GenerateAcquisitionFile { properties: Api_GenerateH120Property[]; - property_coordinator: Api_GeneratePerson; + property_coordinator?: Api_GeneratePerson | Api_GenerateOrganization; primary_owner?: Api_GenerateOwner; owners: Api_GenerateOwner[]; person_owners: Api_GenerateOwner[]; @@ -32,9 +32,10 @@ export class Api_GenerateAcquisitionFile { file_name: string; project_number: string; project_name: string; - prov_solicitor?: Api_GeneratePerson; + prov_solicitor?: Api_GeneratePerson | Api_GenerateOrganization; + prov_solicitor_attn?: Api_GeneratePerson; owner_solicitor?: Api_GenerateInterestHolder; - neg_agent?: Api_GeneratePerson; + neg_agent?: Api_GeneratePerson | Api_GenerateOrganization; project?: Api_GenerateProject; product?: Api_GenerateProduct; @@ -47,8 +48,8 @@ export class Api_GenerateAcquisitionFile { interestHolders = [], }: IApiGenerateAcquisitionFileInput) { this.owners = file?.acquisitionFileOwners?.map(owner => new Api_GenerateOwner(owner)) ?? []; - this.property_coordinator = new Api_GeneratePerson(coordinatorContact); - this.neg_agent = new Api_GeneratePerson(negotiatingAgent); + this.property_coordinator = this.getTeam(coordinatorContact); + this.neg_agent = this.getTeam(negotiatingAgent, true); const allInterestHoldersPropertes = interestHolders.flatMap( ih => ih?.interestHolderProperties ?? [], ); @@ -84,7 +85,8 @@ export class Api_GenerateAcquisitionFile { this.primary_owner = new Api_GenerateOwner( file?.acquisitionFileOwners?.find(owner => owner.isPrimaryContact) ?? null, ); - this.prov_solicitor = new Api_GeneratePerson(provincialSolicitor); + this.prov_solicitor = this.getTeam(provincialSolicitor); + this.prov_solicitor_attn = new Api_GeneratePerson(provincialSolicitor?.primaryContact); this.owner_solicitor = new Api_GenerateInterestHolder(ownerSolicitor); this.person_owners = file?.acquisitionFileOwners @@ -99,4 +101,22 @@ export class Api_GenerateAcquisitionFile { this.all_owners_string = this.owners.map(owner => owner.owner_string).join(', '); this.all_owners_string_and = this.owners.map(owner => owner.owner_string).join(' And '); } + + getTeam = (team: Api_AcquisitionFileTeam | null, overrideOrgAddress: boolean = false) => { + if (!team) return undefined; + + if (team.person) { + return new Api_GeneratePerson(team.person); + } + + const org = new Api_GenerateOrganization(team.organization); + const primary = new Api_GeneratePerson(team.primaryContact); + //replace organization contact info with primary contact info but leave address, name. + org.phone = primary?.phone; + org.email = primary?.email; + if (overrideOrgAddress) { + org.address = primary?.address; + } + return org; + }; }