From a8a6baf979fbfc4c1a7d0ab01b073b1ddf4ecf3c Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 17 Jul 2026 08:30:04 -0400 Subject: [PATCH] printedparts stage 13: pick alert recipients from shopdb users Contract 0.13.0 puts the User model on the plugin surface. The settings page gains a checkbox picker over the user list; selected users receive low-stock alerts at their account email, merged and deduped with the free-text address list, inactive accounts skipped, site alert_recipients still the fallback when both are empty. --- CLAUDE.md | 2 +- docs/PLUGIN-HOOKS.md | 4 +- docs/PLUGIN-LAB-PRINTEDPARTS.md | 15 ++++++ docs/ROADMAP.md | 2 +- .../views/settings/PrintedPartsSettings.vue | 51 +++++++++++++++++-- plugins/printedparts/api/routes.py | 26 ++++++++-- plugins/printedparts/plugin.py | 3 ++ shopdb/__init__.py | 2 +- shopdb/api/__init__.py | 2 + .../test_plugins/test_printedparts_ledger.py | 40 +++++++++++++++ 10 files changed, 136 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index adc9001..3516a50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -43,7 +43,7 @@ Refactor phases 0-5 landed; phase 6 (multi-site distribution readiness) largely ### Active state - 966 tests, naming/style check green, Gitea Actions CI (backend + naming + frontend build + a migrations-mysql job that runs the real fresh upgrade on utf8mb4 MySQL 8) -- `__contract_version__` at 0.12.0 (0.12.0 adds the mailer to the plugin surface) (product `__version__` 0.7.0, tags v0.5.0/v0.6.0/v0.7.0 - distinct series, ADR-007) +- `__contract_version__` at 0.13.0 (0.12.0 added the mailer, 0.13.0 the User model, to the plugin surface) (product `__version__` 0.7.0, tags v0.5.0/v0.6.0/v0.7.0 - distinct series, ADR-007) - 12 bundled plugins all satisfy contract: computers, employees, geenforce, knowledgebase, machines, measuringtools, network, notifications, printers, slides, usb, warranty - Core Alembic chain: baseline `68b3947ae14f` -> head `7d25_drop_redundant_indexes` (32 core migrations). Each plugin owns its own chain (ADR-008); deploy runs `flask db upgrade` then `flask plugin upgrade-all`. Reproducible + idempotent from empty (env.py relaxes session sql_mode so the chain runs on strict MySQL 8). - Legacy import: `docs/IMPORT-API.md` is the schema-agnostic import contract; `docs/IMPORT-ADOPTION.md` + `docs/PILOT-DEPLOY.md` cover adopting a site; `scripts/site_imports/wjf/` is the West Jefferson reference loader (all 15 stages, validated end-to-end including on a Windows + MySQL 8 VM). diff --git a/docs/PLUGIN-HOOKS.md b/docs/PLUGIN-HOOKS.md index 4c32eff..05f47d8 100644 --- a/docs/PLUGIN-HOOKS.md +++ b/docs/PLUGIN-HOOKS.md @@ -9,7 +9,7 @@ The contract is locked in [ADR-001](../docs/adr/ADR-001-asset-as-platform-contra The framework declares its contract version in `shopdb/__init__.py`: ```python -__contract_version__ = '0.12.0' +__contract_version__ = '0.13.0' ``` Each plugin's `manifest.json` declares the range of contract versions it supports: @@ -479,6 +479,8 @@ What `shopdb.api` exposes: - Import mode: `apply_import_timestamps`, `import_mode_active`, `parse_import_datetime` - Legacy employee directory: `employee_connection` +- `User` (0.13.0) - the account model, e.g. resolving alert recipients' + emails from selected user ids - Mailer (0.12.0): `send_email(to, subject, html, text=None)` and `send_alert(subject, html, text=None)` - settings-first, no-op safe when email is unconfigured; send_alert targets the site's alert_recipients diff --git a/docs/PLUGIN-LAB-PRINTEDPARTS.md b/docs/PLUGIN-LAB-PRINTEDPARTS.md index 44116ba..75b53a9 100644 --- a/docs/PLUGIN-LAB-PRINTEDPARTS.md +++ b/docs/PLUGIN-LAB-PRINTEDPARTS.md @@ -320,6 +320,21 @@ it. The page is ordinary: 3. `get_settings_cards` on the plugin pointing at `/settings/printedparts` - the card appears in the rail's catalog while the plugin is enabled. +## Stage 13 (extension) - alert recipients picked from shopdb users + +Free-text emails rot; user accounts do not. Another contract addition: +`User` joins the surface (0.13.0 - export, PLUGIN-HOOKS, version bump, the +docs-drift guard again). +1. Setting `printedparts_alert_userids` (comma-separated user ids), seeded + beside the others. +2. `_alert_recipients()`: resolve each selected id to an ACTIVE user's + account email, merge with the free-text list, dedupe order-preserving; + empty result still falls back to the site alert_recipients. +3. Settings page: checkbox picker over `usersApi.list()` (the page is + admin-only, matching the endpoint), saving joined ids. +4. Test: active user's email + free-text merge deduped, inactive user + skipped (`test_alert_recipients_merge_users_and_freetext`). + --- ## Where each pattern lives (cheat sheet) diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index f460428..6e83d00 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # Roadmap -shopdb-flask is at `__contract_version__ = '0.12.0'` (pre-1.0; product `__version__ 0.7.0`, tags through v0.7.0). This document captures what stands between today and a stable `1.0.0` release. Maintained as scope evolves; supersedes nothing in the ADRs. +shopdb-flask is at `__contract_version__ = '0.13.0'` (pre-1.0; product `__version__ 0.7.0`, tags through v0.7.0). This document captures what stands between today and a stable `1.0.0` release. Maintained as scope evolves; supersedes nothing in the ADRs. ## Phase status diff --git a/frontend/src/views/settings/PrintedPartsSettings.vue b/frontend/src/views/settings/PrintedPartsSettings.vue index 605d40a..d06096e 100644 --- a/frontend/src/views/settings/PrintedPartsSettings.vue +++ b/frontend/src/views/settings/PrintedPartsSettings.vue @@ -34,7 +34,23 @@
- + +
+ +

No users loaded

+
+

+ Selected users receive low-stock alerts at their account email. +

+
+ +
+

@@ -53,21 +69,25 @@