diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9a954a5..6996d87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,10 +75,16 @@ jobs: python-version: '3.12' cache: pip - run: pip install -r requirements.txt - - name: Force utf8mb4 on the CI database + - name: Prime the CI database (utf8mb4 + native auth for pymysql) + # MySQL 8 defaults root to caching_sha2_password, which pymysql can + # only speak with the 'cryptography' package. Rather than add that + # dependency (and its offline wheel), switch root to native auth here + # via the mysql CLI, so the app's pymysql connections work as-is. run: | mysql -h 127.0.0.1 -uroot -proot -e \ - "ALTER DATABASE shopdb_ci CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" + "ALTER DATABASE shopdb_ci CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; \ + ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; \ + FLUSH PRIVILEGES;" - name: Fresh core upgrade from empty run: flask db upgrade - name: Install every bundled plugin (runs its chain) diff --git a/docs/PLUGIN-EXTERNAL-REPO.md b/docs/PLUGIN-EXTERNAL-REPO.md index a03fcd8..99fd565 100644 --- a/docs/PLUGIN-EXTERNAL-REPO.md +++ b/docs/PLUGIN-EXTERNAL-REPO.md @@ -10,7 +10,7 @@ is required for v1 (pip distribution is deferred to v2 per ADR-003). > **Windows / VS Code:** command examples use the Linux venv path > `venv/bin/python`; on Windows use `venv\Scripts\python` and > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: -> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). +> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md). If you have not written a plugin before, start with @@ -77,6 +77,11 @@ git clone https://github.com/ge-aero/wjsf-shipping.git cd shopdb-flask ln -s ../../wjsf-shipping plugins/shipping # (use an absolute path if you prefer: ln -s "$(pwd)/../wjsf-shipping" plugins/shipping) +# +# Windows: use a directory junction instead of ln -s. In an ADMIN prompt +# (or with Developer Mode on) from the shopdb-flask dir: +# mklink /D plugins\shipping ..\..\wjsf-shipping +# The plugin loader treats a junction the same as a real directory. # 3. Set up the framework as usual. python3 -m venv venv diff --git a/docs/PLUGIN-GUIDE.md b/docs/PLUGIN-GUIDE.md index 00240e8..3a69003 100644 --- a/docs/PLUGIN-GUIDE.md +++ b/docs/PLUGIN-GUIDE.md @@ -8,7 +8,7 @@ way it does by walking the shipped code of the exemplar plugin. > **Windows / VS Code:** command examples use the Linux venv path > `venv/bin/python`; on Windows use `venv\Scripts\python` and > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: -> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). +> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md). `measuringtools` was chosen as the exemplar on purpose. It is the first plugin built after the framework matured (ADR-005 scoped it; ADR-008 changed how plugin diff --git a/docs/PLUGIN-LAB-PRINTEDPARTS.md b/docs/PLUGIN-LAB-PRINTEDPARTS.md index ef5ac30..1513cce 100644 --- a/docs/PLUGIN-LAB-PRINTEDPARTS.md +++ b/docs/PLUGIN-LAB-PRINTEDPARTS.md @@ -17,7 +17,7 @@ decision records: `docs/proposals/printedparts-plugin.md`. > **Windows / VS Code:** command examples below use the Linux venv path > `venv/bin/python`; on Windows use `venv\Scripts\python` and > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: -> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). +> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md). Know before you start @@ -262,7 +262,7 @@ def downgrade(): See it work: ``` -flask plugin install printedparts && flask plugin enable printedparts +flask plugin install printedparts; flask plugin enable printedparts mysql> SHOW TABLES LIKE 'printed%'; -- both tables mysql> SELECT * FROM alembic_version_printedparts; -- printedparts0001baseline flask plugin upgrade-all -- printedparts: ok @@ -299,7 +299,7 @@ printedparts_bp = Blueprint('printedparts', __name__) @printedparts_bp.route('/items', methods=['GET']) -@jwt_required(optional=True) # stage 6a tightens this to view-gated +@jwt_required(optional=True) # stage 16a tightens this to view-gated def list_items(): """List printed items, paginated; search + low-stock filter.""" page, per_page = get_pagination_params(request) @@ -629,10 +629,11 @@ def _external_lookup(kind, digits): f'WHERE {column} = %s', (digits,)) row = cursor.fetchone() if row: - sso = str(row['SSO']) - name = f"{(row['First_Name'] or '').strip()} " \ - f"{(row['Last_Name'] or '').strip()}".strip() - return sso, name + # pymysql may return a tuple or a dict cursor - handle both. + sso = str(row[0] if not isinstance(row, dict) else row['SSO']) + first = row[1] if not isinstance(row, dict) else row['First_Name'] + last = row[2] if not isinstance(row, dict) else row['Last_Name'] + return sso, f"{(first or '').strip()} {(last or '').strip()}".strip() except Exception: logger.exception('HR directory lookup failed for %s %s', kind, digits) finally: @@ -798,17 +799,25 @@ catch you (below). def _kiosk_find_item(itemcode): """Resolve a scanned or typed code to an active item. - Accepts the full code (WJRP0042) or bare digits from the touch keypad - - the digits in a minted code ARE the row id, so id lookup keeps working - even for labels printed under an older prefix.""" - itemcode = (itemcode or '').strip() + Matches the internal code OR the gage-lab tag exactly; bare keypad + digits match the numeric tail of EITHER identifier, and only when + exactly one active item matches (see stage 17).""" + scanned = (itemcode or '').strip().upper() item = PrintedItem.query.filter( - PrintedItem.itemcode == itemcode, + or_(PrintedItem.itemcode == scanned, + PrintedItem.gagelabtag == scanned), PrintedItem.isactive == True).first() - if not item and itemcode.isdigit(): - candidate = db.session.get(PrintedItem, int(itemcode)) - if candidate and candidate.isactive: - item = candidate + if not item and scanned.isdigit(): + wanted = int(scanned) + matches = [] + for candidate in PrintedItem.query.filter_by(isactive=True).all(): + for value in (candidate.itemcode, candidate.gagelabtag): + tail = ''.join(ch for ch in (value or '') if ch.isdigit()) + if tail and int(tail) == wanted: + matches.append(candidate) + break + if len(matches) == 1: + item = matches[0] return item @@ -920,7 +929,8 @@ function onWedgeEnter() { ``` Manual fallbacks use the TouchKeypad: badge entry is digits (an SSO), and -item entry is bare digits resolved by row id server-side - no alphanumeric +item entry is bare digits matched server-side against the numeric tail of +either the internal code or the gage-lab tag (unique match only) - no alphanumeric on-screen keyboard needed. Success screen auto-resets after a few seconds. Full component (~250 lines) at the tag. @@ -1093,7 +1103,7 @@ failure. Summaries here; complete diffs at the tags. hit the deny policy. Lesson: anything resolving PEOPLE must honor the site's directory mode (the stage-5 code above is the corrected version). - **16 - touchscreen findings** (`lab-stage-16`): the focus-steal guard and - keypad-driven manual entry (the stage-7 code above is the corrected + keypad-driven manual entry (the kiosk resolver above is the stage-17 version). - **17 - the gage-lab asset tag** (`lab-stage-17`): the field team assigns @@ -1134,11 +1144,12 @@ for a contributor: 2. **Build in stage-sized commits** exactly as this lab does - each commit a working checkpoint with its tests. Subject line: short, plain English, present tense ("printedparts stage 5: the ledger"); body says WHY. -3. **Before every push**, the same three gates CI runs: - ```bash - bash scripts/check-naming-and-style.sh - venv/bin/python -m pytest tests/ -q - cd frontend && npx vitest run && npm run build +3. **Before every push**, run the three gates CI runs (in VS Code: the + **Check: naming + tests + build** task). By hand in PowerShell: + ```powershell + venv\Scripts\python -m pytest tests/ -q + cd frontend; npx vitest run; npm run build; cd .. + bash scripts/check-naming-and-style.sh # naming - runs via Git Bash ``` 4. **Push your branch and open a Pull Request** against `main`: ```bash @@ -1179,4 +1190,4 @@ for a contributor: | List/Detail master templates | `PrintersList.vue`, `PrinterDetail.vue` | | Reports hook + CSV | `plugins/warranty/` + `shopdb/core/api/reports.py` | | Permissions declaration | `plugins/usb/plugin.py::get_permissions` | -| The finished plugin itself | branch `feat/printedparts-plugin`, tags `lab-stage-01..16` | +| The finished plugin itself | branch `feat/printedparts-plugin`, tags `lab-stage-01..17` | diff --git a/docs/PLUGIN-QUICKSTART.md b/docs/PLUGIN-QUICKSTART.md index aaec1ad..3b1e800 100644 --- a/docs/PLUGIN-QUICKSTART.md +++ b/docs/PLUGIN-QUICKSTART.md @@ -7,7 +7,7 @@ For the full hook reference, see [PLUGIN-HOOKS.md](PLUGIN-HOOKS.md). > **Windows / VS Code:** command examples below use the Linux venv path > `venv/bin/python`; on Windows use `venv\Scripts\python` and > `$env:FLASK_APP="shopdb"` (not `export`). Full Windows onboarding: -> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP). +> [DEVELOPMENT-SETUP](DEVELOPMENT-SETUP.md). For the architectural decisions behind the contract, see [docs/adr/](../docs/adr/). diff --git a/docs/PLUGINS.md b/docs/PLUGINS.md index 75d5fcd..8853e6a 100644 --- a/docs/PLUGINS.md +++ b/docs/PLUGINS.md @@ -62,7 +62,7 @@ which revisions each plugin has applied in `migrations_applied`. For sister-site plugins (per [ADR-003](adr/ADR-003-plugin-distribution.md)): -- Plugin lives in its own git repo: `gitea.proudtech.net//` +- Plugin lives in its own git repo: `//` - Adopting site clones or symlinks into their `/plugins//` - Plugin manifest declares `core_version` range matching the framework version they target - Plugin readme explains: what it tracks, who maintains it, where to file issues diff --git a/docs/adr/ADR-006-collector-contract.md b/docs/adr/ADR-006-collector-contract.md index e277f5a..37a37d4 100644 --- a/docs/adr/ADR-006-collector-contract.md +++ b/docs/adr/ADR-006-collector-contract.md @@ -145,4 +145,4 @@ Migration path: - `shopdb/plugins/base.py` (`get_collector_schema` + `apply_collector_payload` hooks) - ADR-001 (asset model the collectors target) - ADR-002 (collector schema is part of plugin contract; changes to the hook signature are major bumps) -- The PXE project (`/home/camp/projects/pxe/`) which feeds the computers collector +- The PXE project (the PXE imaging project) which feeds the computers collector