Add email sending (service + 3 flows) and a general asset label generator
All checks were successful
CI / backend (push) Successful in 1m23s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

Email: a stdlib SMTP mailer (settings-first config, graceful no-op when
unconfigured), a test-email endpoint wired to the Email settings page,
forced first-login password change (users.mustchangepassword, migration
7d23, /change-password flow), new-user welcome mail, and on-demand
report/alert delivery (POST /api/reports/email + Email Report buttons)
with an external-cron-with-a-scoped-PAT path documented for automation.
All tests patch smtplib - no network.

Labels: a shared /print/asset-label/<type>/<id> view any asset detail
page opens - card or plain style, QR or barcode, configurable encoding.
Per-type qr_target_* templates plus label_default_style/codetype/encodes
settings on the Printing page. Measuring-tool labels default to encoding
their inspection-operation code (derived from the location name, e.g.
0615), so every tool in an area shares the area code - verified by
decoding the rendered QR. Machine labels default to the machine number;
blank-serial handled gracefully.

808 tests pass; both features verified live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 11:58:30 -04:00
parent 7d309aabeb
commit a846587f39
34 changed files with 1819 additions and 12 deletions

View File

@@ -12,6 +12,49 @@ ADR-007 and ADR-002.
### Added
- Email sending. A stdlib-only mail service (`shopdb/utils/mailer.py`;
`smtplib`/`ssl`/`email`) reads the existing `email` SMTP settings
settings-first with an `SMTP_*` env fallback, sends multipart HTML+text, and
is a graceful no-op (logs a warning, returns False) when email is disabled or
the host is unset. The SMTP password is never logged. Three flows use it:
(1) New-user welcome + forced first-login password change. Admin-created users
(POST `/api/users`) are flagged `mustchangepassword` (new `users` column,
migration `7d23_user_mustchangepassword`, default false) and sent a
best-effort welcome email with the facility name, username, temporary
password, and sign-in link; the account is created even if mail fails
(response carries a `warning`). Login returns `mustchangepassword`; the
frontend forces the user through a new `/change-password` view (POST
`/api/auth/change-password`, jwt-guarded) before the app, and changing the
password clears the flag and resets lockout counters. A self-service "Change
password" entry is also available from the user menu.
(2) Test email. POST `/api/settings/test-email` (settings.edit) sends a probe
and surfaces any SMTP error with the password scrubbed; wires up the Email /
SMTP settings page "Send Test Email" button.
(3) On-demand alert/report delivery. POST `/api/reports/email`
(reports.export) mails `{subject, columns, rows}` as an HTML table to a
supplied recipient or the site `alert_recipients`; an "Email report" button on
the Warranty and Toner report pages posts the loaded rows. There is no
scheduler: automation is an external cron hitting the endpoint with a scoped
API token (PAT). Documented in `docs/CONFIG.md`.
- Shared asset label/code generator: a single `/print/asset-label/<assettype>/<id>`
page (public, like the other `/print/*` routes) that any asset detail page
opens via a "Print Label" button (machines, computers, printers, network
devices, measuring tools). A no-print controls panel toggles the layout
(`card` badge vs `plain` code-only), the code type (QR vs CODE128 barcode),
and what the code encodes: the asset page link, asset number, serial number,
a per-type custom target template, or - for measuring tools by default - the
tool's inspection location code so every tool at one operation shares one
code (e.g. `0615`). QR codes reuse the shared logo-overlay renderer. New
`printing` settings seed and surface on the Printing & Labels settings page:
`qr_target_machine`, `qr_target_computer`, `qr_target_network_device`,
`qr_target_measuring_tool`; `label_default_style` (default `card`) and
`label_default_codetype` (default `qr`); and a per-asset-type default for what
the code encodes, `label_default_encodes_<type>` (machines default to their
machine number, measuring tools to their inspection location code, the rest to
a page link), all overridable on the label page itself. When the chosen field
has no value (e.g. serial number on an asset with none), the label states so
instead of rendering an empty code. Asset payloads now carry a derived
`locationcode` (leading token of the resolved own/inherited location name).
- Support-team contact UX: the settings Support Teams page now manages each
team's contacts in a per-team "Contacts (N)" modal (name, SSO, active, plus
Add/Edit/Delete) instead of an inline row expander, and the application