Add email sending (service + 3 flows) and a general asset label generator
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:
@@ -170,6 +170,19 @@ them under `instance/branding/`.
|
||||
| `qr_target_printer` | (empty) | Custom URL template for printer QR labels. Blank = link to the printer page on this instance. Placeholders: `{printerid}`, `{assetid}`, `{assetnumber}`, `{serialnumber}`, `{ip}`, `{hostname}`. |
|
||||
| `qr_target_usb` | (empty) | Custom URL template for USB label QR codes. Blank = link to the USB device page. Placeholders: `{id}`, `{serialnumber}`, `{alias}`. |
|
||||
| `usb_label_style` | `barcode` | USB mini-label code style: `barcode` (CODE128 of the serial) or `qr` (QR code linking to the USB QR target). |
|
||||
| `qr_target_machine` | (empty) | Custom URL template for machine labels. Blank = link to the machine page. Placeholders: `{assetid}`, `{assetnumber}`, `{serialnumber}`, `{name}`, `{pluginid}`. |
|
||||
| `qr_target_computer` | (empty) | Custom URL template for computer labels. Blank = link to the computer page. Placeholders: `{assetid}`, `{assetnumber}`, `{serialnumber}`, `{name}`, `{pluginid}`. |
|
||||
| `qr_target_network_device` | (empty) | Custom URL template for network-device labels. Blank = link to the device page. Placeholders: `{assetid}`, `{assetnumber}`, `{serialnumber}`, `{name}`, `{pluginid}`. |
|
||||
| `qr_target_measuring_tool` | (empty) | Custom URL template for measuring-tool labels. Blank = link to the tool page. Placeholders: `{assetid}`, `{assetnumber}`, `{serialnumber}`, `{name}`, `{pluginid}`, `{locationcode}`, `{locationname}`. |
|
||||
| `label_default_style` | `card` | Default asset-label layout used when a label first opens: `card` (badge with image and identity) or `plain` (just the code and a caption). |
|
||||
| `label_default_codetype` | `qr` | Default asset-label code type used when a label first opens: `qr` (QR code) or `barcode` (CODE128). |
|
||||
| `label_default_encodes_machine` | `assetnumber` | What a machine label encodes by default. |
|
||||
| `label_default_encodes_computer` | `assetpage` | What a computer label encodes by default. |
|
||||
| `label_default_encodes_printer` | `assetpage` | What a printer label encodes by default. |
|
||||
| `label_default_encodes_network_device` | `assetpage` | What a network-device label encodes by default. |
|
||||
| `label_default_encodes_measuring_tool` | `location` | What a measuring-tool label encodes by default. Values across these five: `assetpage`, `assetnumber`, `serialnumber`, `location` (measuring tools only), or `custom`. Overridable on the label page. |
|
||||
|
||||
The shared asset-label generator lives at `/print/asset-label/<assettype>/<id>` (public, like the other `/print/*` pages; `assettype` is one of `machine`, `computer`, `printer`, `network_device`, `measuring_tool`, and `id` is the asset's plugin id). It can encode the asset page link, the asset number, the serial number, a custom `qr_target_<type>` template, or - for measuring tools by default - the asset's inspection location code (the leading token of the location name, e.g. `0615`). A measuring tool with no location falls back to its asset page.
|
||||
|
||||
### map
|
||||
|
||||
@@ -210,7 +223,48 @@ them under `instance/branding/`.
|
||||
| `smtp_use_tls` | `true` | Use TLS for the SMTP connection. |
|
||||
| `smtp_from_address` | (empty) | From address for outgoing email. |
|
||||
| `smtp_from_name` | `ShopDB` | From name for outgoing email. |
|
||||
| `alert_recipients` | (empty) | Default alert recipients (comma-separated). |
|
||||
| `alert_recipients` | (empty) | Default alert/report recipients (comma-separated). |
|
||||
|
||||
#### Email flows and delivery model
|
||||
|
||||
The mail service (`shopdb/utils/mailer.py`, stdlib `smtplib`/`ssl`/`email`
|
||||
only) reads the keys above settings-first via the cached settings map, with an
|
||||
environment-variable fallback (`SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`,
|
||||
`SMTP_PASSWORD`, `SMTP_USE_TLS`, `SMTP_FROM_ADDRESS`, `SMTP_FROM_NAME`,
|
||||
`SMTP_ALERT_RECIPIENTS`, `SMTP_ENABLED`) applied only when any `SMTP_*` env var
|
||||
is present. When `smtp_enabled` is false or `smtp_host` is blank, every send is
|
||||
a graceful no-op that logs a warning and returns without error, so an
|
||||
unconfigured site never crashes. The SMTP password is never logged.
|
||||
|
||||
Three flows use it:
|
||||
|
||||
- Welcome email. When an admin creates a user (POST `/api/users`), the account
|
||||
is flagged `mustchangepassword` and a best-effort welcome email is sent with
|
||||
the facility name (`facility_name`), the username, the temporary password,
|
||||
and the sign-in link (`site_base_url` + `/login`). Mail is best-effort: the
|
||||
user is created even if the send fails (the response carries a `warning`). On
|
||||
first login the API returns `mustchangepassword: true`; the frontend forces
|
||||
the user through `/change-password` (POST `/api/auth/change-password`) before
|
||||
the app. Changing the password clears the flag and resets lockout counters.
|
||||
Set `sendwelcome: false` or `mustchangepassword: false` in the create body to
|
||||
opt out.
|
||||
|
||||
- Test email. POST `/api/settings/test-email` (settings.edit) sends a probe to
|
||||
the supplied `to` (or `alert_recipients`). The Email / SMTP settings page
|
||||
"Send Test Email" button calls it and shows the result; a real SMTP error is
|
||||
surfaced with the password scrubbed out.
|
||||
|
||||
- Alerts and report delivery (on-demand). POST `/api/reports/email`
|
||||
(reports.export) takes `{subject, columns, rows, intro?, to?}` and mails the
|
||||
rows as an HTML table. Recipients default to `alert_recipients` when `to` is
|
||||
omitted, so the same endpoint serves both report delivery and alerts. Report
|
||||
pages (Warranty, Toner) carry an "Email report" button that posts the rows
|
||||
they already loaded.
|
||||
|
||||
There is NO scheduler in this app: sending is on-demand. To automate a
|
||||
recurring send (e.g. a nightly warranty digest), point an external cron job
|
||||
at `/api/reports/email` using an API token (PAT) scoped to `reports.export`.
|
||||
See `docs/IMPORT-API.md` for the token model.
|
||||
|
||||
### audit
|
||||
|
||||
|
||||
Reference in New Issue
Block a user