1. Apa itu THR
THR (Todak HRMS) = Production-grade Human Resource Management System untuk Todak Holdings group. Platform multi-tenant, multi-country, multi-currency yang serve 24 organizations across Malaysia + Indonesia.
612Employee records
245Auth users
24Organizations
~100Public tables
255Functions / RPCs
26Database views
10Storage buckets
60+DB triggers
~148KLines of code
Tujuan
- Self-service HR: employee check in/out, apply leave, submit claims, tengok payslip, update profile sendiri
- Manager workflows: approve leave/claims, tengok team reports, manage org assignments (sekarang underused โ tengok ยง11)
- HR admin: payroll batch processing, EA forms, statutory compliance (EPF/SOCSO/PCB MY + BPJS/PPh21 ID), setup leave entitlement
- System admin: users, roles, organizations, audit trail, system configuration
- Comms: internal messaging, memos dengan read receipts, notifications (in-app + email + WhatsApp)
- Documents: employee photos, contracts, certificates, payslips, company policies
Production URLs
| Env | URL | Branch |
| Production | https://thr.todak.io | main |
| Staging | https://staging-thr.netlify.app | staging |
Kedua-dua share Supabase database yang sama โ cuma frontend yang berbeza.
Live Activity Snapshot (2026-04-21)
15Users logged in hari ni
58Attendance check-ins hari ni
2Active dalam sejam lepas
40Active 7 hari lepas
2. Tech Stack
Frontend
| Layer | Library | Version |
| Framework | React | 18.3.1 |
| Build tool | Vite | 4.5.3 |
| UI library | Material-UI (MUI) | 5.16.4 |
| UI extras | @mui/lab, @mui/x-date-pickers | 7 / 6.20 |
| Routing | React Router DOM | 6.24.1 |
| State | React Context API (takde Redux/Zustand) | โ |
| Styling | Emotion (CSS-in-JS) | 11.11 |
| Date | date-fns | 2.30.0 |
| i18n | i18next + react-i18next | 25.7 / 16.5 |
| Drag & Drop | @dnd-kit + @hello-pangea/dnd | 6.3 / 16.6 |
| Resizable | react-resizable + react-grid-layout | 3.0 / 1.5 |
Data & Backend
| Layer | Service |
| Database | Supabase (PostgreSQL 15) โ project ftbtsxlujsnobujwekwx |
| Region | ap-southeast-1 (Singapore) |
| Auth | Supabase Auth + Google OAuth |
| Storage | Supabase Storage (10 buckets, 3,125 objects) |
| Realtime | Supabase Realtime (messaging, presence) |
| Serverless 1 | Netlify Functions (Node.js) โ 7 functions |
| Serverless 2 | Supabase Edge Functions (TypeScript/Deno) โ 8 functions |
| Hosting | Netlify (CDN, branch deploys) |
| Cron | pg_cron (2 active jobs) |
Visualization & Reports
Chart.js 4.5
Recharts 3.5
React Big Calendar
React Org Chart
React Grid Layout
jsPDF + autoTable
pdf-lib (EA Form)
html2canvas
xlsx (Excel export)
AI Layer
- OpenAI GPT-4 (openai 6.9.1) โ claims form auto-fill
- Sofia AI Assistant โ internal chatbot framework (dah provisioned, belum rolled out)
- Multi-AI service โ abstraction layer untuk switch antara providers (multiAIService.js)
- Edge Functions: ai-intent (intent classification), sofia-multi-ai, ai-generate-view, process-document-ai
Testing (coverage minimal)
- Jest 29.7 + jsdom + React Testing Library
- Puppeteer 24.15 (capability E2E ada, tapi mostly tak guna)
- Hanya ada sikit test files: Login.test.jsx, LeaveManagementRefactor.test.jsx, staging-smoke-test.js
3. Architecture
USER BROWSER (PC / Mobile)
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ NETLIFY (CDN, Static Hosting) โ
โ โข Frontend (React/Vite/MUI) โ
โ โข thr.todak.io ยท staging-thr... โ
โ โข Netlify Functions (Node.js) โ
โ - attendance-check-in/out/qr โ
โ - attendance-status/team โ
โ - ai-chat / sofia-ai โ
โโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ HTTPS / WSS
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ SUPABASE (ftbtsxlujsnobujwekwx) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ PostgreSQL 15 โ โ
โ โ โข ~100 tables (thr_*) โ โ
โ โ โข 26 views โ โ
โ โ โข 255 functions/RPCs โ โ
โ โ โข 60+ triggers โ โ
โ โ โข pg_cron (2 jobs) โ โ
โ โ โข pgvector 0.8 (reserved) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ Auth (Google OAuth + Email) โ โ
โ โ Storage (10 buckets, 3K obj) โ โ
โ โ Edge Functions (Deno): โ โ
โ โ - send-email โ โ
โ โ - process-document-ai โ โ
โ โ - sofia-multi-ai / ai-intent โ โ
โ โ - ai-generate-view โ โ
โ โ - cleanup-attendance-selfies โ โ
โ โ - auto-checkout โ โ
โ โ - leave-year-end โ โ
โ โ Realtime (messaging, presence) โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โฒ
โ
โโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโ
โ EXTERNAL INTEGRATIONS โ
โ โข OpenAI API (GPT-4) โ
โ โข Google OAuth โ
โ โข Email SMTP (noreply@dm.todak.io) โ
โ โข WhatsApp via OpenClaw RPCs โ
โ (separate VPS 178.104.201.151) โ
โ โข IP geolocation (3 fallbacks) โ
โ โข ntfy push (provisioned) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Data Flow Pattern
React Component
โ
โผ
useEmployeeService hook / direct supabase.from()
โ
โผ
Service Layer (/shared/services/) โ business logic, org scoping
โ
โผ
Supabase JS client โ API key + JWT
โ
โผ
Postgres REST (PostgREST)
โ
โผ
RLS check (if enabled) โ SQL execution โ trigger fires โ audit row
โ
โผ
JSON response
โ
โผ
React state update โ re-render
4. Repository Layout
| Path | Purpose |
/src/main.jsx | Entry point, i18n init, mount React, hide skeleton |
/src/App.jsx | Router setup (~30 routes), lazy-loaded, error boundaries |
/src/core/lib/supabase.js | Supabase client config, OAuth callback URL |
/src/core/contexts/ | AuthContext, AccessLevelProvider, DashboardProvider, UserTimezoneProvider |
/src/modules/ | Feature modules: admin, atlas, attendance, auth, claims, documents, employees, leave, messaging, notifications, payroll |
/src/shared/ | Shared components, hooks, utils, services (37+ files) |
/src/shared/services/ | Business logic services (tengok ยง17) |
/src/shared/hooks/ | useAuth, useAuthSync, useInactivityTimeout, useEmployeeService, dll. |
/src/shared/utils/accessLevelUtils.js | Role definitions, permission checks |
/src/locales/{en,id}/ | i18n translation JSONs (14 namespaces setiap satu) |
/netlify/functions/ | Serverless Node.js functions (7 files) |
/supabase/functions/ | Edge Functions Deno/TypeScript (8 functions) |
/supabase/migrations/ | SQL migration history |
/sql/ | Manual SQL utilities, audit scripts, fixes |
/dev-tools/ | Developer scripts (data fixes, migrations, audits) |
/tests/ | E2E tests (coverage minimal) |
/public/ | Static assets, locales JSONs (i18n http-backend) |
/CLAUDE.md | ๐จ Critical AI rules: JSONB rules, org-scoping, gotchas |
/.claude/CODEBASE_MAP.md | Module/service index auto-generated |
/.claude/codebase-index.json | Codebase index machine-readable |
/THR_DOCUMENTATION_SUMMARY.md | Tech summary sedia ada |
/THR_FULL_DOCUMENTATION.json | Full schema machine-readable |
/RLS-STATUS-REPORT.md | RLS audit report |
/netlify.toml | Build config, security headers, redirects |
/vite.config.js | Build config, path aliases (@modules, @shared, @core, dll.) |
/package.json | v1.2.0 โ 30+ runtime deps |
Path Aliases (vite config)
'@' โ './src'
'@modules' โ './src/modules'
'@shared' โ './src/shared'
'@core' โ './src/core'
'@hooks' โ './src/hooks'
'@payroll' โ './src/modules/payroll'
'@employees'โ './src/modules/employees'
'@claims' โ './src/modules/claims'
5. Database Overview
Project: ftbtsxlujsnobujwekwx ยท Region: Singapore (ap-southeast-1) ยท Engine: PostgreSQL 15
Table Groups (ikut domain prefix)
| Group | Tables | Purpose |
| Employee & Org | thr_employees, thr_organizations, thr_departments, thr_positions, thr_sections, thr_employee_organizations, thr_employment_types, thr_employment_history, thr_brands, thr_countries, thr_admin_org_assignments | Identity, structure, multi-org membership |
| Access | thr_access_levels (11 levels), thr_access_capabilities | Permission tiers |
| Leave | thr_leave_applications, thr_leave_balances, thr_leave_entitlements, thr_leave_types (10 types), thr_leave_policies, thr_leave_adjustments, thr_leave_records | Leave management |
| Attendance | thr_attendance, thr_attendance_logs, thr_attendance_locations | Check-in/out harian, geo, selfie, OT |
| Claims | thr_claims, thr_claim_items, thr_claim_receipts, thr_claim_types, thr_claim_limits, thr_claim_approval_rules | Expense reimbursement |
| Payroll | thr_payroll_batches, thr_payroll_transactions, thr_payroll_records, thr_payroll_periods, thr_employee_salary, thr_salary_components, thr_employee_allowances, thr_employee_deductions, thr_salary_history, thr_salary_changes | Salary processing |
| Tax (MY+ID) | thr_tax_brackets, thr_statutory_rates, thr_pph21_brackets, thr_ptkp_rates, thr_employee_indonesian_payroll | Statutory compliance |
| Audit | thr_audit_trail (7,817), thr_audit_logs (10,353), thr_activity_logs (11,464), thr_change_audit_log, thr_change_requests, thr_organization_changes_audit, thr_system_checkup_logs | Activity tracking (4 systems yang overlap) |
| Notifications | thr_notifications, thr_notification_preferences, thr_notification_templates, thr_notification_types, thr_email_queue, thr_email_settings | Push + email alerts |
| Messaging | thr_messages, thr_conversations, thr_message_reactions, thr_blocked_users, thr_user_presence | Internal chat dengan reactions |
| Memos | thr_memos, thr_memo_reads, thr_memo_comments, thr_memo_categories, thr_announcements, thr_announcement_reads | Internal announcements dengan read tracking |
| Documents | thr_documents, thr_employee_documents, thr_document_types, thr_document_categories, thr_employee_photos | File metadata |
| Dashboards | thr_custom_dashboards, thr_dashboard_widgets, thr_dashboard_templates, thr_widget_types | Widget grid customizable (drag/drop/resize) |
| AI | thr_ai_conversations, thr_ai_preferences, thr_ai_saved_views, sofia_* (5 tables, semua empty) | AI assistant (dah provisioned) |
| Reference | thr_public_holidays, thr_milestone_types, thr_approval_rules, thr_system_settings, thr_banks, thr_exchange_rates | Lookups, config |
| Legacy/Backup | master_hr2000 (518), master_hr2000_backup_2025_07_12, thr_employees_backup_20251231 (578), thr_organizations_backup_20251231, thr_employee_salary_backup_20251231 | Year-end snapshots, legacy HR2000 import |
Top 20 Tables ikut Row Count
| Table | Rows |
| thr_activity_logs | 11,464 |
| thr_audit_logs | 10,353 |
| thr_audit_trail | 7,817 |
| thr_notifications | 3,170 |
| thr_attendance | 2,765 |
| thr_leave_balances | 1,761 |
| thr_audit_trail_archive | 1,397 |
| thr_employment_history | 1,101 |
| thr_memo_reads | 907 |
| thr_dashboard_widgets | 867 |
| thr_email_queue | 743 |
| thr_payroll_transactions | 723 |
| thr_employees | 612 |
| thr_change_audit_log | 606 |
| thr_employees_backup_20251231 | 578 |
| master_hr2000 | 518 |
| thr_employee_salary | 514 |
| thr_employee_organizations | 510 |
| thr_change_requests | 424 |
| thr_employee_employment_types | 328 |
Postgres Extensions
pg_cron 1.6 โ scheduler
pg_graphql 1.5
pg_net 0.14 โ HTTP from SQL
pg_stat_statements
pgcrypto
pgjwt
supabase_vault
uuid-ossp
vector 0.8 (pgvector โ reserved for AI)
6. Table Utama โ Schema Detail
thr_employees (PK: id uuid) โ 612 rows
Model JSONB-heavy โ kebanyakan fields hidup dalam JSONB blobs (tengok ยง7 untuk struktur blob):
- Identity: employee_no, full_name, ic_no, nickname, photo_url, thumbnail_url
- FKs: organization_id, department_id, position_id, section_id, reporting_to_id (self), auth_user_id (auth.users), access_level (thr_access_levels)
- JSONB blobs: personal_info, contact_info, employment_info, compensation, tax_info, bank_info, address_info, emergency_contact, custom_permissions, profile_completion_details
- Status: employment_status (active/resigned/archived), active_status (bool โ ada drift dengan employment_status, ~37 row mismatch)
- Columns baru (akhir 2025): assigned_shift_id, ntfy_topic, push_notifications_enabled, is_system_user, hidden_from_hierarchy, id_type
โ ๏ธ Critical rule (dari CLAUDE.md)
Selalu guna access_level COLUMN untuk permission checks. Jangan sekali-kali baca dari JSONB. Bug lepas (Dec 2025) menyebabkan 509 employees ada access_level yang salah sebab JSONB/column desync.
Sebaliknya, employment_status hanya wujud dalam JSONB employment_info->>'employment_status' โ takde top-level column.
thr_organizations (PK: organization_id uuid) โ 24 rows
- Note: PK ialah
organization_id (bukan id) โ unusual, hati-hati dalam joins
- Identity: name, organization_code (contoh TCSB, TASB), ssm_number, tin
- FKs: brand_id, country (MY/ID), currency (MYR/IDR)
- Employee numbering: employee_id_prefix, employee_id_counter, employee_id_format (setiap org auto-mint employee_no sendiri)
- Attendance config: work_start_time, work_end_time, break_duration_minutes, grace_period_minutes, working_days (jsonb), timezone, require_attendance_selfie
- Shifts (baru): shifts_enabled, shifts (jsonb), default_shift_id
- Overtime: overtime_enabled, overtime_rate, auto_checkout_enabled, auto_checkout_time
thr_attendance (PK: id) โ 2,765 rows
- Date range: 2025-12-14 โ sekarang (~4 bulan live)
- FKs: employee_id, organization_id
- Times: date, check_in, check_out, overtime_check_in/out
- Geolocation: check_in_latitude/longitude, check_out_latitude/longitude (GPS guna Browser Geolocation API + IP fallback)
- WiFi (network fingerprint): check_in_wifi_ssid/bssid, check_out_wifi_ssid/bssid
- Selfies: check_in_selfie_path, check_out_selfie_path, overtime_selfie_path โ simpan dalam
attendance-selfies bucket (3,063 objects)
- OT: overtime_minutes, has_overtime_session
- Compliance flags: is_late, late_minutes, early_checkout_minutes, status, check_in_method, check_out_method
thr_leave_applications (PK: id) โ 11 rows (usage rendah)
- FKs: employee_id, leave_type_id (thr_leave_types), organization_id, reporting_to_id, approved_by
- start_date, end_date, total_days, days_taken, reason, status, approval_remarks, rejection_reason, approved_at, rejected_at
- Quirk: legacy column
leave_type (varchar) hidup sama-sama dengan leave_type_id (FK). CLAUDE.md kata: guna leave_type string untuk query.
thr_claims (PK: id) โ 3 rows
- FKs: employee_id, submitted_by, approved_by, rejected_by
- Auto-numbering: claim_no via trigger
trg_generate_claim_number
- Multi-currency: amount, currency + original_amount, original_currency, exchange_rate
- claim_type, claim_date, status, payment_method, payment_reference, paid_at, receipt_url
thr_payroll_batches + thr_payroll_transactions (13 + 723)
thr_payroll_batches = pay period grouping (contoh batch April 2026).
thr_payroll_transactions = per-employee per-component line items (basic salary, EPF, SOCSO, allowances, deductions, claims).
- FKs: batch_id, employee_id, component_id, claim_id (linked claim)
- transaction_type, component_code, component_name, amount, is_employer_contribution, calculation_basis
thr_employee_salary (PK: id) โ 514 rows
- employee_id, basic_salary, currency, effective_date, end_date, is_active
- payment_method, bank_name, bank_account_no
- epf_employee_rate, epf_employer_rate, socso_category, tax_number
- Versioned ikut effective_date / end_date (history salary changes)
thr_change_requests (PK: id) โ 424 rows
- State: approved 225 / pending 197 / rejected 2 (47% pending backlog!)
- FKs: employee_id, requested_by, reviewed_by
- request_type, field_category, field_name, field_path
- current_value (jsonb), requested_value (jsonb), supporting_documents (jsonb)
- priority, auto_approve_after, requires_document
Guna bila employee nak update profile fields sendiri โ HR review sebelum apply.
thr_audit_trail (PK: id) โ 7,817 rows
- table_name, record_id, action, user_id, employee_id, organization_id
- old_values (jsonb), new_values (jsonb), changed_fields (text[])
- module, severity, description, metadata (jsonb)
- ip_address, user_agent, session_id, request_id
thr_admin_org_assignments (PK: id bigint) โ 65 rows
- Critical untuk HR Admin (Level 7) org-scoping
- admin_user_id (auth.users.id), employee_id, organization_id, assigned_by, is_active, notes
- Digunakan oleh
hrOrganizationService.getChangeRequestScope()
thr_access_levels โ 11 levels defined
| Lvl | Name | Population |
| 0 | Employee | 584 (95%) |
| 1 | Senior Employee | 0 |
| 2 | Team Lead | 1 |
| 3 | Supervisor | 0 |
| 4 | Senior Supervisor | 0 |
| 5 | Manager | 0 |
| 6 | Senior Manager | 0 |
| 7 | HR Administrator (org-scoped) | 22 |
| 8 | System Administrator | 5 |
| 70 | Admin (special) | 0 |
| 80 | Super Admin (special) | 0 |
Levels 1, 3-6, 70, 80 defined tapi tak digunakan.
thr_messages + thr_conversations (77 + 12)
- thr_conversations: 1:1 atau group chat threads
- thr_messages: individual messages (text, attachments via storage)
- thr_message_reactions: emoji reactions
- thr_blocked_users: block list per-user
- Real-time delivery guna Supabase Realtime subscriptions
- Triggers:
create_message_notification, update_conversation_on_message
thr_memos + thr_memo_reads (30 + 907)
- Memos: internal announcements formal dengan categories
- memo_reads: 907 read receipts (engagement tinggi atas memos)
- memo_comments: threaded comments
- Auto-numbering:
trigger_set_memo_number
- Email notification:
queue_memo_email RPC
7. JSONB Blob Schemas
Table thr_employees pakai extensive JSONB blobs supaya schema flexible. Ni apa yang sebenarnya ada dalam setiap blob (verified dari production data):
personal_info
| Key | Description |
ic_number / ic_no | Malaysian/Indonesian ID number |
date_of_birth | YYYY-MM-DD |
gender | male / female |
marital_status | single / married / divorced / widowed |
race | Malay / Chinese / Indian / Other |
religion | Islam / Christianity / dll. |
nationality | Malaysian / Indonesian / Foreign |
display_name | Preferred display name |
nickname | Short name |
email | Personal email (vs work email dalam contact_info) |
spouse_details | JSONB info pasangan |
spouse_history | JSONB array |
contact_info
| Key | Description |
phone / phone_number | Mobile (guna untuk WhatsApp lookup!) |
email | Work email (biasanya Google Workspace) |
emails | Array additional emails |
address | JSONB nested address |
emergency_contact | JSONB {name, phone, relationship} |
employment_info
| Key | Description |
employment_status | active / resigned / archived (CRITICAL โ hanya ada dalam JSONB) |
employee_type | Permanent / Contract / Intern |
hire_date | YYYY-MM-DD |
resign_date / last_working_date / termination_date | Exit dates |
rehire_effective_date | Kalau rehired |
tenure_years | Tenure yang dikira |
probation_months | Probation period |
starting_salary | Salary pertama masa hire |
department_id / department / section_id | Kadang-kadang duplicate dari columns |
active_override | Bool untuk override special active flag |
inactive_reason | Reason untuk inactive |
historical_snapshot | JSONB preserve historical state |
compensation
| Key | Description |
basic_salary | Basic salary semasa (juga ada dalam thr_employee_salary) |
salary_effective_date | Effective date salary semasa |
fixed_allowances | JSONB array allowances recurring |
allowances | JSONB array (format alternate) |
bank_info
| Key | Description |
bank_name / name | Nama bank (contoh Maybank) |
account_no | Account number |
code | Bank code |
type / payment_type | Account type |
payment_via | Bank transfer / Cash / Cheque |
payment_frequency | Monthly / Bi-weekly |
tax_info
| Key | Description |
income_tax | Tax number (LHDN MY) |
epf | EPF number (Malaysia) |
socso | SOCSO number (Malaysia) |
eis | EIS contribution flag/number |
zakat | Zakat deduction setting |
๐ก Query patterns untuk JSONB
-- Ambil employment_status (hanya dalam JSONB)
SELECT employment_info->>'employment_status' AS status FROM thr_employees;
-- Filter ikut JSONB key
SELECT * FROM thr_employees
WHERE employment_info->>'employment_status' = 'active';
-- Update JSONB key
UPDATE thr_employees
SET employment_info = jsonb_set(employment_info, '{hire_date}', '"2026-01-15"');
-- Search nested
SELECT * FROM thr_employees
WHERE personal_info @> '{"gender": "male"}';
8. Views & RPCs
Notable Views (26 total)
| Category | View | Purpose |
| Employee | thr_employees_view | Active employees dengan email/phone/display_name denormalized dari JSONB |
| Employee | thr_employees_full_view | Variant full-detail |
| Employee | vw_employee_access_variations | Audit access pattern |
| Employee | vw_employee_all_organizations | Multi-org employee view |
| Org | thr_organizations_with_country | Orgs joined dengan country lookup |
| Org | v_admin_accessible_orgs | Orgs yang filtered untuk admin user semasa |
| Org | thr_recent_organization_changes | Org modifications terkini |
| Attendance | thr_attendance_today | Check-ins hari ni (320 rows) |
| Leave | v_leave_balances_json | Leave balances aggregated sebagai JSON |
| Claims | thr_claims_with_conversion / _with_currency | Claims dengan FX conversion baked in |
| Claims | v_employee_claims_summary | Claims totals per-employee |
| Payroll | vw_payroll_summary / vw_payroll_complete_summary | Batch & component breakdown |
| Audit | v_audit_trail_summary | Audit metrics aggregated |
| Audit | v_audit_user_activity / _module_activity / _critical_actions | Audit slicing ikut dimension |
| Activity | v_activity_summary / v_user_activity_stats | App activity stats |
| Memos | thr_memos_with_access | Memos dengan permission filter |
| Memos | employee_memos | Memo feed per-employee |
| Memos | thr_support_messages_view | View macam support ticket |
| System | vw_latest_system_checkup | Health check result terkini |
| Documents | thr_employee_documents_view, thr_employee_photos_view | Document/photo metadata |
RPCs / Functions (255 total โ business logic pilihan)
Employee Lifecycle
archive_employee(id, reason) ยท restore_archived_employee(id)
create_employee_in_organization(...) โ auto-mint employee_no ikut format org
can_delete_employee(id)
calculate_profile_completion(id) โ return percentage + missing fields
sync_users_with_employees() ยท link_auth_user_to_employee() ยท manually_link_auth_users()
search_employees_flexible(query) โ full-text employee search across name/email/phone/dll
get_current_employee() โ resolve auth.uid() โ employee row
Access / Permissions
is_super_admin() ยท is_super_admin_simple()
is_org_admin(org) ยท is_hr_in_organization(org)
can_user_access_employee(id) ยท can_user_update_employee(id)
get_user_accessible_orgs() โ uuid[]
get_user_data_scope()
check_user_capability(capability)
admin_can_access_org(org_id)
suggest_access_level_strategy(employee_id)
update_access_level_by_email(email, level) / _by_email_and_org
Attendance
get_attendance_today() ยท get_organization_attendance_today(org)
get_attendance_summary(emp, start, end)
get_attendance_status(employee_id)
get_pending_checkouts() โ fed masuk cron auto-checkout
calculate_work_hours(in, out)
Leave (tambahan kepada WhatsApp variants โ tengok ยง22)
initialize_employee_leave_balances_by_org / _ctk / initialize_leave_balances_smart
update_org_leave_balances() / _fixed
sync_leave_balance_to_json ยท refresh_leave_balances_json
get_leaves_for_payroll(period)
Claims
generate_claim_number / set_claim_number / generate_claim_no
get_claims_for_payroll(period)
mark_claims_as_paid(batch_id) โ int
Payroll & Tax
calculate_pcb_simple(monthly_salary) โ numeric โ Malaysia PCB tax
generate_batch_no() โ auto batch numbering
calculate_asset_depreciation โ numeric (untuk asset claims)
format_currency(amount, currency) ยท get_exchange_rate(from, to, date)
Notifications
create_notification_from_template ยท mark_notification_read ยท get_unread_notification_count
notify_claim_submission ยท notify_claim_status_update
notify_leave_application ยท notify_leave_status_change/update
notify_payroll_ready ยท notify_announcement ยท notify_new_message
queue_memo_email (2 overloads) ยท track_email_read
Audit & Maintenance
audit_log(table, record_id, action, ...) โ generic writer
- Trigger fns:
audit_employee_changes, audit_organization_changes, audit_claims_changes, audit_payroll_changes
cleanup_activity_logs() ยท archive_old_audit_records()
create_activity_log_partition / drop_old_activity_log_partitions
search_audit_trail(query, filters)
log_activity(module, action, target)
Team / Org
get_team_members(manager_id) ยท get_team_count
update_reporting_structure
search_leadership_by_org
delete_organization_cascade(org_id) / _with_cleanup
auto_assign_organization ยท trigger assign_position_organization
validate_employee_organization_correct/prefix
Dashboard / Misc
get_employee_dashboard_stats(employee_id) โ json
check_expiring_documents()
refresh_materialized_view(name)
- Meta helpers:
exec_sql, list_tables, get_all_tables, get_policies_for_table, get_policy_definition, get_table_relationships
9. Storage Buckets
| Bucket | Public? | Size Limit | Objects | Purpose |
| attendance-selfies | โ Private | 512 KB | 3,063 | Check-in/out selfies (auto-cleaned daily) |
| memos | โ
Public | 10 MB | 30 | Memo attachments |
| employee-photos | โ
Public | (takde) | 28 | Profile photos |
| claim-receipts | โ
Public | (takde) | 1 | Expense claim receipts |
| employee-documents | โ Private | 10 MB | 3 | Personal documents (IC, dll.) |
| company-policies | โ
Public | 10 MB | 0 | HR policy documents |
| employee-certificates | โ Private | 5 MB | 0 | Training/cert files |
| employee-contracts | โ Private | 5 MB | 0 | Employment contracts |
| payslips | โ Private | 2 MB | 0 | Payslip PDFs yang di-generate |
| thr-documents | โ Private | 10 MB | 0 | Generic THR documents |
๐ฆ Total: 3,125 objects โ 98% adalah attendance selfies
Setiap check-in/out capture satu selfie (bila org enable require_attendance_selfie). Daily cron cleanup-attendance-selfies-daily @ 02:00 prune objects lama.
โ ๏ธ Kebanyakan buckets kosong
company-policies, employee-certificates, employee-contracts, payslips, thr-documents โ semua 0 objects. Either features underutilized ATAU storage pindah tempat lain.
10. Authentication Flow
Methods yang Disupport
- โ
Google OAuth (primary โ semua 245 users ada
provider_id dari Google)
- โ
Email + password (Supabase native)
- โ
Magic link (Supabase native)
- โ Takde SAML/SSO custom integrations
Login Flow
USER opens https://thr.todak.io/login
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Login.jsx โ
โ โข Demo mode fallback โ
โ โข OAuth button (Google) โ
โ โข Email/password form โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ
โ supabase.auth.signInWithOAuth({provider:'google'})
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Google OAuth Consent Screen โ
โ (browser redirect) โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ
โ redirect โ /auth/callback
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Supabase exchanges code โ JWT โ
โ Stores session in localStorage โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ useAuthSync() hook โ
โ โข Look up thr_employees row โ
โ WHERE auth_user_id = uid โ
โ โข If missing โ onboarding โ
โ โข Set currentUser context โ
โโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโ
โ
โผ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ DashboardProvider + โ
โ AccessLevelProvider โ
โ โ Render based on access_level โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Session Management
- Token storage: Supabase JWT dalam
localStorage
- Auto-refresh: enabled (
autoRefreshToken: true)
- State sync:
onAuthStateChange() trigger re-render
- Inactivity timeout: hook
useInactivityTimeout() (auto-logout lepas idle)
- Custom claims: TAK digunakan โ access_level hidup dalam DB row, bukan JWT
Auth Hooks
| Hook | Returns | Purpose |
useAuth() | { user, currentUser } | Core auth context |
useAuthSync() | โ | Sync auth.users โ thr_employees masa login |
useAuthErrorHandler() | โ | Error handling centralized |
useInactivityTimeout() | โ | Auto-logout lepas idle |
Auth Metadata Inspection
Semua 245 auth.users ada structure yang identical (Google OAuth populated):
| Key | Value |
provider_id | User ID dari Google |
full_name / name | Dari Google profile |
email / email_verified | Google email + verified flag |
iss / sub | OAuth issuer + subject |
picture / avatar_url | Google profile photo (243/245) |
custom_claims | OAuth IdP claims (BUKAN app-injected) |
11. Roles & Permissions
Access Level System (numeric 0-8 + special 70/80)
| Lvl | Role | Capabilities | Active |
| 0 | Employee | Self-service: apply leave, clock in, submit claims, tengok payslip/profile sendiri | 584 |
| 1 | Senior Employee | Extended viewing, mentoring features | 0 |
| 2 | Team Lead | Team view, basic approvals | 1 |
| 3 | Supervisor | Team management, approvals terhad | 0 |
| 4 | Senior Supervisor | Department access, leave approvals, team reports | 0 |
| 5 | Manager | Multi-department, advanced approvals | 0 |
| 6 | Senior Manager | Division access | 0 |
| 7 | HR Administrator (org-scoped) | HR functions, payroll, leave config โ terhad kepada orgs yang assigned via thr_admin_org_assignments | 22 |
| 8 | System Administrator | System-wide access, semua orgs, takde scoping | 5 |
โ ๏ธ Reality sebenar
Walaupun ada 11 levels, hanya 4 yang populated (0, 2, 7, 8). Manager tiers di tengah-tengah (1, 3, 4, 5, 6) tak digunakan โ sekarang setiap non-admin user adalah Level 0 (Employee).
Approval workflows yang secara teori route ke "Manager (Lvl 4+)" actually pergi terus ke HR Admin (Lvl 7) sebab takde managers exist.
Macam Mana Permissions Di-check
1. Frontend (UI gating)
// Inline checks (paling biasa)
if (empData.access_level >= 7) { showHRAdminMenu() }
if (empData.access_level === 8) { showSystemAdminMenu() }
// Context provider
<AccessLevelProvider>
<Routes>
<Route path="/payroll" element={
<RequireLevel min={7}>...</RequireLevel>
} />
</Routes>
</AccessLevelProvider>
2. Backend (DB SECURITY DEFINER functions)
SELECT is_super_admin(); -- bool (level 8)
SELECT is_hr_in_organization(:org); -- bool (level 7+ assigned)
SELECT get_user_accessible_orgs(); -- uuid[]
SELECT can_user_access_employee(:id); -- bool (RBAC check)
3. Org-scoping untuk HR Admin (Level 7)
HR Admins nampak hanya orgs yang assigned. Table mapping:
thr_admin_org_assignments
โโโ admin_user_id (auth.users.id)
โโโ employee_id (thr_employees.id)
โโโ organization_id
โโโ assigned_by
โโโ is_active
โโโ notes
Service: hrOrganizationService.getChangeRequestScope() return { canViewAll: boolean, organizationIds: string[] }. Digunakan dalam 19+ files.
Note: Bug lepas Dec 2025 โ nama column salah employee_id vs admin_user_id menyebabkan HR Admins hilang access. Sudah dibetulkan dalam hrOrganizationService.
Approval Workflows
Leave Application Flow
Employee submits โ status: pending
โ
โผ
Manager (Lvl 4+) reviews
โ
โโโ Approves โ status: approved โ notify_leave_status_change()
โ
โโโ Rejects โ leaveEscalationService.js
โ
โผ
HR (Lvl 6+) reviews
โ
โโโ Final decision
Claims Flow
Employee submits claim โ AI auto-fills (OpenAI GPT-4)
โ
โผ
trg_generate_claim_number โ CLM-{org}-{seq}
โ
โผ
Manager approves โ notify_claim_status_update()
โ
โผ
HR includes in next payroll batch
โ
โผ
mark_claims_as_paid(batch_id)
12. Multi-Tenancy
Organization Structure
24 organizations โ Todak Holdings group companies. Semua is_active=true.
| Country | Currency | Org Count |
| Malaysia ๐ฒ๐พ | MYR | 21 |
| Indonesia ๐ฎ๐ฉ | IDR | 3 (PNJR, PT_TEST_ID, PTNG) |
Semua Organizations Aktif
| Code | Name | Employees |
| TCSB | Todak Culture Sdn. Bhd. | 149 |
| TASB | Todak Academy Sdn. Bhd. | 127 |
| TSSB | Todak Studios Sdn. Bhd. | 84 |
| TH | Todak Holdings Sdn. Bhd. | 76 |
| TDSB | Todak Digitech Sdn. Bhd. | 29 |
| HSB | Hyleen Sdn. Bhd. | 21 |
| TTK | Tadika Todak Kids | 21 |
| MTSB | My Barber Tech | 18 |
| LTCM | Lan Todak Consultation | 18 |
| PTNG | PT Todak Nusantara Group (ID) | 13 |
| TFSB | Todak Fusion | 11 |
| TPSB | Todak Paygate | 10 |
| 10C | 10 Camp Enterprise | 9 |
| MH | Muscle Hub | 7 |
| โ | Sepinggan 11 Resources | 5 |
| โ | Sarcom Technology | 5 |
| TORG | TEST ORG โ ๏ธ | 4 |
| โ | Todak RC Enterprise | 3 |
| PT_TEST_ID | PT Test Indonesia โ ๏ธ | 2 |
| PNJR | PT Nusantara Jiwa Rasa (ID) | โ |
Macam Mana Tenants Isolate Data
- Discriminator: column
organization_id atas thr_employees + kebanyakan thr_* tables
- Per-org config: work hours, shifts, attendance selfie requirement, employee_no format prefix, overtime rules, currency โ semua hidup sebagai columns atas
thr_organizations
- Multi-org employee: M:N support via
thr_employee_organizations (510 rows) โ employees boleh belong to multiple orgs
- Per-org leave types: overrides via
thr_leave_entitlements
- Filtering: Frontend services filter ikut
organization_id; backend functions macam get_user_accessible_orgs() return scoped lists
๐จ Takde tenant-level enforcement
Multi-tenancy di-enforce di application layer sahaja (filter ikut organization_id dalam service code). Takde tenant claim dalam JWT dan RLS disabled kat key tables (tengok ยง13).
Kalau frontend ada bug yang tertinggal org filter, ATAU kalau sesiapa dapat service-role key, semua data boleh di-query cross-tenant.
13. Row Level Security Status
โ ๏ธ ~70 tables ada RLS enabled ยท ~27 TAK ada
Gaps yang notable termasuk tables paling sensitive. Beberapa ada policies defined tapi RLS disabled โ maknanya policies tu jadi dead code.
Tables Tanpa RLS (yang sensitive)
thr_employees
thr_organizations
thr_attendance
thr_leave_applications
thr_leave_balances
thr_leave_entitlements
thr_notifications
thr_notification_preferences
thr_activity_logs
thr_audit_trail
thr_employee_indonesian_payroll
thr_pph21_brackets / thr_ptkp_rates
thr_salary_changes
thr_admin_org_assignments
thr_system_settings
thr_public_holidays
Tables dengan RLS Policies Paling Banyak
| Table | Policies | RLS Enabled? |
| thr_claims | 18 | โ
Ya (enforcement paling berat) |
| thr_notifications | 16 | โ OFF โ policies dead |
| thr_leave_applications | 11 | โ OFF โ policies dead |
| sofia_conversations | 10 | (table kosong) |
| thr_messages | 10 | โ
Ya |
| thr_change_requests | 9 | โ
Ya |
| thr_payroll_transactions | 8 | โ
Ya |
| thr_organizations | 7 | โ OFF |
| thr_staff_categories | 6 | โ |
| thr_payroll_batches / positions / salary_history / activity_logs | 5 setiap satu | โ |
Apa Maksudnya Dalam Practice
- Frontend guna
VITE_SUPABASE_PUBLISHABLE_KEY (anon-style, respect RLS)
- Kalau RLS OFF atas satu table โ mana-mana authenticated user boleh baca semua benda
- Filtering di application-layer je jadi barrier (dan maknanya bug UI boleh leak data)
- Edge Functions / Netlify Functions guna service-role key โ bypass semua RLS regardless
๐ Recommendation
Either enable RLS atas 27 tables yang unprotected (guna dead policies yang sedia ada sebagai starting point) atau padam dormant policies untuk elak confusion masa depan. Document security model dalam RLS-STATUS-REPORT.md.
14. Security Posture (Honest Assessment)
Yang Kuat
- โ
OAuth-only effective auth โ Google handle MFA, password rotation
- โ
HTTPS everywhere โ Netlify TLS, Supabase TLS
- โ
Secrets management โ env vars, takde secrets dalam repo
- โ
Audit trail โ extensive logging via 4 audit systems
- โ
Security headers โ X-Frame-Options DENY, XSS-Protection, dll. dalam netlify.toml
- โ
Inactivity timeout โ auto-logout via useInactivityTimeout
- โ
Org-scoped HR Admin โ proper data partitioning di application level
Yang Lemah
- ๐ด RLS disabled kat key tables (employees, attendance, leave) โ tengok ยง13
- ๐ด Service-role key ada DB-wide access โ satu leak = total exposure
- ๐ก Takde MFA enforcement untuk non-Google logins (email/password path)
- ๐ก Takde tenant claim dalam JWT โ multi-tenancy purely app-layer
- ๐ก Staging share prod DB โ schema migrations affect kedua-dua
- ๐ก Cron embed service JWT inline โ rotation kena edit cron commands
- ๐ก Test orgs dalam production (TORG, PT_TEST_ID) bercampur dengan data sebenar
- ๐ข Nginx basic auth password dalam CLAUDE.md memory (lan/lan1q2w3e โ untuk CCC, bukan THR)
Actions yang Disyorkan
- Enable RLS atas critical tables โ start dengan thr_employees, thr_attendance, thr_leave_applications
- Asingkan Supabase project untuk staging โ isolate dari production
- Rotate service-role JWT dalam cron jobs โ guna Supabase Vault
- Padam test orgs dari production (TORG, PT_TEST_ID)
- Tambah MFA enforcement untuk super admin accounts
- Audit log retention policy โ sekarang takde automatic pruning untuk activity_logs (11k+ rows makin membesar)
15. Semua Routes
| Path | Min Lvl | Component | Purpose |
/login | Public | Login | Sign in (Google OAuth + email/password) |
/auth/callback | Public | โ | OAuth redirect handler |
/reset ยท /logout | Public | โ | Password reset ยท sign out |
/dashboard | 0 | Dashboard | Main hub, role-based widgets, customizable layout |
/employees | 4 | EmployeeDirectory | Browse semua employees dalam scope |
/employee/:id | 0 | EmployeeDetailsPage | Profile (sendiri atau yang accessible) |
/leave | 0 | LeaveManagement | Apply leave, tengok balance, history |
/leave/approvals | 4 | LeaveApprovals | Approve/reject team requests |
/leave-balance | 7 | LeaveManagementAdmin | Configure leave types / entitlement |
/attendance | 0 | AttendanceManagement | Clock in/out, selfie, geo, calendar |
/attendance-admin | 7 | AttendanceManagementAdmin | Org-wide attendance, late tracker |
/payroll | 7 | PayrollManagementV3 | Batches, EA forms, payslips |
/my-payroll | 0 | EmployeePayslipView | Tengok payslip sendiri |
/claims | 0 | ClaimsFixed | Submit / manage expenses |
/admin | 8 | AdminSettings | System configuration |
/admin/access-control | 8 | AccessControl | Role assignment |
/admin/users | 8 | UsersManagement | Create/edit users |
/admin/org-assignments | 7 | AdminOrgAssignments | Map HR admins โ orgs |
/admin/audit-trail | 8 | AuditTrail | Activity logs viewer |
/admin/change-requests | 7 | ChangeRequests | Approve profile changes (197 pending!) |
/admin/activity-dashboard | 7 | ActivityDashboard | Activity overview |
/admin/system-checkup | 8 | SystemCheckup | Health check |
/admin/milestone-types | 8 | MilestoneTypesManagement | Career milestones config |
/admin/employment-types | 8 | EmploymentTypesManagement | Employment type config |
/pcb-management | 7 | PCBManagement | Config Malaysia PCB tax brackets |
/documents | 0 | DocumentManagement | Files (employee/org) |
/messages | 0 | Messages | Internal real-time chat |
/notifications | 0 | NotificationCenter | Alerts + announcements feed |
/memos ยท /memos/:id | 0 | Memos / MemoDetail | Tengok & comment memos |
/memos/create | 7 | CreateMemo | Tulis memo baru |
/profile | 0 | Profile | Edit profile sendiri |
/onboarding | 0 | EmployeeProfileCompletion | Lengkapkan profile yang missing |
16. Modules Breakdown
Located dalam /src/modules/ โ 11 modules:
| Module | Pages | Min Level | Purpose |
| employees | EmployeeDirectory, EmployeeDetailsPage, Profile, Organizations, MyOrganizations, EmployeeProfileCompletion, Memos, CreateMemo, MemoDetail, CareerDevelopment | 0+ | Identity, profiles, photos, memos, onboarding |
| attendance | AttendancePage, AttendanceManagement | 0 / 7 | Clock in/out, selfie, geo, calendar |
| leave | LeaveManagement, LeaveApprovals, TeamReports, LeaveManagementAdmin | 0 / 4 / 7 | Apply, approve, configure |
| payroll | PayrollManagementV3, EmployeePayslipView | 7 / 0 | Batches, EA, payslips, statutory |
| claims | ClaimsFixed | 0+ | Submit, AI fill, approve |
| admin | AdminSettings, AccessControl, UsersManagement, PCBManagement, AuditTrail, ChangeRequests, ActivityLogs, ActivityDashboard, SystemCheckup, MilestoneTypesManagement, EmploymentTypesManagement | 7 / 8 | System config, role mgmt, audit |
| documents | DocumentManagement, OrganizationDocumentManagement | 0 / 7 | File upload/organize |
| messaging | Messages | 0+ | Real-time chat (Supabase Realtime) |
| notifications | NotificationCenter, NotificationSettings, NotificationSetup | 0+ | In-app + email alerts |
| auth | Login, Reset | Public | Authentication UI |
| atlas | (placeholder โ tengok ยง26) | โ | FUTURE: Asset management |
17. Service Layer
Located dalam /src/shared/services/ โ 37+ business logic services. Frontend components call services, services call Supabase.
Core Services
| Service | Purpose |
employeeService.js | CRUD employee records, JSONB blob handling |
employeeService-multiorg-fix.js | Multi-org employee handling (post-Dec 2025 fix) |
employeeService-safe-fix.js | Defensive employee operations |
hrOrganizationService.js | HR Admin org-scoping, getChangeRequestScope() |
departmentValidationService.js | Validate department assignments |
changeRequestService.js | Profile change request workflow |
Leave & Attendance
| Service | Purpose |
leaveEntitlementService.js | Kira leave entitlements per employee |
leaveYearEndService.js | Year-end leave reset/carryover |
locationService.js | GPS + IP geolocation (3 fallback APIs) |
publicHolidayService.js | Public holiday calendar (67 holidays loaded) |
Communications
| Service | Purpose |
memoService.js | Memo CRUD, read tracking |
notificationIntegrationService.js | Multi-channel notification dispatch |
emailPriorityService.js | Email queue priority |
ntfyService.js | ntfy push notification (dah provisioned) |
AI Layer (multi-provider)
| Service | Purpose |
aiService.js | Generic AI client (semasa) |
aiServiceLocal.js | Local LLM fallback |
aiServiceSecure.js | Server-side AI (elak expose API key client-side) |
multiAIService.js | Provider abstraction layer |
openaiDirect.js.disabled | Disabled โ direct OpenAI client (security risk) |
enhancedSofiaService.js | Sofia AI assistant features |
sofiaDataService.js | Sofia data layer |
sofiaInsightsService.js | Sofia analytics |
sofiaKnowledgeService.js | Sofia RAG knowledge base |
thrSystemKnowledge.js | System knowledge untuk AI context |
Agent Routing System
| Service | Purpose |
agentEnforcement.js | Enforce agent boundaries |
agentRoutingSystem.js | Route requests ke AI agent yang sesuai |
autoRouter.js ยท autoRouterTest.js | Auto-routing logic + tests |
memoryAgentService.js | Agent memory layer |
taskAnalyzer.js | Analyze task type untuk routing |
AGENT_ROUTING_README.md | Routing documentation |
Infrastructure
| Service | Purpose |
auditService.js | Write ke thr_audit_trail |
errorLoggingService.js | Error tracking centralized |
storageService.js | Generic file upload/download |
secureStorageService.js | Encrypted storage |
featureFlagsService.js | Feature flag toggles |
hookManager.js | Webhook/event manager |
queueManager.js | Background job queue |
dashboardService.js | Dashboard data aggregation |
18. Attendance โ Deep Dive
Macam Mana Check-In Berfungsi
USER opens /attendance โ click "Check In"
โ
โผ
1. Browser asks for Geolocation permission (10s timeout)
โโโ Granted โ use GPS lat/long
โโโ Denied/timeout โ fallback IP geolocation
(ipapi.co โ ipgeolocation.io โ ipwhois.io)
โ
โผ
2. If org.require_attendance_selfie = true:
โ Open camera, capture selfie
โ Upload to attendance-selfies bucket (max 512KB)
โ
โผ
3. Capture WiFi info (if available via browser API)
โ SSID + BSSID
โ
โผ
4. Capture device fingerprint
โ User-Agent โ browser, OS, device brand/model
โ
โผ
5. INSERT INTO thr_attendance (...)
โ
โผ
6. Trigger: validate against work_start_time + grace_period
โ Set is_late, late_minutes
โ
โผ
7. Edge Function notify if late
Auto-Checkout (cron)
- Schedule: Setiap 15 minit (
*/15 * * * *)
- Job name:
auto-checkout-every-15min
- Action: POST ke
/functions/v1/auto-checkout
- Logic: Call
get_pending_checkouts(), apply auto_checkout_time org
Selfie Cleanup (cron)
- Schedule: Setiap hari 02:00 (
0 2 * * *)
- Job name:
cleanup-attendance-selfies-daily
- Action: POST ke
/functions/v1/cleanup-attendance-selfies
Stats โ Activity Pattern
| Date | Check-ins | Day |
| 2026-04-21 | 58 | Tue |
| 2026-04-20 | 59 | Mon |
| 2026-04-19 | 9 | Sun (low) |
| 2026-04-18 | 6 | Sat (low) |
| 2026-04-17 | 56 | Fri |
| 2026-04-16 | 55 | Thu |
| 2026-04-15 | 55 | Wed |
~55-60 check-ins per weekday โ hanya ~10% daripada 612 employees. Menandakan usage terhad kepada specific orgs (kemungkinan TCSB, TASB).
19. Leave Management
Leave Types (10 seeded)
| Code | Name |
| AL | Annual Leave |
| MC | Medical Certificate |
| EL | Emergency Leave |
| CL | Compassionate Leave |
| MAT | Maternity Leave |
| PAT | Paternity Leave |
| HL | Hospitalization Leave |
| ML | Marriage Leave |
| UPL | Unpaid Leave |
| RL | Replacement Leave |
Approval Workflow
Employee submits โ thr_leave_applications (status: pending)
โ
โผ trigger: thr_handle_leave_application_fields_trigger (BEFORE INSERT)
โผ trigger: trg_notify_leave_application
โผ Email to manager (reporting_to_id)
โ
โผ
Manager reviews
โ
โโโ Approves โ status: approved
โ โ Decrement thr_leave_balances
โ โ trg_notify_leave_status_change โ notify employee
โ
โโโ Rejects โ leaveEscalationService.js
โ Escalate to HR (Level 6+)
โ status: rejected (after final HR call)
โ ๏ธ Duplicate triggers detected
Atas thr_leave_applications: kedua-dua trg_notify_leave_application DAN trigger_notify_leave_application wujud untuk event yang sama. Kemungkinan fire dua kali โ duplicate notifications.
Year-End Processing
Edge Function leave-year-end handle annual carryover. Service: leaveYearEndService.js.
Stats
- Total applications (lifetime): 11
- Dalam 14 hari lepas: 1 (2026-04-08)
- Leave balances tracked: 1,761 rows (per employee ร per leave type)
- Leave entitlements configured: 181
20. Claims (Expense Reimbursement)
Features
- AI-assisted form fill โ OpenAI GPT-4 baca receipt, suggest fields
- Multi-currency โ original_amount + original_currency + exchange_rate
- Receipt upload โ ke
claim-receipts bucket
- Auto-numbering โ trigger
trg_generate_claim_number generate CLM-{org}-{seq}
- Approval chain โ employee โ manager โ finance
- Payroll integration โ
get_claims_for_payroll(period) + mark_claims_as_paid(batch_id)
Stats
- Total claims: 3 (usage sangat rendah)
- Claims dalam 14 hari lepas: 0
- Claim items: 0
โ ๏ธ Feature underutilized
0 claims submitted dalam 14 hari. Kemungkinan: feature belum rolled out, staff guna manual forms, atau ada UI/UX friction. File ClaimsFixed.jsx ada TODO untuk "Edit functionality pending".
21. Payroll & Statutory
Multi-Country Statutory Calculations
| Country | Component | Rate |
| ๐ฒ๐พ Malaysia | EPF (Employees Provident Fund) | Employee 11%, Employer 12-13% |
| SOCSO | Tiered ikut salary |
| EIS (Employment Insurance) | 0.2% kedua-dua belah |
| PCB (Income Tax) | Progressive โ calc via calculate_pcb_simple() |
| ๐ฎ๐ฉ Indonesia | BPJS Ketenagakerjaan TK JHT | Employee 2%, Employer 3.7% |
| JP (Pension) | 1% / 2% |
| Kesehatan (Health) | 1% / 4% |
Payroll Tables
| Table | Rows | Purpose |
| thr_payroll_batches | 13 | Pay period grouping |
| thr_payroll_transactions | 723 | Per-employee per-component line items |
| thr_payroll_records | โ | Records yang detailed |
| thr_payroll_periods | โ | Period definitions |
| thr_employee_salary | 514 | Basic salary rates aktif |
| thr_employee_salary_items | 115 | Salary item details |
| thr_employee_allowances | 62 | Recurring allowances |
| thr_employee_deductions | 98 | Recurring deductions |
| thr_pph21_brackets | โ | Indonesia tax brackets |
| thr_ptkp_rates | โ | Indonesia PTKP allowances |
| thr_employee_indonesian_payroll | โ | Indonesia-specific payroll data |
EA Form Generation (Malaysia)
- Service:
eaFormService.js
- Guna
pdf-lib untuk fill official EA form template
- Per-employee, per-tax-year
Indonesia Payroll
- Service:
indonesianPayrollService.js
- Hanya 15 ID employees tapi full infrastructure dah ready
- PPh21 calculation, PTKP allowances
Payslip Output
- PDF generation:
jsPDF + jspdf-autotable
- Simpan dalam
payslips bucket (sekarang 0 files โ generated on-demand)
- Self-service view:
/my-payroll (Level 0)
22. WhatsApp Bot Integration
๐ฑ Hidden integration surface
DB expose 10+ RPC functions untuk WhatsApp bot interaction. Kemungkinan dikonsumsi oleh OpenClaw multi-agent platform (project lain atas VPS berbeza kat 178.104.201.151).
WhatsApp RPC Functions
| Function | Purpose |
checkin_via_whatsapp(phone, ...) | Check in via WhatsApp message |
checkout_via_whatsapp(phone, ...) | Check out |
apply_leave_via_whatsapp(phone, type, start, end, reason) | Submit leave request |
approve_leave_via_whatsapp(...) | Manager approval |
reject_leave_via_whatsapp(...) | Manager rejection |
cancel_leave_via_whatsapp(...) | Cancel pending |
get_my_leave_applications_via_whatsapp(phone) | Self-query |
get_my_leave_balance_via_whatsapp(phone) | Balance check |
get_org_leave_applications_via_whatsapp(...) | HR org overview |
get_org_leave_balances_via_whatsapp(...) | HR org balances |
get_org_leave_summary_via_whatsapp(...) | HR summary |
get_memos_by_phone(phone) / _filtered | Baca memos |
read_memo_by_phone(phone, memo_id) | Mark memo read |
Lookups: phone numbers kemungkinan resolve ke thr_employees via field JSONB contact_info->>'phone'.
Architecture
WhatsApp message
โ
โผ
OpenClaw VPS (178.104.201.151)
โ
โผ Baileys WhatsApp client
โผ Gemini AI parses intent
โ
โผ HTTP/RPC call
THR Supabase โ *_via_whatsapp() RPC functions
โ
โผ SQL operations on thr_* tables
โ
โผ Response
โ
โผ
OpenClaw โ reply via WhatsApp
23. Internationalization
Bahasa yang Disupport
- ๐ฌ๐ง English (en) โ default
- ๐ฎ๐ฉ Indonesian (id)
- โ Bahasa Malaysia (BM sekarang tak disupport walaupun user base utama Malaysian โ biasanya fall back ke English)
Setup
- Library: i18next 25.7 + react-i18next 16.5
- Detection: Browser preference โ localStorage
thr_language override
- Loading: i18next-http-backend (lazy load JSON per namespace dari
/public/locales/)
Namespaces (per file JSON bahasa)
common
auth
dashboard
employees
attendance
navigation
sofia
memos
messages
admin
help
leave
changeRequests
documents
Usage Pattern
const { t } = useTranslation(['namespace', 'common']);
<Button>{t('namespace:submit_button_label')}</Button>
24. Notifications & Email
Multi-Channel Delivery
| Channel | Status | Notes |
| In-app | โ
Active | thr_notifications + NotificationCenter UI |
| Email (SMTP) | โ
Active | From: noreply@dm.todak.io |
| Push (ntfy) | ๐ง Provisioned | thr_employees ada ntfy_topic, push_notifications_enabled |
| WhatsApp | ๐ง Via RPCs | Triggered oleh OpenClaw bot yang berasingan |
Anti-Spam Architecture (4-layer dedup)
- Frontend dedupe โ UI elak double-submit
- Service dedupe โ service layer check recent sends
- UNIQUE INDEX โ DB constraint atas (recipient, content_hash, date)
- Upsert ignoreDuplicates โ safety net terakhir
Incident lepas (Dec 2025): Multi-org employee dapat 10+ duplicate memo emails. Fixed dengan tambah keempat-empat layers.
Email Queue
| Field | Value |
| Total queued (lifetime) | 743 emails |
| Last queued | 2026-03-18 (idle 30+ hari) |
| Test mode flag | thr_email_settings.test_mode |
| Delivery | Edge Function send-email |
๐ญ Email queue idle 30+ hari
Takde emails baru queued sejak 2026-03-18. Either email feature di-pause, diganti, atau ada regression yang patut disiasat.
Notification Triggers
notify_claim_submission ยท notify_claim_status_update
notify_leave_application ยท notify_leave_status_change/update
notify_payroll_ready
notify_announcement ยท notify_new_message
queue_memo_email (2 overloads)
25. Sofia AI Assistant
๐ค Built tapi belum launched
Sofia AI adalah internal chatbot framework yang dah provisioned tapi belum digunakan. Kesemua 5 sofia_* tables kosong.
Sofia Tables (semua kosong)
sofia_conversations โ chat history
sofia_analytics โ usage tracking
sofia_knowledge_base โ RAG content
sofia_document_queue โ doc ingestion
sofia_learning_queue โ feedback loop
Edge Functions Ready
sofia-multi-ai โ multi-model chat
ai-intent โ classify user intent
ai-generate-view โ AI-generated UI views
process-document-ai โ doc analysis
Frontend Services Ready
enhancedSofiaService.js
sofiaDataService.js
sofiaInsightsService.js
sofiaKnowledgeService.js
thrSystemKnowledge.js (AI context)
Future Capability
Extension pgvector 0.8 dah installed โ ready untuk semantic search atas knowledge base bila launched nanti.
26. ATLAS Module (Future)
๐ฆ Asset Management โ placeholder module
Located kat /src/modules/atlas/. Status: FUTURE INTEGRATION. Belum diimplementasi.
Planned Structure
components/ โ UI components berkaitan Asset
pages/ โ Asset management pages (AssetDashboard, AssetInventory, dll.)
services/ โ Asset API services
hooks/ โ React hooks specific untuk Asset
utils/ โ Utility functions Asset
Current State
// /src/modules/atlas/index.js
export const ATLAS_MODULE_READY = false;
// Future exports will include:
// export { default as AssetDashboard } from './pages/AssetDashboard';
// export { default as AssetInventory } from './pages/AssetInventory';
// export * from './services/assetService';
Integration Rules (dari README)
- Letak semua code specific ATLAS dalam module ni
- Guna shared services untuk authentication
- Communicate dengan THR modules via events/services
- JANGAN import terus dari feature modules lain
Future scope kemungkinan: laptop/equipment tracking, asset depreciation (function calculate_asset_depreciation dah wujud dalam DB).
27. Netlify Functions
Located kat /netlify/functions/ โ Node.js serverless functions:
| Function | Purpose |
attendance-check-in.js | Check-in server-side (validate geolocation, upload selfie, write ke thr_attendance) |
attendance-check-out.js | Check-out server-side |
attendance-qr.js | Attendance berasaskan QR code (scan QR kat office) |
attendance-status.js | Dapatkan current attendance status untuk employee |
attendance-team.js | Team attendance overview untuk managers |
ai-chat.js | AI chat endpoint (kemungkinan OpenAI proxy) |
sofia-ai.js | Sofia AI endpoint (multi-model) |
utils/ | Shared helpers |
Kenapa Netlify Functions vs Edge Functions?
- Netlify Functions โ Node.js, cold start lebih panjang, libs lebih banyak. Guna untuk endpoints yang browser-facing.
- Edge Functions (Supabase) โ Deno, lebih laju, run dekat dengan DB. Guna untuk cron/background tasks.
28. Edge Functions (Supabase)
Located kat /supabase/functions/ โ Deno/TypeScript:
| Function | Purpose |
send-email | Send emails via SMTP, process thr_email_queue |
process-document-ai | AI document processing (OCR, extraction) |
sofia-multi-ai | Multi-provider AI chat (OpenAI / Gemini / dll.) |
ai-intent | Classify user intent untuk routing |
ai-generate-view | AI-generated dashboard views/widgets |
auto-checkout | Auto-checkout attendance yang pending (cron @ */15min) |
cleanup-attendance-selfies | Prune attendance selfies lama (cron @ daily 02:00) |
leave-year-end | Year-end leave reset/carryover processing |
_shared/ | Shared TypeScript utilities |
Invocation
- Cron: via pg_cron + pg_net HTTP POST
- Manual:
POST https://ftbtsxlujsnobujwekwx.supabase.co/functions/v1/<name>
- Auth: Service role JWT dalam Authorization header
29. Database Triggers & Cron Jobs
Cron Jobs (pg_cron)
| Name | Schedule | Action |
cleanup-attendance-selfies-daily | 0 2 * * * (daily 02:00) | POST /functions/v1/cleanup-attendance-selfies |
auto-checkout-every-15min | */15 * * * * | POST /functions/v1/auto-checkout |
โ ๏ธ Service JWT inline dalam cron
Kedua-dua cron commands embed service-role JWT terus dalamnya. Rotation kena edit cron command. Consider Supabase Vault untuk secret management.
Important Triggers (~60 total atas public tables)
Audit (auto-write ke thr_audit_trail)
audit_claims_changes atas thr_claims (INSERT/UPDATE/DELETE)
audit_employee_changes atas thr_employees
audit_organization_changes atas thr_organizations
audit_department_changes atas thr_departments
audit_position_changes atas thr_positions
audit_payroll_changes atas thr_payroll_transactions
Auto-numbering
trg_generate_claim_number atas thr_claims
trigger_set_memo_number atas thr_memos
Notifications (DUPLICATES! โ ๏ธ)
trigger_notify_claim_submission ยท _status_update
trg_notify_leave_application + trigger_notify_leave_application โ ๏ธ (duplicate!)
trg_notify_leave_status_change + trigger_notify_leave_status_update โ ๏ธ
trigger_notify_payroll_ready
trigger_notify_announcement
create_message_notification atas thr_messages
Sync / Validation
sync_active_status_trigger atas thr_employees
trg_update_profile_completion atas thr_employees
validate_organization_assignment atas thr_employees
trigger_assign_position_organization atas thr_positions (auto-fill org)
update_employee_photo_url_trigger atas thr_employee_photos (sync URL balik ke thr_employees)
update_conversation_on_message atas thr_messages
30. Audit Trails (4 systems yang overlap)
๐ค Tak jelas yang mana canonical
THR ada EMPAT tables audit/activity yang overlap. Patut consolidate atau document yang mana source of truth.
| Table | Rows | Apparent Purpose |
thr_activity_logs | 11,464 | REST API hits / activity general (partitioned) |
thr_audit_logs | 10,353 | Audit log table (legacy?) |
thr_audit_trail | 7,817 | Change tracking detailed (semasa via triggers) |
thr_audit_trail_archive | 1,397 | Rows audit_trail lama yang diarkib |
thr_change_audit_log | 606 | Change tracking specific |
thr_organization_changes_audit | 125 | Changes org-specific |
thr_system_checkup_logs | 63 | System health checks |
thr_audit_trail (paling detail)
- table_name, record_id, action (INSERT/UPDATE/DELETE)
- user_id, employee_id, organization_id
- old_values (jsonb), new_values (jsonb), changed_fields (text[])
- module, severity, description, metadata (jsonb)
- ip_address, user_agent, session_id, request_id
Maintenance Functions
cleanup_activity_logs() โ int โ prune rows lama
archive_old_audit_records() โ int โ pindah ke archive
create_activity_log_partition() ยท drop_old_activity_log_partitions()
search_audit_trail(query, filters)
31. Deployment
Build & Deploy
| Setting | Value |
| Build tool | Vite 4.5 |
| Output | dist/ |
| Node version | 18 (Netlify default) |
| Install command | npm ci |
| Build command | npm run build |
| Pre-deploy | npm run pre-deploy (security audit) |
Environment Variables yang Diperlukan
| Variable | Purpose |
VITE_SUPABASE_URL | https://ftbtsxlujsnobujwekwx.supabase.co |
VITE_SUPABASE_ANON_KEY atau VITE_SUPABASE_PUBLISHABLE_KEY | Client API key |
VITE_GOOGLE_CLIENT_ID | Google OAuth |
VITE_OPENAI_API_KEY (optional) | Claims AI auto-fill |
VITE_SUPPORT_ADMIN_ID (optional) | Default admin ID untuk support routes |
Netlify Config (netlify.toml)
- Security headers: X-Frame-Options DENY, X-XSS-Protection, X-Content-Type-Options nosniff
- Cache: Hashed assets โ 1 tahun, HTML โ never
- SPA fallback: Semua non-asset routes โ /index.html (untuk client-side routing)
- Functions: 7 Netlify Functions (Node.js)
Branch Strategy
main โ Production (thr.todak.io)
staging โ Staging (staging-thr.netlify.app)
- Kedua-dua share Supabase database yang sama โ takde DB isolation
โ ๏ธ Takde DB isolation
Staging dan Production guna database yang sama. Apa-apa schema migration yang di-test atas staging akan affect production terus. Recommend asingkan Supabase project untuk staging/dev.
Local Development
# Clone
git clone https://github.com/broneotodak/THR.git
cd THR
# Install
npm ci
# Setup .env (copy .env.example)
cp .env.example .env
# Isi VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, VITE_GOOGLE_CLIENT_ID
# Run dev server
npm run dev # โ http://localhost:5173
32. Live Activity Stats (30 hari lepas)
Database Activity
| Metric | Last 30d |
| Activity log entries | 3,309 |
| Attendance check-ins | 1,103 |
| Audit trail events | 634 |
| Audit log entries | 516 |
| Notifications sent | 14 |
| Change requests | 7 |
| Leave applications | 4 |
| Claims submitted | 0 โ ๏ธ |
| Emails queued | 0 โ ๏ธ |
| Messages sent | 0 โ ๏ธ |
| Memos created | 0 โ ๏ธ |
User Login Activity
| Metric | Count |
| Total registered (auth.users) | 245 |
| Login sejam lepas | 2 |
| Login hari ni | 15 |
| Login 7 hari lepas | 40 (~16% users) |
Daily Active Users (7 hari)
| Day | Users |
| Tue 04-21 | 15 โฌ๏ธ |
| Mon 04-20 | 2 |
| Sun 04-19 | 1 |
| Sat 04-18 | 1 |
| Fri 04-17 | 5 |
| Thu 04-16 | 6 |
| Wed 04-15 | 10 |
Data Date Ranges
- thr_attendance: 2025-12-14 โ 2026-04-21 (~4 bulan live)
- thr_audit_trail: 2025-10-31 โ 2026-04-21
- thr_employees record paling lama: 2025-07-13
- thr_leave_applications: 2025-12-24 โ 2026-04-08
- thr_email_queue write terakhir: 2026-03-18 (idle 30+ hari)
33. Common Code Recipes
Ambil Employee Record User Semasa
import { supabase } from '@/core/lib/supabase';
const { data: { user } } = await supabase.auth.getUser();
const { data: employee } = await supabase
.from('thr_employees')
.select('*, organization:thr_organizations(*)')
.eq('auth_user_id', user.id)
.single();
Check User Permission Level
// โ SALAH โ baca dari JSONB
const level = employee.employment_info?.access_level;
// โ
BETUL โ baca dari column
const level = employee.access_level;
if (level >= 7) { /* HR Admin */ }
HR Admin: Dapatkan Orgs yang Accessible
import hrOrganizationService from '@shared/services/hrOrganizationService';
const scope = await hrOrganizationService.getChangeRequestScope(userId);
// { canViewAll: false, organizationIds: ['uuid1', 'uuid2'] }
if (scope.canViewAll) {
query = query; // takde filter โ super admin
} else {
query = query.in('organization_id', scope.organizationIds);
}
Apply Leave (dengan semua required fields)
const { data, error } = await supabase
.from('thr_leave_applications')
.insert({
employee_id: employee.id,
organization_id: employee.organization_id,
leave_type: 'AL', // โ
string, BUKAN leave_type_id
start_date: '2026-05-01',
end_date: '2026-05-03',
total_days: 3,
reason: 'Family vacation',
reporting_to_id: employee.reporting_to_id,
status: 'pending'
});
Submit Claim dengan Receipt
// 1. Upload receipt
const fileName = `${employee.id}/${Date.now()}.jpg`;
await supabase.storage
.from('claim-receipts')
.upload(fileName, file);
const { data: { publicUrl } } = supabase.storage
.from('claim-receipts')
.getPublicUrl(fileName);
// 2. Create claim (claim_no auto-generated oleh trigger)
await supabase.from('thr_claims').insert({
employee_id: employee.id,
claim_type: 'meal',
claim_date: '2026-04-20',
amount: 45.50,
currency: 'MYR',
receipt_url: publicUrl,
status: 'pending',
submitted_by: user.id
});
Check In Attendance
// Dapatkan geolocation
const position = await new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, {
timeout: 10000
});
});
// Capture selfie (kalau perlu)
let selfiePath = null;
if (org.require_attendance_selfie) {
const selfieFile = await captureSelfie(); // camera logic ko
selfiePath = `${employee.id}/${Date.now()}.jpg`;
await supabase.storage
.from('attendance-selfies')
.upload(selfiePath, selfieFile);
}
// Insert attendance
await supabase.from('thr_attendance').insert({
employee_id: employee.id,
organization_id: employee.organization_id,
date: new Date().toISOString().split('T')[0],
check_in: new Date().toISOString(),
check_in_latitude: position.coords.latitude,
check_in_longitude: position.coords.longitude,
check_in_selfie_path: selfiePath,
check_in_method: 'web'
});
Dapatkan Attendance Hari Ni
// Guna view (dah denormalized)
const { data } = await supabase
.from('thr_attendance_today')
.select('*');
// Atau RPC untuk org-scoped
const { data } = await supabase
.rpc('get_organization_attendance_today', { org_id: orgId });
Real-Time Subscription (messages)
const channel = supabase
.channel('messages-channel')
.on('postgres_changes', {
event: 'INSERT',
schema: 'public',
table: 'thr_messages',
filter: `conversation_id=eq.${conversationId}`
}, (payload) => {
setMessages(prev => [...prev, payload.new]);
})
.subscribe();
return () => supabase.removeChannel(channel);
34. Known Issues & Pending Work
Code TODOs
| File | Issue |
modules/employees/pages/AttendanceManagement.jsx | Re-enable team attendance filtering untuk direct reports |
modules/employees/components/CareerDevelopment.jsx | Dapatkan current user ID sebenar |
modules/employees/services/employeeUpdateService.js | Module sync queue belum dibuat |
modules/shared/pages/CustomDashboard.jsx | Widget configuration dialog belum implement |
modules/shared/pages/ClaimsFixed.jsx | Edit functionality pending |
Incidents Lepas (dari CLAUDE.md)
| Date | Incident | Root Cause | Fix |
| 2025-12-17 | 509 employees ada access_level yang salah | JSONB/column desync โ code baca dari JSONB instead of column | Cleanup scripts + rule: selalu guna COLUMN access_level |
| 2025-12-19 | Employee dapat 10+ duplicate memo emails (multi-org) | Employee yang sama dalam multiple orgs trigger N notifications | 4-layer dedup architecture |
| 2025-12-29 | HR Admin org assignment lookup gagal | Query column salah (employee_id vs admin_user_id) | Fixed dalam hrOrganizationService |
Pending Backlog
| Issue | Severity |
| RLS disabled kat key tables (thr_employees, thr_attendance, dll.) | ๐ด Security |
| Dormant RLS policies kat thr_notifications, thr_leave_applications | ๐ก Cleanup |
| Duplicate notification triggers kat thr_leave_applications | ๐ก Bug |
| 197 pending change_requests (47% dari total) | ๐ก Backlog |
| 4 audit systems yang overlap | ๐ก Tech debt |
| Email queue idle 30+ hari | ๐ก Investigation |
| 0 claims submitted dalam 14 hari | ๐ก Adoption |
| active_status vs employment_status drift (~37 row mismatch) | ๐ก Data quality |
| Sofia AI dah provisioned tapi tak guna | ๐ข Opportunity |
| BM (Bahasa Malaysia) i18n tak disupport | ๐ข UX |
| Takde staging DB isolation | ๐ก Risk |
| Test orgs (TORG, PT_TEST_ID) bercampur dengan production | ๐ข Cleanup |
| Manager tier (Lvl 1-6) defined tapi tak guna | ๐ข Roll-out gap |
| 5 storage buckets kosong (certificates, contracts, payslips, dll.) | ๐ข Adoption |
| thr_employees ada 612 rows tapi thr_employee_organizations hanya 510 | ๐ข Data drift |
Critical Coding Rules (dari CLAUDE.md)
๐จ Ingat selalu
- Guna COLUMN
access_level, bukan field JSONB
- Guna JSONB
employment_info->>employment_status (takde top-level column)
- Guna field string
leave_type, bukan leave_type_id
- Check kalau column
updated_at wujud sebelum query โ banyak tables takde
- HR Admin scoping: query
thr_admin_org_assignments.admin_user_id BUKAN employee_id
- Organization PK adalah
organization_id BUKAN id
35. Future Roadmap (Inferred dari Codebase)
Dah Dalam Progress
- Shifts management โ baru ditambah:
thr_organizations.shifts_enabled, shifts jsonb, default_shift_id, thr_employees.assigned_shift_id
- ntfy push notifications โ dah provisioned:
thr_employees.ntfy_topic, push_notifications_enabled
- Sofia AI assistant โ dah built tapi belum launched
Planned (placeholder modules)
- ATLAS Module โ asset management (depreciation function dah wujud)
- Career Development โ UI separa siap dalam
CareerDevelopment.jsx
Next Steps yang Disyorkan
- Security โ Enable RLS atas critical tables, asingkan staging DB, rotate service JWT
- Roll out manager tier โ assign Levels 4-6 kepada managers sebenar (sekarang kosong)
- Siasat adoption โ usage claims/leave sangat rendah, cari friction points
- Consolidate audit โ pilih satu audit table canonical, deprecate yang lain
- Email queue debug โ cari kenapa takde emails sejak 2026-03-18
- Launch Sofia AI โ infrastructure ready, populate knowledge base
- Tambah BM i18n โ user base utama adalah Malaysian
- Mobile app โ sekarang web-only je, attendance check-in akan lebih baik dengan native app
- WhatsApp self-service โ RPCs dah ada, expose lebih banyak kepada OpenClaw bot
Integration Opportunities
- Google Workspace (sekarang connected via GAM7 kat lan-claude VPS) โ auto-provision/deprovision Workspace accounts masa hire/resign
- Drive integration โ auto-backup employee documents ke Workspace Drive
- Calendar sync โ push leave yang approved ke Workspace Calendar
- Gmail audit โ monitor sensitive email patterns