diff --git a/CHANGELOG.md b/CHANGELOG.md index c82080a1f49..e0e5a8e4b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fetch child identifier in view record - Auth token, ip address, remote address redacted from server log - **Align Patient data model with FHIR**: Previously we were using `string[]` for `Patient.name.family` field instead of `string` as mentioned in the FHIR standard. We've now aligned the field with the standard. +- **Certificate Fetching**: Removed certificates from the database, allowing them to be fetched directly from the country configuration via a simplified API endpoint. ### New features @@ -26,6 +27,10 @@ - Deploy UI-Kit Storybook to [opencrvs.pages.dev](https://opencrvs.pages.dev) to allow extending OpenCRVS using the component library - Record audit action buttons are moved into action menu [#7390](https://github.com/opencrvs/opencrvs-core/issues/7390) - Reoder the sytem user add/edit field for surname to be first, also change labels from `Last name` to `User's surname` and lastly remove the NID question from the form [#6830](https://github.com/opencrvs/opencrvs-core/issues/6830) +- **Template Selection for Certified Copies**: Added support for multiple certificate templates for each event (birth, death, marriage). Users can now select a template during the certificate issuance process. +- **Template-based Payment Configuration**: Implemented payment differentiation based on the selected certificate template, ensuring the correct amount is charged. +- **Template Action Tracking**: Each template printed is tracked in the history table, showing which specific template was used. +- **Template Selection Dropdown**: Updated print workflow to include a dropdown menu for template selection when issuing a certificate. - Auth now allows exchanging user's token for a new record-specific token [#7728](https://github.com/opencrvs/opencrvs-core/issues/7728) ## Bug fixes diff --git a/packages/client/graphql.schema.json b/packages/client/graphql.schema.json index 0e734fdd950..4abbfabc0dc 100644 --- a/packages/client/graphql.schema.json +++ b/packages/client/graphql.schema.json @@ -3582,39 +3582,376 @@ "deprecationReason": null }, { - "name": "data", + "name": "hasShowedVerifiedDocument", + "description": null, + "args": [], + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "payments", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "Payment", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "templateConfig", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "CertificateConfigData", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateConfigData", + "description": null, + "fields": [ + { + "name": "event", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fee", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CertificateFee", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "CertificateLabel", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lateRegistrationTarget", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "printInAdvance", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "registrationTarget", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "svgUrl", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateConfigDataInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "event", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fee", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CertificateFeeInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "label", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "CertificateLabelInput", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lateRegistrationTarget", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "printInAdvance", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "registrationTarget", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "svgUrl", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateFee", + "description": null, + "fields": [ + { + "name": "delayed", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "hasShowedVerifiedDocument", + "name": "late", "description": null, "args": [], "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "payments", + "name": "onTime", "description": null, "args": [], "type": { - "kind": "LIST", + "kind": "NON_NULL", "name": null, "ofType": { - "kind": "OBJECT", - "name": "Payment", + "kind": "SCALAR", + "name": "Float", "ofType": null } }, @@ -3629,28 +3966,75 @@ }, { "kind": "INPUT_OBJECT", - "name": "CertificateInput", + "name": "CertificateFeeInput", "description": null, "fields": null, "inputFields": [ { - "name": "collector", + "name": "delayed", "description": null, "type": { - "kind": "INPUT_OBJECT", - "name": "RelatedPersonInput", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } }, "defaultValue": null, "isDeprecated": false, "deprecationReason": null }, { - "name": "data", + "name": "late", "description": null, "type": { - "kind": "SCALAR", - "name": "String", + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onTime", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Float", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "collector", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "RelatedPersonInput", "ofType": null }, "defaultValue": null, @@ -3684,6 +4068,136 @@ "defaultValue": null, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "templateConfig", + "description": null, + "type": { + "kind": "INPUT_OBJECT", + "name": "CertificateConfigDataInput", + "ofType": null + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "CertificateLabel", + "description": null, + "fields": [ + { + "name": "defaultMessage", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "INPUT_OBJECT", + "name": "CertificateLabelInput", + "description": null, + "fields": null, + "inputFields": [ + { + "name": "defaultMessage", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "id", + "description": null, + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "defaultValue": null, + "isDeprecated": false, + "deprecationReason": null } ], "interfaces": null, @@ -6829,6 +7343,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "templateConfig", + "description": null, + "args": [], + "type": { + "kind": "OBJECT", + "name": "CertificateConfigData", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "user", "description": null, diff --git a/packages/client/src/declarations/index.ts b/packages/client/src/declarations/index.ts index de3e7fc5436..b1256746140 100644 --- a/packages/client/src/declarations/index.ts +++ b/packages/client/src/declarations/index.ts @@ -264,11 +264,14 @@ type RelationForCertificateCorrection = | 'CHILD' export type ICertificate = { - collector?: Partial<{ type: Relation | string }> + collector?: Partial<{ + type: Relation | string + certificateTemplateId?: string + }> corrector?: Partial<{ type: RelationForCertificateCorrection | string }> hasShowedVerifiedDocument?: boolean payments?: Payment - data?: string + certificateTemplateId?: string } /* diff --git a/packages/client/src/declarations/selectors.ts b/packages/client/src/declarations/selectors.ts index f8369701a7b..ce95c3cf9b6 100644 --- a/packages/client/src/declarations/selectors.ts +++ b/packages/client/src/declarations/selectors.ts @@ -12,8 +12,9 @@ import { IDeclaration, IDeclarationsState } from '@client/declarations/index' import { IStoreState } from '@client/store' import { useSelector } from 'react-redux' -export const getDraftsState = (store: IStoreState): IDeclarationsState => - store.declarationsState +export const getDraftsState = (store: IStoreState): IDeclarationsState => { + return store.declarationsState +} function getKey( store: IStoreState, @@ -29,8 +30,11 @@ export const getInitialDeclarationsLoaded = ( export const selectDeclaration = (declarationId: string) => - (store: IStoreState) => - getKey(store, 'declarations').find(({ id }) => declarationId === id) as T + (store: IStoreState) => { + return getKey(store, 'declarations').find( + ({ id }) => declarationId === id + ) as T + } export const useDeclaration = ( declarationId: string diff --git a/packages/client/src/declarations/submissionMiddleware.test.ts b/packages/client/src/declarations/submissionMiddleware.test.ts index a74b063b0a9..d306085da2d 100644 --- a/packages/client/src/declarations/submissionMiddleware.test.ts +++ b/packages/client/src/declarations/submissionMiddleware.test.ts @@ -12,6 +12,7 @@ import { ApolloError } from '@apollo/client' import { SubmissionAction } from '@client/forms' import { ACTION_STATUS_MAP, + mockBirthRegistrationSectionData, mockDeclarationData, mockOfflineDataDispatch } from '@client/tests/util' @@ -140,30 +141,7 @@ describe('Submission middleware', () => { } it(`should handle ${ACTION_STATUS_MAP[submissionAction]} ${event} declarations`, async () => { - mockDeclarationData.registration.certificates[0] = { - collector: { - relationship: 'OTHER', - affidavit: { - contentType: 'image/jpg', - data: 'data:image/png;base64,2324256' - }, - individual: { - name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], - identifier: [{ id: '123456', type: 'PASSPORT' }] - } - }, - hasShowedVerifiedDocument: true, - payments: [ - { - paymentId: '1234', - type: 'MANUAL', - amount: 50, - outcome: 'COMPLETED', - date: '2018-10-22' - } - ], - data: 'data:image/png;base64,2324256' - } + mockDeclarationData.registration = mockBirthRegistrationSectionData const action = declarationReadyForStatusChange({ id: 'mockDeclaration', data: mockDeclarationData, diff --git a/packages/client/src/declarations/submissionMiddleware.ts b/packages/client/src/declarations/submissionMiddleware.ts index 9322d0cda31..ded98060b82 100644 --- a/packages/client/src/declarations/submissionMiddleware.ts +++ b/packages/client/src/declarations/submissionMiddleware.ts @@ -305,12 +305,6 @@ export const submissionMiddleware: Middleware<{}, IStoreState> = details: graphqlPayload } }) - //delete data from certificates to identify event in workflow for markEventAsIssued - if (declaration.data.registration.certificates) { - delete ( - declaration.data.registration.certificates as ICertificate[] - )?.[0].data - } updateDeclaration(dispatch, { ...declaration, registrationStatus: RegStatus.Certified, diff --git a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts index d306f3df5be..1c7bffa7a50 100644 --- a/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts +++ b/packages/client/src/forms/certificate/fieldDefinitions/collectorSection.ts @@ -36,6 +36,7 @@ import { identityHelperTextMapper, identityNameMapper } from './messages' import { Event } from '@client/utils/gateway' import { IDeclaration } from '@client/declarations' import { issueMessages } from '@client/i18n/messages/issueCertificate' +import { ICertificateConfigData } from '@client/utils/referenceApi' interface INameField { firstNamesField: string @@ -1012,7 +1013,8 @@ const marriageIssueCollectorFormOptions = [ ] function getCertCollectorGroupForEvent( - declaration: IDeclaration + declaration: IDeclaration, + certificates: ICertificateConfigData[] ): IFormSectionGroup { const informant = (declaration.data.informant.otherInformantType || declaration.data.informant.informantType) as string @@ -1039,12 +1041,25 @@ function getCertCollectorGroupForEvent( birthCertCollectorOptions, marriageCertCollectorOptions ) - + const certificateTemplateOptions = + certificates + .filter((x) => x.event === declaration.event) + .map((x) => ({ label: x.label, value: x.id })) || [] + const certTemplateDefaultValue = certificates.find((x) => x.isDefault)?.id return { id: 'certCollector', title: certificateMessages.whoToCollect, error: certificateMessages.certificateCollectorError, fields: [ + { + name: 'certificateTemplateId', + type: 'SELECT_WITH_OPTIONS', + label: certificateMessages.certificateTemplateSelectLabel, + required: true, + initialValue: certTemplateDefaultValue, + validator: [], + options: certificateTemplateOptions + }, { name: 'type', type: RADIO_GROUP, @@ -1061,7 +1076,8 @@ function getCertCollectorGroupForEvent( } export function getCertificateCollectorFormSection( - declaration: IDeclaration + declaration: IDeclaration, + certificates: ICertificateConfigData[] ): IFormSection { return { id: CertificateSection.Collector, @@ -1069,7 +1085,7 @@ export function getCertificateCollectorFormSection( name: certificateMessages.printCertificate, title: certificateMessages.certificateCollectionTitle, groups: [ - getCertCollectorGroupForEvent(declaration), + getCertCollectorGroupForEvent(declaration, certificates), otherCertCollectorFormGroup(declaration.event), affidavitCertCollectorGroup ] diff --git a/packages/client/src/forms/index.ts b/packages/client/src/forms/index.ts index c157e88245c..8ef12d4503b 100644 --- a/packages/client/src/forms/index.ts +++ b/packages/client/src/forms/index.ts @@ -1333,5 +1333,5 @@ export interface ICertificate { collector?: IFormSectionData hasShowedVerifiedDocument?: boolean payments?: Payment[] - data?: string + certificateTemplateId?: string } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts index fcedb64513c..b53a3ed6ed1 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.test.ts @@ -32,46 +32,51 @@ describe('Birth registration mutation mapping related tests', () => { expect(transformedData.registration.trackingId).toEqual('BDSS0SE') expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, + certificateTemplateId: 'birth-certificate', + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ], collector: { - relationship: 'OTHER', - otherRelationship: 'Uncle', - name: [ - { - use: 'en', - firstNames: 'Mushraful', - familyName: 'Hoque' - } - ], - identifier: [ - { - id: '123456789', - type: 'PASSPORT' - } - ], + otherRelationship: 'OTHER', + name: [{ firstNames: 'Doe', familyName: 'Jane', use: 'en' }], + identifier: [{ id: '123456', type: 'PASSPORT' }], affidavit: [ { - contentType: 'abc', - data: 'BASE64 data' + contentType: 'image/jpg', + data: 'data:image/png;base64,2324256' } ] - }, - hasShowedVerifiedDocument: true + } } ]) }) - it('Test certificate mapping without any data', () => { + it('Test certificate mapping template config data', () => { const transformedData: TransformedData = { registration: {} } + const mockBirthDeclaration = cloneDeep({ + ...mockDeclarationData, + registration: { + ...mockDeclarationData.registration, + certificates: [{}] + } + }) setBirthRegistrationSectionTransformer( transformedData, - mockDeclarationData, + mockBirthDeclaration, 'registration' ) expect(transformedData.registration).toBeDefined() expect(transformedData.registration.registrationNumber).toEqual( '201908122365BDSS0SE1' ) - expect(transformedData.registration.certificates).toEqual([{}]) + expect(transformedData.registration.certificates).toEqual([]) }) }) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts index be0cf4b2bae..17c9d92202c 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/mutation/registration-mappings.ts @@ -48,11 +48,16 @@ export function setBirthRegistrationSectionTransformer( }) } - if (draftData[sectionId].certificates) { - transformCertificateData( - transformedData, - (draftData[sectionId].certificates as ICertificate[])[0], - sectionId - ) + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { + const updatedCertificates = transformCertificateData(certificates.slice(-1)) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } + return transformedData } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts index 9d7817a622c..feabbb6245e 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/birth/query/registration-mappings.ts @@ -33,6 +33,17 @@ export function getBirthRegistrationSectionTransformer( if (queryData[sectionId].status) { transformStatusData(transformedData, queryData[sectionId].status, sectionId) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] + } } export function mosipAidTransformer( diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts index 26fbe52da1b..b759336e70f 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/deceased-mappings.test.ts @@ -32,10 +32,13 @@ describe('Death registration mutation mapping related tests', () => { ) expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, collector: { - relationship: 'MOTHER' + otherRelationship: 'MOTHER', + name: [{ use: 'en' }], + identifier: [{}] }, - hasShowedVerifiedDocument: true + certificateTemplateId: 'death-certificate' } ]) }) @@ -54,24 +57,14 @@ describe('Death registration mutation mapping related tests', () => { expect(transformedData.registration.trackingId).toEqual('DDSS0SE') expect(transformedData.registration.certificates).toEqual([ { + hasShowedVerifiedDocument: true, + certificateTemplateId: 'death-certificate', collector: { relationship: 'OTHER', otherRelationship: 'Uncle', - name: [ - { - use: 'en', - firstNames: 'Mushraful', - familyName: 'Hoque' - } - ], - identifier: [ - { - id: '123456789', - type: 'PASSPORT' - } - ] - }, - hasShowedVerifiedDocument: true + name: [{ use: 'en', firstNames: 'Mushraful', familyName: 'Hoque' }], + identifier: [{ id: '123456789', type: 'PASSPORT' }] + } } ]) }) diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts index 2741b81e4c8..f005c340354 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/mutation/registration-mappings.ts @@ -51,14 +51,19 @@ export function setDeathRegistrationSectionTransformer( }) } - if (draftData.registration.certificates) { - transformCertificateData( - transformedData, - (draftData.registration.certificates as ICertificate[])[0], - 'registration' + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { + const updatedCertificates = transformCertificateData( + certificates.slice(-1) ) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } } - return transformedData } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts index acd0fb058fd..276c9263c66 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/death/query/registration-mappings.ts @@ -16,37 +16,44 @@ import { transformStatusData } from '@client/forms/register/mappings/query/utils export function getDeathRegistrationSectionTransformer( transformedData: IFormData, queryData: any, - _sectionId: string + sectionId: string ) { - if (!transformedData['registration']) { - transformedData['registration'] = {} + if (!transformedData[sectionId]) { + transformedData[sectionId] = {} } - if (queryData['registration'].id) { - transformedData['registration']._fhirID = queryData['registration'].id + if (queryData[sectionId].id) { + transformedData[sectionId]._fhirID = queryData[sectionId].id } - if (queryData['registration'].trackingId) { - transformedData['registration'].trackingId = - queryData['registration'].trackingId + if (queryData[sectionId].trackingId) { + transformedData[sectionId].trackingId = queryData[sectionId].trackingId } - if (queryData['registration'].registrationNumber) { - transformedData['registration'].registrationNumber = - queryData['registration'].registrationNumber + if (queryData[sectionId].registrationNumber) { + transformedData[sectionId].registrationNumber = + queryData[sectionId].registrationNumber } - if ( - queryData['registration'].type && - queryData['registration'].type === 'DEATH' - ) { - transformedData['registration'].type = Event.Death + if (queryData[sectionId].type && queryData[sectionId].type === 'DEATH') { + transformedData[sectionId].type = Event.Death } - if (queryData['registration'].status) { + if (queryData[sectionId].status) { transformStatusData( transformedData, - queryData['registration'].status as GQLRegWorkflow[], - 'registration' + queryData[sectionId].status as GQLRegWorkflow[], + sectionId ) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] + } } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts index fb9c8a5cbc5..46e2aed2251 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/mutation/registration-mappings.ts @@ -50,12 +50,19 @@ export function setMarriageRegistrationSectionTransformer( }) } - if (draftData[sectionId].certificates) { - transformCertificateData( - transformedData, - (draftData[sectionId].certificates as ICertificate[])[0], - sectionId + const certificates: ICertificate[] = draftData[sectionId] + .certificates as ICertificate[] + if (Array.isArray(certificates) && certificates.length) { + const updatedCertificates = transformCertificateData( + certificates.slice(-1) ) + transformedData[sectionId].certificates = + updatedCertificates.length > 0 && + Object.keys(updatedCertificates[0]).length > 0 && + updatedCertificates[0].collector // making sure we are not sending empty object as certificate + ? updatedCertificates + : [] } } + return transformedData } diff --git a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts index 0995b1b5a61..9c398b96eda 100644 --- a/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts +++ b/packages/client/src/forms/register/mappings/event-specific-fields/marriage/query/registration-mappings.ts @@ -42,6 +42,17 @@ export function getMarriageRegistrationSectionTransformer( sectionId ) } + + if ( + Array.isArray(queryData[sectionId].certificates) && + queryData[sectionId].certificates.length > 0 + ) { + transformedData[sectionId].certificates = [ + queryData[sectionId].certificates[ + queryData[sectionId].certificates.length - 1 + ] + ] + } } export function groomSignatureTransformer( diff --git a/packages/client/src/forms/register/mappings/mutation/utils.ts b/packages/client/src/forms/register/mappings/mutation/utils.ts index 802ca2fd848..ba12b7481ec 100644 --- a/packages/client/src/forms/register/mappings/mutation/utils.ts +++ b/packages/client/src/forms/register/mappings/mutation/utils.ts @@ -10,19 +10,33 @@ */ import type { GQLRelatedPersonInput } from '@client/utils/gateway-deprecated-do-not-use' -import { ICertificate, IFileValue, TransformedData } from '@client/forms' +import { ICertificate, IFileValue } from '@client/forms' import { omit } from 'lodash' -export function transformCertificateData( - transformedData: TransformedData, - certificateData: ICertificate, - sectionId: string -) { - transformedData[sectionId].certificates = [ +export function stripTypename(obj: any): any { + if (Array.isArray(obj)) { + return obj.map(stripTypename) + } else if (obj !== null && typeof obj === 'object') { + const newObj: any = {} + for (const key in obj) { + if (key !== '__typename' && Object.hasOwn(obj, key)) { + newObj[key] = stripTypename(obj[key]) + } + } + return newObj + } + return obj +} +export function transformCertificateData(certificates: ICertificate[]) { + const certificateData = certificates[0] + + // Prepare the base certificate data + const updatedCertificates: ICertificate[] = [ { ...omit(certificateData, 'collector') } ] + // for collector mapping if (certificateData && certificateData.collector) { let collector: GQLRelatedPersonInput = {} @@ -58,6 +72,9 @@ export function transformCertificateData( } ] } - transformedData[sectionId].certificates[0].collector = collector + updatedCertificates[0].collector = collector as any } + + // Return the processed certificates array + return updatedCertificates } diff --git a/packages/client/src/i18n/messages/views/certificate.ts b/packages/client/src/i18n/messages/views/certificate.ts index 3c3601da743..fcd61124444 100644 --- a/packages/client/src/i18n/messages/views/certificate.ts +++ b/packages/client/src/i18n/messages/views/certificate.ts @@ -14,6 +14,7 @@ interface ICertificateMessages extends Record { certificateCollectionTitle: MessageDescriptor addAnotherSignature: MessageDescriptor + certificateTemplateSelectLabel: MessageDescriptor certificateConfirmationTxt: MessageDescriptor certificateIsCorrect: MessageDescriptor certificateReceiptHeader: MessageDescriptor @@ -58,6 +59,7 @@ interface ICertificateMessages receiptPaidAmount: MessageDescriptor receiptService: MessageDescriptor selectSignature: MessageDescriptor + selectedCertificateTemplateLabel: MessageDescriptor service: MessageDescriptor amountDue: MessageDescriptor typeOfID: MessageDescriptor @@ -95,6 +97,11 @@ const messagesToDefine: ICertificateMessages = { description: 'The title of print certificate action', id: 'print.certificate.section.title' }, + certificateTemplateSelectLabel: { + defaultMessage: 'Type', + description: 'The title of select certificate template action', + id: 'certificate.selectTemplate' + }, certificateConfirmationTxt: { defaultMessage: 'Edit', description: 'Edit', @@ -330,6 +337,11 @@ const messagesToDefine: ICertificateMessages = { description: 'The label for choose signature select', id: 'print.certificate.selectSignature' }, + selectedCertificateTemplateLabel: { + defaultMessage: 'Selected certificate template', + description: 'The title of selected certificate template label', + id: 'certificate.selectedTemplate' + }, service: { defaultMessage: 'Service: Birth registration after {service, plural, =0 {0 month} one {1 month} other{{service} months}} of D.o.B.
Amount Due:', diff --git a/packages/client/src/navigation/index.ts b/packages/client/src/navigation/index.ts index 886a57da816..62bd3d86927 100644 --- a/packages/client/src/navigation/index.ts +++ b/packages/client/src/navigation/index.ts @@ -293,13 +293,13 @@ export function goToBirthRegistrationAsParent(declarationId: string) { export function goToPrintCertificate( registrationId: string, - event: string, + eventType: string, groupId?: string ) { return push( formatUrl(CERTIFICATE_COLLECTOR, { - registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + registrationId, + eventType, groupId: groupId || 'certCollector' }) ) @@ -319,13 +319,13 @@ export function goToIssueCertificate( export function goToVerifyIssueCollector( registrationId: string, - event: string, + eventType: string, collector: string ) { return push( formatUrl(ISSUE_VERIFY_COLLECTOR, { registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + eventType, collector: collector.toLowerCase().toString() }) ) @@ -360,11 +360,14 @@ export function goToVerifyCorrector(declarationId: string, corrector: string) { ) } -export function goToReviewCertificate(registrationId: string, event: Event) { +export function goToReviewCertificate( + registrationId: string, + eventType: string +) { return push( formatUrl(REVIEW_CERTIFICATE, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + eventType }), { isNavigatedInsideApp: true } ) @@ -372,13 +375,13 @@ export function goToReviewCertificate(registrationId: string, event: Event) { export function goToVerifyCollector( registrationId: string, - event: string, + eventType: string, collector: string ) { return push( formatUrl(VERIFY_COLLECTOR, { registrationId: registrationId.toString(), - eventType: event.toLowerCase().toString(), + eventType: eventType.toLowerCase().toString(), collector: collector.toLowerCase().toString() }) ) @@ -386,24 +389,24 @@ export function goToVerifyCollector( export function goToPrintCertificatePayment( registrationId: string, - event: Event + eventType: string ) { return push( formatUrl(PRINT_CERTIFICATE_PAYMENT, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + eventType }) ) } export function goToIssueCertificatePayment( registrationId: string, - event: Event + eventType: string ) { return push( formatUrl(ISSUE_CERTIFICATE_PAYMENT, { - registrationId: registrationId.toString(), - eventType: event + registrationId, + eventType }) ) } diff --git a/packages/client/src/offline/actions.ts b/packages/client/src/offline/actions.ts index a6d2398d83b..456798d980b 100644 --- a/packages/client/src/offline/actions.ts +++ b/packages/client/src/offline/actions.ts @@ -26,8 +26,7 @@ import { LoadFormsResponse, LoadValidatorsResponse, LoadConditionalsResponse, - LoadHandlebarHelpersResponse, - CertificateConfiguration + LoadHandlebarHelpersResponse } from '@client/utils/referenceApi' import { System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' @@ -116,20 +115,6 @@ type CertificateLoadFailedAction = { payload: Error } -export const CERTIFICATE_CONFIGURATION_LOADED = - 'OFFLINE/CERTIFICATE_CONFIGURATION_LOADED' -type CertificateConfigurationLoadedAction = { - type: typeof CERTIFICATE_CONFIGURATION_LOADED - payload: CertificateConfiguration -} - -export const CERTIFICATE_CONFIGURATION_LOAD_FAILED = - 'OFFLINE/CERTIFICATE_CONFIGURATION_LOAD_FAILED' -type CertificateConfigurationLoadFailedAction = { - type: typeof CERTIFICATE_CONFIGURATION_LOAD_FAILED - payload: Error -} - export const UPDATE_OFFLINE_CONFIG = 'OFFLINE/UPDATE_OFFLINE_CONFIG' as const type ApplicationConfigUpdatedAction = { type: typeof UPDATE_OFFLINE_CONFIG @@ -274,20 +259,6 @@ export const certificateLoadFailed = ( payload }) -export const certificateConfigurationLoaded = ( - payload: CertificateConfiguration -): CertificateConfigurationLoadedAction => ({ - type: CERTIFICATE_CONFIGURATION_LOADED, - payload -}) - -export const certificateConfigurationLoadFailed = ( - payload: CertificateConfigurationLoadFailedAction['payload'] -): CertificateConfigurationLoadFailedAction => ({ - type: CERTIFICATE_CONFIGURATION_LOAD_FAILED, - payload -}) - export const configFailed = (error: Error): ApplicationConfigFailedAction => ({ type: APPLICATION_CONFIG_FAILED, payload: error @@ -362,8 +333,6 @@ export type Action = | ApplicationConfigFailedAction | ApplicationConfigUpdatedAction | CertificateLoadFailedAction - | CertificateConfigurationLoadedAction - | CertificateConfigurationLoadFailedAction | UpdateOfflineSystemsAction | IFilterLocationsAction | ReturnType diff --git a/packages/client/src/offline/reducer.ts b/packages/client/src/offline/reducer.ts index 41adfb050cd..38703ded0a7 100644 --- a/packages/client/src/offline/reducer.ts +++ b/packages/client/src/offline/reducer.ts @@ -27,14 +27,14 @@ import { referenceApi, CertificateConfiguration, IFacilitiesDataResponse, - IOfficesDataResponse + IOfficesDataResponse, + ICertificateConfigData } from '@client/utils/referenceApi' import { ILanguage } from '@client/i18n/reducer' import { filterLocations } from '@client/utils/locationUtils' import { Event, System } from '@client/utils/gateway' import { UserDetails } from '@client/utils/userUtils' import { isOfflineDataLoaded } from './selectors' -import { ISVGTemplate } from '@client/pdfRenderer' import { merge } from 'lodash' import { isNavigatorOnline } from '@client/utils' import { ISerializedForm } from '@client/forms' @@ -105,11 +105,7 @@ export interface IOfflineData { fonts?: CertificateConfiguration['fonts'] // Certificates might not be defined in the case of // a field agent using the app. - certificates?: { - birth: ISVGTemplate - death: ISVGTemplate - marriage: ISVGTemplate - } + certificates: ICertificateConfigData[] } assets: { logo: string @@ -205,14 +201,6 @@ const CONFIG_CMD = Cmd.run(() => referenceApi.loadConfig(), { failActionCreator: actions.configFailed }) -const CERTIFICATE_CONFIG_CMD = Cmd.run( - () => referenceApi.loadCertificateConfiguration(), - { - successActionCreator: actions.certificateConfigurationLoaded, - failActionCreator: actions.certificateConfigurationLoadFailed - } -) - const CONTENT_CMD = Cmd.run(() => referenceApi.loadContent(), { successActionCreator: actions.contentLoaded, failActionCreator: actions.contentFailed @@ -247,7 +235,6 @@ function getDataLoadingCommands() { FACILITIES_CMD, LOCATIONS_CMD, CONFIG_CMD, - CERTIFICATE_CONFIG_CMD, CONDITIONALS_CMD, VALIDATORS_CMD, HANDLEBARS_CMD, @@ -373,58 +360,14 @@ function reducer( case actions.APPLICATION_CONFIG_LOADED: { const { certificates, config, systems } = action.payload merge(window.config, config) - const birthCertificateTemplate = certificates.find( - ({ event }) => event === Event.Birth - ) - - const deathCertificateTemplate = certificates.find( - ({ event }) => event === Event.Death - ) - - const marriageCertificateTemplate = certificates.find( - ({ event }) => event === Event.Marriage - ) - - let newOfflineData: Partial - if ( - birthCertificateTemplate && - deathCertificateTemplate && - marriageCertificateTemplate - ) { - const certificatesTemplates = { - birth: { - definition: birthCertificateTemplate.svgCode - }, - death: { - definition: deathCertificateTemplate.svgCode - }, - marriage: { - definition: marriageCertificateTemplate.svgCode - } - } - - newOfflineData = { - ...state.offlineData, - config, - systems, - templates: { - ...state.offlineData.templates, - certificates: certificatesTemplates - } - } - } else { - newOfflineData = { - ...state.offlineData, - config, - systems, - - // Field agents do not get certificate templates from the config service. - // Our loading logic depends on certificates being present and the app would load infinitely - // without a value here. - // This is a quickfix for the issue. If done properly, we should amend the "is loading" check - // to not expect certificate templates when a field agent is logged in. - templates: {} + const newOfflineData = { + ...state.offlineData, + config, + systems, + templates: { + ...state.offlineData.templates, + certificates } } @@ -482,23 +425,6 @@ function reducer( ) } - case actions.CERTIFICATE_CONFIGURATION_LOADED: { - return { - ...state, - offlineData: { - ...state.offlineData, - templates: { - ...state.offlineData.templates, - fonts: action.payload.fonts - } - } - } - } - - case actions.CERTIFICATE_CONFIGURATION_LOAD_FAILED: { - return loop(state, delay(CERTIFICATE_CONFIG_CMD, RETRY_TIMEOUT)) - } - /* * Locations */ diff --git a/packages/client/src/setupTests.ts b/packages/client/src/setupTests.ts index bc635072148..8d844ab9a87 100644 --- a/packages/client/src/setupTests.ts +++ b/packages/client/src/setupTests.ts @@ -40,11 +40,7 @@ const config = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 0, - DELAYED: 0 - } + PRINT_IN_ADVANCE: true }, COUNTRY: 'BGD', CURRENCY: { @@ -53,10 +49,7 @@ const config = { }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - } + PRINT_IN_ADVANCE: true }, FEATURES: { DEATH_REGISTRATION: true, @@ -201,7 +194,6 @@ vi.doMock( languages: mockOfflineData.languages }), loadConfig: () => Promise.resolve(mockConfigResponse), - loadCertificateConfiguration: () => Promise.resolve({}), loadConfigAnonymousUser: () => Promise.resolve(mockConfigResponse), loadForms: () => Promise.resolve(mockOfflineData.forms.forms), importConditionals: () => Promise.resolve({}), diff --git a/packages/client/src/tests/languages.json b/packages/client/src/tests/languages.json index e3329737a31..ab5d4e0bf42 100644 --- a/packages/client/src/tests/languages.json +++ b/packages/client/src/tests/languages.json @@ -52,6 +52,8 @@ "buttons.upload": "Upload", "buttons.yes": "Yes", "certificate.confirmCorrect": "Please confirm that the informant has reviewed that the information on the certificate is correct and that you are ready to print.", + "certificate.selectTemplate": "Select certificate template", + "certificate.selectedTemplate": "Selected certificate template", "certificate.isCertificateCorrect": "Is the {event} certificate correct?", "certificate.label.birth": "Birth", "certificate.label.death": "Death", @@ -1200,6 +1202,8 @@ "buttons.upload": "আপলোড", "buttons.yes": "হ্যাঁ", "certificate.confirmCorrect": "অনুগ্রহ করে নিশ্চিত করুন যে নিবন্ধনটি পর্যালোচনা হয়েছে তার তথ্য সঠিক এবং আপনি মুদ্রণ করতে প্রস্তুত", + "certificate.selectTemplate": "নিবন্ধন টেমপ্লেট নির্বাচন করুন", + "certificate.selectedTemplate": "নির্বাচিত নিবন্ধন টেমপ্লেট", "certificate.isCertificateCorrect": "জন্ম নিবন্ধনটি কি সঠিক?", "certificate.label.birth": "জন্ম", "certificate.label.death": "মৃত্যু", diff --git a/packages/client/src/tests/mock-offline-data.ts b/packages/client/src/tests/mock-offline-data.ts index 25da8514b92..bc1bcb4d382 100644 --- a/packages/client/src/tests/mock-offline-data.ts +++ b/packages/client/src/tests/mock-offline-data.ts @@ -426,27 +426,14 @@ export const mockOfflineData = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 15, - DELAYED: 20 - }, PRINT_IN_ADVANCE: true }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, MARRIAGE: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, FEATURES: { diff --git a/packages/client/src/tests/schema.graphql b/packages/client/src/tests/schema.graphql index eef8e51b9c2..f3cd1807ecc 100644 --- a/packages/client/src/tests/schema.graphql +++ b/packages/client/src/tests/schema.graphql @@ -293,7 +293,6 @@ input AvatarInput { type Birth { REGISTRATION_TARGET: Int LATE_REGISTRATION_TARGET: Int - FEE: BirthFee PRINT_IN_ADVANCE: Boolean } @@ -317,22 +316,9 @@ type BirthEventSearchSet implements EventSearchSet { fatherIdentifier: String } -type BirthFee { - ON_TIME: Float - LATE: Float - DELAYED: Float -} - -input BirthFeeInput { - ON_TIME: Float - LATE: Float - DELAYED: Float -} - input BirthInput { REGISTRATION_TARGET: Int LATE_REGISTRATION_TARGET: Int - FEE: BirthFeeInput PRINT_IN_ADVANCE: Boolean } @@ -403,41 +389,14 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - data: String + certificateTemplateId: String } input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String -} - -enum CertificateStatus { - ACTIVE - INACTIVE -} - -type CertificateSVG { - id: ID! - svgCode: String! - svgFilename: String! - svgDateUpdated: String! - svgDateCreated: String! - user: String! - event: Event! - status: CertificateStatus! -} - -input CertificateSVGInput { - id: ID - svgCode: String! - svgFilename: String! - svgDateUpdated: Int - svgDateCreated: Int - user: String! - event: Event! - status: CertificateStatus! + certificateTemplateId: String } type CertificationMetric { @@ -572,7 +531,6 @@ scalar FieldValue type Death { REGISTRATION_TARGET: Int - FEE: DeathFee PRINT_IN_ADVANCE: Boolean } @@ -586,19 +544,8 @@ type DeathEventSearchSet implements EventSearchSet { operationHistories: [OperationHistorySearchSet] } -type DeathFee { - ON_TIME: Float - DELAYED: Float -} - -input DeathFeeInput { - ON_TIME: Float - DELAYED: Float -} - input DeathInput { REGISTRATION_TARGET: Int - FEE: DeathFeeInput PRINT_IN_ADVANCE: Boolean } @@ -934,7 +881,6 @@ scalar Map type Marriage { REGISTRATION_TARGET: Int - FEE: MarriageFee PRINT_IN_ADVANCE: Boolean } @@ -950,19 +896,8 @@ type MarriageEventSearchSet implements EventSearchSet { operationHistories: [OperationHistorySearchSet] } -type MarriageFee { - ON_TIME: Float - DELAYED: Float -} - -input MarriageFeeInput { - ON_TIME: Float - DELAYED: Float -} - input MarriageInput { REGISTRATION_TARGET: Int - FEE: MarriageFeeInput PRINT_IN_ADVANCE: Boolean } @@ -1418,8 +1353,6 @@ type Query { sortBy: String sortOrder: String ): [SystemRole!] - getCertificateSVG(status: CertificateStatus!, event: Event!): CertificateSVG - getActiveCertificatesSVG: [CertificateSVG!] fetchSystem(clientId: ID!): System informantSMSNotifications: [SMSNotification!] } diff --git a/packages/client/src/tests/templates.json b/packages/client/src/tests/templates.json index bc7f832fae7..5d844ca3d50 100644 --- a/packages/client/src/tests/templates.json +++ b/packages/client/src/tests/templates.json @@ -7,19 +7,106 @@ "bolditalics": "NotoSans-Regular.ttf" } }, - "certificates": { - "birth": { - "definition": " {eventDate} Was born on Place of birth {placeOfBirth} {informantName} This is to certify that {registrationNumber} Birth Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-birth-certificate-v3.svg" + "certificates": [ + { + "id": "birth-certificate", + "event": "birth", + "label": { + "id": "certificates.birth.certificate", + "defaultMessage": "Birth Certificate", + "description": "The label for a birth certificate" + }, + + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, + "isDefault": true, + "svgUrl": "/api/countryconfig/certificates/birth-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } }, - "death": { - "definition": " {eventDate} Died on Place of death {placeOfDeath} {informantName} This is to certify that {registrationNumber} Death Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-death-certificate-v3.svg" + { + "id": "birth-certificate-copy", + "event": "birth", + "label": { + "id": "certificates.birth.certificate.copy", + "defaultMessage": "Birth Certificate certified copy", + "description": "The label for a birth certificate" + }, + + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, + "isDefault": false, + "svgUrl": "/api/countryconfig/certificates/birth-certificate-certified-copy.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } }, - "marriage": { - "definition": " {eventDate} Died on Place of death {placeOfDeath} {informantName} This is to certify that {registrationNumber} Death Registration No Date of issuance of certificate:{certificateDate} {registrarName} ({role}) This event was registered at{registrationLocation} ", - "fileName": "farajaland-marriage-certificate-v1.svg", - "lastModifiedDate": "1678106545001" + { + "id": "death-certificate", + "event": "death", + "label": { + "id": "certificates.death.certificate", + "defaultMessage": "Death Certificate", + "description": "The label for a death certificate" + }, + + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, + "isDefault": true, + "svgUrl": "/api/countryconfig/certificates/death-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } + }, + { + "id": "marriage-certificate", + "event": "marriage", + "label": { + "id": "certificates.marriage.certificate", + "defaultMessage": "Marriage Certificate", + "description": "The label for a marriage certificate" + }, + + "fee": { + "onTime": 0, + "late": 5.5, + "delayed": 15 + }, + "isDefault": true, + "svgUrl": "/api/countryconfig/certificates/marriage-certificate.svg", + "fonts": { + "Noto Sans": { + "normal": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bold": "/api/countryconfig/fonts/NotoSans-Bold.ttf", + "italics": "/api/countryconfig/fonts/NotoSans-Regular.ttf", + "bolditalics": "/api/countryconfig/fonts/NotoSans-Regular.ttf" + } + } } - } + ] } diff --git a/packages/client/src/tests/util.tsx b/packages/client/src/tests/util.tsx index 4e49085df6e..a197a8a8fb7 100644 --- a/packages/client/src/tests/util.tsx +++ b/packages/client/src/tests/util.tsx @@ -538,7 +538,6 @@ export const mockDeclarationData = { }, registration: { informantsSignature: 'data:image/png;base64,abcd', - registrationNumber: '201908122365BDSS0SE1', regStatus: { type: 'REGISTERED', @@ -547,7 +546,32 @@ export const mockDeclarationData = { officeAddressLevel3: 'Gazipur', officeAddressLevel4: 'Dhaka' }, - certificates: [{}] + certificates: [ + { + collector: { + relationship: 'OTHER', + affidavitFile: { + type: 'image/jpg', + data: 'data:image/png;base64,2324256' + }, + firstName: 'Doe', + lastName: 'Jane', + iD: '123456', + iDType: 'PASSPORT' + }, + certificateTemplateId: 'birth-certificate', + hasShowedVerifiedDocument: true, + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ] + } + ] }, documents: {} } @@ -645,9 +669,10 @@ export const mockDeathDeclarationData = { certificates: [ { collector: { - type: 'MOTHER' + relationship: 'MOTHER' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'death-certificate' } ] } @@ -676,7 +701,8 @@ export const mockMarriageDeclarationData = { collector: { type: 'BRIDE' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'marriage-certificate' } ] }, @@ -757,18 +783,27 @@ export const mockBirthRegistrationSectionData = { certificates: [ { collector: { - type: 'OTHER', - relationship: 'Uncle', - firstName: 'Mushraful', - lastName: 'Hoque', - iDType: 'PASSPORT', - iD: '123456789', + relationship: 'OTHER', affidavitFile: { - type: 'abc', - data: 'BASE64 data' - } + type: 'image/jpg', + data: 'data:image/png;base64,2324256' + }, + firstName: 'Doe', + lastName: 'Jane', + iD: '123456', + iDType: 'PASSPORT' }, - hasShowedVerifiedDocument: true + certificateTemplateId: 'birth-certificate', + hasShowedVerifiedDocument: true, + payments: [ + { + paymentId: '1234', + type: 'MANUAL', + amount: 50, + outcome: 'COMPLETED', + date: '2018-10-22' + } + ] } ] } @@ -795,33 +830,108 @@ export const mockDeathRegistrationSectionData = { iDType: 'PASSPORT', iD: '123456789' }, - hasShowedVerifiedDocument: true + hasShowedVerifiedDocument: true, + certificateTemplateId: 'death-certificate' } ] } const mockFetchCertificatesTemplatesDefinition = [ { - id: '12313546', - event: Event.Birth, - status: 'ACTIVE', - svgCode: - '\n\n\n\n{registrarName}
({role}) \n \n \nThis event was registered at {registrationLocation} \n{eventDate} \nDied on \nPlace of death \n \n{placeOfDeath} \n{informantName} \nThis is to certify that \n{registrationNumber} \nDeath Registration No \nDate of issuance of certificate: {certificateDate}\n\n\n\n\n\n\n\n\n\n\n', - svgDateCreated: '1640696680593', - svgDateUpdated: '1644326332088', - svgFilename: 'oCRVS_DefaultZambia_Death_v1.svg', - user: '61d42359f1a2c25ea01beb4b' + id: 'birth-certificate', + event: 'birth' as Event, + label: { + id: 'certificates.birth.certificate', + defaultMessage: 'Birth Certificate', + description: 'The label for a birth certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + isDefault: true, + svgUrl: '/api/countryconfig/certificates/birth-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } + }, + { + id: 'birth-certificate-copy', + event: 'birth' as Event, + label: { + id: 'certificates.birth-certificate-copy', + defaultMessage: 'Birth Certificate certified copy', + description: 'The label for a birth certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + isDefault: false, + svgUrl: '/api/countryconfig/certificates/birth-certificate-copy.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } }, { - id: '25313546', - event: Event.Death, - status: 'ACTIVE', - svgCode: - '\n\n\n\n{registrarName}
({role}) \n \n \nThis event was registered at {registrationLocation} \n{eventDate} \nWas born on \nPlace of birth \n \n{placeOfBirth} \n{informantName} \nThis is to certify that \n{registrationNumber} \nBirth Registration No \nDate of issuance of certificate: {certificateDate}\n\n\n\n\n\n\n\n\n\n\n', - svgDateCreated: '1640696804785', - svgDateUpdated: '1643885502999', - svgFilename: 'oCRVS_DefaultZambia_Birth_v1.svg', - user: '61d42359f1a2c25ea01beb4b' + id: 'death-certificate', + event: 'death' as Event, + label: { + id: 'certificates.death.certificate', + defaultMessage: 'Death Certificate', + description: 'The label for a death certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + isDefault: true, + svgUrl: '/api/countryconfig/certificates/death-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } + }, + { + id: 'marriage-certificate', + event: 'marriage' as Event, + label: { + id: 'certificates.marriage.certificate', + defaultMessage: 'Marriage Certificate', + description: 'The label for a marriage certificate' + }, + fee: { + onTime: 0, + late: 5.5, + delayed: 15 + }, + isDefault: true, + svgUrl: '/api/countryconfig/certificates/marriage-certificate.svg', + fonts: { + 'Noto Sans': { + normal: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bold: '/api/countryconfig/fonts/NotoSans-Bold.ttf', + italics: '/api/countryconfig/fonts/NotoSans-Regular.ttf', + bolditalics: '/api/countryconfig/fonts/NotoSans-Regular.ttf' + } + } } ] diff --git a/packages/client/src/transformer/index.ts b/packages/client/src/transformer/index.ts index f801defa8f4..9a1bb2d62d0 100644 --- a/packages/client/src/transformer/index.ts +++ b/packages/client/src/transformer/index.ts @@ -433,7 +433,14 @@ export const gqlToDraftTransformer = ( transformedData[section.id]._fhirID = queryData[section.id].id } if (section.mapping && section.mapping.query) { - section.mapping.query(transformedData, queryData, section.id) + section.mapping.query( + transformedData, + queryData, + section.id, + undefined, + undefined, + offlineData + ) } if (section.mapping?.template) { if (!transformedData.template) { diff --git a/packages/client/src/utils/gateway.ts b/packages/client/src/utils/gateway.ts index 3c50650dab7..3fc5f1fd1a2 100644 --- a/packages/client/src/utils/gateway.ts +++ b/packages/client/src/utils/gateway.ts @@ -383,16 +383,29 @@ export type BookmarkedSeachItem = { export type Certificate = { __typename?: 'Certificate' collector?: Maybe - data?: Maybe hasShowedVerifiedDocument?: Maybe payments?: Maybe>> + certificateTemplateId?: Maybe } export type CertificateInput = { collector?: InputMaybe - data?: InputMaybe hasShowedVerifiedDocument?: InputMaybe payments?: InputMaybe>> + certificateTemplateId?: InputMaybe +} + +export type CertificateLabel = { + __typename?: 'CertificateLabel' + defaultMessage: Scalars['String'] + description: Scalars['String'] + id: Scalars['String'] +} + +export type CertificateLabelInput = { + defaultMessage: Scalars['String'] + description: Scalars['String'] + id: Scalars['String'] } export type CertificationMetric = { @@ -718,6 +731,7 @@ export type History = { signature?: Maybe statusReason?: Maybe system?: Maybe + certificateTemplateId?: Maybe user?: Maybe } @@ -3235,6 +3249,28 @@ export type FetchBirthRegistrationForReviewQuery = { trackingId?: string | null registrationNumber?: string | null mosipAid?: string | null + certificates?: Array<{ + __typename?: 'Certificate' + hasShowedVerifiedDocument?: boolean | null + certificateTemplateId?: string | null + collector?: { + __typename?: 'RelatedPerson' + relationship?: string | null + otherRelationship?: string | null + name?: Array<{ + __typename?: 'HumanName' + use?: string | null + firstNames?: string | null + familyName?: string | null + } | null> | null + telecom?: Array<{ + __typename?: 'ContactPoint' + system?: string | null + value?: string | null + use?: string | null + } | null> | null + } | null + } | null> | null duplicates?: Array<{ __typename?: 'DuplicatesInfo' compositionId?: string | null @@ -3303,6 +3339,7 @@ export type FetchBirthRegistrationForReviewQuery = { reason?: string | null duplicateOf?: string | null potentialDuplicates?: Array | null + certificateTemplateId?: string | null documents: Array<{ __typename?: 'Attachment' id: string diff --git a/packages/client/src/utils/referenceApi.ts b/packages/client/src/utils/referenceApi.ts index 341257463b4..5cfec26a14c 100644 --- a/packages/client/src/utils/referenceApi.ts +++ b/packages/client/src/utils/referenceApi.ts @@ -77,9 +77,22 @@ interface ILoginBackground { backgroundImage?: string imageFit?: string } -export interface ICertificateTemplateData { +export interface ICertificateConfigData { + id: string event: Event - svgCode: string + label: { + id: string + defaultMessage: string + description: string + } + isDefault: boolean + fee: { + onTime: number + late: number + delayed: number + } + svgUrl: string + fonts?: Record } export interface ICurrency { isoCode: string @@ -98,29 +111,16 @@ export interface IApplicationConfig { BIRTH: { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } COUNTRY_LOGO: ICountryLogo CURRENCY: ICurrency DEATH: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } MARRIAGE: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } FEATURES: { @@ -143,7 +143,7 @@ export interface IApplicationConfig { } export interface IApplicationConfigResponse { config: IApplicationConfig - certificates: ICertificateTemplateData[] + certificates: ICertificateConfigData[] systems: System[] } @@ -250,33 +250,6 @@ async function importHandlebarHelpers(): Promise { return {} } } -async function loadCertificateConfiguration(): Promise { - const url = `${window.config.COUNTRY_CONFIG_URL}/certificate-configuration` - - const res = await fetch(url, { - method: 'GET' - }) - - // for backward compatibility, if the endpoint is unimplemented - if (res.status === 404) { - return { - fonts: { - notosans: { - normal: 'NotoSans-Light.ttf', - bold: 'NotoSans-Regular.ttf', - italics: 'NotoSans-Light.ttf', - bolditalics: 'NotoSans-Regular.ttf' - } - } - } - } - - if (!res.ok) { - throw Error(res.statusText) - } - - return res.json() -} async function loadContent(): Promise { const url = `${window.config.COUNTRY_CONFIG_URL}/content/client` @@ -415,7 +388,6 @@ async function loadFacilities(): Promise { export const referenceApi = { loadLocations, loadFacilities, - loadCertificateConfiguration, loadContent, loadConfig, loadForms, diff --git a/packages/client/src/views/DataProvider/birth/queries.ts b/packages/client/src/views/DataProvider/birth/queries.ts index d92e431c83b..8f7d1bb5108 100644 --- a/packages/client/src/views/DataProvider/birth/queries.ts +++ b/packages/client/src/views/DataProvider/birth/queries.ts @@ -149,6 +149,24 @@ export const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certificateTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } duplicates { compositionId trackingId @@ -207,6 +225,7 @@ export const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` requesterOther noSupportingDocumentationRequired hasShowedVerifiedDocument + certificateTemplateId date action regStatus @@ -295,6 +314,7 @@ export const GET_BIRTH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument + certificateTemplateId collector { relationship otherRelationship @@ -571,6 +591,7 @@ export const GET_BIRTH_REGISTRATION_FOR_CERTIFICATE = gql` } certificates { hasShowedVerifiedDocument + certificateTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/death/queries.ts b/packages/client/src/views/DataProvider/death/queries.ts index 1f42392fb84..62d4a92d1d5 100644 --- a/packages/client/src/views/DataProvider/death/queries.ts +++ b/packages/client/src/views/DataProvider/death/queries.ts @@ -213,6 +213,24 @@ export const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certificateTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } duplicates { compositionId trackingId @@ -288,6 +306,7 @@ export const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` requester requesterOther hasShowedVerifiedDocument + certificateTemplateId noSupportingDocumentationRequired date action @@ -363,6 +382,7 @@ export const GET_DEATH_REGISTRATION_FOR_REVIEW = gql` } certificates { hasShowedVerifiedDocument + certificateTemplateId collector { relationship otherRelationship @@ -628,6 +648,7 @@ export const GET_DEATH_REGISTRATION_FOR_CERTIFICATION = gql` } certificates { hasShowedVerifiedDocument + certificateTemplateId collector { relationship otherRelationship diff --git a/packages/client/src/views/DataProvider/marriage/queries.ts b/packages/client/src/views/DataProvider/marriage/queries.ts index 4c7eef18a50..e1ad4e7d258 100644 --- a/packages/client/src/views/DataProvider/marriage/queries.ts +++ b/packages/client/src/views/DataProvider/marriage/queries.ts @@ -158,6 +158,24 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` contactRelationship contactPhoneNumber contactEmail + certificates { + hasShowedVerifiedDocument + certificateTemplateId + collector { + relationship + otherRelationship + name { + use + firstNames + familyName + } + telecom { + system + value + use + } + } + } groomSignature brideSignature witnessOneSignature @@ -228,6 +246,7 @@ const GET_MARRIAGE_REGISTRATION_FOR_REVIEW = gql` otherReason requester hasShowedVerifiedDocument + certificateTemplateId noSupportingDocumentationRequired date action diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx index 90492339a88..2b21513c3ff 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueCollectorForm.tsx @@ -63,7 +63,8 @@ export function IssueCollectorForm({ certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + certificateTemplateId: certificate.certificateTemplateId } ] } @@ -76,12 +77,17 @@ export function IssueCollectorForm({ function continueButtonHandler() { const relationship = declaration.data.registration.certificates[0].collector?.type - const event = declaration.event if (!relationship) return if (relationship === 'OTHER') { dispatch(goToIssueCertificate(declaration.id, 'otherCollector')) } else { - dispatch(goToVerifyIssueCollector(declaration.id, event, relationship)) + dispatch( + goToVerifyIssueCollector( + declaration.id, + declaration.event, + relationship + ) + ) } } diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx index bf8a07d5f38..d93ddd9ef28 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssueFormForOthers.tsx @@ -59,7 +59,11 @@ export const IssueCollectorFormForOthers = ({ const dispatch = useDispatch() const config = useSelector(getOfflineData) const user = useSelector(getUserDetails) - + const { relationship, ...collectorForm }: { [key: string]: any } = + (declaration && + declaration.data.registration.certificates && + declaration.data.registration.certificates[0].collector) || + {} const fields: IFormField[] = collectorFormFieldsForOthers(declaration.event) const handleChange = ( sectionData: ICertificate['collector'], @@ -79,7 +83,8 @@ export const IssueCollectorFormForOthers = ({ certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + certificateTemplateId: certificate.certificateTemplateId } ] } @@ -100,8 +105,7 @@ export const IssueCollectorFormForOthers = ({ } function continueButtonHandler() { - const event = declaration.event - dispatch(goToIssueCertificatePayment(declaration.id, event)) + dispatch(goToIssueCertificatePayment(declaration.id, declaration.event)) } return ( @@ -138,12 +142,7 @@ export const IssueCollectorFormForOthers = ({ setAllFieldsDirty={false} fields={replaceInitialValues( fields, - (declaration && - declaration.data.registration.certificates && - declaration.data.registration.certificates[ - declaration.data.registration.certificates.length - 1 - ].collector) || - {}, + collectorForm, declaration && declaration.data, config, user diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx index 1a8c4a62b94..4affe015586 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.test.tsx @@ -88,7 +88,7 @@ describe('verify collector tests for issuance', () => { history }) expect(testComponent.find('#service').hostNodes().text()).toContain('Birth') - expect(testComponent.find('#amountDue').hostNodes().text()).toContain('20') + expect(testComponent.find('#amountDue').hostNodes().text()).toContain('15') testComponent.find('#Continue').hostNodes().simulate('click') }) @@ -140,7 +140,7 @@ describe('in case of death declaration renders issue payment component', () => { history }) expect(testComponent.find('#service').hostNodes().text()).toContain('Death') - expect(testComponent.find('#amountDue').hostNodes().text()).toContain('0.0') + expect(testComponent.find('#amountDue').hostNodes().text()).toContain('15') testComponent.find('#Continue').hostNodes().simulate('click') }) }) diff --git a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx index ffd2b09c28f..f966bb3ec72 100644 --- a/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx +++ b/packages/client/src/views/IssueCertificate/IssueCollectorForm/IssuePayment.tsx @@ -10,6 +10,7 @@ */ import { + ICertificate, IDeclaration, IPrintableDeclaration, modifyDeclaration, @@ -81,7 +82,8 @@ export const IssuePayment = () => { event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + certificate as ICertificate ) certificate.payments = { type: 'MANUAL' as const, @@ -117,7 +119,8 @@ export const IssuePayment = () => { event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + declaration.data.registration.certificates[0] ) const serviceMessage = getServiceMessage( diff --git a/packages/client/src/views/PrintCertificate/PDFUtils.ts b/packages/client/src/views/PrintCertificate/PDFUtils.ts index 91fe857eec4..4a79878c098 100644 --- a/packages/client/src/views/PrintCertificate/PDFUtils.ts +++ b/packages/client/src/views/PrintCertificate/PDFUtils.ts @@ -14,11 +14,7 @@ import { createIntl, createIntlCache } from 'react-intl' -import { - AdminStructure, - ILocation, - IOfflineData -} from '@client/offline/reducer' +import { AdminStructure, ILocation } from '@client/offline/reducer' import { IPDFTemplate } from '@client/pdfRenderer' import { certificateBaseTemplate } from '@client/templates/register' import * as Handlebars from 'handlebars' @@ -27,7 +23,10 @@ import { getOfflineData } from '@client/offline/selectors' import isValid from 'date-fns/isValid' import format from 'date-fns/format' import { getHandlebarHelpers } from '@client/forms/handlebarHelpers' -import { FontFamilyTypes } from '@client/utils/referenceApi' +import { + CertificateConfiguration, + FontFamilyTypes +} from '@client/utils/referenceApi' type TemplateDataType = string | MessageDescriptor | Array function isMessageDescriptor( @@ -223,12 +222,15 @@ src: url("${url}") format("truetype"); const serializer = new XMLSerializer() return serializer.serializeToString(svg) } -export function svgToPdfTemplate(svg: string, offlineResource: IOfflineData) { +export function svgToPdfTemplate( + svg: string, + certificateFonts: CertificateConfiguration +) { const pdfTemplate: IPDFTemplate = { ...certificateBaseTemplate, fonts: { ...certificateBaseTemplate.fonts, - ...offlineResource.templates.fonts + ...certificateFonts } } diff --git a/packages/client/src/views/PrintCertificate/Payment.test.tsx b/packages/client/src/views/PrintCertificate/Payment.test.tsx index 9896a006a72..9e9772f8973 100644 --- a/packages/client/src/views/PrintCertificate/Payment.test.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.test.tsx @@ -27,111 +27,115 @@ import { vi, Mock } from 'vitest' import { WORKQUEUE_TABS } from '@client/components/interface/Navigation' import { REGISTRAR_HOME_TAB } from '@client/navigation/routes' import { formatUrl } from '@client/navigation' +import { IFormSectionData } from '@client/forms' +// Mock setup const getItem = window.localStorage.getItem as Mock ;(queries.fetchUserDetails as Mock).mockReturnValue(mockUserResponse) +// Common mock data +const birthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Birth +} + +const deathDeclaration = { + id: 'mockDeath1234', + data: { + ...mockDeathDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Death +} + +// Helper function to set up test +async function setupPaymentTest({ + registrationId, + eventType, + declaration, + store, + history, + mockLocation +}: { + registrationId: string + eventType: string + declaration: any + store: any + history: any + mockLocation: any +}) { + store.dispatch(storeDeclaration(declaration)) + await flushPromises() + + const testComponent = await createTestComponent( + , + { store, history } + ) + + return testComponent +} + describe('verify collector tests', () => { const { store, history } = createStore() const mockLocation: any = vi.fn() - const birthDeclaration = { - id: 'mockBirth1234', - data: { - ...mockDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Birth - } - - const deathDeclaration = { - id: 'mockDeath1234', - data: { - ...mockDeathDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Death - } - describe('in case of birth declaration', () => { beforeAll(async () => { getItem.mockReturnValue(validToken) await store.dispatch(checkAuth()) await flushPromises() - // @ts-ignore store.dispatch(storeDeclaration(birthDeclaration)) }) - it('when mother is collector renders Payment component', async () => { - const testComponent = await createTestComponent( - , - { store, history } - ) + it('renders Payment component when mother is collector', async () => { + const testComponent = await setupPaymentTest({ + registrationId: 'mockBirth1234', + eventType: 'birth', + declaration: birthDeclaration, + store, + history, + mockLocation + }) expect(testComponent.find('#service').hostNodes().text()).toContain( 'Birth' ) - expect(testComponent.find('#amountDue').hostNodes().text()).toContain( - '20' + '15' ) testComponent.find('#Continue').hostNodes().simulate('click') }) - /* - - // Commenting out this test because receipt templates are not currently configurable - - it('print payment receipt', async () => { - const printMoneyReceiptSpy = vi.spyOn(PDFUtils, 'printMoneyReceipt') - const testComponent = await createTestComponent( - , - { store, history } - ) - - testComponent.find('#print-receipt').hostNodes().simulate('click') - - expect(printMoneyReceiptSpy).toBeCalled() - })*/ - - it('invalid declaration id', async () => { + it('redirects to home on invalid declaration id', async () => { await createTestComponent( { match={{ params: { registrationId: 'mockBirth', - eventType: Event.Birth + eventType: 'birth' }, isExact: true, path: '', @@ -148,6 +152,7 @@ describe('verify collector tests', () => { />, { store, history } ) + expect(history.location.pathname).toEqual( formatUrl(REGISTRAR_HOME_TAB, { tabId: WORKQUEUE_TABS.readyToPrint, @@ -157,29 +162,20 @@ describe('verify collector tests', () => { }) }) - describe('in case of death declaration renders payment component', () => { + describe('in case of death declaration renders Payment component', () => { beforeAll(() => { - // @ts-ignore store.dispatch(storeDeclaration(deathDeclaration)) }) - it('when informant is collector', async () => { - const testComponent = await createTestComponent( - , - { store, history } - ) + it('renders Payment component when informant is collector', async () => { + const testComponent = await setupPaymentTest({ + registrationId: 'mockDeath1234', + eventType: 'death', + declaration: deathDeclaration, + store, + history, + mockLocation + }) expect(testComponent.find('#service').hostNodes().text()).toContain( 'Death' diff --git a/packages/client/src/views/PrintCertificate/Payment.tsx b/packages/client/src/views/PrintCertificate/Payment.tsx index 296af04acc1..77c08f82071 100644 --- a/packages/client/src/views/PrintCertificate/Payment.tsx +++ b/packages/client/src/views/PrintCertificate/Payment.tsx @@ -118,7 +118,8 @@ const PaymentComponent = ({ event, eventDate, registeredDate, - offlineCountryConfig + offlineCountryConfig, + declaration.data.registration.certificates[0] ) const serviceMessage = getServiceMessage( @@ -197,7 +198,7 @@ function mapStatetoProps( props: RouteComponentProps<{ registrationId: string; eventType: string }> ) { const { registrationId, eventType } = props.match.params - const event = getEvent(eventType) + const event = getEvent(eventType) as Event const declaration = state.declarationsState.declarations.find( (app) => app.id === registrationId && app.event === event ) as IPrintableDeclaration diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx index dd723b7643d..78b3a1d253f 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.test.tsx @@ -8,10 +8,10 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { Mock } from 'vitest' +import { vi, Mock } from 'vitest' import * as React from 'react' import { createStore } from '@client/store' -import { storeDeclaration, IDeclaration } from '@client/declarations' +import { storeDeclaration } from '@client/declarations' import { createTestComponent, mockDeclarationData, @@ -30,6 +30,22 @@ import { waitForElement } from '@client/tests/wait-for-element' import { push } from 'connected-react-router' import { useParams } from 'react-router' +const mockSvgTemplate = 'Sample Certificate' +const birthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] as unknown as IFormSectionData + }, + event: Event.Birth +} + const deathDeclaration = { id: 'mockDeath1234', data: { @@ -40,140 +56,171 @@ const deathDeclaration = { regStatus: 'REGISTERED', reinstated: false } - ] + ] as unknown as IFormSectionData }, event: Event.Death } -describe('when user wants to review death certificate', () => { - it('displays the "Confirm & Print" button', async () => { - const { history, match } = createRouterProps( - '/', - { isNavigatedInsideApp: false }, +const marriageDeclaration = { + id: 'mockMarriage1234', + data: { + ...mockMarriageDeclarationData, + history: [ { - matchParams: { - registrationId: 'mockDeath1234', - eventType: Event.Death - } + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false } - ) - ;(useParams as Mock).mockImplementation(() => match.params) + ] as unknown as IFormSectionData + }, + event: Event.Marriage +} - const { store } = createStore() +async function setupTest({ + declaration, + registrationId +}: { + declaration: any + registrationId: string +}) { + global.fetch = vi.fn().mockImplementation(() => + Promise.resolve({ + text: vi.fn().mockResolvedValue(mockSvgTemplate) + }) + ) + + const { history, match } = createRouterProps( + '/', + { isNavigatedInsideApp: false }, + { + matchParams: { + registrationId + } + } + ) - loginAsFieldAgent(store) + ;(useParams as Mock).mockImplementation(() => match.params) - const component = await createTestComponent(, { - store, - history - }) + const { store } = createStore(history) + loginAsFieldAgent(store) + const clonedDeclaration = cloneDeep(declaration) - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - component.update() + await flushPromises() + store.dispatch(storeDeclaration(clonedDeclaration)) - const confirmBtn = component.find('#confirm-print') - const confirmBtnExist = !!confirmBtn.hostNodes().length - expect(confirmBtnExist).toBe(true) + const component = await createTestComponent(, { + store, + history }) -}) -describe('back button behavior tests of review certificate action', () => { - let component: ReactWrapper + await flushPromises() + component.update() - const mockBirthDeclarationData = { - ...cloneDeep(mockDeclarationData), - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - } - mockBirthDeclarationData.registration.certificates[0] = { - collector: { - type: 'PRINT_IN_ADVANCE' - } - } + return component +} - it('takes user history back when navigated from inside app', async () => { - const { history, match } = createRouterProps( - '/previous-route', - { isNavigatedInsideApp: true }, - { - matchParams: { - registrationId: 'asdhdqe2472487jsdfsdf', - eventType: Event.Birth - } - } - ) - ;(useParams as Mock).mockImplementation(() => match.params) +describe('Review Certificate Tests', () => { + let component: ReactWrapper<{}, {}> - const { store } = createStore(history) + describe('when user wants to review death certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: deathDeclaration, + registrationId: 'mockDeath1234' + }) + }) - store.dispatch(push('/new-route', { isNavigatedInsideApp: true })) + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) + }) + }) - loginAsFieldAgent(store) - const birthDeclaration = { - id: 'asdhdqe2472487jsdfsdf', - data: mockBirthDeclarationData, - event: Event.Birth - } - store.dispatch( - // @ts-ignore - storeDeclaration(birthDeclaration) - ) - component = await createTestComponent(, { - store, - history + describe('when user wants to review birth certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: birthDeclaration, + registrationId: 'mockBirth1234' + }) }) - component.find('#action_page_back_button').hostNodes().simulate('click') - expect(history.location.pathname).toBe('/previous-route') + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) + }) + + it('shows the Confirm Print Modal', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + const modal = await waitForElement(component, '#confirm-print-modal') + const modalIsDisplayed = !!modal.hostNodes().length + expect(modalIsDisplayed).toBe(true) + }) + + it('closes the modal on clicking the print the button', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + component.find('#print-certificate').hostNodes().simulate('click') + component.update() + + const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() + .length + expect(modalIsClosed).toBe(false) + }) }) - it('takes user to registration home when navigated from external link', async () => { - const { history, match } = createRouterProps( - '/previous-route', - { isNavigatedInsideApp: false }, - { - matchParams: { - registrationId: 'asdhdqe2472487jsdfsdf', - eventType: Event.Birth - } - } - ) - ;(useParams as Mock).mockImplementation(() => match.params) - const { store } = createStore(history) + describe('when user wants to review marriage certificate', () => { + beforeEach(async () => { + component = await setupTest({ + declaration: marriageDeclaration, + registrationId: 'mockMarriage1234' + }) + }) - loginAsFieldAgent(store) - store.dispatch( - // @ts-ignore - storeDeclaration({ - id: 'asdhdqe2472487jsdfsdf', - data: mockBirthDeclarationData, - event: Event.Birth - } as IDeclaration) - ) - component = await createTestComponent(, { - store, - history + it('displays the "Confirm & Print" button', async () => { + const confirmBtnExist = !!( + await waitForElement(component, '#confirm-print') + ).hostNodes().length + expect(confirmBtnExist).toBe(true) }) - component.find('#action_page_back_button').hostNodes().simulate('click') - await flushPromises() - expect(history.location.pathname).toContain('/registration-home/print/') + it('shows the Confirm Print Modal', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + const modalIsDisplayed = !!( + await waitForElement(component, '#confirm-print-modal') + ).hostNodes().length + expect(modalIsDisplayed).toBe(true) + }) + + it('closes the modal on clicking the print the button', async () => { + const confirmBtn = await waitForElement(component, '#confirm-print') + confirmBtn.hostNodes().simulate('click') + component.update() + component.find('#print-certificate').hostNodes().simulate('click') + component.update() + + const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() + .length + expect(modalIsClosed).toBe(false) + }) }) }) -describe('when user wants to review birth certificate', () => { - let component: ReactWrapper<{}, {}> +describe('Back button behavior tests of review certificate action', () => { + let component: ReactWrapper - beforeEach(async () => { + it('takes user history back when navigated from inside app', async () => { const { history, match } = createRouterProps( - '/', - { isNavigatedInsideApp: false }, + '/previous-route', + { isNavigatedInsideApp: true }, { matchParams: { registrationId: 'asdhdqe2472487jsdfsdf', @@ -182,81 +229,30 @@ describe('when user wants to review birth certificate', () => { } ) ;(useParams as Mock).mockImplementation(() => match.params) + const { store } = createStore(history) - const mockBirthDeclarationData = cloneDeep(mockDeclarationData) - mockBirthDeclarationData.registration.certificates[0] = { - collector: { - type: 'PRINT_IN_ADVANCE' - } - } + store.dispatch(push('/new-route', { isNavigatedInsideApp: true })) loginAsFieldAgent(store) - await flushPromises() - store.dispatch( - storeDeclaration({ - id: 'asdhdqe2472487jsdfsdf', - data: { - ...mockBirthDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] as unknown as IFormSectionData - }, - event: Event.Birth - }) - ) + + store.dispatch(storeDeclaration(birthDeclaration)) component = await createTestComponent(, { store, history }) - await flushPromises() - component.update() - }) - it('displays have the Continue and print Button', () => { - const confirmBtnExist = !!component.find('#confirm-print').hostNodes() - .length - expect(confirmBtnExist).toBe(true) - }) - - it('shows the Confirm Print Modal', () => { - const confirmBtn = component.find('#confirm-print').hostNodes() - confirmBtn.simulate('click') - component.update() - const modalIsDisplayed = !!component - .find('#confirm-print-modal') - .hostNodes().length - expect(modalIsDisplayed).toBe(true) - }) - - it('closes the modal on clicking the print the button', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - confirmBtn.hostNodes().simulate('click') - component.update() - component.find('#print-certificate').hostNodes().simulate('click') - component.update() - - const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() - .length - - expect(modalIsClosed).toBe(false) + component.find('#action_page_back_button').hostNodes().simulate('click') + expect(history.location.pathname).toBe('/previous-route') }) -}) - -describe('when user wants to review marriage certificate', () => { - let component: ReactWrapper<{}, {}> - beforeEach(async () => { + it('takes user to registration home when navigated from external link', async () => { const { history, match } = createRouterProps( - '/', + '/previous-route', { isNavigatedInsideApp: false }, { matchParams: { - registrationId: '1234896128934719', + registrationId: 'mockBirth1234', eventType: Event.Birth } } @@ -264,61 +260,17 @@ describe('when user wants to review marriage certificate', () => { ;(useParams as Mock).mockImplementation(() => match.params) const { store } = createStore(history) - const mockMarriageData = cloneDeep(mockMarriageDeclarationData) - loginAsFieldAgent(store) - await flushPromises() - store.dispatch( - storeDeclaration({ - id: '1234896128934719', - data: { - ...mockMarriageData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] as unknown as IFormSectionData - }, - event: Event.Marriage - }) - ) + + store.dispatch(storeDeclaration(birthDeclaration)) component = await createTestComponent(, { store, history }) - await flushPromises() - component.update() - }) - - it('displays have the Continue and print Button', () => { - const confirmBtnExist = !!component.find('#confirm-print').hostNodes() - .length - expect(confirmBtnExist).toBe(true) - }) - - it('shows the Confirm Print Modal', () => { - const confirmBtn = component.find('#confirm-print').hostNodes() - confirmBtn.simulate('click') - component.update() - const modalIsDisplayed = !!component - .find('#confirm-print-modal') - .hostNodes().length - expect(modalIsDisplayed).toBe(true) - }) - - it('closes the modal on clicking the print the button', async () => { - const confirmBtn = await waitForElement(component, '#confirm-print') - confirmBtn.hostNodes().simulate('click') - component.update() - component.find('#print-certificate').hostNodes().simulate('click') - component.update() - const modalIsClosed = !!component.find('#confirm-print-modal').hostNodes() - .length + component.find('#action_page_back_button').hostNodes().simulate('click') - expect(modalIsClosed).toBe(false) + expect(history.location.pathname).toContain('/registration-home/print/') }) }) diff --git a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx index ac5ee08d4b1..7a36f05e120 100644 --- a/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx +++ b/packages/client/src/views/PrintCertificate/ReviewCertificateAction.tsx @@ -21,7 +21,7 @@ import { } from '@opencrvs/components' import React from 'react' import { useDispatch } from 'react-redux' -import { useLocation, useParams } from 'react-router' +import { useLocation } from 'react-router' import { messages as certificateMessages } from '@client/i18n/messages/views/certificate' import { useIntl } from 'react-intl' import { buttonMessages } from '@client/i18n/messages/buttons' @@ -110,19 +110,17 @@ const ReviewCertificateFrame = ({ } export const ReviewCertificate = () => { - const { registrationId } = useParams<{ registrationId: string }>() - const { - svg, + svgCode, handleCertify, isPrintInAdvance, canUserEditRecord, handleEdit - } = usePrintableCertificate(registrationId) + } = usePrintableCertificate() const intl = useIntl() const [modal, openModal] = useModal() - if (!svg) { + if (!svgCode) { return ( @@ -183,7 +181,7 @@ export const ReviewCertificate = () => { { - const { store, history } = createStore() - - const birthDeclaration = { - id: 'mockBirth1234', - data: { - ...mockDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] - }, - event: Event.Birth - } - - const deathDeclaration = { - id: 'mockDeath1234', - data: { +// Common mock data +const birthDeclaration = { + id: 'mockBirth1234', + data: { + ...mockDeclarationData, + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] + }, + event: Event.Birth +} + +const deathDeclaration = { + id: 'mockDeath1234', + data: { + ...{ ...mockDeathDeclarationData, - history: [ - { - date: '2022-03-21T08:16:24.467+00:00', - regStatus: 'REGISTERED', - reinstated: false - } - ] + deathEvent: { + ...mockDeathDeclarationData.deathEvent, + deathDate: new Date().toISOString().slice(0, 10) + } }, - event: Event.Death - } + history: [ + { + date: '2022-03-21T08:16:24.467+00:00', + regStatus: 'REGISTERED', + reinstated: false + } + ] + }, + event: Event.Death +} + +// Helper function for setting up tests +async function setupTest({ + registrationId, + event, + collector, + declaration, + store, + history +}: { + registrationId: string + event: string + collector: string + declaration: any + store: any + history: any +}) { + store.dispatch(storeDeclaration(declaration)) + await flushPromises() + + const testComponent = await createTestComponent( + // @ts-ignore + , + { store, history } + ) + + return testComponent +} + +describe('verify collector tests', () => { + const { store, history } = createStore() describe('in case of birth declaration', () => { + let testComponent: ReactWrapper + beforeAll(async () => { - // @ts-ignore - store.dispatch(storeDeclaration(birthDeclaration)) + testComponent = await setupTest({ + registrationId: 'mockBirth1234', + event: 'birth', + collector: 'mother', + declaration: birthDeclaration, + store, + history + }) }) - it('when mother is collector renders idVerifier component', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - + it('renders idVerifier component when mother is collector', () => { expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) }) - it('should takes user go back', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - + it('takes user back on clicking back button', async () => { testComponent .find('#action_page_back_button') .hostNodes() .simulate('click') - await new Promise((resolve) => { - setTimeout(resolve, 500) - }) - + await flushPromises() testComponent.update() expect(history.location.pathname).toBe('/') }) + }) - describe('when informant is collector', () => { - let testComponent: ReactWrapper - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - }) - beforeEach(async () => { - testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - }) - - it('renders idVerifier compomnent', () => { - expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) - }) - - it('clicking on yes button takes user to review certificate if there is no fee', () => { - testComponent - .find('#idVerifier') - .find('#verifyPositive') - .hostNodes() - .simulate('click') + describe('when informant is collector for death declaration', () => { + let testComponent: ReactWrapper - expect(history.location.pathname).toContain('review') + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockDeath1234', + event: 'death', + collector: 'informant', + declaration: deathDeclaration, + store, + history }) + }) - describe('when father is collector', () => { - let testComponent: ReactWrapper - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(birthDeclaration)) - }) - beforeEach(async () => { - testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) - }) + it('renders idVerifier component', () => { + expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) + }) - it('clicking on send button on modal takes user to payment if there is fee', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') + it('redirects to review page on clicking yes button with no fee', () => { + testComponent + .find('#idVerifier #verifyPositive') + .hostNodes() + .simulate('click') - testComponent.update() + expect(history.location.pathname).toContain('review') + }) + it('clicking on cancel button hides the modal', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - testComponent - .find('#withoutVerificationPrompt') - .find('#send') - .hostNodes() - .simulate('click') + testComponent.update() + testComponent + .find('#withoutVerificationPrompt #cancel') + .hostNodes() + .simulate('click') - expect(history.location.pathname).toContain('payment') - }) - }) + testComponent.update() + expect( + testComponent.find('#withoutVerificationPrompt').hostNodes() + ).toHaveLength(0) + }) + it('shows modal on clicking no button', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - it('clicking on no button shows up modal', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') + testComponent.update() + expect( + testComponent.find('#withoutVerificationPrompt').hostNodes() + ).toHaveLength(1) + }) + }) - testComponent.update() + describe('when father is collector for birth declaration', () => { + let testComponent: ReactWrapper - expect( - testComponent.find('#withoutVerificationPrompt').hostNodes() - ).toHaveLength(1) + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockBirth1234', + event: 'birth', + collector: 'father', + declaration: birthDeclaration, + store, + history }) + }) - it('clicking on cancel button hides the modal', () => { - testComponent - .find('#idVerifier') - .find('#verifyNegative') - .hostNodes() - .simulate('click') - - testComponent.update() - - testComponent - .find('#withoutVerificationPrompt') - .find('#cancel') - .hostNodes() - .simulate('click') + it('takes user to payment page on clicking send button when there is fee', () => { + testComponent + .find('#idVerifier #verifyNegative') + .hostNodes() + .simulate('click') - testComponent.update() + testComponent.update() + testComponent + .find('#withoutVerificationPrompt #send') + .hostNodes() + .simulate('click') - expect( - testComponent.find('#withoutVerificationPrompt').hostNodes() - ).toHaveLength(0) - }) + expect(history.location.pathname).toContain('payment') }) }) - describe('in case of death declaration renders idVerifier component', () => { - beforeAll(() => { - // @ts-ignore - store.dispatch(storeDeclaration(deathDeclaration)) - }) + describe('in case of death declaration with informant as collector', () => { + let testComponent: ReactWrapper - it('when informant is collector', async () => { - const testComponent = await createTestComponent( - // @ts-ignore - , - { store, history } - ) + beforeAll(async () => { + testComponent = await setupTest({ + registrationId: 'mockDeath1234', + event: 'death', + collector: 'informant', + declaration: deathDeclaration, + store, + history + }) + }) + it('renders idVerifier component', () => { expect(testComponent.find('#idVerifier').hostNodes()).toHaveLength(1) }) }) diff --git a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx index 5ae1f8aff0a..4765491b787 100644 --- a/packages/client/src/views/PrintCertificate/VerifyCollector.tsx +++ b/packages/client/src/views/PrintCertificate/VerifyCollector.tsx @@ -14,7 +14,6 @@ import { modifyDeclaration, writeDeclaration } from '@client/declarations' -import { Event } from '@client/utils/gateway' import { messages } from '@client/i18n/messages/views/certificate' import { formatUrl, @@ -48,6 +47,7 @@ import { IForm } from '@client/forms' import { getEventRegisterForm } from '@client/forms/register/declaration-selectors' import { UserDetails } from '@client/utils/userUtils' import { getUserDetails } from '@client/profile/profileSelectors' +import { Event } from '@client/utils/gateway' interface IMatchParams { registrationId: string @@ -95,7 +95,7 @@ class VerifyCollectorComponent extends React.Component { if ( isFreeOfCost( - event, + declaration.data.registration.certificates[0], eventDate, registeredDate, offlineCountryConfiguration diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx index 32b2dbf0184..fcc39216393 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.test.tsx @@ -17,17 +17,20 @@ import { inValidImageB64String, mockDeclarationData, mockDeathDeclarationData, - mockMarriageDeclarationData + mockMarriageDeclarationData, + mockOfflineData, + flushPromises } from '@client/tests/util' import { ReactWrapper } from 'enzyme' import * as React from 'react' import { CollectorForm } from './CollectorForm' import { waitFor, waitForElement } from '@client/tests/wait-for-element' import { createLocation, History } from 'history' -import { merge } from 'lodash' +import { cloneDeep, merge } from 'lodash' import { Event } from '@client/utils/gateway' -import { storeDeclaration } from '@client/declarations' +import { modifyDeclaration, storeDeclaration } from '@client/declarations' import { vi } from 'vitest' +import { getOfflineDataSuccess } from '@client/offline/actions' let store: AppStore let history: History @@ -282,7 +285,9 @@ describe('Certificate collector test for a birth registration without father det /* * Who is collecting the certificate? */ - store.dispatch(storeDeclaration(birthDeclaration)) + const declaration = cloneDeep(birthDeclaration) + declaration.data.registration.certificates = [] + store.dispatch(storeDeclaration(declaration)) component = await createTestComponent( { - birthDeclaration.data.child.childBirthDate = '2022-09-20' - store.dispatch(storeDeclaration(birthDeclaration)) + // setting date of birth today + const clonedBirthDeclaration = cloneDeep(birthDeclaration) + clonedBirthDeclaration.data.child.childBirthDate = new Date() + .toISOString() + .slice(0, 10) + store.dispatch(modifyDeclaration(clonedBirthDeclaration)) + + // setting on time birth certificate fee amount to 0 + const offlineDataResponse = JSON.stringify({ + ...mockOfflineData, + templates: { + ...mockOfflineData.templates, + certificates: mockOfflineData.templates.certificates.map( + (x: any) => { + if (x.event === 'birth') { + x.fee.onTime = 0 + } + return x + } + ) + } + }) + store.dispatch(getOfflineDataSuccess(offlineDataResponse)) + await flushPromises() + const comp = await waitForElement( component, '#noAffidavitAgreementAFFIDAVIT' diff --git a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx index 6a465b56984..394e6c94169 100644 --- a/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx +++ b/packages/client/src/views/PrintCertificate/collectorForm/CollectorForm.tsx @@ -210,7 +210,6 @@ class CollectorFormComponent extends React.Component { const certificates = declaration.data.registration.certificates const certificate = (certificates && certificates[0]) || {} const collector = { ...(certificate.collector || {}), ...sectionData } - this.props.modifyDeclaration({ ...declaration, data: { @@ -220,7 +219,8 @@ class CollectorFormComponent extends React.Component { certificates: [ { collector: collector, - hasShowedVerifiedDocument: false + hasShowedVerifiedDocument: false, + certificateTemplateId: collector.certificateTemplateId } ] } @@ -324,7 +324,7 @@ class CollectorFormComponent extends React.Component { .props as PropsWhenDeclarationIsFound if ( isFreeOfCost( - event, + declaration.data.registration.certificates[0], getEventDate(declaration.data, event), getRegisteredDate(declaration.data), offlineCountryConfiguration @@ -507,7 +507,10 @@ const mapStateToProps = ( const userOfficeId = userDetails?.primaryOffice?.id const registeringOfficeId = getRegisteringOfficeId(declaration) - const certFormSection = getCertificateCollectorFormSection(declaration) + const certFormSection = getCertificateCollectorFormSection( + declaration, + state.offline.offlineData.templates?.certificates || [] + ) const isAllowPrintInAdvance = event === Event.Birth @@ -545,7 +548,7 @@ const mapStateToProps = ( declaration.data.registration.certificates && declaration.data.registration.certificates[ declaration.data.registration.certificates.length - 1 - ].collector) || + ]?.collector) || {}, declaration && declaration.data, offlineCountryConfiguration, diff --git a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts index e69966f3389..2d9ec6e9a88 100644 --- a/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts +++ b/packages/client/src/views/PrintCertificate/usePrintableCertificate.ts @@ -44,6 +44,9 @@ import { formatLongDate } from '@client/utils/date-formatting' import { AdminStructure, IOfflineData } from '@client/offline/reducer' import { getLocationHierarchy } from '@client/utils/locationUtils' import { printPDF } from '@client/pdfRenderer' +import { useEffect, useState } from 'react' +import { useParams } from 'react-router' +import { ICertificateConfigData } from '@client/utils/referenceApi' const withEnhancedTemplateVariables = ( declaration: IPrintableDeclaration | undefined, @@ -60,7 +63,8 @@ const withEnhancedTemplateVariables = ( declaration.event, eventDate, registeredDate, - offlineData + offlineData, + declaration.data.registration.certificates[0] ) const locationKey = userDetails?.primaryOffice?.id @@ -100,10 +104,13 @@ const withEnhancedTemplateVariables = ( } } -export const usePrintableCertificate = (declarationId: string) => { +export const usePrintableCertificate = () => { + const { registrationId } = useParams<{ + registrationId: string + }>() const declarationWithoutAllTemplateVariables = useDeclaration< IPrintableDeclaration | undefined - >(declarationId) + >(registrationId) const userDetails = useSelector(getUserDetails) const offlineData = useSelector(getOfflineData) const declaration = withEnhancedTemplateVariables( @@ -120,28 +127,38 @@ export const usePrintableCertificate = (declarationId: string) => { declaration?.event !== Event.Marriage && (hasRegisterScope(scope) || hasRegistrationClerkScope(scope)) - let svg = undefined - const certificateTemplate = - declaration && - offlineData.templates.certificates?.[declaration.event].definition - if (certificateTemplate) { - const svgWithoutFonts = compileSvg( - certificateTemplate, - { ...declaration.data.template, preview: true }, - state + const [svgCode, setSvgCode] = useState() + const certificateTemplateConfig: ICertificateConfigData | undefined = + offlineData.templates.certificates.find( + (x) => + x.id === + declaration?.data.registration.certificates[0].certificateTemplateId ) - const svgWithFonts = addFontsToSvg( - svgWithoutFonts, - offlineData.templates.fonts ?? {} - ) - svg = svgWithFonts - } + const certificateFonts = certificateTemplateConfig?.fonts ?? {} + + useEffect(() => { + const certificateUrl = + (declaration && certificateTemplateConfig?.svgUrl) || '' + + if (certificateUrl && declaration) { + fetch(certificateUrl) + .then((res) => res.text()) + .then((certificateTemplate) => { + if (!certificateTemplate) return + const svgWithoutFonts = compileSvg( + certificateTemplate, + { ...declaration.data.template, preview: true }, + state + ) + const svgWithFonts = addFontsToSvg(svgWithoutFonts, certificateFonts) + setSvgCode(svgWithFonts) + }) + } + // eslint-disable-next-line + }, [declaration]) const handleCertify = async () => { - if ( - !declaration || - !offlineData.templates.certificates?.[declaration.event].definition - ) { + if (!declaration || !certificateTemplateConfig) { return } const draft = cloneDeep(declaration) @@ -159,7 +176,8 @@ export const usePrintableCertificate = (declarationId: string) => { draft.event, eventDate, registeredDate, - offlineData + offlineData, + declaration.data.registration.certificates[0] ) certificate.payments = { type: 'MANUAL' as const, @@ -169,23 +187,24 @@ export const usePrintableCertificate = (declarationId: string) => { } } + const svgTemplate = await fetch(certificateTemplateConfig.svgUrl).then( + (res) => res.text() + ) const svg = await compileSvg( - offlineData.templates.certificates[draft.event].definition, + svgTemplate, { ...draft.data.template, preview: false }, state ) - draft.data.registration = { ...draft.data.registration, certificates: [ { - ...certificate, - data: svg || '' + ...certificate } ] } - const pdfTemplate = svgToPdfTemplate(svg, offlineData) + const pdfTemplate = svgToPdfTemplate(svg, certificateFonts) printPDF(pdfTemplate, draft.id) @@ -214,12 +233,12 @@ export const usePrintableCertificate = (declarationId: string) => { } dispatch( - goToCertificateCorrection(declarationId, CorrectionSection.Corrector) + goToCertificateCorrection(registrationId, CorrectionSection.Corrector) ) } return { - svg, + svgCode, handleCertify, isPrintInAdvance, canUserEditRecord, diff --git a/packages/client/src/views/PrintCertificate/utils.ts b/packages/client/src/views/PrintCertificate/utils.ts index d40371b1c3c..ea9479493ed 100644 --- a/packages/client/src/views/PrintCertificate/utils.ts +++ b/packages/client/src/views/PrintCertificate/utils.ts @@ -13,7 +13,7 @@ import { Event } from '@client/utils/gateway' import { dynamicMessages } from '@client/i18n/messages/views/certificate' import { getAvailableLanguages } from '@client/i18n/utils' import { ILanguageState } from '@client/i18n/reducer' -import { IPrintableDeclaration } from '@client/declarations' +import { ICertificate, IPrintableDeclaration } from '@client/declarations' import { IntlShape } from 'react-intl' import { IOfflineData } from '@client/offline/reducer' import differenceInDays from 'date-fns/differenceInDays' @@ -57,72 +57,79 @@ export function getCountryTranslations( return certificateCountries } -interface IDayRange { - rangeData: { [key in Event]?: IRange[] } -} - -function getDayRanges(offlineData: IOfflineData): IDayRange { - const BIRTH_REGISTRATION_TARGET = offlineData.config.BIRTH.REGISTRATION_TARGET - const BIRTH_LATE_REGISTRATION_TARGET = - offlineData.config.BIRTH.LATE_REGISTRATION_TARGET - const BIRTH_ON_TIME_FEE = offlineData.config.BIRTH.FEE.ON_TIME - const BIRTH_LATE_FEE = offlineData.config.BIRTH.FEE.LATE - const BIRTH_DELAYED_FEE = offlineData.config.BIRTH.FEE.DELAYED - - const DEATH_REGISTRATION_TARGET = offlineData.config.DEATH.REGISTRATION_TARGET - const DEATH_ON_TIME_FEE = offlineData.config.DEATH.FEE.ON_TIME - const DEATH_DELAYED_FEE = offlineData.config.DEATH.FEE.DELAYED - - const MARRIAGE_REGISTRATION_TARGET = - offlineData.config.MARRIAGE.REGISTRATION_TARGET - const MARRIAGE_ON_TIME_FEE = offlineData.config.MARRIAGE.FEE.ON_TIME - const MARRIAGE_DELAYED_FEE = offlineData.config.MARRIAGE.FEE.DELAYED - - const birthRanges = [ - { start: 0, end: BIRTH_REGISTRATION_TARGET, value: BIRTH_ON_TIME_FEE }, - { - start: BIRTH_REGISTRATION_TARGET + 1, - end: BIRTH_LATE_REGISTRATION_TARGET, - value: BIRTH_LATE_FEE - }, - { start: BIRTH_LATE_REGISTRATION_TARGET + 1, value: BIRTH_DELAYED_FEE } - ] +function getDayRanges( + offlineData: IOfflineData, + certificate: ICertificate +): IRange[] { + const templateConfig = offlineData.templates.certificates.find( + (x) => x.id === certificate.certificateTemplateId + ) + switch (templateConfig?.event) { + case Event.Birth: { + const BIRTH_REGISTRATION_TARGET = + offlineData.config.BIRTH.REGISTRATION_TARGET + const BIRTH_LATE_REGISTRATION_TARGET = + offlineData.config.BIRTH.LATE_REGISTRATION_TARGET + const BIRTH_ON_TIME_FEE = templateConfig?.fee.onTime + const BIRTH_LATE_FEE = templateConfig?.fee.late + const BIRTH_DELAYED_FEE = templateConfig?.fee.delayed + const birthRanges = [ + { start: 0, end: BIRTH_REGISTRATION_TARGET, value: BIRTH_ON_TIME_FEE }, + { + start: BIRTH_REGISTRATION_TARGET + 1, + end: BIRTH_LATE_REGISTRATION_TARGET, + value: BIRTH_LATE_FEE + }, + { start: BIRTH_LATE_REGISTRATION_TARGET + 1, value: BIRTH_DELAYED_FEE } + ] + return birthRanges + } - const deathRanges = [ - { start: 0, end: DEATH_REGISTRATION_TARGET, value: DEATH_ON_TIME_FEE }, - { start: DEATH_REGISTRATION_TARGET + 1, value: DEATH_DELAYED_FEE } - ] + case Event.Death: { + const DEATH_REGISTRATION_TARGET = + offlineData.config.DEATH.REGISTRATION_TARGET + const DEATH_ON_TIME_FEE = templateConfig?.fee.onTime + const DEATH_DELAYED_FEE = templateConfig?.fee.delayed - const marriageRanges = [ - { - start: 0, - end: MARRIAGE_REGISTRATION_TARGET, - value: MARRIAGE_ON_TIME_FEE - }, - { start: MARRIAGE_REGISTRATION_TARGET + 1, value: MARRIAGE_DELAYED_FEE } - ] + const deathRanges = [ + { start: 0, end: DEATH_REGISTRATION_TARGET, value: DEATH_ON_TIME_FEE }, + { start: DEATH_REGISTRATION_TARGET + 1, value: DEATH_DELAYED_FEE } + ] + return deathRanges + } + case Event.Marriage: { + const MARRIAGE_REGISTRATION_TARGET = + offlineData.config.MARRIAGE.REGISTRATION_TARGET + const MARRIAGE_ON_TIME_FEE = templateConfig?.fee.onTime + const MARRIAGE_DELAYED_FEE = templateConfig?.fee.delayed + const marriageRanges = [ + { + start: 0, + end: MARRIAGE_REGISTRATION_TARGET, + value: MARRIAGE_ON_TIME_FEE + }, + { start: MARRIAGE_REGISTRATION_TARGET + 1, value: MARRIAGE_DELAYED_FEE } + ] - return { - rangeData: { - [Event.Birth]: birthRanges, - [Event.Death]: deathRanges, - [Event.Marriage]: marriageRanges + return marriageRanges } + default: + return [] } } function getValue( offlineData: IOfflineData, - event: Event, + certificate: ICertificate, check: number ): IRange['value'] { - const rangeByEvent = getDayRanges(offlineData).rangeData[event] as IRange[] + const rangeByEvent = getDayRanges(offlineData, certificate) as IRange[] const foundRange = rangeByEvent.find((range) => range.end ? check >= range.start && check <= range.end : check >= range.start ) - return foundRange ? foundRange.value : rangeByEvent[0].value + return foundRange ? foundRange.value : rangeByEvent[0]?.value || 0 } export function calculateDaysFromToday(doE: string) { @@ -162,10 +169,12 @@ export function calculatePrice( event: Event, eventDate: string, registeredDate: string, - offlineData: IOfflineData + offlineData: IOfflineData, + certificate: ICertificate ) { + if (!certificate) return 0 const days = calculateDays(eventDate, registeredDate) - const result = getValue(offlineData, event, days) + const result = getValue(offlineData, certificate, days) return result } @@ -220,13 +229,13 @@ export function getServiceMessage( } export function isFreeOfCost( - event: Event, + certificate: ICertificate, eventDate: string, registeredDate: string, offlineData: IOfflineData ): boolean { const days = calculateDays(eventDate, registeredDate) - const result = getValue(offlineData, event, days) + const result = getValue(offlineData, certificate, days) return result === 0 } diff --git a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx index 0ea39cd6490..8f9c8919221 100644 --- a/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx +++ b/packages/client/src/views/RecordAudit/ActionDetailsModal.tsx @@ -452,6 +452,27 @@ const ActionDetailsModalListTable = ({ width: 100 } ] + + const selectedCertificateTemplate = [ + { + key: 'certTemplate', + label: intl.formatMessage( + certificateMessages.selectedCertificateTemplateLabel + ), + width: 200 + } + ] + + const certificateTemplateMessageDescriptor = + offlineData.templates?.certificates?.find( + (x) => x.id === actionDetailsData.certificateTemplateId + )?.label + + const selectedCertificateTemplateName = { + certTemplate: certificateTemplateMessageDescriptor + ? intl.formatMessage(certificateTemplateMessageDescriptor) + : '' + } const pageChangeHandler = (cp: number) => setCurrentPage(cp) const content = prepareComments(actionDetailsData, draft) const requesterLabel = requesterLabelMapper( @@ -609,6 +630,17 @@ const ActionDetailsModalListTable = ({ onPageChange={pageChangeHandler} /> )} + {!isEmpty(collectorData) && !!actionDetailsData.certificateTemplateId && ( + + )} {/* Matched to */} {actionDetailsData.potentialDuplicates && diff --git a/packages/client/typings/window.d.ts b/packages/client/typings/window.d.ts index c67120df95b..fa123a9b073 100644 --- a/packages/client/typings/window.d.ts +++ b/packages/client/typings/window.d.ts @@ -16,11 +16,6 @@ interface Window { BIRTH: { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } CONFIG_API_URL: string @@ -35,18 +30,10 @@ interface Window { } DEATH: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } MARRIAGE: { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } FEATURES: { diff --git a/packages/commons/src/fhir/extension.ts b/packages/commons/src/fhir/extension.ts index 50a36130455..148a0e5c6df 100644 --- a/packages/commons/src/fhir/extension.ts +++ b/packages/commons/src/fhir/extension.ts @@ -128,6 +128,10 @@ export type StringExtensionType = { valueString?: string valueBoolean: boolean } + 'http://opencrvs.org/specs/extension/certificateTemplateId': { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + valueString?: string + } 'http://opencrvs.org/specs/extension/regLastOffice': { url: 'http://opencrvs.org/specs/extension/regLastOffice' valueReference: { reference: ResourceIdentifier } diff --git a/packages/commons/src/fhir/transformers/input.ts b/packages/commons/src/fhir/transformers/input.ts index 683563e4020..493e064b101 100644 --- a/packages/commons/src/fhir/transformers/input.ts +++ b/packages/commons/src/fhir/transformers/input.ts @@ -208,7 +208,7 @@ interface Certificate { collector?: RelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certificateTemplateId?: string } interface Deceased { deceased?: boolean diff --git a/packages/config/src/handlers/application/applicationConfigHandler.test.ts b/packages/config/src/handlers/application/applicationConfigHandler.test.ts index ce69652eb3e..8fa569a6aa1 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.test.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.test.ts @@ -37,11 +37,6 @@ const mockConfig = { BIRTH: { REGISTRATION_TARGET: 45, LATE_REGISTRATION_TARGET: 365, - FEE: { - ON_TIME: 0, - LATE: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, COUNTRY_LOGO: { @@ -54,18 +49,10 @@ const mockConfig = { }, DEATH: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, MARRIAGE: { REGISTRATION_TARGET: 45, - FEE: { - ON_TIME: 0, - DELAYED: 0 - }, PRINT_IN_ADVANCE: true }, PHONE_NUMBER_PATTERN: '^0(7|9)[0-9]{8}$', diff --git a/packages/config/src/handlers/application/applicationConfigHandler.ts b/packages/config/src/handlers/application/applicationConfigHandler.ts index 15a461d1b15..a30a5dfce31 100644 --- a/packages/config/src/handlers/application/applicationConfigHandler.ts +++ b/packages/config/src/handlers/application/applicationConfigHandler.ts @@ -35,7 +35,7 @@ export default async function configHandler( ) { try { const [certificates, config, systems] = await Promise.all([ - getCertificates(request, h), + getCertificatesConfig(request, h), getApplicationConfig(request, h), getSystems(request, h) ]) @@ -53,7 +53,10 @@ export default async function configHandler( } } -async function getCertificates(request: Hapi.Request, h: Hapi.ResponseToolkit) { +async function getCertificatesConfig( + request: Hapi.Request, + h: Hapi.ResponseToolkit +) { const authToken = getToken(request) const decodedOrError = pipe(authToken, verifyToken) if (decodedOrError._tag === 'Left') { @@ -67,12 +70,18 @@ async function getCertificates(request: Hapi.Request, h: Hapi.ResponseToolkit) { scope.includes(RouteScope.VALIDATE) || scope.includes(RouteScope.NATLSYSADMIN)) ) { - return Promise.all( - (['birth', 'death', 'marriage'] as const).map(async (event) => { - const response = await getEventCertificate(event, getToken(request)) - return response - }) - ) + const url = new URL(`/certificates`, env.COUNTRY_CONFIG_URL).toString() + + const res = await fetch(url, { + headers: { Authorization: `Bearer ${authToken}` } + }) + + if (!res.ok) { + throw new Error( + `Failed to fetch certificates configuration: ${res.statusText} ${url}` + ) + } + return res.json() } return [] } @@ -86,27 +95,6 @@ async function getConfigFromCountry(authToken?: string) { return res.json() } -async function getEventCertificate( - event: 'birth' | 'death' | 'marriage', - authToken: string -) { - const url = new URL( - `/certificates/${event}.svg`, - env.COUNTRY_CONFIG_URL - ).toString() - - const res = await fetch(url, { - headers: { Authorization: `Bearer ${authToken}` } - }) - - if (!res.ok) { - throw new Error(`Failed to fetch ${event} certificate: ${res.statusText}`) - } - const responseText = await res.text() - - return { svgCode: responseText, event } -} - async function getApplicationConfig( request?: Hapi.Request, h?: Hapi.ResponseToolkit @@ -172,37 +160,18 @@ const applicationConfigResponseValidation = Joi.object({ .keys({ REGISTRATION_TARGET: Joi.number().required(), LATE_REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - LATE: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), DEATH: Joi.object() .keys({ REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), MARRIAGE: Joi.object() .keys({ REGISTRATION_TARGET: Joi.number().required(), - FEE: Joi.object() - .keys({ - ON_TIME: Joi.number().required(), - DELAYED: Joi.number().required() - }) - .required(), PRINT_IN_ADVANCE: Joi.boolean().required() }) .required(), diff --git a/packages/config/src/models/config.ts b/packages/config/src/models/config.ts index 22f2cb17215..475dc6d6551 100644 --- a/packages/config/src/models/config.ts +++ b/packages/config/src/models/config.ts @@ -12,27 +12,14 @@ import { model, Schema, Document } from 'mongoose' interface IBirth { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IDeath { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IMarriage { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface ICurrency { @@ -66,29 +53,16 @@ export interface IApplicationConfigurationModel extends Document { const birthSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, LATE_REGISTRATION_TARGET: { type: Number, default: 365 }, - FEE: { - ON_TIME: Number, - LATE: Number, - DELAYED: Number - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) const deathSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, - FEE: { - ON_TIME: Number, - DELAYED: Number - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) const marriageSchema = new Schema({ REGISTRATION_TARGET: { type: Number, default: 45 }, - FEE: { - ON_TIME: { type: Number, default: 10 }, - DELAYED: { type: Number, default: 45 } - }, PRINT_IN_ADVANCE: { type: Boolean, default: true } }) diff --git a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap index 02451a6d6a3..60150988146 100644 --- a/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap +++ b/packages/gateway/src/features/registration/__snapshots__/type-resolvers.test.ts.snap @@ -170,6 +170,7 @@ Object { "foetalDeathsToMother": null, "history": Array [ Object { + "certificateTemplateId": null, "certificates": Array [ null, ], @@ -655,6 +656,7 @@ Object { "femaleDependentsOfDeceased": 4, "history": Array [ Object { + "certificateTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T11:52:48.611+00:00", @@ -1203,6 +1205,7 @@ Object { }, "history": Array [ Object { + "certificateTemplateId": null, "certificates": Array [], "comments": Array [], "date": "2023-09-22T08:54:57.825+00:00", diff --git a/packages/gateway/src/features/registration/schema.graphql b/packages/gateway/src/features/registration/schema.graphql index eab611f0358..9f9dd81fde0 100644 --- a/packages/gateway/src/features/registration/schema.graphql +++ b/packages/gateway/src/features/registration/schema.graphql @@ -159,6 +159,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean + certificateTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String #This doesn't resolve to the System model properly rather @@ -422,14 +423,14 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String + certificateTemplateId: String } type Certificate { # -> Document Reference collector: RelatedPerson # -> .extension hasShowedVerifiedDocument: Boolean # -> .extension payments: [Payment] # -> .extension - data: String # -> .content.attachment.data base64 + certificateTemplateId: String } input QuestionnaireQuestionInput { diff --git a/packages/gateway/src/features/registration/type-resolvers.ts b/packages/gateway/src/features/registration/type-resolvers.ts index 407a8af2c8c..2b6147e3d92 100644 --- a/packages/gateway/src/features/registration/type-resolvers.ts +++ b/packages/gateway/src/features/registration/type-resolvers.ts @@ -1243,6 +1243,13 @@ export const typeResolvers: GQLResolver = { } return false + }, + certificateTemplateId(docRef: DocumentReference, _) { + const certificateTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certificateTemplateId`, + docRef.extension as Extension[] + ) + return certificateTemplateId?.valueString } }, Identifier: { @@ -1408,7 +1415,6 @@ export const typeResolvers: GQLResolver = { `${OPENCRVS_SPECIFICATION_URL}extension/hasShowedVerifiedDocument`, task.extension as Extension[] ) - if (hasShowedDocument?.valueString) { return Boolean(hasShowedDocument?.valueString) } @@ -1419,7 +1425,13 @@ export const typeResolvers: GQLResolver = { return false }, - + certificateTemplateId: (task: Task) => { + const certificateTemplateId = findExtension( + `${OPENCRVS_SPECIFICATION_URL}extension/certificateTemplateId`, + task.extension as Extension[] + ) + return certificateTemplateId?.valueString + }, noSupportingDocumentationRequired: (task: Task) => { const hasShowedDocument = findExtension( NO_SUPPORTING_DOCUMENTATION_REQUIRED, diff --git a/packages/gateway/src/graphql/schema.d.ts b/packages/gateway/src/graphql/schema.d.ts index 6807e1a9b80..81b819f07b9 100644 --- a/packages/gateway/src/graphql/schema.d.ts +++ b/packages/gateway/src/graphql/schema.d.ts @@ -746,6 +746,7 @@ export interface GQLHistory { requester?: string requesterOther?: string hasShowedVerifiedDocument?: boolean + certificateTemplateId?: string noSupportingDocumentationRequired?: boolean otherReason?: string system?: GQLIntegratedSystem @@ -1233,7 +1234,7 @@ export interface GQLCertificate { collector?: GQLRelatedPerson hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certificateTemplateId?: string } export interface GQLDuplicatesInfo { @@ -1555,7 +1556,7 @@ export interface GQLCertificateInput { collector?: GQLRelatedPersonInput hasShowedVerifiedDocument?: boolean payments?: Array - data?: string + certificateTemplateId?: string } export interface GQLIdentityInput { @@ -6225,6 +6226,7 @@ export interface GQLHistoryTypeResolver { requester?: HistoryToRequesterResolver requesterOther?: HistoryToRequesterOtherResolver hasShowedVerifiedDocument?: HistoryToHasShowedVerifiedDocumentResolver + certificateTemplateId?: HistoryToCertificateTemplateIdResolver noSupportingDocumentationRequired?: HistoryToNoSupportingDocumentationRequiredResolver otherReason?: HistoryToOtherReasonResolver system?: HistoryToSystemResolver @@ -6344,6 +6346,18 @@ export interface HistoryToHasShowedVerifiedDocumentResolver< ): TResult } +export interface HistoryToCertificateTemplateIdResolver< + TParent = any, + TResult = any +> { + ( + parent: TParent, + args: {}, + context: Context, + info: GraphQLResolveInfo + ): TResult +} + export interface HistoryToNoSupportingDocumentationRequiredResolver< TParent = any, TResult = any @@ -7922,7 +7936,7 @@ export interface GQLCertificateTypeResolver { collector?: CertificateToCollectorResolver hasShowedVerifiedDocument?: CertificateToHasShowedVerifiedDocumentResolver payments?: CertificateToPaymentsResolver - data?: CertificateToDataResolver + certificateTemplateId?: CertificateToCertificateTemplateIdResolver } export interface CertificateToCollectorResolver { @@ -7955,7 +7969,10 @@ export interface CertificateToPaymentsResolver { ): TResult } -export interface CertificateToDataResolver { +export interface CertificateToCertificateTemplateIdResolver< + TParent = any, + TResult = any +> { ( parent: TParent, args: {}, diff --git a/packages/gateway/src/graphql/schema.graphql b/packages/gateway/src/graphql/schema.graphql index 0dbf8e1d4bc..6ff47e98326 100644 --- a/packages/gateway/src/graphql/schema.graphql +++ b/packages/gateway/src/graphql/schema.graphql @@ -867,6 +867,7 @@ type History { requester: String requesterOther: String hasShowedVerifiedDocument: Boolean + certificateTemplateId: String noSupportingDocumentationRequired: Boolean otherReason: String system: IntegratedSystem @@ -1330,7 +1331,7 @@ type Certificate { collector: RelatedPerson hasShowedVerifiedDocument: Boolean payments: [Payment] - data: String + certificateTemplateId: String } type DuplicatesInfo { @@ -1651,7 +1652,7 @@ input CertificateInput { collector: RelatedPersonInput hasShowedVerifiedDocument: Boolean payments: [PaymentInput] - data: String + certificateTemplateId: String } input IdentityInput { diff --git a/packages/metrics/src/configApi.ts b/packages/metrics/src/configApi.ts index 2a56205689f..caca8aa1238 100644 --- a/packages/metrics/src/configApi.ts +++ b/packages/metrics/src/configApi.ts @@ -21,28 +21,15 @@ import { interface IBirth { REGISTRATION_TARGET: number LATE_REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - LATE: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IDeath { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } interface IMarriage { REGISTRATION_TARGET: number - FEE: { - ON_TIME: number - DELAYED: number - } PRINT_IN_ADVANCE: boolean } export interface ICountryLogo { diff --git a/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts b/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts new file mode 100644 index 00000000000..5bbe022ece3 --- /dev/null +++ b/packages/migration/src/migrations/application-config/20241002232752-remove-certificate-collection.ts @@ -0,0 +1,66 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ + +import { Db, MongoClient } from 'mongodb' + +export const up = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collectionExists = await db + .listCollections({ name: 'certificates' }) + .hasNext() + + if (collectionExists) { + await db.collection('certificates').drop() + console.log('Certificates collection removed successfully') + } else { + console.log('Certificates collection does not exist, skipping removal') + } + }) + } catch (error) { + console.error( + 'Error occurred while removing certificates collection:', + error + ) + throw error + } finally { + session.endSession() + } +} + +export const down = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const collectionExists = await db + .listCollections({ name: 'certificates' }) + .hasNext() + + if (!collectionExists) { + await db.createCollection('certificates') + console.log('Certificates collection recreated successfully') + } else { + console.log( + 'Certificates collection already exists, skipping recreation' + ) + } + }) + } catch (error) { + console.error( + 'Error occurred while recreating certificates collection:', + error + ) + throw error + } finally { + session.endSession() + } +} diff --git a/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts new file mode 100644 index 00000000000..9b3f6fbd842 --- /dev/null +++ b/packages/migration/src/migrations/hearth/20241029171702-add-certificateTemplateId-to-missing-docs.ts @@ -0,0 +1,157 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * OpenCRVS is also distributed under the terms of the Civil Registration + * & Healthcare Disclaimer located at http://opencrvs.org/license. + * + * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. + */ +import { Db, MongoClient } from 'mongodb' + +export const up = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const bulkOps = [ + { + $match: { + 'extension.url': { + $ne: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } + } + }, + { + $set: { + certType: { + $arrayElemAt: [ + { + $filter: { + input: '$type.coding', + as: 'coding', + cond: { + $regexMatch: { + input: '$$coding.system', + regex: /certificate-type/ + } + } + } + }, + 0 + ] + } + } + }, + { + $set: { + certificateTemplateId: { + $switch: { + branches: [ + { + case: { $eq: ['$certType.code', 'BIRTH'] }, + then: 'birth-certificate' + }, + { + case: { $eq: ['$certType.code', 'DEATH'] }, + then: 'death-certificate' + }, + { + case: { $eq: ['$certType.code', 'MARRIAGE'] }, + then: 'marriage-certificate' + } + ], + default: null + } + } + } + }, + { + $match: { + certificateTemplateId: { $ne: null } + } + }, + { + $set: { + extension: { + $concatArrays: [ + '$extension', + [ + { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: '$certificateTemplateId' + } + ] + ] + } + } + }, + { + $unset: ['certType', 'certificateTemplateId'] + }, + { + $merge: { + into: 'DocumentReference', + whenMatched: 'replace' + } + } + ] + + await db.collection('DocumentReference').aggregate(bulkOps).toArray() + }) + } catch (error) { + console.error('Error occurred while updating document references:', error) + throw error + } finally { + session.endSession() + } +} + +export const down = async (db: Db, client: MongoClient) => { + const session = client.startSession() + try { + await session.withTransaction(async () => { + const bulkOps = [ + { + $match: { + extension: { + $elemMatch: { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId' + } + } + } + }, + { + $set: { + extension: { + $filter: { + input: '$extension', + as: 'ext', + cond: { + $ne: [ + '$$ext.url', + 'http://opencrvs.org/specs/extension/certificateTemplateId' + ] + } + } + } + } + }, + { + $merge: { + into: 'DocumentReference', + whenMatched: 'merge', + whenNotMatched: 'discard' + } + } + ] + await db.collection('DocumentReference').aggregate(bulkOps).toArray() + console.log('Reverted certificateTemplateId extension from all documents') + }) + } catch (error) { + console.error('Error occurred while reverting document references:', error) + throw error + } finally { + session.endSession() + } +} diff --git a/packages/workflow/src/documents.ts b/packages/workflow/src/documents.ts index 671c4b444c2..faf3c014dbb 100644 --- a/packages/workflow/src/documents.ts +++ b/packages/workflow/src/documents.ts @@ -61,12 +61,7 @@ export async function uploadCertificateAttachmentsToDocumentsStore< affidavit.data = await uploadBase64ToMinio(affidavit.data, authHeader) } } - if ('data' in certificateDetails) { - certificateDetails.data = await uploadBase64ToMinio( - certificateDetails.data, - authHeader - ) - } + return certificateDetails } diff --git a/packages/workflow/src/records/fhir.ts b/packages/workflow/src/records/fhir.ts index 78d714d1b95..8078f8d7228 100644 --- a/packages/workflow/src/records/fhir.ts +++ b/packages/workflow/src/records/fhir.ts @@ -218,6 +218,7 @@ export function createDocumentReferenceEntryForCertificate( temporaryRelatedPersonId: UUID, eventType: EVENT_TYPE, hasShowedVerifiedDocument: boolean, + certificateTemplateId?: string, attachmentUrl?: string, paymentUrl?: URNReference | ResourceIdentifier ): BundleEntry { @@ -240,6 +241,10 @@ export function createDocumentReferenceEntryForCertificate( url: 'http://opencrvs.org/specs/extension/hasShowedVerifiedDocument', valueBoolean: hasShowedVerifiedDocument }, + { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: certificateTemplateId + }, ...(paymentUrl ? [ { @@ -912,8 +917,20 @@ export async function createUnassignedTask( return unassignedTask } -export function createCertifiedTask(previousTask: SavedTask): SavedTask { - return createNewTaskResource(previousTask, [], 'CERTIFIED') +export function createCertifiedTask( + previousTask: SavedTask, + certificateTemplateId: string +): SavedTask { + return createNewTaskResource( + previousTask, + [ + { + url: 'http://opencrvs.org/specs/extension/certificateTemplateId', + valueString: certificateTemplateId + } + ], + 'CERTIFIED' + ) } export function createIssuedTask(previousTask: SavedTask): SavedTask { diff --git a/packages/workflow/src/records/handler/certify.test.ts b/packages/workflow/src/records/handler/certify.test.ts index 56a9af5e170..ccc6c437d27 100644 --- a/packages/workflow/src/records/handler/certify.test.ts +++ b/packages/workflow/src/records/handler/certify.test.ts @@ -111,7 +111,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - data: 'data:application/pdf;base64,AXDWYZ', + certificateTemplateId: 'birth-certificate', collector: { relationship: 'INFORMANT' } @@ -215,7 +215,7 @@ describe('Certify record endpoint', () => { event: 'BIRTH', certificate: { hasShowedVerifiedDocument: true, - data: 'data:application/pdf;base64,AXDWYZ', + certificateTemplateId: 'birth-certificate', collector: { relationship: 'Other', otherRelationship: 'Uncle', diff --git a/packages/workflow/src/records/handler/issue.test.ts b/packages/workflow/src/records/handler/issue.test.ts index b7804701913..7a7ad897b59 100644 --- a/packages/workflow/src/records/handler/issue.test.ts +++ b/packages/workflow/src/records/handler/issue.test.ts @@ -112,6 +112,7 @@ describe('Issue record endpoint', () => { collector: { relationship: 'INFORMANT' }, + certificateTemplateId: 'birth-certificate', payment: { type: 'MANUAL', amount: 100, diff --git a/packages/workflow/src/records/state-transitions.ts b/packages/workflow/src/records/state-transitions.ts index d190b5cd1f4..afa89711e1d 100644 --- a/packages/workflow/src/records/state-transitions.ts +++ b/packages/workflow/src/records/state-transitions.ts @@ -942,7 +942,10 @@ export async function toCertified( certificateDetails: CertifyInput ): Promise { const previousTask = getTaskFromSavedBundle(record) - const taskWithoutPractitionerExtensions = createCertifiedTask(previousTask) + const taskWithoutPractitionerExtensions = createCertifiedTask( + previousTask, + certificateDetails.certificateTemplateId + ) const [certifiedTask, practitionerResourcesBundle] = await withPractitionerDetails(taskWithoutPractitionerExtensions, token) @@ -961,7 +964,7 @@ export async function toCertified( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, - certificateDetails.data + certificateDetails.certificateTemplateId ) const certificateSection: CompositionSection = { @@ -1043,6 +1046,7 @@ export async function toIssued( temporaryRelatedPersonId, eventType, certificateDetails.hasShowedVerifiedDocument, + certificateDetails.certificateTemplateId, undefined, paymentEntry.fullUrl ) diff --git a/packages/workflow/src/records/validations.ts b/packages/workflow/src/records/validations.ts index fb81ff2b635..477bc3106f7 100644 --- a/packages/workflow/src/records/validations.ts +++ b/packages/workflow/src/records/validations.ts @@ -18,7 +18,7 @@ export const CertifyRequestSchema = z.object({ event: z.custom(), certificate: z.object({ hasShowedVerifiedDocument: z.boolean(), - data: z.string(), + certificateTemplateId: z.string(), collector: z .object({ relationship: z.string(), @@ -63,9 +63,9 @@ const PaymentSchema = z.object({ export const IssueRequestSchema = z.object({ event: z.custom(), - certificate: CertifyRequestSchema.shape.certificate - .omit({ data: true }) - .and(z.object({ payment: PaymentSchema })) + certificate: CertifyRequestSchema.shape.certificate.and( + z.object({ payment: PaymentSchema }) + ) }) export const ChangedValuesInput = z.array(