49 Commits

Author SHA1 Message Date
Leo ff270697d5 fix: GPS only from real check-ins — never from Cantaloupe import
GPS coordinates from Cantaloupe are address-geocoded estimates, not real
field data. Both UPDATE and INSERT paths now set latitude/longitude to
None — GPS comes exclusively from tech check-ins via the main app.
2026-06-02 22:56:09 -04:00
Leo 25aec1490a feat: Asset Cleanup tool with bulk edit + name generator
Adds a new Cleanup page in the admin Dashboard for reviewing
and sanitizing asset records.

Features:
- Filter by Company, Place/City, or Make with live counts
- Inline edit for make, model, building, floor on each asset
- Bulk edit bar: set any field for ALL matching assets at once
- Auto-generates names in '<make> <model> @ <company> - <Building> <Floor>' format
- Paginated results (50/page) with prev/next navigation
- Backend: POST /api/assets/batch-update for efficient batch updates
- Backend: company/place/make filter params on GET /api/assets
2026-06-02 22:07:37 -04:00
Leo 4ab08ca7bd fix: admin server must use shared assets.db — stale local copy caused 'asset not found'
Root cause: Admin server's DB_PATH defaulted to its own assets.db (local copy
with 7,560 assets) instead of the main app's DB (10,148 assets). Machine ID
68026 didn't exist in the stale copy.

Fix:
- Copied main app's assets.db over (now synced)
- Set CANTEEN_DB_PATH in run.sh to default to ../canteen-asset-tracker/assets.db
  (env var can still override for custom setups)
2026-06-02 19:01:38 -04:00
Leo 92e5170b47 feat: add Clear GPS card to Dashboard page 2026-06-02 18:54:19 -04:00
Leo fb98de229d feat(admin): rewire launch-app to check-for-updates UI nav + frontend polling 2026-06-02 18:20:04 -04:00
Leo 7a2d283379 feat: Add Remember Me to admin login
- Added Remember Me checkbox to login form with 30-day session expiry
- Sessions default to 1 day; when remember_me=true, expire in 30 days
- User info stored in localStorage (persistent) vs sessionStorage (per-tab)
- initAuth() checks localStorage first, then sessionStorage for user info
2026-06-01 13:10:26 -04:00
Leo ed5e3ff9ec Add work order details display and asset table WO column
- Backend: new /api/admin/workorders/by-asset endpoint for
  work orders linked to a specific asset name
- Backend: enhanced WO search to also match by
  msdyn_customerasset!name and msdyn_serviceaccount!name
- Frontend: added 🔧 column to asset table linking to
  work orders tab pre-filtered by asset name
- Frontend: added showAssetWorkOrders() function
- Frontend: made asset name in WO detail modal clickable
  (navigates to asset search)

Closes #21
2026-05-31 23:54:17 -04:00
Leo 7024b5f820 Merge coffee-category: Markets replaces Kiosk 2026-05-31 01:09:59 -04:00
Leo 4988fdaa21 Markets category replaces Kiosk in seed data
- Updated seed categories: Kiosk → Markets
2026-05-31 01:09:44 -04:00
Leo ae6685a401 feat: add Coffee () category to seed data 2026-05-31 00:52:31 -04:00
Leo fb2db1e76a Add Kiosk category to seed data, clean .gitignore
- Kiosk (🏪) added to fresh-install seed data in admin_server.py
- .gitignore now excludes .venv/, *.db, *.xlsx, admin.db
2026-05-31 00:40:22 -04:00
shawn d58834bc56 feat: category icons sync + unknown type dashboard widget
- Synced categories table: added Equipment/Appliances with icons,
  removed unused Furniture/Utensils & Serveware
- Updated seed data to match real asset categories
- Added category_health query to /api/stats (finds asset categories
  not in the categories lookup table)
- Added Category Health card to dashboard UI (red warning, shown
  only when unmapped categories exist)
2026-05-31 00:28:07 -04:00
Leo a3328c765d ux: activity logging for all CRUD ops, batch status management, dashboard now shows recent activity 2026-05-30 21:20:49 -04:00
Leo 2cafe6879a feat: add Machine Replacements history view
- New /api/admin/replacements/history endpoint detects replacements via:
  - Sync batch diff_summary inline replacements + crossref (new vs removed)
  - Location-group detection (same building_name, active+inactive pair)
- New Replacements page in admin SPA with:
  - Table showing old MID → new MID, location, statuses, confidence
  - Search/filter by location or MID
  - Clickable MID links navigating to asset detail
- 146 historical replacement events detected from location data
2026-05-27 20:26:48 -04:00
Leo 6a37ea9f77 ux: 5.0 heuristic scorecard — dashboard skeleton, inline edit visual, help modal, keyboard shortcuts, guided empty states 2026-05-27 18:57:29 -04:00
Leo 6bf8a3e970 ux: format activity feed JSON, remove default creds hint, add login loading state 2026-05-27 18:35:52 -04:00
Leo 510bafc919 feat: multi-role support — comma-separated roles with checkbox UI
- Added validate_roles() and roles_contain() helpers for comma-separated roles
- Updated create_user/update_user to accept role combinations (e.g. 'admin,technician')
- Changed all exact role queries (='technician', IN('technician','admin')) to LIKE
- Replaced single <select> with checkboxes in add/edit user modals
- Added renderRoleBadges() to display multiple role badges per user
- Added checkbox-group/checkbox-label CSS styles
- Updated tech-photo-upload technician query to use LIKE
- Updated db reset auth check to use roles_contain()
- Set shawn's role to 'admin,technician'
- Cleaned up unused imports (hashlib, io, etc.)

Applies to canteen-admin-server, canteen-admin-server-dev, and tech-photo-upload
2026-05-25 23:11:00 -04:00
shawn 750cde68c9 feat: multi-role support — same as prod 2026-05-25 23:10:53 -04:00
shawn 605353c533 Add AGENTS.md for AI agent context 2026-05-25 20:18:57 -04:00
Leo e7cdac7c44 fix: remove duplicate brace causing JS parse failure on admin page 2026-05-23 18:49:44 -04:00
Leo afc38438b0 fix: remove old raw detail tables from pending review card — use per-field approval UI only 2026-05-23 18:46:59 -04:00
Leo 8fc6a93ff2 fix: per-field review UI polish — blank-fill auto-select, btn-accent CSS, card-bg fixes, sticky bar z-index 2026-05-23 18:35:27 -04:00
Leo c35e2a1af9 feat: per-field approval backend + geotagger-style frontend
Backend (cantaloupe_sync.py):
- GET /api/admin/cantaloupe/batches/{id}/field-changes — flat list of per-field diffs
- POST /api/admin/cantaloupe/batches/{id}/apply-field-changes — apply selected changes only
- PerFieldApprovalRequest/FieldChangeItem Pydantic models
- Activity log entries for apply-field-changes

Frontend (static/index.html):
- toggleFieldReview() — loads field changes via API, shows/hides review area
- renderFieldChanges() — geotagger-style cards with checkboxes per field, grouped by machine
- applyFieldChanges() — POST selected changes with confirmation modal
- Auto-select blank-fill changes (is_blank_fill flag)
- Sticky apply bar at bottom with live selected count
- Toggle all, toggle per-asset, toggle per-field helpers
- Review Changes button in pending review card
2026-05-23 18:24:39 -04:00
Leo ea1feaaad1 feat: replacement candidate review UI + approve/reject workflow
- GET /api/admin/cantaloupe/batches/{id}/replacements returns candidates
- POST /api/admin/cantaloupe/batches/{id}/replacements resolves (approve/reject)
- Approve retires old asset (status=retired), sets install_date on new
- Reject records decision without modifying assets
- Batch approve blocks if unresolved replacements exist (use ?force=true)
- Frontend shows replacement cards with confidence badges in batch detail
- Per-replacement ✓ Approve / ✗ Reject buttons + ✓ Approve All bulk action
- History view shows replacement counts with unresolved warnings
2026-05-23 17:55:54 -04:00
Leo 78f4b74675 feat: detect machine replacements in cantaloupe sync
Add detect_replacements() function that identifies when a new Asset ID
appears at the same customer + address as an existing asset not in the
current import. Uses two confidence levels:
- high: exact address match
- medium: same building_name or same street name

Stored in diff_summary under 'replacements' key for admin review UI.
2026-05-23 17:42:55 -04:00
Leo cd53e612ef Add dex_report_date, deployed, pulled_date to ASSET_COLUMNS reference list 2026-05-23 17:34:14 -04:00
Leo f8fad7ac14 feat(cantaloupe_sync): add dex_report_date, deployed, pulled_date column handling
- Add heuristic entries: dex_report_date, deployed, pulled_date
- Include in COMPARE_FIELDS for diff detection
- Add to approve_batch() UPDATE field_map and INSERT statement
- Add v4 DB migration for three new TEXT columns
- Datetime serialization already handled by parse_excel()

Kanban: t_3bef3c9c
2026-05-23 17:30:09 -04:00
Leo 4070214fe7 v5 migration: add dex_report_date and install_date columns to assets table 2026-05-23 17:26:13 -04:00
Leo 8d5deb735b Fix route ordering: /search before /{asset_id}
- /api/assets/search must be registered before /api/assets/{asset_id}
  to prevent FastAPI path param from catching 'search' as an asset_id.
  Returning a 422 with array detail caused frontend to show [object Object].
- Improve error message extraction in clearAssetGps catch block.
2026-05-22 23:45:26 -04:00
Leo 9794d8076c Add clear asset GPS endpoint + UI
- DELETE /api/assets/{id}/gps: clears lat/lng to NULL
- GET /api/assets/{id}: asset lookup by ID
- GET /api/assets/search?machine_id=: asset lookup by machine ID
- Export & Admin page: Clear Asset GPS card with lookup + confirmation
- Tests: 204 success + 404 not found
2026-05-22 23:36:40 -04:00
Leo ae20c0507f fix: dynamic VALID_CATEGORIES from DB categories table
Same issue as canteen-asset-tracker — hardcoded category set was
{Furniture, Appliances, Utensils, Equipment, Other}, missing all
vending-machine categories (Bev, Snack, GF Bev, GF Food, etc.).

- admin_server.py: _load_categories() from DB at startup
- cantaloupe_sync.py: reads from categories table instead of
  hardcoded set (was silently downgrading GF Bev→Other on sync!)
2026-05-22 19:46:36 -04:00
Leo a1dfe3665a feat: add is_disney flag to asset sync - sets is_disney=1 for D- customers
INSERT and UPDATE now set is_disney based on whether the customer name starts with 'D-'.
Admin DB also backfilled with the same logic.
2026-05-22 18:34:17 -04:00
Leo cd1f04a519 fix(admin): sidebar hamburger menu for mobile, remove auto Cantaloupe sync
- Replace broken icon-only sidebar collapse with off-canvas hamburger menu
  that slides in from left (hidden by default on <768px)
- Add sidebar overlay backdrop, auto-close on nav/logout
- Rename 'Cantaloupe Sync' to 'Import' in sidebar and 'Excel Import' in page
- Remove 'Sync Now' button and backend subprocess auto-export entirely
- Keep manual Excel file upload as the only import path
- Update empty-state text and upload handler to work without Sync Now btn
2026-05-22 16:08:07 -04:00
Leo 301c3d5fb1 Add missing /api/auth/me endpoint to admin server
The /api/auth/me endpoint was never added after the admin server was
split from the main server. Without it, auth tokens couldn't be validated
against the admin server — the SPA catch-all would serve the frontend instead.
2026-05-22 15:17:05 -04:00
Leo e19d061844 Add CRUD tests for admin server endpoints moved from main
Covers: customers (create/read/update/delete/list), locations (crud + latlng),
rooms (create/list/filter), geofences (crud), settings categories (create/list/delete).
All 23 tests pass against the admin server. Uses UUID-based names to avoid
shared-DB collisions with the production assets.db.
2026-05-22 15:15:03 -04:00
Leo 6056f3078d Fix admin seed: use INSERT OR IGNORE so admin/changeme user is always ensured, not just on empty DB 2026-05-22 15:08:44 -04:00
Leo cb71bcaaef Fix: move SPA catch-all route to end so API routes match first
The catch-all @app.get('/{full_path:path}') was defined before API routes,
causing GET requests for /api/* endpoints to be intercepted and served
the admin SPA index.html instead of the actual API response. Moving
the catch-all to after all API routes ensures proper route resolution.
2026-05-22 15:02:00 -04:00
Leo 90a426bc9e Fix Cantaloupe Sync: use sys.executable + add Excel file upload button
- Changed subprocess from 'python' to 'sys.executable' (fixes FileNotFoundError
  in systemd — venv python not in system PATH)
- Added '📤 Upload Excel' button to admin frontend sync page
- uploadSyncFile() sends file to the same /api/admin/cantaloupe/sync endpoint
  (which already supported file upload — frontend just didn't expose it)
- Users can now use 'Sync Now' for auto-download or 'Upload Excel' for manual
2026-05-21 23:10:22 -04:00
Leo 08c77669b2 Session expiry: tokens expire after 1 day (auth consistency) 2026-05-21 22:53:25 -04:00
Leo b91ed6bc7b Fix: add responsive breakpoints for mobile (480px + 768px) — sidebar, stats grid, Cantaloupe Sync page, form rows 2026-05-21 20:05:52 -04:00
Leo 20002f4fcb fix: update login password hint from Brett85!@ to changeme 2026-05-21 19:47:03 -04:00
Leo 070e8c2ddb Add Cantaloupe Sync admin UI page
- New 'Cantaloupe Sync' nav item in sidebar with sync icon
- Section 1: Sync Overview showing latest batch status, diff badges
- Section 2: Pending Batch Review with approve/reject buttons and diff detail
- Section 3: Import History with expandable rows for full diff view
- Added static file serving (FileResponse) to admin_server.py
- Fixed formatDate to handle space-separated SQLite datetimes
- Dark theme matching existing admin UI, vanilla JS with fetch()
2026-05-21 19:38:36 -04:00
Leo e9d18cb8b7 fix: cantaloupe export subprocess cwd points to cantaloupe-downloader project
The subprocess.run for cantaloupe export was using cwd=admin_server_dir,
which meant 'python -m cantaloupe' couldn't find the local cantaloupe module.
Changed cwd to ~/projects/cantaloupe-downloader where the module lives.

Also added scripts/cantaloupe-sync.sh — the 6h cron job wrapper script.
2026-05-21 19:32:44 -04:00
Leo f7ffa0e6cc cantaloupe_sync: fix column mapping heuristic, add comprehensive tests
- Fix greedy matching where 'name' keyword matched 'Location Name' header
- Use word-subset matching instead of substring matching
- Normalize keywords (replace _ and # with spaces) to match header normalization
- Reorder heuristics: multi-word specific patterns before single-word generics
- Add 10 unit tests for mapping, diff, and Excel parsing
- Add 23 integration tests for all sync API endpoints
- All 33 tests pass
2026-05-21 19:16:53 -04:00
Leo d2c76aea7f feat: Add Cantaloupe sync API with staged import workflow
- New cantaloupe_sync.py module with complete pipeline
- Heuristic Excel column mapping (20+ keyword patterns)
- Diff engine: detects new/changed/removed/unchanged assets
- 5 API endpoints: sync, list batches, view batch, approve, reject
- cantaloupe_sync_batches table with status workflow
- Admin server port changed to 8090
- openpyxl added to requirements.txt
2026-05-21 19:00:19 -04:00
Leo 377b3868cb fix: move admin server to port 8090 (avoids webhook receiver on 8900), set CANTEEN_DB_PATH to shared assets.db 2026-05-21 18:56:10 -04:00
Leo 19bdbf0ec3 t_6549bb2b: Align SPA with admin server API (reset endpoint, initAuth, remove SQL query) 2026-05-21 17:19:39 -04:00
Leo d1c7853877 Initial commit: Admin API server extracted from canteen-asset-tracker 2026-05-21 17:19:14 -04:00
shawn c4e45c2569 t_6549bb2b: Create admin frontend SPA with full CRUD, dashboard, activity, export 2026-05-21 17:18:36 -04:00