Fix CSV and directory export to use family address fallback#7964
Fix CSV and directory export to use family address fallback#7964
Conversation
When exporting CSV or generating church directory, persons without personal addresses now fall back to their family's address data. This ensures members are included in exports with complete address information even if they haven't entered personal address details. Fixes #7937
There was a problem hiding this comment.
Pull request overview
This PR addresses #7937 by ensuring that CSV exports and the PDF church directory include address/contact info for people who don’t have personal address data, by falling back to their family’s address/contact fields.
Changes:
- Update CSV export row-building to prefer person address/contact fields but fall back to family fields when person fields are empty.
- Update Directory report rendering for single-person entries to use the same family fallback behavior.
- Add Cypress coverage to assert address columns appear in CSV exports (default and rollup).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/CSVCreateFile.php | Adds person→family fallback for address/contact fields when generating CSV rows. |
| src/Reports/DirectoryReport.php | Adds person→family fallback for address/contact fields for single-person directory entries. |
| cypress/e2e/ui/reports/standard.csv.reports.spec.js | Adds two tests asserting address columns exist in exported CSV headers. |
| cy.request({ | ||
| method: "POST", | ||
| url: "/CSVCreateFile.php", | ||
| form: true, | ||
| body: { | ||
| output: "csv", | ||
| format: "default", | ||
| familyonly: "false", |
There was a problem hiding this comment.
The request body uses format (lowercase), but CSVCreateFile.php reads $_POST['Format'] (capital F) and the UI form uses name="Format" (see src/CSVExport.php). With the current payload this test may not exercise the intended code path (and can trigger unintended default filters). Update the POST parameter to Format (and consider matching the UI values like Default/Rollup), and include any required date-range fields so the export returns real rows.
| form: true, | ||
| body: { | ||
| output: "csv", | ||
| format: "rollup", |
There was a problem hiding this comment.
Same issue here: the test posts format: "rollup", but the endpoint expects Format (capital F). As written, this may not actually be testing rollup behavior and could silently return an empty export/header-only response due to missing expected filters. Use Format: "Rollup" (or at least Format: "rollup") and post the other form fields the script assumes are present.
| format: "rollup", | |
| Format: "Rollup", |
src/CSVCreateFile.php
Outdated
| if ($sFormat === 'rollup') { | ||
| // Even in rollup format, use person data (no family inheritance) | ||
| $sHomePhone = $per_HomePhone ?? ''; | ||
| // Rollup format: use person data with family fallback for address/contact fields | ||
| $sHomePhone = !empty($per_HomePhone) ? $per_HomePhone : ($fam_HomePhone ?? ''); | ||
| $sWorkPhone = $per_WorkPhone ?? ''; | ||
| $sCellPhone = $per_CellPhone ?? ''; | ||
| $sCountry = $per_Country ?? ''; | ||
| $sAddress1 = $per_Address1 ?? ''; | ||
| $sAddress2 = $per_Address2 ?? ''; | ||
| $sCity = $per_City ?? ''; | ||
| $sState = $per_State ?? ''; | ||
| $sZip = $per_Zip ?? ''; | ||
| $sEmail = $per_Email ?? ''; | ||
| $sCountry = !empty($per_Country) ? $per_Country : ($fam_Country ?? ''); | ||
| $sAddress1 = !empty($per_Address1) ? $per_Address1 : ($fam_Address1 ?? ''); | ||
| $sAddress2 = !empty($per_Address2) ? $per_Address2 : ($fam_Address2 ?? ''); | ||
| $sCity = !empty($per_City) ? $per_City : ($fam_City ?? ''); | ||
| $sState = !empty($per_State) ? $per_State : ($fam_State ?? ''); | ||
| $sZip = !empty($per_Zip) ? $per_Zip : ($fam_Zip ?? ''); | ||
| $sEmail = !empty($per_Email) ? $per_Email : ($fam_Email ?? ''); | ||
| } else { | ||
| // Individual data - use person data only | ||
| $sHomePhone = $per_HomePhone ?? ''; | ||
| // Default format: use person data with family fallback for address/contact fields | ||
| $sHomePhone = !empty($per_HomePhone) ? $per_HomePhone : ($fam_HomePhone ?? ''); | ||
| $sWorkPhone = $per_WorkPhone ?? ''; | ||
| $sCellPhone = $per_CellPhone ?? ''; | ||
| $sCountry = $per_Country ?? ''; | ||
| $sAddress1 = $per_Address1 ?? ''; | ||
| $sAddress2 = $per_Address2 ?? ''; | ||
| $sCity = $per_City ?? ''; | ||
| $sState = $per_State ?? ''; | ||
| $sZip = $per_Zip ?? ''; | ||
| $sEmail = $per_Email ?? ''; | ||
| $sCountry = !empty($per_Country) ? $per_Country : ($fam_Country ?? ''); | ||
| $sAddress1 = !empty($per_Address1) ? $per_Address1 : ($fam_Address1 ?? ''); | ||
| $sAddress2 = !empty($per_Address2) ? $per_Address2 : ($fam_Address2 ?? ''); | ||
| $sCity = !empty($per_City) ? $per_City : ($fam_City ?? ''); | ||
| $sState = !empty($per_State) ? $per_State : ($fam_State ?? ''); | ||
| $sZip = !empty($per_Zip) ? $per_Zip : ($fam_Zip ?? ''); | ||
| $sEmail = !empty($per_Email) ? $per_Email : ($fam_Email ?? ''); | ||
| } |
There was a problem hiding this comment.
The rollup and default branches now perform the same address/contact-field assignments. This conditional duplication makes the code harder to maintain (any future change must be applied twice). Consider collapsing this into a single assignment block (or a small helper) and only branching where rollup/default behavior actually differs.
- Fix POST parameter name: use 'Format' (capital F) to match CSVCreateFile.php - Deduplicate address fallback code: remove conditional since rollup and default use identical address/contact field assignments
| it("should include address columns in CSV export with family fallback", () => { | ||
| // Test default format includes address fields when requested | ||
| cy.request({ | ||
| method: "POST", | ||
| url: "/CSVCreateFile.php", | ||
| form: true, | ||
| body: { | ||
| output: "csv", | ||
| Format: "default", | ||
| familyonly: "false", | ||
| Source: "all", | ||
| Address1: "1", | ||
| Address2: "1", | ||
| City: "1", | ||
| State: "1", | ||
| Zip: "1" | ||
| } | ||
| }).then((response) => { | ||
| expect(response.status).to.eq(200); | ||
| expect(response.headers["content-type"]).to.include("text/csv"); | ||
|
|
||
| // Verify CSV header contains address columns | ||
| const lines = response.body.split('\n'); | ||
| expect(lines.length).to.be.greaterThan(1); | ||
| const header = lines[0].toLowerCase(); | ||
| expect(header).to.include("address 1"); | ||
| expect(header).to.include("city"); | ||
| expect(header).to.include("state"); | ||
| }); |
There was a problem hiding this comment.
The new Cypress tests only verify that the CSV headers include address columns; they don’t validate the actual behavior introduced by this PR (person address fields falling back to family values when personal values are empty). As written, these tests would still pass even if the fallback logic were broken. Consider asserting at least one exported data row contains non-empty address values for a known person-without-address / family-with-address case (or creating such a record during the test) and also asserting the body doesn’t contain PHP fatal/parse errors like the existing test does.
| // Use person data with fallback to family data for addresses/contact | ||
| // This ensures members without personal addresses still get exported with their family's address | ||
| $sHomePhone = !empty($per_HomePhone) ? $per_HomePhone : ($fam_HomePhone ?? ''); | ||
| $sWorkPhone = $per_WorkPhone ?? ''; | ||
| $sCellPhone = $per_CellPhone ?? ''; | ||
| $sCountry = !empty($per_Country) ? $per_Country : ($fam_Country ?? ''); | ||
| $sAddress1 = !empty($per_Address1) ? $per_Address1 : ($fam_Address1 ?? ''); | ||
| $sAddress2 = !empty($per_Address2) ? $per_Address2 : ($fam_Address2 ?? ''); | ||
| $sCity = !empty($per_City) ? $per_City : ($fam_City ?? ''); | ||
| $sState = !empty($per_State) ? $per_State : ($fam_State ?? ''); | ||
| $sZip = !empty($per_Zip) ? $per_Zip : ($fam_Zip ?? ''); | ||
| $sEmail = !empty($per_Email) ? $per_Email : ($fam_Email ?? ''); |
There was a problem hiding this comment.
This person→family fallback block is now duplicated with similar logic in DirectoryReport.php. To reduce the chance of the two exports drifting (e.g., one adds Country fallback and the other doesn’t), consider centralizing the “resolved address/contact fields” logic in a shared helper/service method and using it in both places.
- Add PHP error checks (Fatal error, Parse error) to CSV export tests - Verify at least one data row exists in export (validates fallback works) - Add code comment noting similar logic in DirectoryReports.php for future consolidation consideration
Summary
Fixes #7937 - Family and person addresses don't export to CSV and some are not included in Church Directory
When exporting CSV reports or generating church directory PDFs, persons without personal address data now fall back to their family's address information. This ensures all family members appear in exports with complete address details, even if they haven't entered personal addresses.
Changes
CSV Export (
src/CSVCreateFile.php)!empty($per_Field) ? $per_Field : ($fam_Field ?? '')Directory Report (
src/Reports/DirectoryReport.php)Tests (
cypress/e2e/ui/reports/standard.csv.reports.spec.js)Why
Previously, when a person had no personal address entered but belonged to a family with address data:
This was unexpected behavior since most church members share their family's address. The fix provides a sensible fallback while still respecting any personal address data when present.
Testing
Files Changed