ERP ID card generation: templates and printing for students & staff¶
An institution issues an ID card to every student and staff member. Today the ERP holds the data those cards need — names, admission/roll numbers, departments, and now photos — but has no way to turn it into a card. An admin falls back to Word, Photoshop, or a print vendor, re-keying names and USNs and pasting photos by hand, disconnected from the system of record and re-done for every new admission. This feature adds a card template (a built-in layout + a swappable background + a theme) and a generate → preview → print flow, so an admin designs a card once and prints a whole cohort from the ERP roster. It ships with its Phase 0 enabler: a photo on the student and staff records.
Status: implemented, dark-launched. Product Mindset Gate passed 2026-07-31.
Phase 0 (photos) + Phase 1 (templates, generation, admin UI) landed in
MySetu-AI/kwiloai_webapp PR #1468 (branch feat/erp-id-card-generation →
staging), behind the erp_id_cards feature flag (default off). Phase 2
(AI-generated backgrounds, card issuance + QR verification) not started.
Related:
attendance.md (student photos also feed face-attendance;
this feature adds a photo to the ERP record itself),
erp-branch-transfer.md (the mutable roll
number / USN printed as the card's ID),
erp-higher-ed-scheduling.md (cohorts an admin
selects from when generating),
erp-institution-standard-fields.md (the
OrgUnit name/logo/address/primary-colour that brand the card header).
Why this exists¶
- Who asked: Self-initiated, adjacent to the seeded Sapthagiri University + School staging tenants — not a logged support ticket. Flagged honestly as a founder hypothesis rather than validated demand. Competing ERPs (Fedena, Classe365, OpenEduCat) all ship it, which is why it reads as expected.
- User pain: An admin producing ID cards works outside the ERP — re-keying names, USNs, and photos the SIS already holds into Word/Photoshop or a print vendor's template — and repeats it for every new-admission cohort. Error-prone (wrong USN on a printed card is expensive) and duplicative.
- Cost of not doing it: A feature-parity gap in higher-ed/K-12 deals — it is a checklist item every competing ERP advertises, so its absence shows up in demos. Real but not operationally blocking; admins keep using external tools. Moderate.
- Validated or guess: Weakly validated. Universal in competitor ERPs and a natural expectation for the seeded tenants, but no specific interview or ticket. This is why Phase 1 stays lean (fixed layouts, no AI, no bespoke designer) — enough to prove the workflow before investing further.
- How we will know it worked: An admin generates a full new-admission cohort's cards in-app, and re-issues a lost card in seconds, without falling back to Word/Photoshop/a vendor. Sean Ellis counterfactual: remove it a week after an onboarding admin adopts it and they complain specifically — "I'm back to making cards in Word for every new batch" — because the manual alternative is tedious and they felt the relief.
Personas¶
Mr. Prakash, an admin/office clerk at an engineering college. Runs the start-of-year card cycle: a few hundred new students plus new staff. He wants to pick a template, select the batch, and print — with the correct USN, photo, and college branding on every card, without touching a design tool.
A student or staff member who receives the printed card. Expects their name, photo, ID number, and department to be correct and legible, and the card to look like it came from the institution (logo, colours), not a generic template.
ICP coverage¶
Unlike branch transfer, this feature serves both higher ed and K-12, and both students and staff — every institution type issues ID cards to both populations. Layouts are holder-typed (student vs staff) but institution-type agnostic; a K-12 school and a university use the same portrait/landscape presets.
Outcomes (user outcomes, not deliverables)¶
- An admin prints a cohort of correct, on-brand ID cards from the ERP without a separate design tool.
- A lost or damaged card is re-printed in seconds from the same template.
- What the admin sees in the editor is exactly what prints — no surprise between preview and paper.
Non-goals¶
- No drag-and-drop field designer (phase 1). Field positions are fixed, code-defined layouts. Admins pick a preset and restyle it (background, accent colour, validity, footer), not reposition every field. A full designer is a later step only if demand proves out.
- No free-form card dimensions. Size is a layout preset (portrait 54×85.6 mm, landscape 85.6×54 mm), not an arbitrary width/height — fields are placed for a specific canvas shape, so a free ratio would distort the layout. New sizes ship as new presets.
- No AI-generated backgrounds (phase 1). Deferred to phase 2 so launch does not depend on the chatbot image agent (which has an outage history). Upload + built-in backgrounds always work.
- No card issuance / QR verification (phase 1). No
IdCardIssuerecord, no re-issue audit trail, no public "is this card genuine" QR endpoint yet — all phase 2. - No server-side image compositing. The backend resolves card data; the frontend renders and prints. No Pillow/ReportLab/font pipeline on the server.
Metrics¶
- North star: cards generated in-app ÷ cards the institution actually issued in the cycle (target: the admin never opens Word/Photoshop for a card).
- Guardrail: zero cards printed with a wrong ID number or mismatched photo — the data is resolved from the SIS, never re-typed.
Risks & mitigations¶
- Records may have no photo. Photos are a phase-0 addition, so many students/staff won't have one yet. Mitigation: the renderer falls back to an initials placeholder; admins upload photos on the student/staff detail surfaces over time. Photo is never required to print a card.
- The generate flow reuses the students/HR rosters. Picking holders calls
the
/erp/studentsand/erp/hr/employeeslist endpoints, so the operator needs access to that module too. Org/unit managers always do; a module-scopedid_cards-only operator would not. Mitigation: accepted for phase 1 (the operators are admins/managers); a dedicated id-cards holder picker is a follow-up. - Print fidelity varies by browser. Print-to-PDF uses the browser's print
dialog with a scoped
@media printrule and true-size card widths. Mitigation: cards render at physical dimensions (portrait ~204px≈54 mm, landscape ~324px≈85.6 mm); the sameIdCardPreviewdrives editor and print so there is no second code path to drift. - Signed-URL cost on large rosters. Every photo and the org logo resolve to
a short-lived GCS signed URL. Mitigation: list/roster endpoints never sign
(they return
photo_url: null); signing happens only on detail reads and in the capped (≤200)/cardsresolution. - Image-gen agent fragility (phase 2 risk, noted now). Phase 2's AI backgrounds will lean on the chatbot media agent, which has retired a model before. Mitigation: AI is an additive background source; upload/builtin remain the always-working default.
Mental model¶
Grounded in the current code (apps/backend/src/erp/identity/,
src/erp/framework/media.py, src/erp/students|hr/models.py).
- A template = a built-in layout + a background + a theme. The layout fixes
the geometry (canvas size and where each field sits); the template only swaps
the background and restyles via a JSON
theme(accent colour, which optional fields show, validity window, footer).IdCardTemplateis per-org-unit, per-holder-type. - Layout geometry lives in code, not the DB.
identity/layouts.pydefines the presets (student_portrait_v1,employee_portrait_v1, and the landscape pair) as pixel coordinates on a CR80 canvas. A template references one bylayout_key. This keeps the "fixed layouts" contract explicit and versioned. - Backend resolves data; frontend renders + prints.
POST /erp/id-cards/cardsturns holder IDs into afieldsdict per card (name, id_number, photo URL, course/dept or designation/department, validity, footer) plus the layout spec and institution branding. The ReactIdCardPreviewdraws it — the same component for the live editor and the print grid, so preview == print by construction. - The ID number is the USN, with a fallback. For a student it is the linked
platform
Student.roll_number(the mutable USN — see erp-branch-transfer.md) if set, else the ERPadmission_number. For staff it isemployee_code. - Photos are a blob key, signed on read.
StudentProfile.photo_key/Employee.photo_keystore a GCS blob key; the wire contract never exposes the key — reads serve a short-lived signed URL, degrading to no-photo on any GCS failure so a card still renders. - Card size is a layout property, not a number. Orientation lives on the layout; the renderer sizes itself by orientation so portrait and landscape each print at true physical size.
User flows¶
Design a template (admin/manager). ID Cards → New template → pick Card for (student/staff) → pick a Layout (portrait or landscape) → set the theme (accent colour, validity months, footer) → optionally upload a background image → a live preview updates to the chosen shape as you edit → save. Edit re-opens the same dialog; a template can be marked default (one default per holder type).
Generate + print (admin/manager). Open a template → Generate cards → the roster for that holder type loads → search + tick the people to print (or select all) → Generate resolves their card data → a preview grid renders every card → Print opens the browser print dialog with only the cards visible, at true size, for Save-as-PDF or direct printing.
Add a photo (any staff on the detail surfaces). A student's detail page and an employee's dialog show the photo with an upload/replace control; the photo then appears on generated cards (JPEG/PNG/WebP, ≤2 MB).
Design decisions¶
- Fixed layouts + swappable background, not a drag-drop designer. Matches the weak-validation posture: prove the workflow cheaply. OpenEduCat's lighter model, not Fedena's full editor.
- AI backgrounds deferred to phase 2. Launch must not ride on the image agent's health; upload/builtin cover the need now.
- Client-side rendering + browser print, no server image pipeline. Avoids Pillow/ReportLab/font server dependencies, and makes preview == print free. Mirrors how the lighter ERPs do "Save as PDF".
- Size as a preset, not a free number. Fields are tuned per canvas shape, so arbitrary dimensions would distort; presets (portrait/landscape now, more later) keep every card well-composed.
- List endpoints never sign photo URLs. Signing is per-row expensive; rosters
return
nulland only detail/generation paths sign. - Migrations chain after
erp0022_student_demographics. Photo (erp0023) and templates (erp0024) were renumbered when rebased onto staging; the demographics + photo additions toStudentProfilewere merged (both kept).
Rollout plan¶
- Deploy backend before frontend — the web app reads
photo_urland the/erp/id-cardsendpoints. - Enable
erp_id_cardsper tenant after deploy (dark-launched; the platform row is seeded disabled by migration00075). The nav item appears once the flag is on and the user is granted the module. - Validate on a seeded tenant (Sapthagiri) — create a template, upload a background, generate + print a small cohort, confirm the USN/photo/branding.
- Phase 2, if adoption warrants: AI-generated backgrounds via the chatbot
media agent; an
IdCardIssuerecord for re-issue tracking; a public QR-verify endpoint; and a fuller field designer only if demand proves out.
Changelog¶
- 2026-07-31 → 08-01: Gate passed; Phase 0 (photos) + Phase 1 (templates, generation, portrait+landscape presets, admin UI) implemented and opened as webapp PR #1468. This doc authored alongside the implementation PR.