Training certificate flow
SFReg Salesforce-issued certificates (V0.99.18 / V0.99.19, DYNDOC DC3). State in the QA and UAT sandboxes on 14 Oct 2026, with Prod noted where it differs.
When a certificate is triggered
| Trigger | Class and job | When it runs | Which bookings | Reason passed | QA / UAT |
|---|---|---|---|---|---|
| Trainer result | ContactAssetRoleRestResource.doPut( PUT /services/apexrest/v1/contactAssetRoles) | Real time, when Mule (integration user "My StJohn") calls it | Status header = CR: the CAR Ids sent by the Training Portal, or the registrations sent by Moodle (registration Id + attendee Id match) | POST_COURSE_PASS_THROUGH | Live |
| Retry batch | CertificateRequestBatch (10 per chunk), run by CertificateRequestBatchSchedulableProd job: CertificateRequest_Hourly_Schedule | Prod: every hour on the hour, NZ time (cron 0 0 * * * ?) | Status__c = 'CR' and the course's order has an invoice number (Asset → OrderItem → Order.InvoiceNumber__c not blank) | RETRY | Not scheduled |
| 8-week auto opt-out | AutoOptOutScheduler → SFReg_PostCourseCertClosureBatch (10 per chunk)Job: SFReg Auto Opt-Out Closure Daily | Daily at autoOptOutDispatchTime__c (10:30 NZ), once AutoOptOutScheduler.scheduleDaily() is run | Role Primary, Type Attendee, FATattendance__c = 'Attended', status C or CR, event at least 8 weeks ago (autoOptOutWeeksAfter__c) and within a 28-day catch-up window, no AutoOptOutClosedAt__c on its Attempts. NOT_READY ones get NZQA Required set FALSE first, so they become STANDARD. | AUTO_OPT_OUT_CLOSURE | Not scheduled (waiting on AK/Ade) |
| Hold release | SFReg_PostCourseCertGate.releaseIfEntitled | When a held attendee's registration-confirmed email signal fires (EMAIL_REG_CONFIRMED / _NZQA), or they opt out of NZQA | Bookings parked at C with a certificate hold | HOLD_RELEASED | Gate off, never runs |
A manual status edit does not trigger a certificate: only these four paths call the request service. To re-send one booking by hand, run SFReg_CertificateRequestService.requestBulk(new Set<Id>{carId}, SFReg_CertificateRequestService.RequestReason.RETRY) as anonymous Apex.
What each step checks
| Step | Class | Passes when | Otherwise |
|---|---|---|---|
| 1. Mode | SFReg_CertificateRequestService.resolveMode | certificateGenerationMode__c is Salesforce (or FIS / blank, both read as Salesforce) | Skip: nothing happens. Mock: logged only. |
| 2. Already sent | requestViaSalesforce | Status is not CS | ALREADY_SENT, nothing done |
| 3. Entitlement (C1) | SFReg_CertificateEntitlement.resolve | NZQA Required add-on (Role AddOn, Type NZQA Required, matched on ExternalCode__c + Asset__c) is missing or FALSE → STANDARD. TRUE and all three Attribute__c rows present: attendeeRegistrationComplete, attendeeNSN not blank, attendeeNSIVerified = 'Yes' → NZQA. | NOT_READY (REGISTRATION_NOT_FINISHED, NSN_MISSING or NSN_NOT_VERIFIED). Gate off: left as is with a WARN log. Gate on: parked at C with a hold and a Case. |
| 4. Queue | queueIssue | Status moved to CR if needed; one SFReg_CertificateIssueQueueable queued | No queueable capacity: stays CR for the retry batch |
| 5. Re-check | SFReg_CertificateIssueQueueable | Still CR and still entitled (up to 5 per hop) | Skipped (moved on, or the answer changed) |
| 6. Render and file | DynamicCertificateService.issueCertificates | The product's chain (Family → Offer → Variant → Option, Definition_Attribute__c Rule rows) resolves CERTIFICATE_NZQA or CERTIFICATE_NON_NZQA to an active template; for NZQA, CERTIFICATE_NZQA_CREDITS also resolves; Certificate registry record active | MISSING_DATA or render error: stays CR, one ERROR row in SFREG_Log__c |
| 7. Email | RegistrationEmailService.dispatchCertificate | Booking locked and still CR. Template rule EMAIL_POST_COURSE_CERT (PostCourseCompletionCert_Dyn), or EMAIL_AUTO_OPT_OUT_CERT (AutoOptOutNZQACertSend_Dyn) when the latest Attempt has AutoOptOutClosedAt__c. PDF attached. | Opted out or no address: not sent, still moves to CS. Send failure: stays CR. |
| 8. Finish | SFReg_CertificateIssueQueueable | NZQA Credit add-on set TRUE (NZQA) or FALSE (standard); status CR → CS | — |
Settings that control it
| Setting (AttendeeRegistrationConfig__mdt Default) | QA | UAT | Effect |
|---|---|---|---|
certificateGenerationMode__c | Salesforce | FIS (read as Salesforce, WARN each request) | Skip is the emergency stop without a deploy |
postCourseCertGateEnabled__c | false | false | Off: not-ready bookings just wait; on: they are held at C |
autoOptOutWeeksAfter__c | 8 | 8 | Weeks after the event before the auto opt-out closes a booking |
autoOptOutDispatchTime__c | 10:30 | 10:30 | Run time used by scheduleDaily() |
Known gaps and risks
- NZQA credits rule. Every NZQA certificate fails with MISSING_DATA until
CERTIFICATE_NZQA_CREDITSexists. QA: missing. UAT: a placeholder on the First Aid Training family, so certificates print "NZQA credits: NZQA credits". The real wording belongs on each course's own (Option) definition. - NSIVerified downgrade (defect). Reopening a finished, NSN-verified registration can run a name search that misses and sets
attendeeNSIVerifiedto No. The NSN-create step then short-circuits on the existing NSN without restoring it, so the certificate is blocked as NSN_NOT_VERIFIED. - Nothing retries in QA or UAT. Neither the retry batch nor the 8-week closure is scheduled, so a failed or not-ready booking waits at CR until someone re-requests it.
- Hourly retry in Prod. Prod runs
CertificateRequestBatchevery hour. After this release, each retry renders a new file (the engine never reuses an earlier one), so a booking that keeps failing would collect a new certificate file every hour. Decide the Prod schedule before promotion. - LMS certificates drawer. The SFDocs handler compares status with the label 'Certificate Sent' instead of the stored value CS, so certificates are never indexed for the drawer.
- Certificate date. The certificate uses the course date (Service Start Date). A trainer marking an attendee competent before the course produces a future-dated certificate.
Prod today
Prod still prints through FIS: CR publishes PRINT_CERTIFICATE to Mule and FIS prints and posts the certificate; the hourly batch re-publishes anything left at CR. Promoting V0.99.18 retires that path, so it needs business and FIS-team sign-off first.
Verified end to end in UAT on 14 Oct 2026 with a test booking: NZQA certificate filed, emailed (redirected to the sandbox test mailboxes) and the booking moved to CS.