Field-team HR PWA on top of Frappe HRMS. Vue 3 SPA that ships as a web PWA + a Capacitor Android APK from a single codebase. Offline-first with a hardened sync engine.
fatehhr/— Frappe v15 app (Python)frontend/— Vue 3 SPA (Vite + Pinia + vue-i18n + idb)android-capacitor/— Capacitor wrapper (native Android build)customers/— per-customer env files (.env.demo, …)scripts/— version bump, build-customer, keystore, asset gendocs/— specs, plans, lessons learned, agent handoff
✅ All 5 phases shipped. Latest APK: fatehhr-demo-1.0.14.apk (versionCode 15).
- Foundation, Offline engine, Attendance/Leave/Expense, Tasks/Payslip/Announcements/Profile, Capacitor APK — all done.
- 56/56 E2E gates passing.
- PWA live at https://hr-demo.enfonoerp.com/fatehhr
- APK on
frontend-devGitHub release
Read docs/AGENT_HANDOFF.md first. It has the full deploy
pipeline, server creds, demo data, APK signing, and the gotcha list. Then skim
docs/LESSONS_LEARNED.md for the running diary of
"we tried this, it broke, here's why."
✅ Backend
fatehhrFrappe app installed on thehr_demosite at AQRAR (https://hr-demo.enfonoerp.com) alongsideerpnext+hrms.- Custom fields applied (Employee PIN hash + api_secret, Employee Checkin GPS + task + selfie + geofence status, Task geofence coords + radius).
- Whitelisted API endpoints:
fatehhr.api.auth.login— email+password → stable api_key/secretfatehhr.api.auth.set_pin/verify_pin/change_pin(w/ lockout)fatehhr.api.me.profile/update_profile(allowlisted fields)fatehhr.api.util.version_compat/reverse_geocode
ensure_capacitor_cors()runs on everybench migrateand writes the Capacitor origins tosite_config.json.
✅ Frontend
- Vite + Vue 3 + Pinia + vue-router + vue-i18n scaffold builds clean.
- Theme plugin reads
CUSTOMER_*env vars → CSS accent vars +manifest.json+theme-colormeta. - Login / PIN / Dashboard views with EN + AR (RTL) locales.
- Session store with api_secret SecureStorage + offline PIN hash cache
- 15-minute inactivity re-prompt.
- 401 silent-retry in the API client.
- Version-compat probe on boot.
✅ Phase 2 — Offline engine + Check-in
idbbootstrap with 4 stores (queue, photos, cache, meta).offline/queue.ts—saveItem(kind, logicalKey, payload, effectiveImages)with(kind, logicalKey)dedup; insertion-order preservation.offline/drain.ts— drain engine; narrow-regexisUnrecoverable; task-timerstart→stopprerequisite gating.offline/photos.ts— the ONE uploader (skill §4.3): memoisesserverUrlafter first upload so drain-time retries never re-upload.offline/orphans.ts— marks queue rows whose photo blobs are missing; never deletes user work.SyncBar(4 states) +PhotoSlot(auto-clears when blob missing)BottomNav+ListRow+MapPreview(Leaflet/OSM).
fatehhr.api.checkin.create+list_mineendpoints: verified end-to-end with geofence classification.- Check-in view with GPS + reverse-geocode + geofence chip +
selfie-mode-aware
PhotoSlot+ online→queue fallback. - Check-in history view.
Phase 3 (Attendance calendar + Leave + Expense), Phase 4 (Tasks timer
- Payslip + Announcements + Notifications + Profile + full Dashboard),
Phase 5 (Capacitor wrap + APK + per-customer build + rollout).
Plans for each at
docs/superpowers/plans/.
-
Clone and install deps:
git clone https://github.com/EnfonoTech/fatehhr.git cd fatehhr/frontend pnpm install -
Copy env template and run dev server:
cp .env.example .env pnpm dev
Vite proxies
/api,/assets,/files→https://hr-demo.enfonoerp.com. The public domain has real SSL, so no Host-header gymnastics needed.
- Admin:
Administrator/admin@123— no linked Employee, so first login will show a bare dashboard. Fine for smoke-testing the auth flow but can't exercise the profile endpoint. - Demo employee:
demo@fatehhr.test/demo@123with a linkedDemo TesterEmployee record. Use this to exercise PIN set + profile display. (Seeded automatically — see Task 14 of the Phase 1 plan.)
- Log in with
demo@fatehhr.test/demo@123→ redirected to PIN setup (4–6 digits). Pick1234. - Dashboard loads with "Good <morning/afternoon/evening>, Demo Tester".
- Reload page → redirected to PIN entry → enter
1234→ back to dashboard. - Wrong PIN 5× → "PIN locked" message → redirected to login.
- Toggle to Arabic via the link on login → labels flip +
dir="rtl". - DevTools → Application → Manifest →
manifest.jsonpresent with correct brand + theme color.
Manually navigate to http://localhost:5173/fatehhr/checkin (BottomNav → Attendance tab) after logging in.
- Online check-in: browser asks for GPS; map preview loads centered on your coords; "Check In" writes to the server. Verify via Desk: http://94.136.186.151/app/employee-checkin (open with Host header tool or just log in as Administrator) — row exists with timestamp + GPS coords.
- Geofence chip: amber "Outside task radius" is the default when no task is picked (task-picker UI lands in Phase 4; for Phase 2 you can test the endpoint directly — see curl examples below).
- Offline check-in: DevTools → Network → Throttle = Offline; tap "Check In" → SyncBar flips to "1 change pending" → set online → SyncBar flips to "Syncing" → "Synced now". Verify row appears on the server.
- Selfie mode:
CUSTOMER_SELFIE_MODE=everyin.env→ PhotoSlot required before Check In submits.
# Login to get api_key:api_secret
curl -X POST https://hr-demo.enfonoerp.com/api/method/fatehhr.api.auth.login \
-H "Content-Type: application/json" \
-d '{"usr":"demo@fatehhr.test","pwd":"demo@123"}'
# With the returned token:
AUTH="token <api_key>:<api_secret>"
# Profile
curl -H "Authorization: $AUTH" \
https://hr-demo.enfonoerp.com/api/method/fatehhr.api.me.profile
# List my check-ins
curl -X POST -H "Authorization: $AUTH" \
-H "Content-Type: application/json" -d '{}' \
https://hr-demo.enfonoerp.com/api/method/fatehhr.api.checkin.list_mine
# Check in with GPS
curl -X POST -H "Authorization: $AUTH" \
-H "Content-Type: application/json" \
-d '{"log_type":"IN","latitude":24.7136,"longitude":46.6753,"task":null,"timestamp":null}' \
https://hr-demo.enfonoerp.com/api/method/fatehhr.api.checkin.create- Server: AQRAR (ID
3beb2d91-86d1-4d2d-ba0b-30955992455c) at185.193.19.184 - Site:
hr_demo - Public domain: https://hr-demo.enfonoerp.com
- Admin Desk: https://hr-demo.enfonoerp.com/app
- Bench path:
/home/v15/frappe-bench(userv15) - Deploys from:
developbranch of this repo - Control server:
207.180.209.80(Server Manager API on port 3847)
All bench ops go through the Server Manager API — see the
enfono-servers skill.
All architectural rules come from the frappe-vue-pwa skill and are
summarised in docs/LESSONS_LEARNED.md.
MIT