Commit graph

28 commits

Author SHA1 Message Date
bcc94024e3 Pre-deploy security/correctness fixes (port log E17)
- Remove dead kitchen->KDS internal API (api/internal.py, verify_internal_secret)
  — KDS reads kitchen_db directly (E16), nothing ever called this endpoint
- Add expires_at to dispute_attachments; public attachment links now expire
  after 30 days instead of staying valid forever (A4)
- Add services/upload_validation.py: sniff real file content via python-magic
  instead of trusting the client-supplied Content-Type header, plus a 20MB
  cap. Applied across invoices/logbook/food_flags/credit_notes/disputes
  upload endpoints (A5) — disputes previously had no file-type check at all
- Fix nginx client_max_body_size drift (800m -> the plan's intended 20m)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:44:58 +00:00
78744278f8 Add iOS install hint + Apple PWA meta tags, fix missing beforeinstallprompt handler
iOS Safari has no beforeinstallprompt API, so the portal's Install
button (?install=1) silently did nothing there. IosInstallHint now
shows Share -> Add to Home Screen steps on Safari, or a prompt to
switch to Safari first if opened in another iOS browser/in-app
webview (those can't install PWAs on iOS at all).

Also added apple-mobile-web-app-title + apple-touch-icon so the
home screen icon isn't a page screenshot, and added the
beforeinstallprompt handler that was missing entirely (Android
Install button did nothing either).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:12:12 +00:00
a3872798ca Add update-available banner + versioned /health endpoint
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 14:11:32 +00:00
fe095d0891 Fix session-expiry redirect breaking standalone PWA out of its shell
AuthGate unconditionally hard-navigated window.top to the central
/login on session expiry, even when not embedded in the portal
iframe — dropping an installed/directly-opened kitchen session into
the portal's framed browser view instead of staying in its own
window.

Now only bounces to central login when actually embedded (passing
?from= so it returns here afterwards); standalone or directly-opened
tabs get an in-app login form and never navigate away. Also wired up
the previously-dead inactivity auto-logout timer (disabled for
installed PWAs, configurable per device otherwise). The legacy
token/restrictedPages/login/logout compat shim is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 17:07:10 +00:00
62f34894e1 Add name, email and sign out button to sidebar footer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-22 11:10:12 +00:00
5022162218 Remove responsive breakpoint — sidebar always visible (desktop-forced via CSS)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 08:54:51 +00:00
1dbd7b32da Force desktop viewport (width=1280) — data-heavy display not suited to responsive layout
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-21 08:14:56 +00:00
d930808487 Remove tracked dist/ build artifacts — covered by .gitignore
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 12:07:16 +00:00
9ed700977b Fix NameError: add Query to fastapi imports in invoices.py
Query was used at lines 2121 and 2242 (token query-param auth on image preview
endpoints) but never imported, causing the backend to crash on startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-14 08:27:27 +00:00
ba075276b1 Audit pass: cookie auth migration, route guards, GP% clamp, CSV export, OCR transaction safety, N+1 fix, and cleanup
- Migrated all 435 frontend fetch calls from Authorization Bearer header to credentials: 'include' (cookie auth)
- Removed ?token= from all file/image URLs (browser history exposure)
- Added ProtectedRoute wrapper to all capability-gated routes in App.tsx
- OCR background task: added transaction boundary, improved error handling and status rollback
- DuplicateDetector: wrapped in non-fatal try/except so crashes don't abort invoice processing
- File upload: commit DB row before writing to disk to prevent orphaned files
- GP% clamped to 100% in GPReport (credit notes can inflate above 100%)
- Added CSV export to GPReport (suppliers, daily data, allowances breakdown)
- Backend file-serving endpoints: cookie auth with ?token= fallback for backward compatibility
- DATA_DIR: moved from hardcoded /app/data to os.getenv in invoices.py and recipes.py
- N+1 fix in list_recipes: batch-loads latest cost snapshot in 1 query (was N)
- Zero-yield sub-recipe: logs warning instead of silently zeroing cost contribution
- Budget spend rate input: rejects negative values
- GPReport allowances toggle: persisted to localStorage across page loads
- DB pool_size/max_overflow: configurable via DB_POOL_SIZE/DB_MAX_OVERFLOW env vars
- Fixed SyntaxWarning from \\d in invoices.py docstring

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-13 10:04:20 +00:00
6f6e16c88f Replace in-app Access Control with JWT cap enforcement
The in-app page-restriction system (admin-only toggles in Settings) was
nav-hiding only and duplicated functionality already covered by JWT caps
in the main stack auth service. All 9 pages in the restriction list were
already gated in Layout.tsx by existing caps.

Backend: add router-level requireCap() to enforce caps at the API layer:
- reports.py: Depends(require_cap("view"))
- logbook.py: Depends(require_cap("logbook"))
- search.py: Depends(require_cap("invoices"))

Frontend: remove the Access Control settings section entirely:
- Drop pageRestrictions query, restrictedPages/accessSaveMessage state,
  savePageRestrictionsMutation, isSectionAccessible helper
- Remove 'access' from SettingsSection type and sidebarItems
- Strip restrictPath from all sidebar items (no longer needed)

Access management is now fully centralised in the main stack auth service.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 23:17:22 +00:00
1564a66283 Wire Azure OCR to central settings toggle; remove redundant Users section
- settings/src/integrations/schema.js: rename azure from 'Azure AD' to
  'Azure Document Intelligence', swap fields to endpoint + api_key
- Add use_global_azure column (migration + model)
- global_settings_service: add azure to check_global_status and apply_global_overrides
- api/settings.py: expose use_global_azure in response/update; apply overrides
  in test_azure_connection before credential check
- Settings.tsx: add 'Use credentials from main stack settings' toggle for
  Azure OCR section (endpoint/key disabled when on, test button enabled when
  global is configured); remove Users section (managed centrally via auth
  service), clean up UserData interface, users query and mutations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 22:58:32 +00:00
d7898ba897 Add 'use main stack settings' toggle for third-party integration credentials
Each of Newbook, Resos, SambaPOS, SMTP, and Nextcloud now has a checkbox at the
top of its credentials block. When enabled, the app reads auth credentials from
the central stack settings service (SETTINGS_URL + STACK_INTERNAL_SECRET) and
the local auth fields are grayed out. App-specific fields (base path, GL codes,
keywords, sync intervals, etc.) remain editable regardless.

Backend: new use_global_* columns on kitchen_settings, migration, global_settings_service
with apply_global_overrides() for in-memory credential injection, GET /api/settings/global-status
endpoint, and apply_global_overrides() called in test-connection endpoints and FileArchivalService.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:57:10 +00:00
62417d59de Update pdf.worker.min.mjs to match pdfjs-dist 5.7.284
npm upgraded pdfjs-dist from 5.4.530 to 5.7.284 (^semver range) but the
manually copied worker file in public/ was left at 5.4.530, causing an
API/Worker version mismatch error at runtime. Copy matching worker from
node_modules/pdfjs-dist/build/.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:37:27 +00:00
742bc91b3b Fix PDF viewing: cookie auth fallback + .mjs MIME type
- /file and /pdf endpoints: make token optional, fall back to hnf_session
  cookie auth when no valid JWT token provided. Kitchen frontend passes the
  literal '__session__' compat shim so token-only auth always 401'd.
- Add Request injection so cookie-based fallback path can read hnf_session.
- nginx: add types block mapping .mjs → text/javascript so pdf.worker.min.mjs
  passes browser strict MIME check for ES module scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:34:51 +00:00
cd9e7dbfef Fix invoice file 500 and pdf.worker MIME error
- Remove stale import of non-existent require_cap_from_token from
  get_invoice_file endpoint — caused ImportError → 500 on every PDF load
- Fix pdf.js worker path from '/pdf.worker.min.mjs' to '/kitchen/pdf.worker.min.mjs'
  in Review.tsx and SearchDefinitions.tsx — worker was being fetched from domain
  root instead of under the /kitchen/ base, causing MIME type rejection

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:28:55 +00:00
1502ab8ed2 Wire Purchases Chart nav to existing Purchases.tsx page
The full Purchases.tsx page at /purchases (multi-week tables, date range picker,
disputes/allowances rows, 24-week comparison chart) was already ported but had
no sidebar nav entry. Adds it under Invoices → Purchases Chart and removes the
simplified single-week PurchasesCalendar placeholder that replaced it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:24:01 +00:00
12aef0adb1 Add Purchases Chart page — weekly table view of invoices by supplier
Backend endpoint /api/reports/purchases/weekly existed but had no frontend.
Adds PurchasesCalendar page under Invoices > Purchases Chart: 7-day columns,
one row per supplier, each cell lists invoice chips (number + total) linking
to the invoice detail. Daily totals row in footer, prev/next week navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 21:14:12 +00:00
0d6ca9b979 Fix internal navigation links: add /kitchen/ prefix to all bare hrefs
Bare <a href="/invoice/..."> tags bypass React Router basename, causing
hard navigations to absolute paths with no NPM route. Added /kitchen/
prefix to all internal href values across 12 components.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 20:11:41 +00:00
9cd1683572 Fix invoicestatus enum case mismatch in invoice list filter
Frontend sends lowercase status (e.g. 'confirmed'), but the DB enum values
are uppercase (CONFIRMED). Uppercase the parameter before comparison.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 20:05:27 +00:00
bc874ac9ff Fix migration transaction poisoning: ADD COLUMN IF NOT EXISTS
Migrations used try/except around ADD COLUMN inside a single engine.begin()
block. When a 'column already exists' error was caught, asyncpg left the
transaction in aborted state, causing all subsequent DDL in the block to fail
with InFailedSQLTransactionError. Replace with IF NOT EXISTS to prevent the
error entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 19:53:26 +00:00
e7e1fda9f6 Fix 413 upload limit + add /api/kds/settings to kitchen backend
- nginx client_max_body_size raised to 800m (was 20m) to allow 577MB backup
  upload; proxy_read_timeout raised to 600s for long restore operations
- Add api/kds_settings.py: GET+PATCH /api/kds/settings served by kitchen
  backend (kitchen Settings page configures KDS timers/GraphQL/course order;
  all config lives in kitchen_settings so kitchen owns these endpoints)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 17:47:28 +00:00
14903e80a6 Seed kitchens id=1 on startup to satisfy kitchen_settings FK
kitchen_settings has a FK to kitchens.id — on a fresh kitchen_db the
kitchens table is empty, so any restore attempt fails with a FK violation.
Idempotent INSERT ON CONFLICT DO NOTHING runs after create_all on every boot.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 16:38:47 +00:00
9905d0e0dd Migrate all kitchen frontend fetch calls to /kitchen/api/ prefix (B5b)
All archive components were calling fetch('/api/...') directly. Replaced
all occurrences of /api/ URLs (string literals, template literals,
window.open, src attributes) with /kitchen/api/ across 37 source files.
The central axios instance in api.ts was already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 12:56:11 +00:00
ecd63fc65f Add get_current_user_from_token to auth.py; fix missing import in 3 api files
?token= query-param endpoints (PO/invoice print previews, recipe image exports,
backup download) call get_current_user_from_token which was not ported from the
original archive auth module. Added the function and fixed the missing import in
backup.py, ingredients.py, and invoices.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 12:34:01 +00:00
907243c2cd Restore api/migrations/models/services source files accidentally removed in gitignore commit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 12:16:28 +00:00
c8fad1cf36 Add .gitignore, remove __pycache__ from tracking
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 12:16:04 +00:00
8d688b459d Initial kitchen scaffold — Phase 1 kitchen port (build-verified 2026-07-11)
FastAPI backend (Python 3.11, MSSQL ODBC for SambaPOS, Azure DI OCR),
kitchen_db on central PG. React/TS/Vite frontend with navy sidebar layout.

Backend: auth.py (APP_SLUG=kitchen, SimpleNamespace — archive routes use
.kitchen_id/.is_admin without modification), main.py (51 migrations, scheduler,
internal router for KDS bookings feed), api/internal.py, full archive API
(31 routers: invoices, recipes, menus, sambapos, resos, newbook, disputes,
purchase_orders, etc.), models, migrations, OCR pipeline.
kitchen_id pinned to 1 (B1 — single hotel).

Frontend: AuthGate (app=kitchen, token shim for archive compat — B5b pending),
Layout (navy sidebar, 6 sections, Lucide icons, teal --app-primary),
App.tsx (Outlet pattern, UploadApp outside Layout), index.css (full :root block).
strict: false — archive components have type issues; build clean.

Note: 45 archive components call fetch('/api/...') without /kitchen/ prefix
(B5b). Runtime 404s; deferred until after initial testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 12:15:39 +00:00