feat(app): implement Product Scan review flow, dynamic batch expiry picker, custom PDFs, PO relationships, and aligned card layouts

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-07-09 12:36:06 +07:00
1 parent 9ff4a4a922
commit 577be04308
69 files changed
+5645 -1657

No files matched your search

+6 -2
View File
@@ -47,14 +47,18 @@ cp backend/.env.example backend/.env # first-time setup; set CUDA_VISIBLE_DEVI
docker compose up --build # full stack (dev mode, hot-reloads pfm-web-app)
docker compose -f docker-compose.yml -f docker-compose.demo.yml up -d --build # production mode, rebuild before every demo
```
**Policy for Demo vs. Development:**
- **Local Development**: ALWAYS use `docker compose up` (`npm run dev`). It enables hot-reloading for rapid iteration.
- **Demos / Field Testing**: YOU MUST use the `.demo.yml` override shown above (`npm start`). The development server has a known throughput ceiling and will bottleneck if multiple devices upload simultaneously. Do not run client demonstrations using the dev server.
See `backend/CLAUDE.md` for the Next.js app commands (`npm run dev/build/lint`), the accuracy regression harness, and the uv/vLLM Python service commands.
## Architecture notes for the Flutter client
- **API base URL is resolved dynamically at startup**, not hardcoded to one value: `AppConfig.initializeApiBaseUrl()` in `lib/config/app_config.dart` tries a fixed ngrok domain first, falling back to a hardcoded LAN IP. If login/upload fails on a device, this is the first thing to check — re-run `start-dev-tunnel.ps1` if the host machine's LAN IP or ngrok tunnel has gone stale.
- **API base URL is resolved dynamically at startup**, not hardcoded to one value: `AppConfig.initializeApiBaseUrl()` in `lib/config/app_config.dart` tries a hardcoded LAN IP first, falling back to a fixed ngrok domain. If login/upload fails on a device, this is the first thing to check — re-run `start-dev-tunnel.ps1` if the host machine's LAN IP or ngrok tunnel has gone stale.
- **Upload flow**: capture → Laplacian-variance blur check → GPS tag → multipart upload → the app polls `GET /documents` every 2s (up to 2 min) until the backend's OCR pass sets `parsed=true` → operator reviews/corrects on-device → `PUT /documents/:id` saves final data → PDF receipt generated/printed locally.
- **Pending-upload queue is persisted to a Hive box** (`lib/features/documents/pending_documents_provider.dart`, `lib/core/storage/local_storage.dart`) — an OS-level app kill mid-upload no longer loses the document; on next launch the queue reloads and resumes anything left non-terminal (fixed 2026-07-08, see `plans/next-enhancements.md` §3.1).
- Auth (`lib/features/auth/`) talks to a demo-stub backend (single hardcoded admin/password, no real server-side token verification) — don't build on top of it assuming real security.
- Auth (`lib/features/auth/`) performs real token verification against the backend `api/v1/auth/login` endpoint. Do not treat it as a demo stub anymore.
## Confidentiality