Skip to content

(feat) O3-5675: add facility header, patient age, page numbers and empty-state styling and fix repeat table column headers across page breaks - #33

Open
BlessedAmrita wants to merge 3 commits into
openmrs:mainfrom
BlessedAmrita:visit-summary-xslt-polish
Open

(feat) O3-5675: add facility header, patient age, page numbers and empty-state styling and fix repeat table column headers across page breaks#33
BlessedAmrita wants to merge 3 commits into
openmrs:mainfrom
BlessedAmrita:visit-summary-xslt-polish

Conversation

@BlessedAmrita

Copy link
Copy Markdown
Contributor

Summary

Polish pass on the visit summary PDF layout, plus a fix for column headers not repeating across page breaks.

What this does

  • The header is now a horizontal band: configured facility logo, name and contact on the left, document title and visit date on the right. The logo resolves through the same app-data-dir mechanism the patient ID sticker uses, and renders nothing rather than failing the PDF when unconfigured or unreadable.

  • The patient block is two compact rows of four fields. Age is derived at the visit start date rather than today, so it stays consistent with the vitals, diagnoses and medications on the same page, and reprinting an old visit does not change what it says. The date of birth prints beside it either way.

  • The footer carries the facility name and Page N of M on every page. Section spacing was tightened without reducing body font sizes.

  • Empty sections render a grey dashed block, visually distinct from the red section-error banner, so a clinician can tell "nothing was recorded" from "this could not be retrieved" on paper alone.

  • The bug fix: column header rows lived in fo:table-body, which XSL-FO treats as ordinary data, so a table spanning a page break opened the next page with unlabeled columns. Page 2 of a lab table showed values and flags with no indication of which column was the result and which was the reference range. Moved to fo:table-header, which repeats by default. Affects diagnoses, conditions, lab results, allergies and medications.

  • Supporting changes: date and datetime patterns are configurable and formatted in the user's locale; Helper.getImageAsDataUri detects PNG, JPEG and GIF from signature bytes instead of labelling every file image/png.

Testing

api 197, omod 13, 0 failures.

The table-header fix has a regression test that renders each affected section through FOP with enough rows to force a page break, then asserts every column header appears on every page. It also asserts the table actually spanned more than one page, so a future change making the data fit on one page cannot turn it into a silent pass. Verified by reverting one header back into fo:table-body and confirming only that test failed.

Adding a test class that sets date-format properties exposed a test-isolation issue: AdministrationService caches global-property values in memory, so a value saved by one test class survives its rolled-back transaction and leaks into later classes. Test classes that set properties now clean up after themselves.

Related

https://openmrs.atlassian.net/browse/O3-5675

Notes

  • New global properties: report.visitSummary.logourl, report.visitSummary.facility.phoneAttributeType, report.visitSummary.dateFormat, report.visitSummary.datetimeFormat. Naming follows report.patientIdSticker.logourl and report.visitSummary.visitNotes.clinicianEncounterRole.
  • The logo key is deliberately separate from the sticker's, since a printed document and an ID label may want different images. The cost is that a site using one logo configures it twice.

…pty-state styling (fix): repeat table column headers across page breaks
@BlessedAmrita

Copy link
Copy Markdown
Contributor Author

Before:

visit-summary.pdf

image image

After:

visit-summary-polished.pdf

image image

@BlessedAmrita

Copy link
Copy Markdown
Contributor Author

cc: @wikumChamith @dkayiwa

Assertions.assertTrue(pageCount > 1,
"test data must force a page break, but the table fit on one page");

for (int page = 1; page <= pageCount; page++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these cases render real multi-page PDFs, this is also the natural place to lock in the footer's "Page N of M" (and the facility name). Those print on every page too, and they're currently the one behavior this PR adds that nothing tests.

The failure mode is quiet: rename the visit-summary-end anchor, or drop the fo:page-number-citation, and every page's total silently breaks with no test to catch it.

The fixture here doesn't emit a <footer> element, so covering it means adding a <footer lbl-page="Page" lbl-of="of" .../> to visitSummaryXml(...) and asserting the "Page N of M" line shows up on each page, alongside the column-header checks you already do.

Non-blocking, and the mechanism itself looks right (the regression run here is green). I'm just flagging the gap while the multi-page harness is sitting right here.

@BlessedAmrita

Copy link
Copy Markdown
Contributor Author

cc: @wikumChamith @dkayiwa

PatientDocumentsConstants.NO_DATA_RECORDED_PLACEHOLDER, Context.getLocale());
}
catch (Exception e) {
label = PatientDocumentsConstants.NO_DATA_RECORDED_PLACEHOLDER;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to log a warning here?

return null;
}
return "data:" + mediaType + ";base64," + Base64.getEncoder().encodeToString(imageBytes);
} catch (IOException e) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to log here?

Comment on lines +155 to +157
Locale locale = Context.getLocale();
try {
return new SimpleDateFormat(pattern, locale).format(date);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could cause a problem on modern Java versions. After Java 9, the Arabic locale's default numbering system is Arabic-Indic. So with the default yyyy-MM-dd pattern, an ar-locale user gets:

en :2026-06-24
arabic :٢٠٢٦-٠٦-٢٤

I'm not sure if this is desirable on a clinical document or not. Feels like we might need to keep the localized text but force Latin (ASCII) digits.

@ibacher, @VeronicaMuthee any suggestions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just looked at core, and core does the same thing. So this feels like a decision we don't need to make for this PR; it's more of a whole-product consideration.

If someone needs a locale with a different language and a different numbering system, we'd need to update core. That would give users the choice to customize it themselves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this and for checking core too, I learned something :D
Agreed it's better handled at the core level than here, and keeping this PR consistent with core makes sense. Good to have it documented in the thread if an ar-locale deployment ever needs a different numbering system.

@sonarqubecloud

Copy link
Copy Markdown

@BlessedAmrita

Copy link
Copy Markdown
Contributor Author

cc: @wikumChamith

@dkayiwa

dkayiwa commented Jul 29, 2026

Copy link
Copy Markdown
Member

@claude review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants