Salesforce metadata and Apex for Program Enrollment invoice PDFs, Course Registration (FormAssembly) file handling on the PE, student confirmation email (responses + invoice attachments), and Program Office notifications.
Repository: github.com/mauricedavis/PDFInvoiceProject
Deploy to production (example): .\Push_UCSF_Prod.ps1 -OrgAlias ucsf-prod -QuickTests (see script for prompts and test options).
Important: Invoice generation via Flow invocables that call generateAttachAndEmail must also chain student confirmation. ProgramEnrollmentInvoiceController.enqueueStudentConfirmationAfterInvoice is invoked from InvocablePEInvoice, InvocablePEInvoiceAttach, PEInvoiceActionOne, PEInvoiceAttachByRecord, and PEInvoiceFlowByRecord so the same path as trigger → ProgramEnrollmentInvoiceQueueable → batch finish is not required for the two-PDF student email.
Work focused on ucsf-prod: student confirmation reliability, enrollee Flow behavior, invoice PDF course line items, and safe rollback.
- Root issue: Flow v18 used Send Email (
emailSimple) with the same template as Apex; that logged anEmailMessagesoProgramEnrollmentStudentEmaildedupe skipped the queueable, often leaving only one attachment (registration PDF, not invoice). - v19 / v20 (enrollee flow): Removed the Flow emailSimple step; confirmation is sent via
PEInvoiceActionOne→enqueueStudentConfirmationAfterInvoice→ProgramEnrollmentStudentEmailQueueable, with template sends usingEmailFileAttachmentfromContentVersion.VersionDatawhen both PDFs load (fallback to entity attachments). PEInvoiceActionOne:ProgramEnrollmentId=$Record.Id;try/finallyso the queueable is still enqueued if generation throws.- NPE fix: Static
programOfficeOweaLookupDonemust be initialized tofalse(uninitializedBooleanisnulland breaksgetProgramOfficeOrgWideEmailAddressId).
Get ContentDocumentLinknow targets$Record.Id, allows no rows, and branches so the flow does not fault whensfProgramEnrollmentId__cis blank or files are only on the PE (still reachesPEInvoiceActionOne).
- Root issue:
generateAttachAndEmailcould create the first invoice before Course Connections existed → $0 / empty table; idempotency then blocked regeneration; the invoice queueable skipped when a file already existed. - Fix: Readiness (same rules as
ProgramEnrollmentInvoiceQueueable) before the first PDF from Flow/invocables;generateAttachAndEmailForBatchfor the batch path (readiness already enforced). Stale detection removesInvoice_%files when course rows are newer than the latest invoice file, then regenerates. OptionalProgram_Enrollment__conhed__Course_Enrollment__cadded to the course query OR clause where the field exists.
| Doc | Purpose |
|---|---|
FLOW_DEPLOY_SCOPE.md |
Enrollee flow v18 vs v19/v20, deploy scope, regression checks |
UCSF_PROD_ROLLBACK.md |
Deployment IDs, Flow Version History (e.g. activate v18), Apex class rollback list |
flow-snapshots/Program_Enrollment_Notification_to_Enrollee_v18.flow-meta.xml |
Frozen v18 flow XML for diff / deliberate redeploy (not the active force-app path) |
Salesforce: deploying the flow creates a new version; older versions remain under Setup → Flows → Version History.
Tests: ProgramEnrollmentInvoiceController_Tests, ProgramEnrollmentStudentEmail_Tests, and full RunLocalTests deploys to ucsf-prod were used during validation.
CourseEnrollmentInvoiceTriggerCore: bulk of Course Enrollment trigger logic (invoice queueable fan-out).- Draft safety flow:
Program_Enrollment_Student_Confirmation_Delayed_Safety— optional delayedProgramEnrollmentStudentEmailenqueue (see flow description).
Now that you’ve created a Salesforce DX project, what’s next? Here are some documentation resources to get you started.
Do you want to deploy a set of changes, or create a self-contained application? Choose a development model.
The sfdx-project.json file contains useful configuration information for your project. See Salesforce DX Project Configuration in the Salesforce DX Developer Guide for details about this file.