diff --git a/package-lock.json b/package-lock.json index 894591a..8a5552f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "handlebars": "^4.7.8", + "node-cache": "^5.1.2", "pg": "^8.16.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", @@ -9440,6 +9441,25 @@ "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "dev": true }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/node-cache/node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/node-emoji": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", diff --git a/package.json b/package.json index fd3f78a..b152295 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "handlebars": "^4.7.8", + "node-cache": "^5.1.2", "pg": "^8.16.3", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", diff --git a/schemas/community-sports-programme.json b/schemas/community-sports-programme.json index 271bb56..f1baa36 100644 --- a/schemas/community-sports-programme.json +++ b/schemas/community-sports-programme.json @@ -4,219 +4,290 @@ "description": "Register for our community sports programme", "fields": [ { - "name": "firstName", - "type": "string", - "label": "First Name", - "required": true, - "validations": { - "min": 2, - "max": 50 - } - }, - { - "name": "lastName", - "type": "string", - "label": "Last Name", + "name": "applicant", + "type": "object", "required": true, - "validations": { - "min": 2, - "max": 50 - } + "fields": [ + { + "name": "firstName", + "type": "string", + "label": "First name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "First name is required" + } + }, + { + "name": "lastName", + "type": "string", + "label": "Last name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "Last name is required" + } + }, + { + "name": "dateOfBirth", + "type": "date", + "label": "Date of birth", + "required": false, + "validations": { + "regex": "^\\d{4}-\\d{2}-\\d{2}$", + "message": "Date of birth is required and must be in YYYY-MM-DD format" + } + }, + { + "name": "sex", + "type": "string", + "label": "Sex", + "required": true, + "validations": { + "regex": "^(male|female)$", + "message": "Must select a valid sex" + } + } + ] }, { - "name": "dateOfBirth", - "type": "date", - "label": "Date of Birth", + "name": "discipline", + "type": "object", "required": true, - "validations": { - "regex": "^\\d{4}-\\d{2}-\\d{2}$", - "message": "Date must be in YYYY-MM-DD format" - } - }, - { - "name": "gender", - "type": "string", - "label": "Gender", - "required": true - }, - { - "name": "disciplineOfInterest", - "type": "string", - "label": "Discipline of Interest", - "required": true - }, - { - "name": "disciplineExperience", - "type": "string", - "label": "Do you have experience in this discipline?", - "required": true - }, - { - "name": "experienceLevel", - "type": "string", - "label": "Experience Level", - "required": false - }, - { - "name": "otherExperienceLevel", - "type": "string", - "label": "Other Experience Level", - "required": false - }, - { - "name": "yearsOfExperience", - "type": "number", - "label": "Years of Experience", - "required": false, - "validations": { - "min": 0, - "max": 50 - } - }, - { - "name": "employmentStatus", - "type": "string", - "label": "Employment Status", - "required": true + "fields": [ + { + "name": "areaOfInterest", + "type": "string", + "label": "Discipline of Interest", + "required": true + }, + { + "name": "hasExperience", + "type": "string", + "label": "Do you have experience in this discipline?", + "required": true, + "validations": { + "regex": "^(yes|no)$", + "message": "Must select a valid option" + } + } + ] }, { - "name": "institutionName", - "type": "string", - "label": "Institution Name", - "required": false, - "validations": { - "max": 100 - } + "name": "experience", + "type": "object", + "fields": [ + { + "name": "levelOfExperience", + "type": "string", + "label": "Experience Level", + "required": false + }, + { + "name": "otherExperience", + "type": "string", + "label": "Other Experience Level", + "required": false + }, + { + "name": "yearsOfExperience", + "type": "number", + "label": "Years of Experience", + "required": false, + "validations": { + "min": 0, + "max": 50 + } + } + ] }, { - "name": "employerName", - "type": "string", - "label": "Employer Name", - "required": false, - "validations": { - "max": 100 - } + "name": "employment", + "type": "object", + "fields": [ + { + "name": "status", + "type": "string", + "label": "Employment Status", + "required": true + }, + { + "name": "institutionName", + "type": "string", + "label": "Institution Name", + "required": false, + "validations": { + "max": 100 + } + }, + { + "name": "companyName", + "type": "string", + "label": "Company or organisation name is require", + "required": false, + "validations": { + "max": 100 + } + }, + { + "name": "otherDetails", + "type": "string", + "label": "Other Employment Details", + "required": false, + "validations": { + "max": 200 + } + } + ] }, { - "name": "otherEmploymentDetails", + "name": "belongsToOrganisations", "type": "string", - "label": "Other Employment Details", - "required": false, + "label": "Do you belong to any organisations?", + "required": true, "validations": { - "max": 200 + "regex": "^(yes|no)$", + "message": "Must select an option" } }, { - "name": "belongsToOrganizations", - "type": "string", - "label": "Do you belong to any sports organizations?", - "required": true - }, - { - "name": "organizationNames", + "name": "organizationDetails", "type": "array", - "label": "Organization Names", + "label": "Organization Details", "required": false, "items": { "type": "object", "properties": { - "value": { + "organizationName": { + "type": "string" + }, + "hasSignificantPosition": { "type": "string" } } } }, { - "name": "addressLine1", - "type": "string", - "label": "Address Line 1", + "name": "contact", + "type": "object", + "label": "Contact Information", "required": true, - "validations": { - "max": 100 - } - }, - { - "name": "addressLine2", - "type": "string", - "label": "Address Line 2", - "required": false, - "validations": { - "max": 100 - } - }, - { - "name": "email", - "type": "string", - "label": "Email address", - "required": true - }, - { - "name": "parish", - "type": "string", - "label": "Parish", - "required": true - }, - { - "name": "telephoneNumber", - "type": "string", - "label": "Telephone Number", - "required": true - }, - { - "name": "emergencyFirstName", - "type": "string", - "label": "Emergency Contact First Name", - "required": true, - "validations": { - "min": 2, - "max": 50 - } - }, - { - "name": "emergencyLastName", - "type": "string", - "label": "Emergency Contact Last Name", - "required": true, - "validations": { - "min": 2, - "max": 50 - } - }, - { - "name": "emergencyRelationship", - "type": "string", - "label": "Relationship to Emergency Contact", - "required": true + "fields": [ + { + "name": "addressLine1", + "type": "string", + "label": "Address Line 1", + "required": true, + "validations": { + "max": 100 + } + }, + { + "name": "addressLine2", + "type": "string", + "label": "Address Line 2", + "required": false, + "validations": { + "max": 100 + } + }, + { + "name": "parish", + "type": "string", + "label": "Parish", + "required": true + }, + { + "name": "email", + "type": "email", + "label": "Email Address", + "required": true + }, + { + "name": "telephoneNumber", + "type": "string", + "label": "Telephone Number", + "required": true + } + ] }, { - "name": "emergencyAddressLine1", - "type": "string", - "label": "Emergency Contact Address Line 1", + "name": "emergency", + "type": "object", + "label": "Contact Information", "required": true, - "validations": { - "max": 100 - } - }, - { - "name": "emergencyAddressLine2", - "type": "string", - "label": "Emergency Contact Address Line 2", - "required": false, - "validations": { - "max": 100 - } - }, - { - "name": "emergencyParish", - "type": "string", - "label": "Emergency Contact Parish", - "required": true - }, - { - "name": "emergencyTelephoneNumber", - "type": "string", - "label": "Emergency Contact Telephone Number", - "required": true + "fields": [ + { + "name": "firstName", + "type": "string", + "label": "First name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "First name is required" + } + }, + { + "name": "lastName", + "type": "string", + "label": "Last name", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "Last name is required" + } + }, + { + "name": "relationship", + "type": "string", + "label": "Relationship", + "required": false, + "validations": { + "max": 100, + "message": "Relationship is required" + } + }, + { + "name": "addressLine1", + "type": "string", + "label": "Address line 1", + "required": true, + "validations": { + "max": 100 + } + }, + { + "name": "addressLine2", + "type": "string", + "label": "Address line 2", + "required": false, + "validations": { + "max": 100 + } + }, + { + "name": "parish", + "type": "string", + "label": "Parish", + "required": true + }, + { + "name": "email", + "type": "email", + "label": "Email Address", + "required": true + }, + { + "name": "telephoneNumber", + "type": "string", + "label": "Telephone Number", + "required": true + } + ] } ], "processors": [ @@ -224,9 +295,17 @@ "type": "email", "config": { "to": "{{db:community-sports-registration:admin_email}}", - "subject": "New Community Sports Programme Registration - {{formData.firstName}} {{formData.lastName}}", + "subject": "New Community Sports Programme Registration - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "community-sports-registration" } + }, + { + "type": "email", + "config": { + "to": "{{formData.email}}", + "subject": "Community Sports Programme Registration - Submission Received", + "template": "community-sports-programme-receipt" + } } ] } diff --git a/schemas/conductor-licence.json b/schemas/conductor-licence.json index 89e447b..fe9452c 100644 --- a/schemas/conductor-licence.json +++ b/schemas/conductor-licence.json @@ -244,6 +244,14 @@ "subject": "Apply for conductor licence - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "apply-for-conductor-licence" } + }, + { + "type": "email", + "config": { + "to": "{{formData.contactDetails.email}}", + "subject": "Conductor Licence Application - Submission Received", + "template": "conductor-licence-receipt" + } } ] } diff --git a/schemas/fire-service-inspection.json b/schemas/fire-service-inspection.json index d74cfb0..f50b2e2 100644 --- a/schemas/fire-service-inspection.json +++ b/schemas/fire-service-inspection.json @@ -118,9 +118,17 @@ "type": "email", "config": { "to": "{{db:request-a-fire-service-inspection:admin_email}}", - "subject": "New Birth Certificate Application - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", + "subject": "New Fire Service Inspection Request - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "request-a-fire-service-inspection" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Fire Service Inspection Request - Submission Received", + "template": "fire-service-inspection-receipt" + } } ] } diff --git a/schemas/get-birth-certificate.json b/schemas/get-birth-certificate.json index 5ef54fc..c5c2562 100644 --- a/schemas/get-birth-certificate.json +++ b/schemas/get-birth-certificate.json @@ -482,39 +482,39 @@ ] } ], - "processors": [ - { - "type": "payment", - "config": { - "provider": "ezpay", - "department": "oag_registration", - "paymentCode": "{{db:get-birth-certificate:payment_code}}", - "amount": "{{formData.order.numberOfCopies * db:get-birth-certificate:payment_amount}}", - "description": "Birth Certificate Processing Fee (per copy)", - "required": true, - "timing": "after_validation", - "responseData": { - "include": ["order.numberOfCopies"] - } - } - }, - { - "type": "email", - "config": { - "to": "{{db:get-birth-certificate:admin_email}}", - "subject": "New Birth Certificate Application - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", - "template": "birth-certificate", - "recipientType": "admin" - } - }, - { - "type": "email", - "config": { - "to": "{{formData.applicant.email}}", - "subject": "Birth Certificate - Submission Received", - "template": "birth-certificate-receipt", - "recipientType": "user" - } - } - ] + "processors": [ + { + "type": "payment", + "config": { + "provider": "ezpay", + "department": "oag_registration", + "paymentCode": "{{db:get-birth-certificate:payment_code}}", + "amount": "{{formData.order.numberOfCopies * db:get-birth-certificate:payment_amount}}", + "description": "Birth Certificate Processing Fee (per copy)", + "required": true, + "timing": "after_validation", + "responseData": { + "include": ["order.numberOfCopies"] + } + } + }, + { + "type": "email", + "config": { + "to": "{{db:get-birth-certificate:admin_email}}", + "subject": "New Birth Certificate Application - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", + "template": "birth-certificate", + "recipientType": "admin" + } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Birth Certificate - Submission Received", + "template": "birth-certificate-receipt", + "recipientType": "user" + } + } + ] } diff --git a/schemas/get-death-certificate.json b/schemas/get-death-certificate.json index 75cc4c1..aa9e150 100644 --- a/schemas/get-death-certificate.json +++ b/schemas/get-death-certificate.json @@ -288,6 +288,14 @@ "template": "death-certificate-receipt", "recipientType": "user" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Death Certificate Application - Submission Received", + "template": "death-certificate-receipt" + } } ] } diff --git a/schemas/jobstart-plus-programme.json b/schemas/jobstart-plus-programme.json index ef71ed6..aeda711 100644 --- a/schemas/jobstart-plus-programme.json +++ b/schemas/jobstart-plus-programme.json @@ -480,7 +480,7 @@ "name": "willingToWorkAtNight", "type": "string", "label": "Are you willing to work at night?", - "required": true, + "required": false, "validations": { "regex": "^(yes|no)$", "message": "Must select an option" @@ -508,6 +508,14 @@ "subject": "New JobStart Plus Programme Application - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "jobstart-plus-programme" } + }, + { + "type": "email", + "config": { + "to": "{{formData.contactDetails.email}}", + "subject": "JobStart Plus Programme Application - Submission Received", + "template": "jobstart-plus-programme-receipt" + } } ] } diff --git a/schemas/permission-to-remove-tree.json b/schemas/permission-to-remove-tree.json index c889fd0..ce54949 100644 --- a/schemas/permission-to-remove-tree.json +++ b/schemas/permission-to-remove-tree.json @@ -145,6 +145,14 @@ "subject": "New Request to remove protected tree - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "permission-to-remove-tree" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Permission to Remove Tree - Submission Received", + "template": "permission-to-remove-tree-receipt" + } } ] } diff --git a/schemas/post-office-redirection-business.json b/schemas/post-office-redirection-business.json index bc81c82..bae3713 100644 --- a/schemas/post-office-redirection-business.json +++ b/schemas/post-office-redirection-business.json @@ -107,6 +107,23 @@ "message": "Last name is required" } }, + { + "name": "idNumber", + "type": "string", + "required": false, + "validations": { + "regex": "^\\d{6}[-]?\\d{4}$", + "message": "Must be in format XXXXXX-XXXX" + } + }, + { + "name": "passportNumber", + "type": "string", + "required": false, + "validations": { + "max": 50 + } + }, { "name": "email", "type": "email", @@ -122,12 +139,12 @@ ] }, { - "name": "permissionDetails", + "name": "positionDetails", "type": "string", - "label": "Tell us what permission you have to act for this business", + "label": "Tell us what position you hold in the business.", "required": true, "validations": { - "message": "Permission details required" + "message": "Position is required" } }, { @@ -175,38 +192,19 @@ "message": "Enter a valid postal code (e.g., BB17004)" } }, - { - "name": "isMovingPermanent", - "type": "string", - "label": "Are you moving permanently?", - "required": true, - "validations": { - "regex": "^(yes|no)$", - "message": "Must select an option" - } - }, { "name": "redirectionStartDate", "type": "date", "label": "Redirection Start Date", - "required": false + "required": true }, { "name": "redirectionEndDate", "type": "date", "label": "Redirection End Date", - "required": false + "required": true } ] - }, - { - "name": "uploadDocumentUrls", - "type": "array", - "label": "Uploaded Documents", - "required": true, - "items": { - "type": "string" - } } ], "processors": [ @@ -217,6 +215,14 @@ "subject": "New Request to Redirect my business mail - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "post-office-redirection-notice" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Business Mail Redirection - Submission Received", + "template": "post-office-redirection-business-receipt" + } } ] } diff --git a/schemas/post-office-redirection-deceased.json b/schemas/post-office-redirection-deceased.json index bf28cc4..e84320b 100644 --- a/schemas/post-office-redirection-deceased.json +++ b/schemas/post-office-redirection-deceased.json @@ -132,14 +132,13 @@ } }, { - "name": "relationshipToDeceased", + "name": "middleName", "type": "string", - "label": "What is your relationship to the person?", - "required": true, + "label": "Middle name", + "required": false, "validations": { - "min": 1, "max": 100, - "message": "Relationship to deceased is required" + "message": "Middle name must be between 1 and 100 characters" } }, { @@ -153,6 +152,17 @@ "message": "Last name is required" } }, + { + "name": "relationshipToDeceased", + "type": "string", + "label": "What is your relationship to the person?", + "required": true, + "validations": { + "min": 1, + "max": 100, + "message": "Relationship to deceased is required" + } + }, { "name": "email", "type": "email", @@ -221,16 +231,6 @@ "message": "Enter a valid postal code (e.g., BB17004)" } }, - { - "name": "isRedirectPermanent", - "type": "string", - "label": "Are you redirecting their mail permanently?", - "required": true, - "validations": { - "regex": "^(yes|no)$", - "message": "Must select an option" - } - }, { "name": "redirectionStartDate", "type": "string", @@ -244,15 +244,6 @@ "required": false } ] - }, - { - "name": "uploadDocumentUrls", - "type": "array", - "label": "Uploaded Documents", - "required": true, - "items": { - "type": "string" - } } ], "processors": [ @@ -263,6 +254,14 @@ "subject": "New Request to redirect mail for a Deceased Person - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "post-office-redirection-notice" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Deceased Mail Redirection - Submission Received", + "template": "post-office-redirection-deceased-receipt" + } } ] } diff --git a/schemas/post-office-redirection-individual.json b/schemas/post-office-redirection-individual.json index f440c7c..0072e96 100644 --- a/schemas/post-office-redirection-individual.json +++ b/schemas/post-office-redirection-individual.json @@ -29,6 +29,15 @@ "message": "First name is required" } }, + { + "name": "middleName", + "type": "string", + "label": "Middle name", + "required": false, + "validations": { + "max": 100 + } + }, { "name": "lastName", "type": "string", @@ -175,16 +184,6 @@ "message": "Enter a valid postal code (e.g., BB17004)" } }, - { - "name": "isMovingPermanent", - "type": "string", - "label": "Are you moving permanently?", - "required": true, - "validations": { - "regex": "^(yes|no)$", - "message": "Must select an option" - } - }, { "name": "redirectionStartDate", "type": "date", @@ -208,14 +207,21 @@ ] }, { - "name": "anyMinorDependents", - "type": "string", - "label": "Are there any minor dependents?", + "name": "dependents", + "type": "object", "required": true, - "validations": { - "regex": "^(yes|no)$", - "message": "Must select an option" - } + "fields": [ + { + "name": "anyMinorDependents", + "type": "string", + "label": "Are there any minor dependents?", + "required": true, + "validations": { + "regex": "^(yes|no)$", + "message": "Must select an option" + } + } + ] }, { "name": "minorDetails", @@ -228,8 +234,60 @@ "firstName": { "type": "string" }, + "middleName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "idNumber": { + "type": "string", + "validations": { + "regex": "^\\d{6}-\\d{4}$" + } + } + } + } + }, + { + "name": "adult", + "type": "object", + "required": true, + "fields": [ + { + "name": "anyAdults", + "type": "string", + "label": "Are there any adults?", + "required": true, + "validations": { + "regex": "^(yes|no)$", + "message": "Must select an option" + } + } + ] + }, + { + "name": "adultDetails", + "type": "array", + "label": "Tell us about the adults that needs their email sent to the new address", + "required": false, + "items": { + "type": "object", + "properties": { + "firstName": { + "type": "string" + }, + "middleName": { + "type": "string" + }, "lastName": { "type": "string" + }, + "idNumber": { + "type": "string", + "validations": { + "regex": "^\\d{6}-\\d{4}$" + } } } } @@ -243,6 +301,14 @@ "subject": "New Request to redirect mail for an individual - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "post-office-redirection-notice" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Mail Redirection - Submission Received", + "template": "post-office-redirection-individual-receipt" + } } ] } diff --git a/schemas/primary-school-textbook-grant.json b/schemas/primary-school-textbook-grant.json index b1e1a52..bd1026b 100644 --- a/schemas/primary-school-textbook-grant.json +++ b/schemas/primary-school-textbook-grant.json @@ -193,9 +193,17 @@ "type": "email", "config": { "to": "{{db:primary-school-textbook-grant:admin_email}}", - "subject": "New Textbook Grant Application - {{formData.beneficiaries.firstName}} {{formData.beneficiaries.lastName}}", + "subject": "New Textbook Grant Application - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "primary-school-textbook-grant" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Primary School Textbook Grant - Submission Received", + "template": "primary-school-textbook-grant-receipt" + } } ] } diff --git a/schemas/project-protege-mentor.json b/schemas/project-protege-mentor.json index 770a665..99ab5f0 100644 --- a/schemas/project-protege-mentor.json +++ b/schemas/project-protege-mentor.json @@ -4,9 +4,9 @@ "description": "Register as a mentor for the Project Protege mentorship program", "fields": [ { - "name": "personal", + "name": "applicant", "type": "object", - "label": "Personal Information", + "label": "Applicant Information", "required": true, "fields": [ { diff --git a/schemas/register-birth-form.json b/schemas/register-birth-form.json index 7b563dd..ed7e245 100644 --- a/schemas/register-birth-form.json +++ b/schemas/register-birth-form.json @@ -53,28 +53,25 @@ } }, { - "name": "hadOtherSurname", + "name": "parish", "type": "string", - "required": false, + "required": true, "validations": { - "regex": "^(yes|no)$" + "min": 2, + "max": 500 } }, { - "name": "otherSurname", + "name": "streetAddress", "type": "string", - "required": false, + "required": true, "validations": { - "max": 100 + "min": 2, + "max": 500 } }, { - "name": "dateOfBirth", - "type": "date", - "required": false - }, - { - "name": "address", + "name": "addressLine2", "type": "string", "required": false, "validations": { @@ -82,11 +79,11 @@ } }, { - "name": "nationalRegistrationNumber", + "name": "idNumber", "type": "string", "required": false, "validations": { - "regex": "^\\d{6}-\\d{4}$", + "regex": "^\\d{6}[-]?\\d{4}$", "message": "Must be in format XXXXXX-XXXX" } }, @@ -99,20 +96,79 @@ } }, { - "name": "passportPlaceOfIssue", + "name": "occupation", "type": "string", "required": false, "validations": { "max": 100 } + } + ] + }, + { + "name": "birth", + "type": "object", + "required": true, + "fields": [ + { + "name": "placeOfBirth", + "type": "string", + "label": "Where did the birth take place?", + "required": true, + "validations": { + "regex": "^(health-facility|residential|other)$", + "message": "Must select an option" + } }, { - "name": "occupation", + "name": "parish", "type": "string", - "required": false, + "label": "Parish", + "required": false + }, + { + "name": "streetAddress", + "type": "string", + "label": "Street Address", + "required": false + }, + { + "name": "numberOfBirths", + "type": "string", + "label": "How many births do you need to register?", + "required": true, "validations": { - "max": 100 + "regex": "^(single|twins|triplets|more-than-triplets)$", + "message": "Must select an option" + } + }, + { + "name": "attendantAtBirth", + "type": "string", + "label": "Attendant at birth", + "required": true, + "validations": { + "regex": "^(doctor|midwife|nurse|relative|none)$", + "message": "Must select an option" } + }, + { + "name": "liveBorn", + "type": "string", + "label": "Live born", + "required": false + }, + { + "name": "stillBorn", + "type": "string", + "label": "Still born", + "required": false + }, + { + "name": "totalStillAlive", + "type": "string", + "label": "Total still alive", + "required": false } ] }, @@ -148,58 +204,54 @@ } }, { - "name": "hadOtherSurname", + "name": "maidenSurname", "type": "string", "required": false, "validations": { - "regex": "^(yes|no)$" + "max": 100 } }, { - "name": "otherSurname", + "name": "parish", "type": "string", - "required": false, + "required": true, "validations": { - "max": 100 + "min": 2, + "max": 500 } }, { - "name": "dateOfBirth", - "type": "date", - "required": true - }, - { - "name": "address", + "name": "streetAddress", "type": "string", "required": true, "validations": { - "min": 5, + "min": 2, "max": 500 } }, { - "name": "nationalRegistrationNumber", + "name": "addressLine2", "type": "string", "required": false, "validations": { - "regex": "^\\d{6}-\\d{4}$", - "message": "Must be in format XXXXXX-XXXX" + "max": 500 } }, { - "name": "passportNumber", + "name": "idNumber", "type": "string", "required": false, "validations": { - "max": 50 + "regex": "^\\d{6}[-]?\\d{4}$", + "message": "Must be in format XXXXXX-XXXX" } }, { - "name": "passportPlaceOfIssue", + "name": "passportNumber", "type": "string", "required": false, "validations": { - "max": 100 + "max": 50 } }, { @@ -219,7 +271,7 @@ "required": true, "fields": [ { - "name": "firstNames", + "name": "firstName", "type": "string", "required": true, "validations": { @@ -228,7 +280,7 @@ } }, { - "name": "middleNames", + "name": "middleName", "type": "string", "required": false, "validations": { @@ -254,51 +306,44 @@ "type": "string", "required": true, "validations": { - "regex": "^(Male|Female)$", - "message": "Must be 'Male' or 'Female'" + "regex": "^(male|female)$", + "message": "Must be 'male' or 'female'" } - }, + } + ] + }, + { + "name": "order", + "type": "object", + "required": true, + "fields": [ { - "name": "parishOfBirth", - "type": "string", + "name": "numberOfCopies", + "type": "number", + "label": "Number of copies", "required": true, "validations": { "min": 1, - "max": 100 + "max": 10, + "message": "You must order at least 1 copy and maximum 10 copies" } } ] - }, - { - "name": "numberOfCertificates", - "type": "number", - "required": true, - "validations": { - "min": 0, - "max": 20 - } - }, - { - "name": "email", - "type": "email", - "required": true - }, - { - "name": "phoneNumber", - "type": "string", - "required": true, - "validations": { - "min": 7, - "max": 20 - } } ], "processors": [ + { + "type": "opencrvs", + "config": { + "eventType": "birth", + "officeName": "Registration District A" + } + }, { "type": "email", "config": { "to": "{{db:register-birth-form:admin_email}}", - "subject": "New Birth Registration - {{formData.child.firstNames}} {{formData.child.lastName}}", + "subject": "New Birth Registration - {{formData.child.firstName}} {{formData.child.lastName}}", "template": "birth-registration" } }, diff --git a/schemas/reserve-company-name.json b/schemas/reserve-company-name.json index 73f80f8..2c3f87f 100644 --- a/schemas/reserve-company-name.json +++ b/schemas/reserve-company-name.json @@ -173,9 +173,17 @@ "type": "email", "config": { "to": "{{db:reserve-company-name:admin_email}}", - "subject": "New Request to Reserve Society Name - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", + "subject": "New Request to Reserve Company Name - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "reserve-company-name" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Reserve Company Name - Submission Received", + "template": "reserve-company-name-receipt" + } } ] } diff --git a/schemas/reserve-society-name.json b/schemas/reserve-society-name.json index 8a24435..6d47f6f 100644 --- a/schemas/reserve-society-name.json +++ b/schemas/reserve-society-name.json @@ -151,6 +151,14 @@ "subject": "New Request to Reserve Society Name - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "reserve-society-name" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Reserve Society Name - Submission Received", + "template": "reserve-society-name-receipt" + } } ] } diff --git a/schemas/sell-goods-services-beach-park.json b/schemas/sell-goods-services-beach-park.json index 5caa9c6..95f52e2 100644 --- a/schemas/sell-goods-services-beach-park.json +++ b/schemas/sell-goods-services-beach-park.json @@ -146,46 +146,102 @@ { "name": "goodsOrServices", "type": "string", + "label": "Selling goods or services", "required": true, "validations": { "regex": "^(goods|services)$", "message": "Must be 'goods' or 'services'" } + } + ] + }, + { + "name": "goods", + "type": "object", + "required": false, + "conditionalOn": { + "field": "selling.goodsOrServices", + "value": "goods" + }, + "fields": [ + { + "name": "from", + "type": "string", + "label": "Where are the goods from?", + "required": true, + "validations": { + "regex": "^(barbados|imported)$", + "message": "Must be 'barbados' or 'imported'" + } }, { "name": "manufacturingLocation", "type": "string", + "label": "Which country are the goods from?", "required": false, + "conditionalOn": { + "field": "goods.from", + "value": "imported" + }, "validations": { + "min": 2, "max": 100, - "message": "Location is required" + "message": "Location must be at least 2 characters" + } + }, + { + "name": "description", + "type": "string", + "label": "Describe the goods you would like to sell", + "required": true, + "validations": { + "min": 2, + "max": 500, + "message": "Description of goods must be at least 2 characters" + } + }, + { + "name": "intendedLocation", + "type": "string", + "label": "Where do you intend to sell your goods?", + "required": true, + "validations": { + "min": 2, + "max": 200, + "message": "Place of doing business must be at least 2 characters" } } ] }, { - "name": "business", + "name": "services", "type": "object", - "required": true, + "required": false, + "conditionalOn": { + "field": "selling.goodsOrServices", + "value": "services" + }, "fields": [ { - "name": "descriptionOfGoodsOrServices", + "name": "description", "type": "string", + "label": "Describe the services you would like to offer", "required": true, "validations": { "min": 2, - "max": 100, - "message": "Description is required" + "max": 500, + "message": "Description of services must be at least 2 characters" } }, { - "name": "intendedPlaceOfDoingBusiness", + "name": "intendedLocation", "type": "string", + "label": "Where do you intend to offer this service?", "required": true, "validations": { "min": 2, - "max": 100, - "message": "Place of doing business is required" + "max": 200, + "message": "Place of doing business must be at least 2 characters" } } ] @@ -373,6 +429,26 @@ } } ] + }, + { + "name": "documents", + "type": "object", + "required": true, + "fields": [ + { + "name": "policeCertificate", + "type": "file", + "label": "Police Certificate of Character", + "required": true + }, + { + "name": "passportPhotos", + "type": "file", + "label": "2 passport-sized photos", + "required": true, + "multiple": true + } + ] } ], "processors": [ @@ -383,6 +459,14 @@ "subject": "New Request to sell good or services at a beach or park - {{formData.applicant.firstName}} {{formData.applicant.lastName}}", "template": "sell-goods-services-beach-park-notice" } + }, + { + "type": "email", + "config": { + "to": "{{formData.applicant.email}}", + "subject": "Sell Goods/Services at Beach or Park - Submission Received", + "template": "sell-goods-services-beach-park-receipt" + } } ] } diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 30e21a0..9991e80 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -53,7 +53,30 @@ export default () => ({ ministry_of_agriculture: process.env.EZPAY_MINISTRY_OF_AGRICULTURE_API_KEY, oag_registration: process.env.EZPAY_OAG_REGISTRATION_API_KEY, + post_office: process.env.EZPAY_OAG_REGISTRATION_API_KEY, //TODO: Update when we get the correct API key default: process.env.EZPAY_API_KEY || 'HWqgTn5EXIHLAzVjXtGpB2mIjgQgj0Ql', }, }, + opencrvs: { + // Use localhost URLs when running locally against OpenCRVS dev environment + localhost: process.env.OPENCRVS_LOCALHOST === 'true', + // Production/QA URLs + authBaseUrl: + process.env.OPENCRVS_AUTH_URL || 'https://auth.barbados-qa.opencrvs.org', + eventsBaseUrl: + process.env.OPENCRVS_EVENTS_URL || + 'https://register.barbados-qa.opencrvs.org', + locationsBaseUrl: + process.env.OPENCRVS_LOCATIONS_URL || + 'https://gateway.barbados-qa.opencrvs.org', + // Authentication credentials + clientId: process.env.OPENCRVS_CLIENT_ID, + clientSecret: process.env.OPENCRVS_CLIENT_SECRET, + // Default location names (resolved to IDs at runtime) + defaultOfficeName: + process.env.OPENCRVS_DEFAULT_OFFICE || + 'Registration Department Records Branch', + defaultHealthFacilityName: process.env.OPENCRVS_DEFAULT_HEALTH_FACILITY, + defaultParishName: process.env.OPENCRVS_DEFAULT_PARISH || 'Christ Church', + }, }); diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 1bdcbbc..22b52ef 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -57,49 +57,65 @@ export class EmailService { private registerHandlebarsHelpers(): void { // Equality helper - Handlebars.registerHelper('eq', function (a, b) { + Handlebars.registerHelper('eq', function(a, b) { return a === b; }); // Not equal helper - Handlebars.registerHelper('ne', function (a, b) { + Handlebars.registerHelper('ne', function(a, b) { return a !== b; }); // Greater than helper - Handlebars.registerHelper('gt', function (a, b) { + Handlebars.registerHelper('gt', function(a, b) { return a > b; }); // Less than helper - Handlebars.registerHelper('lt', function (a, b) { + Handlebars.registerHelper('lt', function(a, b) { return a < b; }); // Greater than or equal helper - Handlebars.registerHelper('gte', function (a, b) { + Handlebars.registerHelper('gte', function(a, b) { return a >= b; }); // Less than or equal helper - Handlebars.registerHelper('lte', function (a, b) { + Handlebars.registerHelper('lte', function(a, b) { return a <= b; }); // Logical AND helper - Handlebars.registerHelper('and', function (...args) { + Handlebars.registerHelper('and', function(...args) { // Remove the last argument which is the options object const values = args.slice(0, -1); return values.every((val) => !!val); }); // Logical OR helper - Handlebars.registerHelper('or', function (...args) { + Handlebars.registerHelper('or', function(...args) { // Remove the last argument which is the options object const values = args.slice(0, -1); return values.some((val) => !!val); }); + Handlebars.registerHelper('today', function(...args) { + const currentDate = new Date(); + return currentDate.toDateString(); + }); + + Handlebars.registerHelper('titleCase', function(...args) { + const values = args.slice(0, -1); + const titled = values + .filter(v => typeof v === 'string') + .map(str => str.toLowerCase() + .replace(/\b\w/g, (char: string) => char.toUpperCase())) + .join(" "); + + return Handlebars.escapeExpression(titled); + }); + this.logger.log('Handlebars helpers registered'); } diff --git a/src/email/templates/apply-for-conductor-licence.hbs b/src/email/templates/apply-for-conductor-licence.hbs index 0dff27a..94fb2ed 100644 --- a/src/email/templates/apply-for-conductor-licence.hbs +++ b/src/email/templates/apply-for-conductor-licence.hbs @@ -12,18 +12,17 @@ .section-title { color: #2c5282; font-size: 18px; font-weight: bold; margin-bottom: 15px; border-bottom: 2px solid #2c5282; padding-bottom: 5px; } .field { margin-bottom: 12px; } .field-label { font-weight: bold; - color: #4a5568; display: inline-block; width: 200px; } .field-value { - color: #1a202c; } .address-comparison { display: flex; gap: 20px; } - .address-box { flex: 1; border: 1px solid #e2e8f0; padding: 15px; - border-radius: 5px; } .old-address { background-color: #fed7d7; } - .new-address { background-color: #c6f6d5; } .footer { margin-top: 30px; - padding: 20px; background-color: #edf2f7; border-radius: 5px; text-align: - center; font-size: 14px; color: #718096; } + color: #4a5568; display: inline-block; width: 250px; } .field-value { + color: #1a202c; } .footer { margin-top: 30px; padding: 20px; + background-color: #edf2f7; border-radius: 5px; text-align: center; + font-size: 14px; color: #718096; } .endorsement-item { background-color: + #f7fafc; padding: 15px; margin: 10px 0; border-left: 3px solid #2c5282; + border-radius: 3px; }
-

📮 Post Office Redirection Notice Request

+

🚌 Conductor Licence Application

@@ -31,103 +30,137 @@

Reference: {{submissionId}}

-
Personal Information
+
Applicant Information
Name: - {{applicant.title}} - {{applicant.firstName}} - {{applicant.lastName}} + {{applicant.title}} {{applicant.firstName}}{{#if applicant.middleName}} {{applicant.middleName}}{{/if}} {{applicant.lastName}} +
+ {{#if applicant.dateOfBirth}} +
+ Date of Birth: + {{applicant.dateOfBirth}} +
+ {{/if}} +
+ +
+
Contact Details
+
+ Address: + {{contactDetails.addressLine1}}{{#if contactDetails.addressLine2}}, {{contactDetails.addressLine2}}{{/if}} +
+
+ Parish: + {{contactDetails.parish}} +
+ {{#if contactDetails.postalCode}} +
+ Postal Code: + {{contactDetails.postalCode}} +
+ {{/if}} +
+ Email: + {{contactDetails.email}}
- Date of Birth: - {{applicant.dateOfBirth}} + Telephone: + {{contactDetails.telephoneNumber}}
+
+ +
+
Licence History
- ID Number: - {{applicant.idNumber}} + Has Previous Licence: + {{licenceHistory.hasPreviousLicence}}
- {{#if applicant.passportNumber}} + {{#if licenceHistory.licenceNumber}}
- Passport Number: - {{applicant.passportNumber}} + Licence Number: + {{licenceHistory.licenceNumber}}
{{/if}} - {{#if applicant.email}} + {{#if licenceHistory.dateOfIssue}}
- Email: - {{applicant.email}} + Date of Issue: + {{licenceHistory.dateOfIssue}}
{{/if}} -
-
Contact Details
-
-
-
- Address Line 1: - {{contactDetails.addressLine1}} -
- {{#if contactDetails.addressLine2}} -
- Address Line 2: - {{contactDetails.addressLine2}} -
- {{/if}} -
- Parish: - {{contactDetails.parish}} -
- {{#if contactDetails.postalCode}} +
Endorsements
+
+ Has Endorsements: + {{hasEndorsements}} +
+ {{#if endorsementDetails}} + {{#each endorsementDetails}} +
- Postal Code: - {{contactDetails.postalCode}} + Type: + {{this.typeOfEndorsement}}
- {{/if}} - - {{#if contactDetails.email}}
- Email: - {{contactDetails.email}} + Date: + {{this.dateOfEndorsement}}
- {{/if}} - {{#if contactDetails.telephoneNumber}}
- Phone Number: - {{contactDetails.telephoneNumber}} + Duration: + {{this.duration}}
- {{/if}} -
-
+
+ {{/each}} + {{/if}}
-
House Member Information
-
- Member Name: - {{houseMembers.firstName}} - {{houseMembers.lastName}} -
+
Disqualifications
- Member ID Number: - {{houseMembers.idNumber}} + Has Been Disqualified: + {{disqualifications.hasDisqualifications}}
- {{#if houseMembers.addAnother}} + {{#if disqualifications.courtName}} +
+ Court Name: + {{disqualifications.courtName}} +
+ {{/if}} + {{#if disqualifications.reasonForDisqualification}} +
+ Reason: + {{disqualifications.reasonForDisqualification}} +
+ {{/if}} + {{#if disqualifications.dateOfDisqualification}} +
+ Date: + {{disqualifications.dateOfDisqualification}} +
+ {{/if}} + {{#if disqualifications.lengthOfDisqualification}}
- Add Another Member: - {{houseMembers.addAnother}} + Length: + {{disqualifications.lengthOfDisqualification}}
{{/if}}
+
+
Criminal Record
+
+ Has Criminal Convictions: + {{hasCriminalConvictions}} +
+
+
- \ No newline at end of file + diff --git a/src/email/templates/birth-certificate.hbs b/src/email/templates/birth-certificate.hbs index 7300dbc..073cee9 100644 --- a/src/email/templates/birth-certificate.hbs +++ b/src/email/templates/birth-certificate.hbs @@ -35,11 +35,6 @@ font-weight: 700; line-height: 1.5; } - h3 { - padding: 10px; - margin: 0px; - } - table { width: 100%; border-collapse: collapse; @@ -86,11 +81,8 @@ margin: 10px 0; } .declaration-text { - margin-bottom: 8px; - } - .staff-only { - background: #d3d3d3; - } + margin-bottom: 8px; +} @@ -98,56 +90,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + + + + + + + + + + + + + + + + + + + + +
@@ -160,37 +126,8 @@ Submission ID: {{ submissionId }}

- Submission date and time: {{ submittedAt }} -

-

- Payment date and time: {{ paymentInfo.processedAt }} -

-

- EZPay transaction ID: {{ - paymentInfo.transactionNumber }} + Copies: {{ order.numberOfCopies }}

-

- Cost of certificate(s): {{ paymentInfo.amount }} -

-
-
-

Staff Use Only

- - - - - - - - - - - - - - - -
Due date:
Reference:
Officer assigned:

Declaration

@@ -312,7 +249,7 @@ Relationship to the person to whom the certificate relates to - {{#if relationshipToPerson}} + {{#if relationshipToPerson }} {{ relationshipToPerson }} {{ else }} {{ relationshipOtherDescription }} {{/if}} diff --git a/src/email/templates/birth-registration.hbs b/src/email/templates/birth-registration.hbs index 5d01c43..f631534 100644 --- a/src/email/templates/birth-registration.hbs +++ b/src/email/templates/birth-registration.hbs @@ -31,9 +31,66 @@

Marriage Status

{{#if marriageStatus}}{{#if - (eq marriageStatus 'yes') - }}Married{{else if (eq marriageStatus 'no')}}Not married{{else}}Not - specified{{/if}}{{else}}Not specified{{/if}}

+ (eq marriageStatus 'yes') + }}Married{{else if (eq marriageStatus 'no')}}Not married{{else}}Not + specified{{/if}}{{else}}Not specified{{/if}}

+
+ + +
+

Birth Details

+ + + {{#if birth.placeOfBirth}} + + + + + {{/if}} + {{#if birth.parish}} + + + + + {{/if}} + {{#if birth.streetAddress}} + + + + + {{/if}} + {{#if birth.numberOfBirths}} + + + + + {{/if}} + {{#if birth.attendantAtBirth}} + + + + + {{/if}} + {{#if birth.liveBorn}} + + + + + {{/if}} + {{#if birth.stillBorn}} + + + + + {{/if}} + {{#if birth.totalStillAlive}} + + + + + {{/if}} + +
Place of birth:{{birth.placeOfBirth}}
Parish:{{birth.parish}}
Street address:{{birth.streetAddress}}
Number of births:{{birth.numberOfBirths}}
Attendant at birth:{{birth.attendantAtBirth}}
Live born:{{birth.liveBorn}}
Still born:{{birth.stillBorn}}
Total still alive:{{birth.totalStillAlive}}
@@ -59,40 +116,40 @@ {{mother.lastName}} {{/if}} - {{#if mother.otherSurname}} + {{#if mother.maidenSurname}} - Previous last name: - {{mother.otherSurname}} + Maiden surname: + {{mother.maidenSurname}} {{/if}} - {{#if mother.dateOfBirth}} + {{#if mother.parish}} - Date of birth: - {{mother.dateOfBirth}} + Parish: + {{mother.parish}} {{/if}} - {{#if mother.address}} + {{#if mother.streetAddress}} - Address: - {{mother.address}} + Street address: + {{mother.streetAddress}} {{/if}} - {{#if mother.nationalRegistrationNumber}} + {{#if mother.addressLine2}} - National registration number: - {{mother.nationalRegistrationNumber}} + Address line 2: + {{mother.addressLine2}} {{/if}} - {{#if mother.passportNumber}} + {{#if mother.idNumber}} - Passport number: - {{mother.passportNumber}} + ID number: + {{mother.idNumber}} {{/if}} - {{#if mother.passportPlaceOfIssue}} + {{#if mother.passportNumber}} - Passport place of issue: - {{mother.passportPlaceOfIssue}} + Passport number: + {{mother.passportNumber}} {{/if}} {{#if mother.occupation}} @@ -129,28 +186,28 @@ {{father.lastName}} {{/if}} - {{#if father.otherSurname}} + {{#if father.parish}} - Previous last name: - {{father.otherSurname}} + Parish: + {{father.parish}} {{/if}} - {{#if father.dateOfBirth}} + {{#if father.streetAddress}} - Date of birth: - {{father.dateOfBirth}} + Street address: + {{father.streetAddress}} {{/if}} - {{#if father.address}} + {{#if father.addressLine2}} - Address: - {{father.address}} + Address line 2: + {{father.addressLine2}} {{/if}} - {{#if father.nationalRegistrationNumber}} + {{#if father.idNumber}} - National registration number: - {{father.nationalRegistrationNumber}} + ID number: + {{father.idNumber}} {{/if}} {{#if father.passportNumber}} @@ -159,12 +216,6 @@ {{father.passportNumber}} {{/if}} - {{#if father.passportPlaceOfIssue}} - - Passport place of issue: - {{father.passportPlaceOfIssue}} - - {{/if}} {{#if father.occupation}} Occupation: @@ -181,16 +232,16 @@

Child's Details

- {{#if child.firstNames}} + {{#if child.firstName}} - + {{/if}} - {{#if child.middleNames}} + {{#if child.middleName}} - + {{/if}} {{#if child.lastName}} @@ -211,12 +262,6 @@ {{/if}} - {{#if child.parishOfBirth}} - - - - - {{/if}}
First name:{{child.firstNames}}{{child.firstName}}
Middle name(s):{{child.middleNames}}{{child.middleName}}
{{child.sexAtBirth}}
Place of birth:{{child.parishOfBirth}}
@@ -227,8 +272,8 @@ - - + +
Number of certificates:{{numberOfCertificates}}Number of copies:{{order.numberOfCopies}}
@@ -262,4 +307,4 @@ - \ No newline at end of file + diff --git a/src/email/templates/community-sports-programme-receipt.hbs b/src/email/templates/community-sports-programme-receipt.hbs new file mode 100644 index 0000000..944b9a5 --- /dev/null +++ b/src/email/templates/community-sports-programme-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Community Sports Programme Registration - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your registration for the Community Sports Programme has been received. Our team will review your application and contact you with information about upcoming programmes, schedules, and any required documentation.

+
+ +
+

Need help?

+

If you have questions about your registration, please contact the Ministry of Youth, Sports and Community Empowerment.

+
+
+ + + + diff --git a/src/email/templates/community-sports-registration.hbs b/src/email/templates/community-sports-registration.hbs index bb6c6d6..f626fe2 100644 --- a/src/email/templates/community-sports-registration.hbs +++ b/src/email/templates/community-sports-registration.hbs @@ -90,6 +90,10 @@
Contact Information
+
+ Email: + {{email}} +
Employment Status: {{employmentStatus}} diff --git a/src/email/templates/conductor-licence-receipt.hbs b/src/email/templates/conductor-licence-receipt.hbs new file mode 100644 index 0000000..412321e --- /dev/null +++ b/src/email/templates/conductor-licence-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Conductor Licence Application - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your conductor licence application has been received and is being processed. You will be contacted regarding any additional documentation required and the next steps for obtaining your licence.

+
+ +
+

Need help?

+

If you have questions about your application, please contact the Ministry of Transport, Works and Water Resources.

+
+
+ +
+
+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

+
+
+ + diff --git a/src/email/templates/death-certificate.hbs b/src/email/templates/death-certificate.hbs index e9894d0..9229d2f 100644 --- a/src/email/templates/death-certificate.hbs +++ b/src/email/templates/death-certificate.hbs @@ -35,11 +35,6 @@ font-weight: 700; line-height: 1.5; } - h3 { - padding: 10px; - margin: 0px; - } - table { width: 100%; border-collapse: collapse; @@ -86,11 +81,8 @@ margin: 10px 0; } .declaration-text { - margin-bottom: 8px; - } - .staff-only { - background: #d3d3d3; - } + margin-bottom: 8px; +} @@ -98,56 +90,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + + + + + + + + + + + + + + + + + + + + +
@@ -160,37 +126,11 @@ Submission ID: {{ submissionId }}

- Submission date and time: {{ submittedAt }} -

-

- Payment date and time: {{ paymentInfo.processedAt }} + Copies: {{ order.numberOfCopies }}

- EZPay transaction ID: {{ - paymentInfo.transactionNumber }} + Relationship to Deceased: {{ relationshipToPerson }}

-

- Cost of certificate(s): {{ paymentInfo.amount }} -

-
-
-

Staff Use Only

- - - - - - - - - - - - - - - -
Due date:
Reference:
Officer assigned:

Declaration

@@ -207,7 +147,8 @@ {{ applicant.firstName }} {{#if applicant.middleName}} - {{ applicant.middleName }} {{/if}} {{ applicant.lastName }} + {{ + applicant.middleName }} {{/if}} {{ applicant.lastName }} @@ -354,8 +295,9 @@ Known date of death - {{#if (eq deceased.knownDateOfDeath 'yes')}} - Yes{{ else }}No{{/if}} + {{#if (eq deceased.knownDateOfDeath 'yes') }} + Yes{{ else + }}No{{/if}} {{/if}} diff --git a/src/email/templates/fire-service-inspection-receipt.hbs b/src/email/templates/fire-service-inspection-receipt.hbs new file mode 100644 index 0000000..5ecfac4 --- /dev/null +++ b/src/email/templates/fire-service-inspection-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Fire Service Inspection Request - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your fire service inspection request has been received. A fire safety officer will contact you to schedule the inspection at your property. Please ensure the premises are accessible on the scheduled date.

+
+ +
+

Need help?

+

If you have questions about your inspection request, please contact the Barbados Fire Service.

+
+
+ +
+
+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

+
+
+ + diff --git a/src/email/templates/jobstart-plus-programme-receipt.hbs b/src/email/templates/jobstart-plus-programme-receipt.hbs new file mode 100644 index 0000000..912b088 --- /dev/null +++ b/src/email/templates/jobstart-plus-programme-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

JobStart Plus Programme Application - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your application for the JobStart Plus Programme has been received. Our team will review your submission and if shortlisted, you will be contacted for an interview. Please ensure your contact information is accurate.

+
+ +
+

Need help?

+

If you have questions about your application, please contact the Ministry of Labour.

+
+
+ +
+
+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

+
+
+ + diff --git a/src/email/templates/jobstart-plus-programme.hbs b/src/email/templates/jobstart-plus-programme.hbs index 7afa026..01be70a 100644 --- a/src/email/templates/jobstart-plus-programme.hbs +++ b/src/email/templates/jobstart-plus-programme.hbs @@ -18,7 +18,11 @@ border-radius: 5px; } .old-address { background-color: #fed7d7; } .new-address { background-color: #c6f6d5; } .footer { margin-top: 30px; padding: 20px; background-color: #edf2f7; border-radius: 5px; text-align: - center; font-size: 14px; color: #718096; } + center; font-size: 14px; color: #718096; } .education-item { + background-color: #f7fafc; padding: 15px; margin-bottom: 10px; + border-radius: 5px; border-left: 3px solid #2c5282; } .employment-item { + background-color: #f7fafc; padding: 15px; margin-bottom: 10px; + border-radius: 5px; border-left: 3px solid #48bb78; } @@ -36,6 +40,7 @@ Name: {{applicant.title}} {{applicant.firstName}} + {{#if applicant.middleName}}{{applicant.middleName}}{{/if}} {{applicant.lastName}}
@@ -43,66 +48,274 @@ {{applicant.dateOfBirth}}
- ID Number: - {{applicant.idNumber}} + Sex: + {{applicant.sex}}
+
+ Marital Status: + {{applicant.maritalStatus}} +
+ {{#if applicant.idNumber}} +
+ ID Number: + {{applicant.idNumber}} +
+ {{/if}} {{#if applicant.passportNumber}}
Passport Number: {{applicant.passportNumber}}
{{/if}} - {{#if applicant.email}} + {{#if applicant.hasNisNumber}}
- Email: - {{applicant.email}} + Has NIS Number: + {{applicant.hasNisNumber}} +
+ {{/if}} + {{#if applicant.nisNumber}} +
+ NIS Number: + {{applicant.nisNumber}} +
+ {{/if}} +
+ Has Disability: + {{applicant.hasDisability}} +
+ {{#if applicant.disabilityDetails}} +
+ Disability Details: + {{applicant.disabilityDetails}}
{{/if}} -
Contact Details
-
-
+
+ Address Line 1: + {{contactDetails.addressLine1}} +
+ {{#if contactDetails.addressLine2}} +
+ Address Line 2: + {{contactDetails.addressLine2}} +
+ {{/if}} +
+ Parish: + {{contactDetails.parish}} +
+ {{#if contactDetails.postalCode}} +
+ Postal Code: + {{contactDetails.postalCode}} +
+ {{/if}} +
+ Email: + {{contactDetails.email}} +
+
+ Phone Number: + {{contactDetails.telephoneNumber}} +
+
+ +
+
Emergency Contact
+
+ Name: + {{emergency.title}} + {{emergency.firstName}} + {{emergency.lastName}} +
+
+ Relationship: + {{emergency.relationship}} +
+
+ Address Line 1: + {{emergency.addressLine1}} +
+ {{#if emergency.addressLine2}} +
+ Address Line 2: + {{emergency.addressLine2}} +
+ {{/if}} +
+ Parish: + {{emergency.parish}} +
+ {{#if emergency.postalCode}} +
+ Postal Code: + {{emergency.postalCode}} +
+ {{/if}} +
+ Email: + {{emergency.email}} +
+
+ Phone Number: + {{emergency.telephoneNumber}} +
+
+ +
+
Primary Education
+
+ School Name: + {{primaryEducation.schoolName}} +
+
+ Start Year: + {{primaryEducation.startYear}} +
+
+ End Year: + {{primaryEducation.endYear}} +
+
+ +
+
Secondary Education
+
+ School Name: + {{secondaryEducation.schoolName}} +
+
+ Start Year: + {{secondaryEducation.startYear}} +
+
+ End Year: + {{secondaryEducation.endYear}} +
+
+ + {{#if postSecondaryEducation}} +
+
Post-Secondary Education
+ {{#each postSecondaryEducation}} +
+ {{#if this.institutionName}} +
+ Institution: + {{this.institutionName}} +
+ {{/if}} + {{#if this.qualificationsObtained}} +
+ Qualifications: + {{this.qualificationsObtained}} +
+ {{/if}} + {{#if this.coursesOrSubjects}} +
+ Courses/Subjects: + {{this.coursesOrSubjects}} +
+ {{/if}} + {{#if this.startDate}} +
+ Start Date: + {{this.startDate}} +
+ {{/if}} + {{#if this.endDate}} +
+ End Date: + {{this.endDate}} +
+ {{/if}} +
+ {{/each}} +
+ {{/if}} + +
+
Employment History
+
+ Has Previous Paid Job: + {{hasPreviousPaidJob}} +
+ {{#if employmentHistory}} + {{#each employmentHistory}} +
+ {{#if this.employerName}} +
+ Employer: + {{this.employerName}} +
+ {{/if}} + {{#if this.occupation}} +
+ Occupation: + {{this.occupation}} +
+ {{/if}} + {{#if this.startDate}} +
+ Start Date: + {{this.startDate}} +
+ {{/if}} + {{#if this.endDate}} +
+ End Date: + {{this.endDate}} +
+ {{/if}} + {{#if this.currentlyWorkingHere}} +
+ Currently Working Here: + {{this.currentlyWorkingHere}} +
+ {{/if}} + {{#if this.mainTasks}} +
+ Main Tasks: + {{this.mainTasks}} +
+ {{/if}} +
+ {{/each}} + {{/if}} +
+ + {{#if eligibility}} +
+
Eligibility Information
+ {{#if eligibility.interests}}
- Address Line 1: - {{contactDetails.addressLine1}} + Job/Trade Interests: + {{eligibility.interests}}
- {{#if contactDetails.addressLine2}} -
- Address Line 2: - {{contactDetails.addressLine2}} -
- {{/if}} + {{/if}} + {{#if eligibility.areYouOver18}}
- Parish: - {{contactDetails.parish}} + Over 18: + {{eligibility.areYouOver18}}
- {{#if contactDetails.postalCode}} -
- Postal Code: - {{contactDetails.postalCode}} -
- {{/if}} - - {{#if contactDetails.email}} -
- Email: - {{contactDetails.email}} -
- {{/if}} - {{#if contactDetails.telephoneNumber}} -
- Phone Number: - {{contactDetails.telephoneNumber}} -
- {{/if}} -
+ {{/if}} + {{#if eligibility.willingToWorkAtNight}} +
+ Willing to Work at Night: + {{eligibility.willingToWorkAtNight}} +
+ {{/if}} + {{#if eligibility.shortTermGoals}} +
+ Short-term Goals: + {{eligibility.shortTermGoals}} +
+ {{/if}}
-
+ {{/if}} - \ No newline at end of file + diff --git a/src/email/templates/marriage-certificate.hbs b/src/email/templates/marriage-certificate.hbs index 216053b..6f799d4 100644 --- a/src/email/templates/marriage-certificate.hbs +++ b/src/email/templates/marriage-certificate.hbs @@ -35,11 +35,6 @@ font-weight: 700; line-height: 1.5; } - h3 { - padding: 10px; - margin: 0px; - } - table { width: 100%; border-collapse: collapse; @@ -86,11 +81,8 @@ margin: 10px 0; } .declaration-text { - margin-bottom: 8px; - } - .staff-only { - background: #d3d3d3; - } + margin-bottom: 8px; +} @@ -98,56 +90,30 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + fill="none" + xmlns="http://www.w3.org/2000/svg"> + + + + + + + + + + + + + + + + + + + + + +
@@ -160,37 +126,8 @@ Submission ID: {{ submissionId }}

- Submission date and time: {{ submittedAt }} -

-

- Payment date and time: {{ paymentInfo.processedAt }} + Copies: {{ order.numberOfCopies }}

-

- EZPay transaction ID: {{ - paymentInfo.transactionNumber }} -

-

- Cost of certificate(s): {{ paymentInfo.amount }} -

-
-
-

Staff Use Only

- - - - - - - - - - - - - - - -
Due date:
Reference:
Officer assigned:

Declaration

@@ -300,7 +237,7 @@
-

Relationship to person named on the certificate

+

Relationship to Person Named on the Certificate

@@ -323,80 +260,81 @@
-
- -
-

Details of marriage certificate required

- - - - - - - - - - - - - - - - - - - - - - + + + +
+

Details of Marriage Certificate Required

+
Husband – Surname{{ husband.lastName }}
Husband – First name{{ husband.firstName }}
Wife (maiden) – Surname{{ wife.maidenName }}
Wife – First name{{ wife.firstName }}
Date of marriage{{ marriageDetails.dateOfMarriage }}
+ + + + + + + + + + + + + + + + + + + + + + + + + + {{#if husband.idNumber}} - - + + - {{#if husband.idNumber}} + {{ else }} + {{#if husband.passportNumber}} + + + + + {{/if}} {{/if}} + {{#if wife.idNumber}} - - + + {{ else }} - {{#if husband.passportNumber}} + {{#if wife.passportNumber}} - - + + {{/if}} {{/if}} - {{#if wife.idNumber}} - - - - - {{ else }} - {{#if wife.passportNumber}} - - - - - {{/if}} {{/if}} - -
Husband – Surname{{ husband.lastName }}
Husband – First name{{ husband.firstName }}
Wife (maiden) – Surname{{ wife.maidenName }}
Wife – First name{{ wife.firstName }}
Date of marriage{{ marriageDetails.dateOfMarriage }}
Place of marriage{{ marriageDetails.placeOfMarriage }}
Place of marriage{{ marriageDetails.placeOfMarriage }}Husband - National registration No.{{ husband.idNumber }}
Husband - Passport number{{ husband.passportNumber }}
Husband - National registration No.{{ husband.idNumber }}Wife - National registration No.{{ wife.idNumber }}
Husband - Passport number{{ husband.passportNumber }}Wife - Passport number{{ wife.passportNumber }}
Wife - National registration No.{{ wife.idNumber }}
Wife - Passport number{{ wife.passportNumber }}
-
- - + + + + + - - + + + diff --git a/src/email/templates/permission-to-remove-tree-receipt.hbs b/src/email/templates/permission-to-remove-tree-receipt.hbs new file mode 100644 index 0000000..9350903 --- /dev/null +++ b/src/email/templates/permission-to-remove-tree-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Permission to Remove Tree - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your application for permission to remove a tree has been received. An environmental officer will review your request and may conduct a site visit. You will be notified of the decision once the review is complete.

+
+ +
+

Need help?

+

If you have questions about your application, please contact the Environmental Protection Department.

+
+
+ + + + diff --git a/src/email/templates/post-office-redirection-business-receipt.hbs b/src/email/templates/post-office-redirection-business-receipt.hbs new file mode 100644 index 0000000..3da672a --- /dev/null +++ b/src/email/templates/post-office-redirection-business-receipt.hbs @@ -0,0 +1,119 @@ + + + + + + +
+
+ +
+
+ +
+

Thank you for your application

+ +

+ We have successfully received your business mail redirection application + for {{businessName}}. +

+ +

Submission ID: {{submissionId}}

+ +
+

Redirection Details

+

+ Start Date: + {{newAddress.redirectionStartDate}}
+ End Date: + {{newAddress.redirectionEndDate}} +

+
+ +
+

What happens next?

+

+ Your mail redirection will be activated on the start date specified. + We will contact you if we require any additional information. +

+
+
+ + + + \ No newline at end of file diff --git a/src/email/templates/post-office-redirection-deceased-receipt.hbs b/src/email/templates/post-office-redirection-deceased-receipt.hbs new file mode 100644 index 0000000..2c5c0d7 --- /dev/null +++ b/src/email/templates/post-office-redirection-deceased-receipt.hbs @@ -0,0 +1,111 @@ + + + + + + +
+
+ +
+
+ +
+

Thank you for your application

+ +

+ We have successfully received your mail redirection request for + {{deceased.firstName}} + {{deceased.lastName}}. +

+ +

Submission ID: {{submissionId}}

+ +
+

What happens next?

+

+ Your mail redirection request for the deceased's mail is being + processed. We will contact you if we require any additional + information. +

+
+
+ + + + \ No newline at end of file diff --git a/src/email/templates/post-office-redirection-individual-receipt.hbs b/src/email/templates/post-office-redirection-individual-receipt.hbs new file mode 100644 index 0000000..6a4f008 --- /dev/null +++ b/src/email/templates/post-office-redirection-individual-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Individual Mail Redirection - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your request to redirect your personal mail has been received. The Barbados Postal Service will process your request and begin redirecting mail to your new address within the specified timeframe. You will receive confirmation once the redirection is active.

+
+ +
+

Need help?

+

If you have questions about your mail redirection, please contact the Barbados Postal Service.

+
+
+ + + + diff --git a/src/email/templates/post-office-redirection-notice.hbs b/src/email/templates/post-office-redirection-notice.hbs index 7638206..9916724 100644 --- a/src/email/templates/post-office-redirection-notice.hbs +++ b/src/email/templates/post-office-redirection-notice.hbs @@ -1,7 +1,7 @@ - - - - - -
-

📮 Post Office Redirection for Deceased - Request Submission

-
- -
-

A new post office redirection request for a deceased person has been - submitted.

- -

Reference: {{submissionId}}

- - -
-
Deceased Person Information
-
- Title: - {{deceased.title}} + word-break: break-all; } .minor-item, .adult-item { background-color: #f7fafc; + padding: 10px; margin-bottom: 8px; border-radius: 5px; + border-left: 3px solid #2c5282; } + + + +
+ {{#if deceased}} +

Post Office Redirection for Deceased - Request Submission

+ {{ else if businessName }} +

Post Office Redirection for Business - Request Submission

+ {{ else }} +

Post Office Redirection for Individual - Request Submission

+ {{/if}}
-
- First Name: - {{deceased.firstName}} -
- {{#if deceased.middleName}} -
- Middle Name: - {{deceased.middleName}} -
- {{/if}} -
- Last Name: - {{deceased.lastName}} -
- {{#if deceased.dateOfDeath}} -
- Date of Death: - {{deceased.dateOfDeath}} -
- {{/if}} -
- - -
-
Old/Previous Address
-
- Address Line 1: - {{oldAddress.addressLine1}} -
- {{#if oldAddress.addressLine2}} -
- Address Line 2: - {{oldAddress.addressLine2}} -
- {{/if}} -
- Parish: - {{oldAddress.parish}} -
- {{#if oldAddress.postcode}} -
- Postcode: - {{oldAddress.postcode}} -
- {{/if}} -
- - -
-
Applicant Information
-
- Title: - {{applicant.title}} -
-
- First Name: - {{applicant.firstName}} -
-
- Last Name: - {{applicant.lastName}} -
-
- Relationship to Deceased: - {{applicant.relationshipToDeceased}} -
-
- Email Address: - {{applicant.email}} -
-
- Telephone Number: - {{applicant.telephoneNumber}} -
-
- - -
-
Permission Details
-
- Permission to Act on Estate: - {{permissionDetails}} -
-
- - -
-
New Address for Mail Redirection
-
- Address Line 1: - {{newAddress.addressLine1}} -
- {{#if newAddress.addressLine2}} -
- Address Line 2: - {{newAddress.addressLine2}} -
- {{/if}} -
- Parish: - {{newAddress.parish}} -
- {{#if newAddress.postcode}} -
- Postcode: - {{newAddress.postcode}} -
- {{/if}} -
- Permanent Redirection: - {{newAddress.isRedirectPermanent}} -
- {{#if newAddress.redirectionStartDate}} -
- Redirection Start Date: - {{newAddress.redirectionStartDate}} -
- {{/if}} - {{#if newAddress.redirectionEndDate}} -
- Redirection End Date: - {{newAddress.redirectionEndDate}} -
- {{/if}} -
- - - {{#if uploadDocumentUrls}} -
-
Uploaded Documents
- {{#if uploadDocumentUrls.length}} -
    - {{#each uploadDocumentUrls}} -
  • {{this}}
  • - {{/each}} -
- {{else}} -

No documents uploaded.

- {{/if}} -
- {{/if}} - - -
- - \ No newline at end of file +
+ {{#if deceased}} +

+ A new post office redirection request for a deceased person has been + submitted. +

+ {{ else if businessName }} +

+ A new post office redirection request for a business has been + submitted. +

+ {{ else }} +

+ A new post office redirection request for an individual has been + submitted. +

+ {{/if}} +

+ Reference: {{ submissionId }} +

+ +
+
Deceased Person Information
+
+ Title: + {{ deceased.title }} +
+
+ First Name: + {{ deceased.firstName }} +
+ {{#if deceased.middleName}} +
+ Title: + {{ deceased.title }} +
+
+ First Name: + {{ deceased.firstName }} +
+ {{#if deceased.middleName}} +
+ Middle Name: + {{ deceased.middleName }} +
+ {{/if}} +
+ Last Name: + {{ deceased.lastName }} +
+ {{#if deceased.dateOfDeath}} +
+ Date of Death: + {{ deceased.dateOfDeath }} +
+ {{/if}} +
+ {{/if}} + + {{#if businessName}} +
+
Business Information
+
+ Business Name: + {{ businessName }} +
+
+ {{/if}} + +
+
+ {{#if businessName}} + Current Address{{ else }}Old/Previous + Address{{/if}} +
+ {{#if oldAddress}} +
+ Address Line 1: + {{ oldAddress.addressLine1 }} +
+ {{#if oldAddress.addressLine2}} +
+ Address Line 2: + {{ oldAddress.addressLine2 }} +
+ {{/if}} +
+ Parish: + {{ oldAddress.parish }} +
+ {{#if oldAddress.postcode}} +
+ Postcode: + {{ oldAddress.postcode }} +
+ {{/if}} + {{#if oldAddress.postalCode}} +
+ Postal Code: + {{ oldAddress.postalCode }} +
+ {{/if}} + {{/if}} + {{#if currentAddress}} +
+ Address Line 1: + {{ currentAddress.addressLine1 }} +
+ {{#if currentAddress.addressLine2}} +
+ Address Line 2: + {{ currentAddress.addressLine2 }} +
+ {{/if}} +
+ Parish: + {{ currentAddress.parish }} +
+ {{#if currentAddress.postcode}} +
+ Postcode: + {{ currentAddress.postcode }} +
+ {{/if}} + {{/if}} +
+ + {{#if applicant}} +
+
+ {{#if deceased}} + Applicant + Information{{ else if businessName }}Contact Person + Information{{ else }}Applicant Information{{/if}} +
+ {{#if applicant.title}} +
+ Title: + {{ applicant.title }} +
+ {{/if}} +
+ First Name: + {{ applicant.firstName }} +
+ {{#if applicant.middleName}} +
+ Middle Name: + {{ applicant.middleName }} +
+ {{/if}} +
+ Last Name: + {{ applicant.lastName }} +
+ {{#if applicant.dateOfBirth}} +
+ Date of Birth: + {{ applicant.dateOfBirth }} +
+ {{/if}} + {{#if applicant.idNumber}} +
+ ID Number: + {{ applicant.idNumber }} +
+ {{/if}} + {{#if applicant.passportNumber}} +
+ Passport Number: + {{ applicant.passportNumber }} +
+ {{/if}} + {{#if applicant.relationshipToDeceased}} +
+ Relationship to Deceased: + {{ applicant.relationshipToDeceased }} +
+ {{/if}} +
+ Email Address: + {{ applicant.email }} +
+
+ Telephone Number: + {{ applicant.telephoneNumber }} +
+
+ {{/if}} + + {{#if permissionDetails}} +
+
Permission Details
+
+ Permission to Act on Estate: + {{ permissionDetails }} +
+
+ {{/if}} + + {{#if positionDetails}} +
+
What position do you hold in the business?
+
+ Position: + {{ positionDetails }} +
+
+ {{/if}} + + {{#if newAddress}} +
+
New Address for Mail Redirection
+
+ Address Line 1: + {{ newAddress.addressLine1 }} +
+ {{#if newAddress.addressLine2}} +
+ Address Line 2: + {{ newAddress.addressLine2 }} +
+ {{/if}} +
+ Parish: + {{ newAddress.parish }} +
+ {{#if newAddress.postcode}} +
+ Postcode: + {{ newAddress.postcode }} +
+ {{/if}} + {{#if newAddress.postalCode}} +
+ Postal Code: + {{ newAddress.postalCode }} +
+ {{/if}} + {{#if newAddress.isRedirectPermanent}} +
+ Permanent Redirection: + {{ newAddress.isRedirectPermanent }} +
+ {{/if}} + {{#if newAddress.isMovingPermanent}} +
+ Moving Permanently: + {{ newAddress.isMovingPermanent }} +
+ {{/if}} + {{#if newAddress.redirectionStartDate}} +
+ Redirection Start Date: + {{ newAddress.redirectionStartDate }} +
+ {{/if}} + {{#if newAddress.redirectionEndDate}} +
+ Redirection End Date: + {{ newAddress.redirectionEndDate }} +
+ {{/if}} +
+ {{/if}} + + {{#if anyMinorDependents}} +
+
Minor Dependents Information
+
+ Any Minor Dependents: + {{ anyMinorDependents }} +
+ {{#if minorDetails}} +
+ Minor Dependents: + {{#each minorDetails}} +
+ {{#if this.firstName}} +
+ First Name: + {{ this.firstName }} +
+ {{/if}} + {{#if this.lastName}} +
+ Last Name: + {{ this.lastName }} +
+ {{/if}} +
+ {{/each}} +
+ {{/if}} +
+ {{/if}} + + {{#if uploadDocumentUrls}} +
+
Uploaded Documents
+ {{#if uploadDocumentUrls.length}} +
    + {{#each uploadDocumentUrls}} +
  • + {{ this }} +
  • + {{/each}} +
+ {{ else }} +

No documents uploaded.

+ {{/if}} +
+ {{/if}} + + {{#if uploadDocumentUrls}} +
+
Uploaded Documents
+ {{#if uploadDocumentUrls.length}} +
    + {{#each uploadDocumentUrls}} +
  • + {{ this }} +
  • + {{/each}} +
+ {{ else }} +

No documents uploaded.

+ {{/if}} +
+ {{/if}} + +
+ + + >>>>>>> 2342370 (Updated schema and email template to match changes) diff --git a/src/email/templates/primary-school-textbook-grant-receipt.hbs b/src/email/templates/primary-school-textbook-grant-receipt.hbs index 37959a9..997e63e 100644 --- a/src/email/templates/primary-school-textbook-grant-receipt.hbs +++ b/src/email/templates/primary-school-textbook-grant-receipt.hbs @@ -2,160 +2,53 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - + Government of Barbados
-

Thank you for your application

+

Primary School Textbook Grant - Submission Received

-
-

- Your textbook grant application has been submitted to the Ministry of - Education. -

+
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

-
-

Application Details

-

- Student: - {{beneficiaries.firstName}} - {{beneficiaries.lastName}} -

-

Class: Class {{beneficiaries.class}}

-

- Applicant: - {{applicant.title}} - {{applicant.firstName}} - {{applicant.lastName}} -

-

Contact: {{contact.telephoneNumber}}

-

Email: {{applicant.email}}

- {{#if guardian.email}} -

Guardian Email: {{guardian.email}}

- {{/if}} +
+

What happens next

+

Your application for the Primary School Textbook Grant has been received. The Ministry of Education will review your application and process it according to grant eligibility criteria. You will be notified of the outcome.

-

What happens next?

-

- Your application will be reviewed and processed according to grant - eligibility criteria. -

+

Need help?

+

If you have questions about your application, please contact the Ministry of Education, Technological and Vocational Training.

-
-

- Government of Barbados - Ministry of Education, Technological and - Vocational Training
- This is an automated confirmation from the Primary School Textbook - Grant Program. -

+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

- \ No newline at end of file + diff --git a/src/email/templates/primary-school-textbook-grant.hbs b/src/email/templates/primary-school-textbook-grant.hbs index 7054611..66b6ea5 100644 --- a/src/email/templates/primary-school-textbook-grant.hbs +++ b/src/email/templates/primary-school-textbook-grant.hbs @@ -17,53 +17,96 @@ background-color: #edf2f7; border-radius: 5px; text-align: center; font-size: 14px; color: #718096; } .financial-highlight { background-color: #fef3c7; padding: 10px; border-radius: 5px; margin-top: - 10px; border-left: 4px solid #f59e0b; } + 10px; border-left: 4px solid #f59e0b; } .beneficiary-item { + background-color: #f0f9ff; padding: 15px; margin-bottom: 15px; + border-radius: 5px; border-left: 4px solid #0284c7; }
-

📚 Primary School Textbook Grant Application

+

Primary School Textbook Grant Application

A new textbook grant application has been submitted.

Reference: {{submissionId}}

-
-
Student Information
-
- Student Name: - {{beneficiaries.firstName}} - {{beneficiaries.lastName}} -
- {{#if beneficiaries.idNumber}} -
- Student ID: - {{beneficiaries.idNumber}} -
- {{/if}} - {{#if beneficiaries.passportNumber}} -
- Passport Number: - {{beneficiaries.passportNumber}} -
- {{/if}} -
- Class/Grade: - Class {{beneficiaries.class}} -
-
- Relationship to Child: - {{beneficiaries.relationshipToChild}} -
-
+ {{#if beneficiaries}} +
+
Student Information (Beneficiaries)
+ {{#each beneficiaries}} +
+ {{#if this.value}} +
+ Student: + {{this.value}} +
+ {{/if}} + {{#if this.guardian}} +
+ Guardian Information: +
+ {{#if this.guardian.title}} +
+ Title: + {{this.guardian.title}} +
+ {{/if}} + {{#if this.guardian.firstName}} +
+ First Name: + {{this.guardian.firstName}} +
+ {{/if}} + {{#if this.guardian.middleName}} +
+ Middle Name: + {{this.guardian.middleName}} +
+ {{/if}} + {{#if this.guardian.lastName}} +
+ Last Name: + {{this.guardian.lastName}} +
+ {{/if}} + {{#if this.guardian.idNumber}} +
+ ID Number: + {{this.guardian.idNumber}} +
+ {{/if}} + {{#if this.guardian.gender}} +
+ Gender: + {{this.guardian.gender}} +
+ {{/if}} + {{#if this.guardian.relationship}} +
+ Relationship: + {{this.guardian.relationship}} +
+ {{/if}} + {{#if this.guardian.email}} +
+ Email: + {{this.guardian.email}} +
+ {{/if}} +
+
+ {{/if}} +
+ {{/each}} +
+ {{/if}}
Applicant Information
Name: - {{applicant.title}} - {{applicant.firstName}} + {{applicant.firstName}} {{applicant.lastName}}
@@ -110,91 +153,47 @@ {{/if}}
-
-
Parent/Guardian Relationship
-
- Are you the parent or guardian? - {{guardianOrParentRelationship}} -
-
- -
-
Guardian Information
- {{#if guardian.title}} -
- Title: - {{guardian.title}} -
- {{/if}} -
- Guardian Name: - {{guardian.firstName}} - {{#if guardian.middleName}}{{guardian.middleName}} - {{/if}}{{guardian.lastName}} -
-
- Guardian ID: - {{guardian.idNumber}} -
- {{#if guardian.gender}} -
- Gender: - {{guardian.gender}} -
- {{/if}} -
- Relationship to Student: - {{guardian.relationship}} -
- {{#if guardian.email}} -
- Email: - {{guardian.email}} + {{#if bankAccount}} +
+
Bank Account Information
+
+
+ Account Holder Name: + {{bankAccount.accountHolderName}} +
+
+ Bank Name: + {{bankAccount.bankName}} +
+
+ Account Number: + {{bankAccount.accountNumber}} +
+ {{#if bankAccount.branchName}} +
+ Branch Name: + {{bankAccount.branchName}} +
+ {{/if}} + {{#if bankAccount.branchCode}} +
+ Branch Code: + {{bankAccount.branchCode}} +
+ {{/if}} + {{#if bankAccount.branchLocation}} +
+ Branch Location: + {{bankAccount.branchLocation}} +
+ {{/if}} +
+ Account Type: + {{bankAccount.accountType}} +
- {{/if}} -
- -
-
Contact Information
-
- Address: - {{contact.addressLine1}}{{#if - contact.addressLine2 - }}, {{contact.addressLine2}}{{/if}} -
-
- Parish: - {{contact.parish}} -
-
- Phone: - {{contact.telephoneNumber}} -
-
- -
-
Bank Account Information
-
- Account Holder Name: - {{bankAccount.accountHolderName}} -
-
- Bank Name: - {{bankAccount.bankName}}
-
- Account Number: - {{bankAccount.accountNumber}} -
-
- Branch Location: - {{bankAccount.branchLocation}} -
-
- Account Type: - {{bankAccount.accountType}} -
-
+ {{/if}}
- \ No newline at end of file + diff --git a/src/email/templates/project-protege-mentor-receipt.hbs b/src/email/templates/project-protege-mentor-receipt.hbs index a9220a0..baf4c0b 100644 --- a/src/email/templates/project-protege-mentor-receipt.hbs +++ b/src/email/templates/project-protege-mentor-receipt.hbs @@ -1,7 +1,7 @@ - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- -
-

Thank you for your application

- -

Dear {{personal.firstName}} {{personal.lastName}},

-

Thank you for submitting your application to become a mentor for the - Project Protege programme.

-

Your application has been successfully received on - {{submittedAt}}.

- -
-

Application Summary

-

Name: - {{personal.firstName}} - {{personal.lastName}}

-

Email: {{contact.email}}

-

Telephone: {{contact.telephoneNumber}}

-

Parish: {{contact.parish}}

-

Employment Status: {{personal.employmentStatus}}

-

Mentee Gender Preference: - {{preferences.menteeGenderPreference}}

- {{#if experience.hasMentorExperience}} -

Previous Mentoring Experience: - {{experience.yearsOfExperience}} - years

- {{/if}} -
- -
-

- Your information has been sent to the Youth Development Programme, - part of the Division of Youth Affairs -

-
- -
-

What happens next?

-

- If you are shortlisted, you will be contacted by phone or email and - invited for an interview. -

-
-
- -
-
-

- Government of Barbados - Civil Registration Department
- This is an automated confirmation email. Please do not reply to this - email. -

-
-
- - \ No newline at end of file + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

Thank you for your application

+

Dear {{ applicant.firstName }} {{ personal.lastName }},

+

+ Thank you for submitting your application to become a mentor for the + Project Protege programme. +

+

+ Your application has been successfully received on + {{ submittedAt }}. +

+
+

Application Summary

+

+ Name: + {{ applicant.firstName }} + {{ applicant.lastName }} +

+

+ Email: {{ contact.email }} +

+

+ Telephone: {{ contact.telephoneNumber }} +

+

+ Parish: {{ contact.parish }} +

+

+ Employment Status: {{ applicant.employmentStatus }} +

+

+ Mentee Gender Preference: + {{ preferences.menteeGenderPreference }} +

+ {{#if experience.hasMentorExperience}} +

+ Previous Mentoring Experience: + {{ experience.yearsOfExperience }} + years +

+ {{/if}} +
+
+

+ Your information has been sent to the Youth Development Programme, + part of the Division of Youth Affairs +

+
+
+

What happens next?

+

+ If you are shortlisted, you will be contacted by phone or email and + invited for an interview. +

+
+
+
+
+

+ Government of Barbados - Civil Registration Department +
+ This is an automated confirmation email. Please do not reply to this + email. +

+
+
+ + diff --git a/src/email/templates/project-protege-mentor.hbs b/src/email/templates/project-protege-mentor.hbs index 973d8d1..23ba08a 100644 --- a/src/email/templates/project-protege-mentor.hbs +++ b/src/email/templates/project-protege-mentor.hbs @@ -1,7 +1,7 @@ - - - - - -
-

🤝 Project Protege Mentor Registration

-
- -
-

A new mentor registration has been submitted for the Project Protege - mentorship program.

-

Reference: {{submissionId}}

- -
-
Personal Information
-
- Name: - {{personal.firstName}} - {{personal.lastName}} -
-
- Date of Birth: - {{personal.dateOfBirth}} -
-
- Employment Status: - {{personal.employmentStatus}} -
- {{#if personal.institutionName}} -
- Institution Name: - {{personal.institutionName}} -
- {{/if}} - {{#if personal.employerName}} -
- Employer Name: - {{personal.employerName}} -
- {{/if}} - {{#if personal.otherEmploymentDetails}} -
- Other Employment Details: - {{personal.otherEmploymentDetails}} -
- {{/if}} -
- -
-
Contact Information
-
- Email: - {{contact.email}} -
-
- Telephone: - {{contact.telephoneNumber}} -
-
- Address: - {{contact.addressLine1}}{{#if - contact.addressLine2 - }}, {{contact.addressLine2}}{{/if}} -
-
- Parish: - {{contact.parish}} -
-
- -
-
Mentoring Information
-
- Why do you want to mentor: -
{{mentorship.whyMentor}}
-
-
- Your Strengths: -
{{mentorship.strengths}}
-
-
- What mentee should learn: -
{{mentorship.menteeLearn}}
-
-
- -
-
Mentee Preferences
-
- Mentee Gender Preference: - {{preferences.menteeGenderPreference}} -
-
- Share Phone Number: - {{preferences.sharePhoneNumber}} -
- {{#if preferences.menteePhoneNumber}} -
- Mentee Phone Number: - {{preferences.menteePhoneNumber}} -
- {{/if}} -
- Has Specific Mentee in Mind: - {{preferences.hasMenteeInMind}} -
- {{#if preferences.menteeInMindName}} -
- Mentee Name: - {{preferences.menteeInMindName}} -
- {{/if}} -
- -
-
Mentoring Experience
-
- Has Previous Mentoring Experience: - {{experience.hasMentorExperience}} -
- {{#if experience.yearsOfExperience}} -
- Years of Experience: - {{experience.yearsOfExperience}} -
- {{/if}} -
- -
-
Professional Reference
-
- Name: - {{professionalReferee.firstName}} - {{professionalReferee.lastName}} -
-
- Relationship: - {{professionalReferee.relationship}} -
-
- Email: - {{professionalReferee.email}} -
-
- Phone: - {{professionalReferee.phone}} -
-
- -
-
Personal Reference
-
- Name: - {{personalReferee.firstName}} - {{personalReferee.lastName}} -
-
- Relationship: - {{personalReferee.relationship}} -
-
- Email: - {{personalReferee.email}} -
-
- Phone: - {{personalReferee.phone}} -
-
- - -
- - \ No newline at end of file + + + +
+

🤝 Project Protege Mentor Registration

+
+
+

+ A new mentor registration has been submitted for the Project Protege + mentorship program. +

+

+ Reference: {{ submissionId }} +

+
+
Personal Information
+
+ Name: + {{ applicant.firstName }} + {{ applicant.lastName }} +
+
+ Date of Birth: + {{ applicant.dateOfBirth }} +
+
+ Employment Status: + {{ applicant.employmentStatus }} +
+ {{#if applicant.institutionName}} +
+ Institution Name: + {{ applicant.institutionName }} +
+ {{/if}} + {{#if applicant.employerName}} +
+ Employer Name: + {{ applicant.employerName }} +
+ {{/if}} + {{#if applicant.otherEmploymentDetails}} +
+ Other Employment Details: + {{ applicant.otherEmploymentDetails }} +
+ {{/if}} +
+
+
Contact Information
+
+ Email: + {{ contact.email }} +
+
+ Telephone: + {{ contact.telephoneNumber }} +
+
+ Address: + {{ contact.addressLine1 }} + {{#if + contact.addressLine2 + }} + , {{ contact.addressLine2 }}{{/if}} +
+
+ Parish: + {{ contact.parish }} +
+
+
+
Mentoring Information
+
+ Why do you want to mentor: +
{{ mentorship.whyMentor }}
+
+
+ Your Strengths: +
{{ mentorship.strengths }}
+
+
+ What mentee should learn: +
{{ mentorship.menteeLearn }}
+
+
+
+
Mentee Preferences
+
+ Mentee Gender Preference: + {{ preferences.menteeGenderPreference }} +
+
+ Share Phone Number: + {{ preferences.sharePhoneNumber }} +
+ {{#if preferences.menteePhoneNumber}} +
+ Mentee Phone Number: + {{ preferences.menteePhoneNumber }} +
+ {{/if}} +
+ Has Specific Mentee in Mind: + {{ preferences.hasMenteeInMind }} +
+ {{#if preferences.menteeInMindName}} +
+ Mentee Name: + {{ preferences.menteeInMindName }} +
+ {{/if}} +
+
+
Mentoring Experience
+
+ Has Previous Mentoring Experience: + {{ experience.hasMentorExperience }} +
+ {{#if experience.yearsOfExperience}} +
+ Years of Experience: + {{ experience.yearsOfExperience }} +
+ {{/if}} +
+
+
Professional Reference
+
+ Name: + {{ professionalReferee.firstName }} + {{ professionalReferee.lastName }} +
+
+ Relationship: + {{ professionalReferee.relationship }} +
+
+ Email: + {{ professionalReferee.email }} +
+
+ Phone: + {{ professionalReferee.phone }} +
+
+
+
Personal Reference
+
+ Name: + {{ personalReferee.firstName }} + {{ personalReferee.lastName }} +
+
+ Relationship: + {{ personalReferee.relationship }} +
+
+ Email: + {{ personalReferee.email }} +
+
+ Phone: + {{ personalReferee.phone }} +
+
+ +
+ + diff --git a/src/email/templates/request-a-fire-service-inspection.hbs b/src/email/templates/request-a-fire-service-inspection.hbs new file mode 100644 index 0000000..feed249 --- /dev/null +++ b/src/email/templates/request-a-fire-service-inspection.hbs @@ -0,0 +1,100 @@ + + + + + + +
+

🔥 Fire Service Inspection Request

+
+ +
+

A new fire service inspection request has been submitted.

+ +
+
Premises Information
+
+ Type of Premises: + + {{#if (eq premises.typeOfPremises 'hotel')}} + Hotel + {{else if (eq premises.typeOfPremises 'daycare')}} + Daycare + {{else if (eq premises.typeOfPremises 'placeOfEntertainment')}} + Place of Entertainment + {{else}} + {{premises.typeOfPremises}} + {{/if}} + +
+
+ Name of Premises: + {{premises.nameOfPremises}} +
+
+ Address: + {{premises.addressLine1}}{{#if premises.addressLine2}}, {{premises.addressLine2}}{{/if}} +
+
+ Parish: + {{premises.parish}} +
+
+ +
+
Certificate Purpose
+
+ Certificate For: + + {{#if (eq purposeOfCertificate 'barbados-tourism-authority')}} + Barbados Tourism Authority + {{else if (eq purposeOfCertificate 'child-care-board')}} + Child Care Board + {{else if (eq purposeOfCertificate 'treasury')}} + Treasury + {{else}} + {{purposeOfCertificate}} + {{/if}} + +
+
+ +
+
Applicant Information
+
+ Name: + {{applicant.firstName}} {{applicant.lastName}} +
+
+ Email: + {{applicant.email}} +
+
+ Telephone: + {{applicant.telephoneNumber}} +
+
+ + +
+ + diff --git a/src/email/templates/reserve-company-name-receipt.hbs b/src/email/templates/reserve-company-name-receipt.hbs new file mode 100644 index 0000000..76db7cd --- /dev/null +++ b/src/email/templates/reserve-company-name-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Reserve Company Name - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your request to reserve a company name has been received. The Corporate Affairs and Intellectual Property Office will review your submission to ensure the name is available and meets all requirements. You will be notified of the outcome.

+
+ +
+

Need help?

+

If you have questions about your application, please contact the Corporate Affairs and Intellectual Property Office.

+
+
+ +
+
+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

+
+
+ + diff --git a/src/email/templates/reserve-company-name.hbs b/src/email/templates/reserve-company-name.hbs index 5941aad..f67c906 100644 --- a/src/email/templates/reserve-company-name.hbs +++ b/src/email/templates/reserve-company-name.hbs @@ -17,7 +17,9 @@ background-color: #edf2f7; border-radius: 5px; text-align: center; font-size: 14px; color: #718096; } .financial-highlight { background-color: #fef3c7; padding: 10px; border-radius: 5px; margin-top: - 10px; border-left: 4px solid #f59e0b; } + 10px; border-left: 4px solid #f59e0b; } .name-choice { + background-color: #e0f2fe; padding: 10px; margin: 10px 0; + border-radius: 5px; border-left: 4px solid #0284c7; } @@ -37,12 +39,40 @@
{{#if companyPresentName}}
- What is the present name of the company? + Present Company Name: {{companyPresentName}}
{{/if}}
+
+
Company Name Choices
+
+
+ First Choice: + {{companyName.firstChoice}} +
+
+
+
+ Second Choice: + {{companyName.secondChoice}} +
+
+
+
+ Third Choice: + {{companyName.thirdChoice}} +
+
+ {{#if companyName.reserveFirstAvailableName}} +
+ Reserve First Available Name: + {{companyName.reserveFirstAvailableName}} +
+ {{/if}} +
+
Business Activities
{{#each businessActivity}} @@ -56,8 +86,7 @@
Applicant Information
Name: - {{applicant.title}} - {{applicant.firstName}} + {{applicant.firstName}} {{applicant.lastName}}
@@ -84,36 +113,6 @@ Phone: {{applicant.telephoneNumber}}
- -
- -
-
Request Details
-
- Purpose: - {{request.purpose}} -
- {{#if request.currentSocietyName}} -
- Current Society Name: - {{request.currentSocietyName}} -
- {{/if}} -
- -
-
Contact Information
-
- Address: - {{applicant.addressLine1}}{{#if - applicant.addressLine2 - }}, {{applicant.addressLine2}}{{/if}} -
-
- Parish: - {{applicant.parish}} -
-
- \ No newline at end of file + diff --git a/src/email/templates/reserve-society-name-receipt.hbs b/src/email/templates/reserve-society-name-receipt.hbs new file mode 100644 index 0000000..dd5f0f9 --- /dev/null +++ b/src/email/templates/reserve-society-name-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Reserve Society Name - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your request to reserve a society name has been received. The Corporate Affairs and Intellectual Property Office will review your submission to ensure the name is available and meets all requirements. You will be notified of the outcome.

+
+ +
+

Need help?

+

If you have questions about your application, please contact the Corporate Affairs and Intellectual Property Office.

+
+
+ +
+
+

Government of Barbados
+ This is an automated confirmation email. Please do not reply.

+
+
+ + diff --git a/src/email/templates/sell-goods-services-beach-park-notice.hbs b/src/email/templates/sell-goods-services-beach-park-notice.hbs index ecbe3d6..d71ad46 100644 --- a/src/email/templates/sell-goods-services-beach-park-notice.hbs +++ b/src/email/templates/sell-goods-services-beach-park-notice.hbs @@ -37,6 +37,7 @@ Name: {{applicant.title}} {{applicant.firstName}} + {{#if applicant.middleName}}{{applicant.middleName}}{{/if}} {{applicant.lastName}}
@@ -44,31 +45,159 @@ {{applicant.dateOfBirth}}
- ID Number: - {{applicant.idNumber}} + Nationality: + {{applicant.nationality}}
+ {{#if applicant.idNumber}} +
+ ID Number: + {{applicant.idNumber}} +
+ {{/if}} {{#if applicant.passportNumber}}
Passport Number: {{applicant.passportNumber}}
{{/if}} - {{#if applicant.email}} +
+ Email: + {{applicant.email}} +
+
+ Phone Number: + {{applicant.telephoneNumber}} +
+
+ +
+
Applicant Address
+
+ Address Line 1: + {{applicant.addressLine1}} +
+ {{#if applicant.addressLine2}} +
+ Address Line 2: + {{applicant.addressLine2}} +
+ {{/if}} +
+ Parish: + {{applicant.parish}} +
+ {{#if applicant.postalCode}} +
+ Postal Code: + {{applicant.postalCode}} +
+ {{/if}} +
+ +
+
Selling Details
+
+ Selling: + {{selling.goodsOrServices}} +
+ {{#if selling.manufacturingLocation}} +
+ Manufacturing Location: + {{selling.manufacturingLocation}} +
+ {{/if}} +
+ +
+
Business Information
+
+ Description of Goods/Services: + {{business.descriptionOfGoodsOrServices}} +
+
+ Intended Place of Business: + {{business.intendedPlaceOfDoingBusiness}} +
+
+ +
+
Professional Referee
+
+ Name: + {{professionalReferee.firstName}} + {{professionalReferee.lastName}} +
+
+ Relationship: + {{professionalReferee.relationship}} +
+
+ Email: + {{professionalReferee.email}} +
+
+ Phone Number: + {{professionalReferee.telephoneNumber}} +
+
+ Address Line 1: + {{professionalReferee.addressLine1}} +
+ {{#if professionalReferee.addressLine2}} +
+ Address Line 2: + {{professionalReferee.addressLine2}} +
+ {{/if}} +
+ Parish: + {{professionalReferee.parish}} +
+ {{#if professionalReferee.postcode}}
- Email: - {{applicant.email}} + Postcode: + {{professionalReferee.postcode}}
{{/if}} - {{#if applicant.email}} +
+ +
+
Personal Referee
+
+ Name: + {{personalReferee.firstName}} + {{personalReferee.lastName}} +
+
+ Relationship: + {{personalReferee.relationship}} +
+
+ Email: + {{personalReferee.email}} +
+
+ Phone Number: + {{personalReferee.telephoneNumber}} +
+
+ Address Line 1: + {{personalReferee.addressLine1}} +
+ {{#if personalReferee.addressLine2}}
- Email: - {{applicant.email}} + Address Line 2: + {{personalReferee.addressLine2}}
{{/if}} - {{#if applicant.telephoneNumber}} +
+ Parish: + {{personalReferee.parish}} +
+ {{#if personalReferee.postcode}}
- Phone Number: - {{applicant.telephoneNumber}} + Postcode: + {{personalReferee.postcode}}
{{/if}}
@@ -76,8 +205,8 @@
- \ No newline at end of file + diff --git a/src/email/templates/sell-goods-services-beach-park-receipt.hbs b/src/email/templates/sell-goods-services-beach-park-receipt.hbs new file mode 100644 index 0000000..bdcdb7f --- /dev/null +++ b/src/email/templates/sell-goods-services-beach-park-receipt.hbs @@ -0,0 +1,54 @@ + + + + + + +
+
+ Government of Barbados +
+
+ +
+

Sell Goods or Services at Beach/Park Notice - Submission Received

+ +
+

Your Submission ID: {{submissionId}}

+

Please save this ID for your records.

+
+ +
+

What happens next

+

Your notice of intent to sell goods or services at a beach or park has been received. The relevant authority will review your submission and contact you regarding permit requirements and any fees that may apply.

+
+ +
+

Need help?

+

If you have questions about your submission, please contact the National Conservation Commission.

+
+
+ + + + diff --git a/src/forms/dto/form-submission-response.dto.ts b/src/forms/dto/form-submission-response.dto.ts index 087b19a..b0588ad 100644 --- a/src/forms/dto/form-submission-response.dto.ts +++ b/src/forms/dto/form-submission-response.dto.ts @@ -1,3 +1,13 @@ +type OpenCRVSIntegrationResult = { + success: boolean; + message: string; + trackingId?: string; +}; + +type IntegrationsResult = { + opencrvs?: OpenCRVSIntegrationResult; +}; + export class FormSubmissionResponseDto { submissionId: string; formId: string; @@ -13,8 +23,11 @@ export class FormSubmissionResponseDto { amount?: number; description?: string; + // Integration results + integrations?: IntegrationsResult; + // Dynamic additional data - [key: string]: any; + [key: string]: unknown; constructor( submissionId: string, @@ -29,7 +42,7 @@ export class FormSubmissionResponseDto { amount?: number; description?: string; }, - additionalData?: Record, + additionalData?: Record, ) { this.submissionId = submissionId; this.formId = formId; @@ -46,7 +59,7 @@ export class FormSubmissionResponseDto { this.description = paymentInfo.description; } - // Add any additional dynamic data + // Add any additional dynamic data (including integrations) if (additionalData) { Object.assign(this, additionalData); } diff --git a/src/forms/forms.service.ts b/src/forms/forms.service.ts index 5291dc9..cc34599 100644 --- a/src/forms/forms.service.ts +++ b/src/forms/forms.service.ts @@ -4,6 +4,7 @@ import { SchemaBuilderService } from '../validation/schema-builder.service'; import { ProcessorPipelineService } from '../processors/processor-pipeline.service'; import { FormSubmissionResponseDto } from './dto'; import { FormUtilsService } from './form-utils.service'; +import { OpenCRVSProcessorResult } from '../opencrvs/types'; import { generateReferenceCode } from '../common/utils'; @Injectable() @@ -83,16 +84,24 @@ export class FormsService { } // Execute processor pipeline for non-payment forms - await this.processorPipeline.execute(formSchemaWithData.processors, { - formId, - submissionId, - data, - }); + const processorResults = await this.processorPipeline.execute( + formSchemaWithData.processors, + { + formId, + submissionId, + data, + }, + ); + + // Build integrations result from processor results + const additionalData = this.buildIntegrationsResult(processorResults); const response = new FormSubmissionResponseDto( submissionId, formId, 'success', + undefined, + additionalData, ); return { @@ -101,6 +110,36 @@ export class FormsService { }; } + /** + * Build integrations result object from processor results + */ + private buildIntegrationsResult( + processorResults: Map, + ): Record | undefined { + const opencrvsResult = processorResults.get('opencrvs') as + | OpenCRVSProcessorResult + | undefined; + + if (!opencrvsResult) { + return undefined; + } + + const integrations: Record = { + opencrvs: opencrvsResult.success + ? { + success: true, + message: 'Birth registration submitted successfully', + trackingId: opencrvsResult.trackingId, + } + : { + success: false, + message: opencrvsResult.error ?? 'Birth registration failed', + }, + }; + + return { integrations }; + } + /** * Process form submission that requires payment */ diff --git a/src/forms/interfaces/form-schema.interface.ts b/src/forms/interfaces/form-schema.interface.ts index 9c2c24e..436ab49 100644 --- a/src/forms/interfaces/form-schema.interface.ts +++ b/src/forms/interfaces/form-schema.interface.ts @@ -74,6 +74,19 @@ export interface ResponseDataConfig { include?: string[]; // Form field paths to include (e.g., ['order.numberOfCopies', 'applicant.email']) } +export interface OpenCRVSProcessorConfig extends ProcessorConfig { + type: 'opencrvs'; + config: { + eventType: 'birth'; // Currently only birth is supported + officeId?: string; // Direct location ID for CRVS office + officeName?: string; // Location name (resolved to ID at runtime) + healthFacilityId?: string; // Direct location ID for health facility + healthFacilityName?: string; // Location name (resolved to ID at runtime) + parishId?: string; // Direct location ID for parish + parishName?: string; // Location name (resolved to ID at runtime) + }; +} + export type EmailRecipientType = 'admin' | 'user'; export interface EmailProcessorConfig extends ProcessorConfig { diff --git a/src/opencrvs/birth-registration.mapper.ts b/src/opencrvs/birth-registration.mapper.ts new file mode 100644 index 0000000..0db256c --- /dev/null +++ b/src/opencrvs/birth-registration.mapper.ts @@ -0,0 +1,543 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { + BirthDeclaration, + Gender, + PlaceOfBirth, + BirthType, + AttendantAtBirth, + MaritalStatus, + IdType, + DomesticAddress, + PersonName, + AgeReference, +} from './types'; + +/** + * Form data structure from register-birth-form.json schema + * Updated to match the current schema structure + */ +export type BirthRegistrationFormData = { + marriageStatus: 'yes' | 'no'; + includeFatherDetails?: 'yes' | 'no'; + + father?: { + firstName?: string; + middleName?: string; + lastName?: string; + age?: string; + parish?: string; + addressLine1?: string; + addressLine2?: string; + idNumber?: string; // National ID in format XXXXXX-XXXX + passportNumber?: string; + occupation?: string; + }; + + birth: { + placeOfBirth: 'health-facility' | 'residential' | 'other'; + parish: string; + streetAddress: string; + numberOfBirths: 'single' | 'twins' | 'triplets' | 'more-than-triplets'; + attendantAtBirth: 'doctor' | 'midwife' | 'nurse' | 'relative' | 'none'; + liveBorn?: string; + stillBorn?: string; + totalStillAlive?: string; + bornAlive?: string; + stillborn?: string; + }; + + mother: { + firstName: string; + middleName?: string; + lastName: string; + maidenSurname?: string; + parish: string; + addressLine1: string; + addressLine2?: string; + idNumber?: string; // National ID in format XXXXXX-XXXX + passportNumber?: string; + occupation: string; + telephoneNumber?: string; + }; + + child: { + firstName: string; + middleName?: string; + lastName: string; + dateOfBirth: string; + sexAtBirth: 'male' | 'female'; + }; + + order: { + numberOfCopies: number; + }; +}; + +/** + * Configuration for the mapper + */ +type MapperConfig = { + parishId: string; + healthFacilityId?: string; + defaultNationality?: string; +}; + +/** + * Service to map form submission data to OpenCRVS BirthDeclaration format + * + * This mapper transforms the data structure from the Barbados birth registration + * form into the format expected by the OpenCRVS API. + * + * Note: Informant fields are not required per OpenCRVS Barbados configuration. + */ +@Injectable() +export class BirthRegistrationMapper { + private readonly logger = new Logger(BirthRegistrationMapper.name); + + /** + * Map form data to OpenCRVS BirthDeclaration + * Accepts Record for flexibility with processor context + */ + mapToBirthDeclaration( + formData: BirthRegistrationFormData | Record, + config: MapperConfig, + ): BirthDeclaration { + const data = formData as BirthRegistrationFormData; + + this.logger.log('Mapping form data to OpenCRVS BirthDeclaration'); + + // Map the form's place of birth to OpenCRVS PlaceOfBirth + const openCRVSPlaceOfBirth = this.mapPlaceOfBirth(data.birth.placeOfBirth); + + const declaration: BirthDeclaration = { + // Child information (required) + 'child.name': this.mapChildName(data.child), + 'child.gender': this.mapGender(data.child.sexAtBirth), + 'child.dob': this.formatDate(data.child.dateOfBirth), + 'child.placeOfBirth': openCRVSPlaceOfBirth, + 'child.birthType': this.mapBirthType(data.birth.numberOfBirths), + 'child.attendantAtBirth': this.mapAttendantAtBirth( + data.birth.attendantAtBirth, + ), + + // Set birth location based on place of birth + ...(openCRVSPlaceOfBirth === 'HEALTH_FACILITY' + ? config.healthFacilityId && { + 'child.birthLocation': config.healthFacilityId, + } + : { + 'child.birthLocation.privateHome': { + addressType: 'DOMESTIC', + country: 'BRB', + administrativeArea: config.parishId, + streetLevelDetails: { + street: data.birth.streetAddress || 'sample address', + }, + }, + }), + + // Mother information (always provided) + 'mother.detailsNotAvailable': false, + 'mother.name': this.mapMotherName(data.mother), + 'mother.age': data.mother.idNumber + ? this.calculateAgeFromNRN(data.mother.idNumber) + : this.calculateAge(data.mother, data.child.dateOfBirth), + 'mother.maritalStatus': this.mapMaritalStatus(data.marriageStatus), + 'mother.nationality': config.defaultNationality ?? 'BRB', + ...this.mapMotherId(data.mother), + 'mother.address': this.mapAddress( + data.mother.addressLine1, + data.mother.addressLine2, + config.parishId, + ), + 'mother.occupation': data.mother.occupation, + 'mother.bornAlive': Number(data.birth.bornAlive), + 'mother.stillborn': Number(data.birth.stillborn), + 'mother.stillAlive': Number(data.birth.totalStillAlive), + + // Informant information + 'informant.relation': data.marriageStatus === 'yes' ? 'PARENT' : 'OTHER', + 'informant.parentsMarried': data.marriageStatus === 'yes' ? 'YES' : 'NO', + 'informant.phoneNo': this.sanitizePhoneNumber( + data.mother.telephoneNumber, + ), + + // Father information (conditional) + ...this.mapFatherDetails(data, config), + }; + + this.logger.log( + `Mapped birth declaration for child: ${declaration['child.name'].firstname} ${declaration['child.name'].surname}`, + ); + + return declaration; + } + + /** + * Map form's place of birth to OpenCRVS PlaceOfBirth enum + */ + private mapPlaceOfBirth( + placeOfBirth: BirthRegistrationFormData['birth']['placeOfBirth'], + ): PlaceOfBirth { + switch (placeOfBirth) { + case 'health-facility': + return 'HEALTH_FACILITY'; + case 'residential': + return 'PRIVATE_HOME'; + case 'other': + default: + return 'OTHER'; + } + } + + /** + * Map number of births to OpenCRVS BirthType + */ + private mapBirthType( + numberOfBirths: BirthRegistrationFormData['birth']['numberOfBirths'], + ): BirthType { + switch (numberOfBirths) { + case 'single': + return 'SINGLE'; + case 'twins': + return 'TWIN'; + case 'triplets': + return 'TRIPLET'; + case 'more-than-triplets': + return 'QUADRUPLET_OR_MORE'; + default: + return 'SINGLE'; + } + } + + /** + * Map attendant at birth to OpenCRVS AttendantAtBirth + */ + private mapAttendantAtBirth( + attendant: BirthRegistrationFormData['birth']['attendantAtBirth'], + ): AttendantAtBirth { + switch (attendant) { + case 'doctor': + return 'DOCTOR'; + case 'midwife': + return 'MIDWIFE'; + case 'nurse': + return 'NURSE'; + case 'relative': + return 'RELATIVE'; + case 'none': + return 'NONE'; + default: + return 'OTHER'; + } + } + + /** + * Map marriage status to OpenCRVS MaritalStatus + */ + private mapMaritalStatus(marriageStatus: 'yes' | 'no'): MaritalStatus { + return marriageStatus === 'yes' ? 'MARRIED' : 'SINGLE'; + } + + /** + * Map child name from form data + */ + private mapChildName(child: BirthRegistrationFormData['child']): PersonName { + const fullFirstName = child.middleName + ? `${child.firstName} ${child.middleName}` + : child.firstName; + + return { + firstname: fullFirstName, + surname: child.lastName, + }; + } + + /** + * Map mother name from form data + */ + private mapMotherName( + mother: BirthRegistrationFormData['mother'], + ): PersonName { + const fullFirstName = mother.middleName + ? `${mother.firstName} ${mother.middleName}` + : mother.firstName; + + return { + firstname: fullFirstName, + surname: mother.lastName, + }; + } + + /** + * Map father name from form data + */ + private mapFatherName( + father: NonNullable, + ): PersonName { + const fullFirstName = father.middleName + ? `${father.firstName} ${father.middleName}` + : father.firstName ?? ''; + + return { + firstname: fullFirstName, + surname: father.lastName ?? '', + }; + } + + /** + * Map sex at birth to OpenCRVS gender + */ + private mapGender(sexAtBirth: 'male' | 'female'): Gender { + return sexAtBirth; + } + + /** + * Format date to YYYY-MM-DD format expected by OpenCRVS + */ + private formatDate(dateString: string): string { + if (!dateString) { + return ''; + } + + // If already in YYYY-MM-DD format, return as is + if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) { + return dateString; + } + + // Try to parse and format the date + try { + const date = new Date(dateString); + return date.toISOString().slice(0, 10); + } catch { + this.logger.warn(`Could not parse date: ${dateString}`); + return dateString; + } + } + + /** + * Calculate age reference from parent data + * OpenCRVS uses age relative to child's DOB + */ + private calculateAge( + parent: { age?: string } | BirthRegistrationFormData['mother'], + childDob: string, + ): AgeReference | undefined { + const ageStr = 'age' in parent ? parent.age : undefined; + if (!ageStr) { + return undefined; + } + + const age = parseInt(ageStr, 10); + if (isNaN(age)) { + return undefined; + } + + return { + age, + asOfDateRef: 'child.dob', + }; + } + + /** + * Map address to OpenCRVS DomesticAddress format + * Uses streetLevelDetails.street as per OpenCRVS API format + */ + private mapAddress( + addressLine1: string, + addressLine2: string | undefined, + parishId: string, + ): DomesticAddress { + const streetAddress = addressLine2 + ? `${addressLine1}, ${addressLine2}` + : addressLine1; + + return { + addressType: 'DOMESTIC', + country: 'BRB', + administrativeArea: parishId, + streetLevelDetails: { + street: streetAddress, + }, + }; + } + + /** + * Map mother's ID type and number + * Note: OpenCRVS Barbados only accepts passport as ID type, not national ID + */ + private mapMotherId(mother: BirthRegistrationFormData['mother']): { + 'mother.idType'?: IdType; + 'mother.passport'?: string; + 'mother.nationalRegistrationNumber'?: string; + } { + if (mother.idNumber) { + return { + 'mother.idType': 'NATIONAL_REGISTRATION_NUMBER', + 'mother.nationalRegistrationNumber': mother.idNumber, + }; + } + if (mother.passportNumber) { + return { + 'mother.idType': 'PASSPORT', + 'mother.passport': mother.passportNumber, + }; + } + + // OpenCRVS Barbados doesn't support national ID (nid field) + // so we set idType to NONE if no passport is provided + return { + 'mother.idType': 'NONE', + }; + } + + /** + * Map father's ID type and number + * Note: OpenCRVS Barbados only accepts passport as ID type, not national ID + */ + private mapFatherId( + father: NonNullable, + ): { + 'father.idType'?: IdType; + 'father.passport'?: string; + 'father.nationalRegistrationNumber'?: string; + } { + if (father.idNumber) { + return { + 'father.idType': 'NATIONAL_REGISTRATION_NUMBER', + 'father.nationalRegistrationNumber': father.idNumber, + }; + } + if (father.passportNumber) { + return { + 'father.idType': 'PASSPORT', + 'father.passport': father.passportNumber, + }; + } + + // OpenCRVS Barbados doesn't support national ID (nid field) + // so we set idType to NONE if no passport is provided + return { + 'father.idType': 'NONE', + }; + } + + /** + * Map father details based on form conditions + * Father details are included if: + * - Parents are married (marriageStatus === 'yes') + * - OR user explicitly wants to include father details (includeFatherDetails === 'yes') + */ + private mapFatherDetails( + formData: BirthRegistrationFormData, + config: MapperConfig, + ): Partial { + const includeFather = + formData.marriageStatus === 'yes' || + formData.includeFatherDetails === 'yes'; + + if (!includeFather || !formData.father) { + return { + 'father.detailsNotAvailable': true, + 'father.reason': 'Details not provided', + }; + } + + const father = formData.father; + + // Check if father has sufficient details + if (!father.firstName || !father.lastName) { + return { + 'father.detailsNotAvailable': true, + 'father.reason': 'Incomplete father details', + }; + } + + // Determine if father uses same address as mother + const useSameAddress = !father.addressLine1; + + return { + 'father.detailsNotAvailable': false, + 'father.name': this.mapFatherName(father), + 'father.age': father.idNumber + ? this.calculateAgeFromNRN(father.idNumber) + : this.calculateAge(father, formData.child.dateOfBirth), + 'father.nationality': config.defaultNationality ?? 'BRB', + ...this.mapFatherId(father), + 'father.occupation': father.occupation, + ...(useSameAddress + ? { 'father.addressSameAs': true } + : { + 'father.address': this.mapAddress( + father.addressLine1!, + father.addressLine2, + config.parishId, + ), + }), + }; + } + + /** + * Calculates age from a National Registration Number (NRN) in format YYMMDD-XXXX + * @param nrn The National Registration Number (e.g., "920320-0016") + * @returns The age in years + */ + private calculateAgeFromNRN(nrn: string): { + age: number; + asOfDateRef: string; + } { + // Extract the date parts from NRN + const year = parseInt(nrn.substring(0, 2)); + const month = parseInt(nrn.substring(2, 4)) - 1; // JavaScript months are 0-indexed + const day = parseInt(nrn.substring(4, 6)); + + // Handle Y2K: Assume 1900s for years 00-20, 2000s for 21-99 + const fullYear = year <= 20 ? 2000 + year : 1900 + year; + + // Create date objects + const birthDate = new Date(fullYear, month, day); + const today = new Date(); + + // Calculate age + let age = today.getFullYear() - birthDate.getFullYear(); + const monthDiff = today.getMonth() - birthDate.getMonth(); + + // Adjust age if birthday hasn't occurred yet this year + if ( + monthDiff < 0 || + (monthDiff === 0 && today.getDate() < birthDate.getDate()) + ) { + age--; + } + + age = Math.max(12, Math.min(120, age)); + + return { + age, + asOfDateRef: 'child.dob', + }; + } + + /** + * Sanitize phone number by removing all non-digit characters + */ + private sanitizePhoneNumber(phone: string | undefined): string | undefined { + if (!phone) { + return undefined; + } + return phone.replace(/\D/g, ''); + } + + /** + * Create annotation object for OpenCRVS + * OpenCRVS has a strict schema and rejects unknown fields, + * so we return an empty object. Form metadata is preserved + * in our own submission records. + */ + createAnnotation( + _formData: BirthRegistrationFormData | Record, + ): Record { + // OpenCRVS rejects custom annotation fields - return empty object + return {}; + } +} diff --git a/src/opencrvs/index.ts b/src/opencrvs/index.ts new file mode 100644 index 0000000..3f43eee --- /dev/null +++ b/src/opencrvs/index.ts @@ -0,0 +1,7 @@ +export * from './opencrvs.module'; +export * from './opencrvs.service'; +export { + BirthRegistrationMapper, + BirthRegistrationFormData, +} from './birth-registration.mapper'; +export * from './types'; diff --git a/src/opencrvs/opencrvs-cache.service.ts b/src/opencrvs/opencrvs-cache.service.ts new file mode 100644 index 0000000..df60dce --- /dev/null +++ b/src/opencrvs/opencrvs-cache.service.ts @@ -0,0 +1,112 @@ +import { Injectable, Logger } from '@nestjs/common'; +import * as NodeCache from 'node-cache'; + +export interface CachedToken { + accessToken: string; + expiresAt: number; +} + +@Injectable() +export class OpenCRVSCacheService { + private readonly logger = new Logger(OpenCRVSCacheService.name); + private readonly cache: NodeCache; + + // Cache keys + private static readonly TOKEN_KEY = 'opencrvs_access_token'; + private static readonly LOCATION_PREFIX = 'location:'; + + // Default TTLs (in seconds) + private static readonly LOCATION_TTL = 3600; // 1 hour for locations + + constructor() { + this.cache = new NodeCache({ + checkperiod: 60, // Check for expired keys every 60 seconds + useClones: false, // Don't clone objects for better performance + }); + + this.cache.on('expired', (key: string) => { + this.logger.debug(`Cache key expired: ${key}`); + }); + } + + /** + * Store access token with TTL based on expiration time + * Includes a 5-minute buffer before actual expiry + */ + setAccessToken(accessToken: string, expiresIn: number): void { + // Apply 5-minute buffer to TTL + const bufferSeconds = 5 * 60; + const ttl = Math.max(expiresIn - bufferSeconds, 0); + + if (ttl <= 0) { + this.logger.warn('Token TTL is too short, not caching'); + return; + } + + this.cache.set(OpenCRVSCacheService.TOKEN_KEY, accessToken, ttl); + this.logger.debug(`Access token cached with TTL: ${ttl}s`); + } + + /** + * Get cached access token if valid + */ + getAccessToken(): string | undefined { + return this.cache.get(OpenCRVSCacheService.TOKEN_KEY); + } + + /** + * Clear the access token from cache + */ + clearAccessToken(): void { + this.cache.del(OpenCRVSCacheService.TOKEN_KEY); + this.logger.debug('Access token cache cleared'); + } + + /** + * Store location ID with TTL + */ + setLocation( + locationType: string, + locationName: string, + locationId: string, + ttl: number = OpenCRVSCacheService.LOCATION_TTL, + ): void { + const key = `${OpenCRVSCacheService.LOCATION_PREFIX}${locationType}:${locationName}`; + this.cache.set(key, locationId, ttl); + this.logger.debug(`Location cached: ${key} -> ${locationId}`); + } + + /** + * Get cached location ID + */ + getLocation(locationType: string, locationName: string): string | undefined { + const key = `${OpenCRVSCacheService.LOCATION_PREFIX}${locationType}:${locationName}`; + return this.cache.get(key); + } + + /** + * Clear all location entries from cache + */ + clearLocations(): void { + const keys = this.cache + .keys() + .filter((key) => key.startsWith(OpenCRVSCacheService.LOCATION_PREFIX)); + keys.forEach((key) => this.cache.del(key)); + this.logger.debug(`Cleared ${keys.length} location cache entries`); + } + + /** + * Clear all cache entries + */ + clearAll(): void { + this.cache.flushAll(); + this.logger.debug('All cache entries cleared'); + } + + /** + * Get cache statistics + */ + getStats(): NodeCache.Stats { + return this.cache.getStats(); + } +} diff --git a/src/opencrvs/opencrvs.module.ts b/src/opencrvs/opencrvs.module.ts new file mode 100644 index 0000000..282ccb5 --- /dev/null +++ b/src/opencrvs/opencrvs.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { OpenCRVSService } from './opencrvs.service'; +import { BirthRegistrationMapper } from './birth-registration.mapper'; +import { OpenCRVSCacheService } from './opencrvs-cache.service'; + +@Module({ + imports: [ConfigModule], + providers: [OpenCRVSCacheService, OpenCRVSService, BirthRegistrationMapper], + exports: [OpenCRVSService, BirthRegistrationMapper], +}) +export class OpenCRVSModule {} diff --git a/src/opencrvs/opencrvs.service.ts b/src/opencrvs/opencrvs.service.ts new file mode 100644 index 0000000..f57dd96 --- /dev/null +++ b/src/opencrvs/opencrvs.service.ts @@ -0,0 +1,364 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { v4 as uuidv4 } from 'uuid'; +import { + TokenResponse, + CreateEventRequest, + CreateEventResponse, + NotifyRequest, + NotifyResponse, + BirthDeclaration, + LocationBundle, + LocationType, +} from './types'; +import { OpenCRVSCacheService } from './opencrvs-cache.service'; + +@Injectable() +export class OpenCRVSService { + private readonly logger = new Logger(OpenCRVSService.name); + + private readonly authBaseUrl: string; + private readonly eventsBaseUrl: string; + private readonly locationsBaseUrl: string; + private readonly clientId: string; + private readonly clientSecret: string; + + constructor( + private readonly configService: ConfigService, + private readonly cacheService: OpenCRVSCacheService, + ) { + const isLocalhost = this.configService.get('opencrvs.localhost'); + + if (isLocalhost) { + this.authBaseUrl = 'http://localhost:4040'; + this.eventsBaseUrl = 'http://localhost:3000'; + this.locationsBaseUrl = 'http://localhost:7070'; + } else { + this.authBaseUrl = this.configService.get( + 'opencrvs.authBaseUrl', + 'https://auth.barbados-qa.opencrvs.org', + ); + this.eventsBaseUrl = this.configService.get( + 'opencrvs.eventsBaseUrl', + 'https://register.barbados-qa.opencrvs.org', + ); + this.locationsBaseUrl = this.configService.get( + 'opencrvs.locationsBaseUrl', + 'https://gateway.barbados-qa.opencrvs.org', + ); + } + + this.clientId = this.configService.get('opencrvs.clientId', ''); + this.clientSecret = this.configService.get( + 'opencrvs.clientSecret', + '', + ); + + this.logger.log( + `OpenCRVS service initialized - Auth: ${this.authBaseUrl}, Events: ${this.eventsBaseUrl}`, + ); + } + + /** + * Get an access token from the OpenCRVS auth service + * Uses node-cache for TTL-based token management + * @param forceRefresh - If true, ignores cached token and fetches a new one + */ + async getAccessToken(forceRefresh = false): Promise { + // Return cached token if still valid (unless force refresh) + if (!forceRefresh) { + const cachedToken = this.cacheService.getAccessToken(); + if (cachedToken) { + return cachedToken; + } + } else { + // Clear the cached token when forcing refresh + this.cacheService.clearAccessToken(); + } + + if (!this.clientId || !this.clientSecret) { + throw new Error( + 'OpenCRVS CLIENT_ID or CLIENT_SECRET not configured in environment', + ); + } + + const url = new URL('/token', this.authBaseUrl); + url.searchParams.set('client_id', this.clientId); + url.searchParams.set('client_secret', this.clientSecret); + url.searchParams.set('grant_type', 'client_credentials'); + + this.logger.log(`Requesting OpenCRVS access token from: ${url.host}`); + + const res = await fetch(url.toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!res.ok) { + const errorText = await res.text(); + this.logger.error(`Token request failed: ${res.status} ${errorText}`); + throw new Error(`OpenCRVS token request failed: ${res.status}`); + } + + const data = (await res.json()) as TokenResponse; + + if (!data.access_token) { + throw new Error('OpenCRVS token response missing access_token'); + } + + // Cache the token with TTL (includes 5-minute buffer) + const expiresIn = data.expires_in ?? 3600; + this.cacheService.setAccessToken(data.access_token, expiresIn); + + this.logger.log('OpenCRVS access token obtained successfully'); + return data.access_token; + } + + /** + * Make an authenticated request with automatic token refresh on 401 + * @param url - The URL to request + * @param options - Fetch options (without Authorization header) + * @param isRetry - Internal flag to prevent infinite retry loops + * @returns The fetch Response + */ + private async authenticatedFetch( + url: string, + options: RequestInit = {}, + isRetry = false, + ): Promise { + const accessToken = await this.getAccessToken(); + + const res = await fetch(url, { + ...options, + headers: { + ...options.headers, + Authorization: `Bearer ${accessToken}`, + }, + }); + + // If we get a 401 and haven't retried yet, refresh token and retry once + if (res.status === 401 && !isRetry) { + this.logger.warn('Received 401, refreshing token and retrying request'); + await this.getAccessToken(true); + return this.authenticatedFetch(url, options, true); + } + + return res; + } + + /** + * Create a new birth event in OpenCRVS + */ + async createBirthEvent(transactionId?: string): Promise { + const payload: CreateEventRequest = { + type: 'birth', + transactionId: transactionId ?? uuidv4(), + dateOfEvent: { fieldId: 'child.dob' }, + }; + + this.logger.log( + `Creating birth event with transactionId: ${payload.transactionId}`, + ); + + const res = await this.authenticatedFetch( + `${this.eventsBaseUrl}/api/events/events`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }, + ); + + if (!res.ok) { + const errorText = await res.text(); + this.logger.error(`Create event failed: ${res.status} ${errorText}`); + throw new Error(`Create birth event failed: ${res.status}`); + } + + const data = (await res.json()) as CreateEventResponse; + + if (!data.id) { + throw new Error('Create event response missing id'); + } + + this.logger.log(`Birth event created: ${data.id} (${data.trackingId})`); + return data; + } + + /** + * Submit a birth notification to OpenCRVS + */ + async notifyBirthEvent( + eventId: string, + declaration: BirthDeclaration, + createdAtLocation: string, + annotation?: Record, + ): Promise { + const payload: NotifyRequest = { + eventId, + transactionId: uuidv4(), + declaration, + annotation: annotation ?? {}, + createdAtLocation, + type: 'NOTIFY', + }; + + this.logger.log(`Notifying birth event: ${eventId}`); + + const res = await this.authenticatedFetch( + `${this.eventsBaseUrl}/api/events/events/notifications`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }, + ); + + if (!res.ok) { + const errorText = await res.text(); + this.logger.error(`Notify event failed: ${res.status} ${errorText}`); + return { + success: false, + error: `Notification failed: ${res.status} - ${errorText}`, + }; + } + + const responseData = await res.json(); + this.logger.log(`Birth notification submitted for event: ${eventId}`); + + return { + success: true, + eventId, + ...responseData, + }; + } + + /** + * Look up a location ID by name and type from OpenCRVS + */ + async getLocationIdByName( + locationName: string, + locationType: LocationType, + ): Promise { + // Check cache first + const cachedId = this.cacheService.getLocation(locationType, locationName); + if (cachedId) { + return cachedId; + } + + const url = `${this.locationsBaseUrl}/location?type=${locationType}`; + + this.logger.log( + `Looking up ${locationType} location: "${locationName}" from ${url}`, + ); + + const res = await fetch(url); + + if (!res.ok) { + const errorText = await res.text(); + this.logger.error( + `Failed to fetch locations: ${res.status} ${errorText}`, + ); + throw new Error(`Failed to fetch locations: ${res.status}`); + } + + const data = (await res.json()) as LocationBundle; + + const match = data.entry?.find((e) => e.resource?.name === locationName); + + if (!match?.resource?.id) { + throw new Error( + `OpenCRVS location not found: "${locationName}" (type: ${locationType})`, + ); + } + + // Cache the result + this.cacheService.setLocation( + locationType, + locationName, + match.resource.id, + ); + + this.logger.log( + `Found location: "${locationName}" -> ${match.resource.id}`, + ); + + return match.resource.id; + } + + /** + * Register a birth by creating an event and submitting a notification + * This is the main method that orchestrates the full registration flow + */ + async registerBirth( + declaration: BirthDeclaration, + officeId: string, + annotation?: Record, + ): Promise<{ + success: boolean; + eventId?: string; + trackingId?: string; + transactionId?: string; + error?: string; + }> { + try { + // Step 1: Create the event + const transactionId = uuidv4(); + const eventResponse = await this.createBirthEvent(transactionId); + + // Step 2: Submit the notification + const notifyResponse = await this.notifyBirthEvent( + eventResponse.id, + declaration, + officeId, + annotation, + ); + + if (!notifyResponse.success) { + return { + success: false, + eventId: eventResponse.id, + error: notifyResponse.error, + }; + } + + return { + success: true, + eventId: eventResponse.id, + trackingId: eventResponse.trackingId, + transactionId, + }; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error occurred'; + this.logger.error(`Birth registration failed: ${errorMessage}`); + + return { + success: false, + error: errorMessage, + }; + } + } + + /** + * Clear the location cache (useful if locations are updated) + */ + clearLocationCache(): void { + this.cacheService.clearLocations(); + this.logger.log('Location cache cleared'); + } + + /** + * Clear the access token cache (useful for testing or token refresh) + */ + clearTokenCache(): void { + this.cacheService.clearAccessToken(); + this.logger.log('Token cache cleared'); + } +} diff --git a/src/opencrvs/types/index.ts b/src/opencrvs/types/index.ts new file mode 100644 index 0000000..3077f3e --- /dev/null +++ b/src/opencrvs/types/index.ts @@ -0,0 +1 @@ +export * from './opencrvs.types'; diff --git a/src/opencrvs/types/opencrvs.types.ts b/src/opencrvs/types/opencrvs.types.ts new file mode 100644 index 0000000..e3d3f26 --- /dev/null +++ b/src/opencrvs/types/opencrvs.types.ts @@ -0,0 +1,239 @@ +/** + * OpenCRVS integration types for Barbados Civil Registration + * Based on the OpenCRVS Barbados QA API format + */ + +// ============================================================================ +// Authentication Types +// ============================================================================ + +export type TokenResponse = { + access_token: string; + token_type: string; + expires_in?: number; +}; + +// ============================================================================ +// Event Types +// ============================================================================ + +export type CreateEventRequest = { + type: 'birth' | 'death' | 'marriage'; + transactionId: string; + dateOfEvent: { fieldId: string }; +}; + +export type CreateEventResponse = { + id: string; + type: string; + createdAt: string; + updatedAt: string; + trackingId: string; + actions: Array; +}; + +// ============================================================================ +// Common Building Blocks +// ============================================================================ + +export type Gender = 'male' | 'female' | 'unknown'; + +export type PlaceOfBirth = 'HEALTH_FACILITY' | 'PRIVATE_HOME' | 'OTHER'; + +export type BirthType = 'SINGLE' | 'TWIN' | 'TRIPLET' | 'QUADRUPLET_OR_MORE'; + +export type AttendantAtBirth = + | 'DOCTOR' + | 'MIDWIFE' + | 'NURSE' + | 'RELATIVE' + | 'NONE' + | 'OTHER'; + +export type MaritalStatus = + | 'SINGLE' + | 'MARRIED' + | 'WIDOWED' + | 'DIVORCED' + | 'SEPARATED' + | 'NOT_STATED'; + +export type InformantRelation = + | 'FATHER' + | 'MOTHER' + | 'GRANDFATHER' + | 'GRANDMOTHER' + | 'BROTHER' + | 'SISTER' + | 'LEGAL_GUARDIAN' + | 'OTHER'; + +export type IdType = + | 'NATIONAL_ID' + | 'PASSPORT' + | 'BIRTH_REGISTRATION_NUMBER' + | 'NATIONAL_REGISTRATION_NUMBER' + | 'NONE'; + +/** + * Address structure matching OpenCRVS API format + * Uses streetLevelDetails.street instead of direct street field + */ +export type DomesticAddress = { + addressType: 'DOMESTIC'; + country: 'BRB' | string; + administrativeArea: string; // parish ID + streetLevelDetails?: { + street?: string; + }; +}; + +export type PersonName = { + firstname: string; + surname: string; +}; + +/** + * Age reference structure used by OpenCRVS + * Age is calculated relative to a date field (e.g., child's DOB) + */ +export type AgeReference = { + age: number; + asOfDateRef: string; // Reference to date field, e.g., "child.dob" +}; + +// ============================================================================ +// Birth Declaration Types +// ============================================================================ + +/** + * Birth declaration payload matching the OpenCRVS Barbados QA API format + * Based on the Postman collection structure + */ +export type BirthDeclaration = { + // Child information (required) + 'child.name': PersonName; + 'child.gender': Gender; + 'child.dob': string; // YYYY-MM-DD + 'child.placeOfBirth': PlaceOfBirth; + 'child.birthLocation'?: string; // Hospital/facility ID when placeOfBirth = HEALTH_FACILITY + 'child.birthLocation.privateHome'?: DomesticAddress; // Address for private home births + 'child.birthType'?: BirthType; + 'child.attendantAtBirth'?: AttendantAtBirth; + 'child.reason'?: string; // Reason for delayed registration + + // Mother information + 'mother.detailsNotAvailable'?: boolean; + 'mother.reason'?: string; + 'mother.name'?: PersonName; + 'mother.age'?: AgeReference; + 'mother.maritalStatus'?: MaritalStatus; + 'mother.nationality'?: string; + 'mother.idType'?: IdType; + 'mother.nid'?: string; + 'mother.passport'?: string; + 'mother.brn'?: string; + 'mother.address'?: DomesticAddress; + 'mother.occupation'?: string; + 'mother.bornAlive'?: number + 'mother.stillborn'?: number; + 'mother.stillAlive'?: number; + + // Informant information + 'informant.relation'?: 'PARENT' | InformantRelation; + 'informant.parentsMarried'?: 'YES' | 'NO'; + 'informant.phoneNo'?: string; + + // Father information + 'father.detailsNotAvailable'?: boolean; + 'father.reason'?: string; + 'father.name'?: PersonName; + 'father.age'?: AgeReference; + 'father.nationality'?: string; + 'father.idType'?: IdType; + 'father.nid'?: string; + 'father.passport'?: string; + 'father.brn'?: string; + 'father.occupation'?: string; + 'father.addressSameAs'?: boolean; // true to use mother's address + 'father.address'?: DomesticAddress; +}; + +// ============================================================================ +// Notification Types +// ============================================================================ + +export type NotifyRequest = { + eventId: string; + transactionId: string; + declaration: BirthDeclaration; + annotation: Record; + createdAtLocation: string; + type: 'NOTIFY'; +}; + +export type NotifyResponse = { + success: boolean; + eventId?: string; + trackingId?: string; + error?: string; +}; + +// ============================================================================ +// Location Types +// ============================================================================ + +export type LocationBundle = { + entry?: Array<{ + resource?: { + id?: string; + name?: string; + }; + }>; +}; + +export type LocationType = + | 'CRVS_OFFICE' + | 'HEALTH_FACILITY' + | 'ADMIN_STRUCTURE'; + +// ============================================================================ +// Service Configuration Types +// ============================================================================ + +export type OpenCRVSConfig = { + authBaseUrl: string; + eventsBaseUrl: string; + locationsBaseUrl: string; + clientId: string; + clientSecret: string; + defaultOffice?: string; + defaultHealthFacility?: string; + defaultParish?: string; +}; + +// ============================================================================ +// Processor Configuration Types +// ============================================================================ + +export type OpenCRVSProcessorConfig = { + eventType: 'birth'; + officeId?: string; // Location ID or use mapping + officeName?: string; // Will be resolved to ID + healthFacilityId?: string; // Location ID or use mapping + healthFacilityName?: string; // Will be resolved to ID + parishId?: string; // Location ID or use mapping + parishName?: string; // Will be resolved to ID +}; + +// ============================================================================ +// Result Types +// ============================================================================ + +export type OpenCRVSProcessorResult = { + success: boolean; + eventId?: string; + trackingId?: string; + transactionId?: string; + error?: string; +}; diff --git a/src/processors/implementations/opencrvs.processor.ts b/src/processors/implementations/opencrvs.processor.ts new file mode 100644 index 0000000..3a754eb --- /dev/null +++ b/src/processors/implementations/opencrvs.processor.ts @@ -0,0 +1,189 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { IProcessor, ProcessorContext } from '../interfaces'; +import { OpenCRVSService, BirthRegistrationMapper } from '../../opencrvs'; +import { + OpenCRVSProcessorConfig, + OpenCRVSProcessorResult, +} from '../../opencrvs/types'; + +/** + * Processor for integrating birth registration form submissions with OpenCRVS + * + * This processor handles: + * 1. Resolving location IDs (office, health facility, parish) from names or IDs + * 2. Mapping form data to OpenCRVS BirthDeclaration format + * 3. Creating the birth event in OpenCRVS + * 4. Submitting the birth notification + * + * Note: Informant fields are not required per OpenCRVS Barbados configuration. + */ +@Injectable() +export class OpenCRVSProcessor implements IProcessor { + readonly type = 'opencrvs'; + private readonly logger = new Logger(OpenCRVSProcessor.name); + + constructor( + private readonly openCRVSService: OpenCRVSService, + private readonly birthMapper: BirthRegistrationMapper, + ) {} + + async execute( + config: Record, + context: ProcessorContext, + ): Promise { + this.logger.log( + `Executing OpenCRVS processor for form: ${context.formId}, submission: ${context.submissionId}`, + ); + + const processorConfig = config as unknown as OpenCRVSProcessorConfig; + + try { + // Validate event type + if (processorConfig.eventType !== 'birth') { + throw new Error( + `Unsupported event type: ${processorConfig.eventType}. Only 'birth' is currently supported.`, + ); + } + + const healthFacilityList = [{ + label: "Queen Elizabeth Hospital", + value: "3d5cd721-df37-493c-86c0-41b8aa42e27d", + }, + { + label: "Bayview Hospital", + value: "9ddfdd4a-4219-4ca0-ad34-5a9fc1071225", + }, + { + label: "MD Alliance Surgery and Birthing Centre", + value: "a1abb507-4a25-4795-a280-c99226cb916f", + }]; + + const healthFacilityName = healthFacilityList.find(hf => hf.value === context?.data?.birth?.healthFacility)?.label || ''; + + // Resolve location IDs + const { officeId, healthFacilityId, parishId } = + await this.resolveLocations({ + ...processorConfig, + healthFacilityName, + }, context.data); + + // Map form data to OpenCRVS declaration format + // Note: Informant fields are not required per OpenCRVS Barbados configuration + const declaration = this.birthMapper.mapToBirthDeclaration(context.data, { + parishId, + healthFacilityId, + defaultNationality: 'BRB', + }); + + // Create annotation (empty - OpenCRVS rejects custom fields) + const annotation = this.birthMapper.createAnnotation(context.data); + + // Register the birth with OpenCRVS + const result = await this.openCRVSService.registerBirth( + declaration, + officeId, + annotation, + ); + + if (result.success) { + this.logger.log( + `Birth registration successful for ${context.submissionId}: eventId=${result.eventId}, trackingId=${result.trackingId}`, + ); + } else { + this.logger.error( + `Birth registration failed for ${context.submissionId}: ${result.error}`, + ); + } + + return result; + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : 'Unknown error'; + this.logger.error( + `OpenCRVS processor failed for ${context.submissionId}: ${errorMessage}`, + error instanceof Error ? error.stack : undefined, + ); + + return { + success: false, + error: errorMessage, + }; + } + } + + /** + * Resolve location IDs from configuration + * Supports both direct IDs and name-based lookups + */ + private async resolveLocations( + config: OpenCRVSProcessorConfig, + formData: Record, + ): Promise<{ + officeId: string; + healthFacilityId?: string; + parishId: string; + }> { + // Resolve CRVS Office ID + let officeId = config.officeId; + if (!officeId && config.officeName) { + officeId = await this.openCRVSService.getLocationIdByName( + config.officeName, + 'CRVS_OFFICE', + ); + } + if (!officeId) { + // Default to main Registration Department + officeId = await this.openCRVSService.getLocationIdByName( + 'Registration Department Records Branch', + 'CRVS_OFFICE', + ); + } + + // Resolve Health Facility ID (optional - only needed if birth.placeOfBirth is health-facility) + let healthFacilityId = config.healthFacilityId; + if (!healthFacilityId && config.healthFacilityName) { + healthFacilityId = await this.openCRVSService.getLocationIdByName( + config.healthFacilityName, + 'HEALTH_FACILITY', + ); + } + + // Resolve Parish ID from config first + let parishId = config.parishId; + if (!parishId && config.parishName) { + parishId = await this.openCRVSService.getLocationIdByName( + config.parishName, + 'ADMIN_STRUCTURE', + ); + } + + // Try to get parish from mother's parish field in form data + if (!parishId) { + const motherData = formData.mother as { parish?: string } | undefined; + const motherParish = motherData?.parish; + + if (motherParish) { + try { + parishId = await this.openCRVSService.getLocationIdByName( + motherParish, + 'ADMIN_STRUCTURE', + ); + } catch { + this.logger.warn( + `Could not resolve parish from mother's address: ${motherParish}`, + ); + } + } + } + + // Default parish if still not resolved + if (!parishId) { + parishId = await this.openCRVSService.getLocationIdByName( + 'Christ Church', + 'ADMIN_STRUCTURE', + ); + } + + return { officeId, healthFacilityId, parishId }; + } +} diff --git a/src/processors/processor-pipeline.service.ts b/src/processors/processor-pipeline.service.ts index 12f7a8a..25ea174 100644 --- a/src/processors/processor-pipeline.service.ts +++ b/src/processors/processor-pipeline.service.ts @@ -18,11 +18,13 @@ export class ProcessorPipelineService { async execute( processorConfigs: ProcessorConfig[], context: ProcessorContext, - ): Promise { + ): Promise> { this.logger.log( `Executing ${processorConfigs.length} processors for form: ${context.formId}`, ); + const results = new Map(); + // Execute all processors in parallel const promises = processorConfigs.map(async (config) => { const processor = this.processors.get(config.type); @@ -36,8 +38,9 @@ export class ProcessorPipelineService { try { this.logger.log(`Executing processor: ${config.type}`); - await processor.execute(config.config, context); + const result = await processor.execute(config.config, context); this.logger.log(`Processor completed: ${config.type}`); + return { type: config.type, result }; } catch (error) { this.logger.error( `Processor ${config.type} failed: ${error.message}`, @@ -47,8 +50,15 @@ export class ProcessorPipelineService { } }); - await Promise.all(promises); + const processorResults = await Promise.all(promises); + + // Collect results by processor type + for (const { type, result } of processorResults) { + results.set(type, result); + } + this.logger.log('All processors completed successfully'); + return results; } /** diff --git a/src/processors/processors.module.ts b/src/processors/processors.module.ts index 9e98dbe..9653f82 100644 --- a/src/processors/processors.module.ts +++ b/src/processors/processors.module.ts @@ -3,21 +3,25 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ProcessorPipelineService } from './processor-pipeline.service'; import { EmailModule } from '../email/email.module'; import { PaymentsModule } from '../payments/payments.module'; +import { OpenCRVSModule } from '../opencrvs/opencrvs.module'; import { Payment, FormSubmissionPayment } from '../database/entities'; import { EmailProcessor } from './implementations/email.processor'; import { PaymentProcessor } from './implementations/payment.processor'; +import { OpenCRVSProcessor } from './implementations/opencrvs.processor'; import { EZPayService, DepartmentMappingService } from '../payments'; @Module({ imports: [ EmailModule, PaymentsModule, + OpenCRVSModule, TypeOrmModule.forFeature([Payment, FormSubmissionPayment]), ], providers: [ ProcessorPipelineService, EmailProcessor, PaymentProcessor, + OpenCRVSProcessor, EZPayService, DepartmentMappingService, ], @@ -28,9 +32,11 @@ export class ProcessorsModule { private readonly pipelineService: ProcessorPipelineService, private readonly emailProcessor: EmailProcessor, private readonly paymentProcessor: PaymentProcessor, + private readonly openCRVSProcessor: OpenCRVSProcessor, ) { // Register all processors this.pipelineService.registerProcessor(this.emailProcessor); this.pipelineService.registerProcessor(this.paymentProcessor); + this.pipelineService.registerProcessor(this.openCRVSProcessor); } }