THR Reference

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

EnvURLBranch
Productionhttps://thr.todak.iomain
Staginghttps://staging-thr.netlify.appstaging

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

LayerLibraryVersion
FrameworkReact18.3.1
Build toolVite4.5.3
UI libraryMaterial-UI (MUI)5.16.4
UI extras@mui/lab, @mui/x-date-pickers7 / 6.20
RoutingReact Router DOM6.24.1
StateReact Context API (takde Redux/Zustand)โ€”
StylingEmotion (CSS-in-JS)11.11
Datedate-fns2.30.0
i18ni18next + react-i18next25.7 / 16.5
Drag & Drop@dnd-kit + @hello-pangea/dnd6.3 / 16.6
Resizablereact-resizable + react-grid-layout3.0 / 1.5

Data & Backend

LayerService
DatabaseSupabase (PostgreSQL 15) โ€” project ftbtsxlujsnobujwekwx
Regionap-southeast-1 (Singapore)
AuthSupabase Auth + Google OAuth
StorageSupabase Storage (10 buckets, 3,125 objects)
RealtimeSupabase Realtime (messaging, presence)
Serverless 1Netlify Functions (Node.js) โ€” 7 functions
Serverless 2Supabase Edge Functions (TypeScript/Deno) โ€” 8 functions
HostingNetlify (CDN, branch deploys)
Cronpg_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

PathPurpose
/src/main.jsxEntry point, i18n init, mount React, hide skeleton
/src/App.jsxRouter setup (~30 routes), lazy-loaded, error boundaries
/src/core/lib/supabase.jsSupabase 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.jsRole 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.mdModule/service index auto-generated
/.claude/codebase-index.jsonCodebase index machine-readable
/THR_DOCUMENTATION_SUMMARY.mdTech summary sedia ada
/THR_FULL_DOCUMENTATION.jsonFull schema machine-readable
/RLS-STATUS-REPORT.mdRLS audit report
/netlify.tomlBuild config, security headers, redirects
/vite.config.jsBuild config, path aliases (@modules, @shared, @core, dll.)
/package.jsonv1.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)

GroupTablesPurpose
Employee & Orgthr_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_assignmentsIdentity, structure, multi-org membership
Accessthr_access_levels (11 levels), thr_access_capabilitiesPermission tiers
Leavethr_leave_applications, thr_leave_balances, thr_leave_entitlements, thr_leave_types (10 types), thr_leave_policies, thr_leave_adjustments, thr_leave_recordsLeave management
Attendancethr_attendance, thr_attendance_logs, thr_attendance_locationsCheck-in/out harian, geo, selfie, OT
Claimsthr_claims, thr_claim_items, thr_claim_receipts, thr_claim_types, thr_claim_limits, thr_claim_approval_rulesExpense reimbursement
Payrollthr_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_changesSalary processing
Tax (MY+ID)thr_tax_brackets, thr_statutory_rates, thr_pph21_brackets, thr_ptkp_rates, thr_employee_indonesian_payrollStatutory compliance
Auditthr_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_logsActivity tracking (4 systems yang overlap)
Notificationsthr_notifications, thr_notification_preferences, thr_notification_templates, thr_notification_types, thr_email_queue, thr_email_settingsPush + email alerts
Messagingthr_messages, thr_conversations, thr_message_reactions, thr_blocked_users, thr_user_presenceInternal chat dengan reactions
Memosthr_memos, thr_memo_reads, thr_memo_comments, thr_memo_categories, thr_announcements, thr_announcement_readsInternal announcements dengan read tracking
Documentsthr_documents, thr_employee_documents, thr_document_types, thr_document_categories, thr_employee_photosFile metadata
Dashboardsthr_custom_dashboards, thr_dashboard_widgets, thr_dashboard_templates, thr_widget_typesWidget grid customizable (drag/drop/resize)
AIthr_ai_conversations, thr_ai_preferences, thr_ai_saved_views, sofia_* (5 tables, semua empty)AI assistant (dah provisioned)
Referencethr_public_holidays, thr_milestone_types, thr_approval_rules, thr_system_settings, thr_banks, thr_exchange_ratesLookups, config
Legacy/Backupmaster_hr2000 (518), master_hr2000_backup_2025_07_12, thr_employees_backup_20251231 (578), thr_organizations_backup_20251231, thr_employee_salary_backup_20251231Year-end snapshots, legacy HR2000 import

Top 20 Tables ikut Row Count

TableRows
thr_activity_logs11,464
thr_audit_logs10,353
thr_audit_trail7,817
thr_notifications3,170
thr_attendance2,765
thr_leave_balances1,761
thr_audit_trail_archive1,397
thr_employment_history1,101
thr_memo_reads907
thr_dashboard_widgets867
thr_email_queue743
thr_payroll_transactions723
thr_employees612
thr_change_audit_log606
thr_employees_backup_20251231578
master_hr2000518
thr_employee_salary514
thr_employee_organizations510
thr_change_requests424
thr_employee_employment_types328

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
LvlNamePopulation
0Employee584 (95%)
1Senior Employee0
2Team Lead1
3Supervisor0
4Senior Supervisor0
5Manager0
6Senior Manager0
7HR Administrator (org-scoped)22
8System Administrator5
70Admin (special)0
80Super 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

KeyDescription
ic_number / ic_noMalaysian/Indonesian ID number
date_of_birthYYYY-MM-DD
gendermale / female
marital_statussingle / married / divorced / widowed
raceMalay / Chinese / Indian / Other
religionIslam / Christianity / dll.
nationalityMalaysian / Indonesian / Foreign
display_namePreferred display name
nicknameShort name
emailPersonal email (vs work email dalam contact_info)
spouse_detailsJSONB info pasangan
spouse_historyJSONB array

contact_info

KeyDescription
phone / phone_numberMobile (guna untuk WhatsApp lookup!)
emailWork email (biasanya Google Workspace)
emailsArray additional emails
addressJSONB nested address
emergency_contactJSONB {name, phone, relationship}

employment_info

KeyDescription
employment_statusactive / resigned / archived (CRITICAL โ€” hanya ada dalam JSONB)
employee_typePermanent / Contract / Intern
hire_dateYYYY-MM-DD
resign_date / last_working_date / termination_dateExit dates
rehire_effective_dateKalau rehired
tenure_yearsTenure yang dikira
probation_monthsProbation period
starting_salarySalary pertama masa hire
department_id / department / section_idKadang-kadang duplicate dari columns
active_overrideBool untuk override special active flag
inactive_reasonReason untuk inactive
historical_snapshotJSONB preserve historical state

compensation

KeyDescription
basic_salaryBasic salary semasa (juga ada dalam thr_employee_salary)
salary_effective_dateEffective date salary semasa
fixed_allowancesJSONB array allowances recurring
allowancesJSONB array (format alternate)

bank_info

KeyDescription
bank_name / nameNama bank (contoh Maybank)
account_noAccount number
codeBank code
type / payment_typeAccount type
payment_viaBank transfer / Cash / Cheque
payment_frequencyMonthly / Bi-weekly

tax_info

KeyDescription
income_taxTax number (LHDN MY)
epfEPF number (Malaysia)
socsoSOCSO number (Malaysia)
eisEIS contribution flag/number
zakatZakat 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)

CategoryViewPurpose
Employeethr_employees_viewActive employees dengan email/phone/display_name denormalized dari JSONB
Employeethr_employees_full_viewVariant full-detail
Employeevw_employee_access_variationsAudit access pattern
Employeevw_employee_all_organizationsMulti-org employee view
Orgthr_organizations_with_countryOrgs joined dengan country lookup
Orgv_admin_accessible_orgsOrgs yang filtered untuk admin user semasa
Orgthr_recent_organization_changesOrg modifications terkini
Attendancethr_attendance_todayCheck-ins hari ni (320 rows)
Leavev_leave_balances_jsonLeave balances aggregated sebagai JSON
Claimsthr_claims_with_conversion / _with_currencyClaims dengan FX conversion baked in
Claimsv_employee_claims_summaryClaims totals per-employee
Payrollvw_payroll_summary / vw_payroll_complete_summaryBatch & component breakdown
Auditv_audit_trail_summaryAudit metrics aggregated
Auditv_audit_user_activity / _module_activity / _critical_actionsAudit slicing ikut dimension
Activityv_activity_summary / v_user_activity_statsApp activity stats
Memosthr_memos_with_accessMemos dengan permission filter
Memosemployee_memosMemo feed per-employee
Memosthr_support_messages_viewView macam support ticket
Systemvw_latest_system_checkupHealth check result terkini
Documentsthr_employee_documents_view, thr_employee_photos_viewDocument/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

BucketPublic?Size LimitObjectsPurpose
attendance-selfiesโŒ Private512 KB3,063Check-in/out selfies (auto-cleaned daily)
memosโœ… Public10 MB30Memo attachments
employee-photosโœ… Public(takde)28Profile photos
claim-receiptsโœ… Public(takde)1Expense claim receipts
employee-documentsโŒ Private10 MB3Personal documents (IC, dll.)
company-policiesโœ… Public10 MB0HR policy documents
employee-certificatesโŒ Private5 MB0Training/cert files
employee-contractsโŒ Private5 MB0Employment contracts
payslipsโŒ Private2 MB0Payslip PDFs yang di-generate
thr-documentsโŒ Private10 MB0Generic 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

HookReturnsPurpose
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):

KeyValue
provider_idUser ID dari Google
full_name / nameDari Google profile
email / email_verifiedGoogle email + verified flag
iss / subOAuth issuer + subject
picture / avatar_urlGoogle profile photo (243/245)
custom_claimsOAuth IdP claims (BUKAN app-injected)

11. Roles & Permissions

Access Level System (numeric 0-8 + special 70/80)

LvlRoleCapabilitiesActive
0EmployeeSelf-service: apply leave, clock in, submit claims, tengok payslip/profile sendiri584
1Senior EmployeeExtended viewing, mentoring features0
2Team LeadTeam view, basic approvals1
3SupervisorTeam management, approvals terhad0
4Senior SupervisorDepartment access, leave approvals, team reports0
5ManagerMulti-department, advanced approvals0
6Senior ManagerDivision access0
7HR Administrator (org-scoped)HR functions, payroll, leave config โ€” terhad kepada orgs yang assigned via thr_admin_org_assignments22
8System AdministratorSystem-wide access, semua orgs, takde scoping5

โš ๏ธ 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.

CountryCurrencyOrg Count
Malaysia ๐Ÿ‡ฒ๐Ÿ‡พMYR21
Indonesia ๐Ÿ‡ฎ๐Ÿ‡ฉIDR3 (PNJR, PT_TEST_ID, PTNG)

Semua Organizations Aktif

CodeNameEmployees
TCSBTodak Culture Sdn. Bhd.149
TASBTodak Academy Sdn. Bhd.127
TSSBTodak Studios Sdn. Bhd.84
THTodak Holdings Sdn. Bhd.76
TDSBTodak Digitech Sdn. Bhd.29
HSBHyleen Sdn. Bhd.21
TTKTadika Todak Kids21
MTSBMy Barber Tech18
LTCMLan Todak Consultation18
PTNGPT Todak Nusantara Group (ID)13
TFSBTodak Fusion11
TPSBTodak Paygate10
10C10 Camp Enterprise9
MHMuscle Hub7
โ€”Sepinggan 11 Resources5
โ€”Sarcom Technology5
TORGTEST ORG โš ๏ธ4
โ€”Todak RC Enterprise3
PT_TEST_IDPT Test Indonesia โš ๏ธ2
PNJRPT 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

TablePoliciesRLS Enabled?
thr_claims18โœ… Ya (enforcement paling berat)
thr_notifications16โŒ OFF โ€” policies dead
thr_leave_applications11โŒ OFF โ€” policies dead
sofia_conversations10(table kosong)
thr_messages10โœ… Ya
thr_change_requests9โœ… Ya
thr_payroll_transactions8โœ… Ya
thr_organizations7โŒ OFF
thr_staff_categories6โ€”
thr_payroll_batches / positions / salary_history / activity_logs5 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

  1. Enable RLS atas critical tables โ€” start dengan thr_employees, thr_attendance, thr_leave_applications
  2. Asingkan Supabase project untuk staging โ€” isolate dari production
  3. Rotate service-role JWT dalam cron jobs โ€” guna Supabase Vault
  4. Padam test orgs dari production (TORG, PT_TEST_ID)
  5. Tambah MFA enforcement untuk super admin accounts
  6. Audit log retention policy โ€” sekarang takde automatic pruning untuk activity_logs (11k+ rows makin membesar)

15. Semua Routes

PathMin LvlComponentPurpose
/loginPublicLoginSign in (Google OAuth + email/password)
/auth/callbackPublicโ€”OAuth redirect handler
/reset ยท /logoutPublicโ€”Password reset ยท sign out
/dashboard0DashboardMain hub, role-based widgets, customizable layout
/employees4EmployeeDirectoryBrowse semua employees dalam scope
/employee/:id0EmployeeDetailsPageProfile (sendiri atau yang accessible)
/leave0LeaveManagementApply leave, tengok balance, history
/leave/approvals4LeaveApprovalsApprove/reject team requests
/leave-balance7LeaveManagementAdminConfigure leave types / entitlement
/attendance0AttendanceManagementClock in/out, selfie, geo, calendar
/attendance-admin7AttendanceManagementAdminOrg-wide attendance, late tracker
/payroll7PayrollManagementV3Batches, EA forms, payslips
/my-payroll0EmployeePayslipViewTengok payslip sendiri
/claims0ClaimsFixedSubmit / manage expenses
/admin8AdminSettingsSystem configuration
/admin/access-control8AccessControlRole assignment
/admin/users8UsersManagementCreate/edit users
/admin/org-assignments7AdminOrgAssignmentsMap HR admins โ†’ orgs
/admin/audit-trail8AuditTrailActivity logs viewer
/admin/change-requests7ChangeRequestsApprove profile changes (197 pending!)
/admin/activity-dashboard7ActivityDashboardActivity overview
/admin/system-checkup8SystemCheckupHealth check
/admin/milestone-types8MilestoneTypesManagementCareer milestones config
/admin/employment-types8EmploymentTypesManagementEmployment type config
/pcb-management7PCBManagementConfig Malaysia PCB tax brackets
/documents0DocumentManagementFiles (employee/org)
/messages0MessagesInternal real-time chat
/notifications0NotificationCenterAlerts + announcements feed
/memos ยท /memos/:id0Memos / MemoDetailTengok & comment memos
/memos/create7CreateMemoTulis memo baru
/profile0ProfileEdit profile sendiri
/onboarding0EmployeeProfileCompletionLengkapkan profile yang missing

16. Modules Breakdown

Located dalam /src/modules/ โ€” 11 modules:

ModulePagesMin LevelPurpose
employeesEmployeeDirectory, EmployeeDetailsPage, Profile, Organizations, MyOrganizations, EmployeeProfileCompletion, Memos, CreateMemo, MemoDetail, CareerDevelopment0+Identity, profiles, photos, memos, onboarding
attendanceAttendancePage, AttendanceManagement0 / 7Clock in/out, selfie, geo, calendar
leaveLeaveManagement, LeaveApprovals, TeamReports, LeaveManagementAdmin0 / 4 / 7Apply, approve, configure
payrollPayrollManagementV3, EmployeePayslipView7 / 0Batches, EA, payslips, statutory
claimsClaimsFixed0+Submit, AI fill, approve
adminAdminSettings, AccessControl, UsersManagement, PCBManagement, AuditTrail, ChangeRequests, ActivityLogs, ActivityDashboard, SystemCheckup, MilestoneTypesManagement, EmploymentTypesManagement7 / 8System config, role mgmt, audit
documentsDocumentManagement, OrganizationDocumentManagement0 / 7File upload/organize
messagingMessages0+Real-time chat (Supabase Realtime)
notificationsNotificationCenter, NotificationSettings, NotificationSetup0+In-app + email alerts
authLogin, ResetPublicAuthentication 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

ServicePurpose
employeeService.jsCRUD employee records, JSONB blob handling
employeeService-multiorg-fix.jsMulti-org employee handling (post-Dec 2025 fix)
employeeService-safe-fix.jsDefensive employee operations
hrOrganizationService.jsHR Admin org-scoping, getChangeRequestScope()
departmentValidationService.jsValidate department assignments
changeRequestService.jsProfile change request workflow

Leave & Attendance

ServicePurpose
leaveEntitlementService.jsKira leave entitlements per employee
leaveYearEndService.jsYear-end leave reset/carryover
locationService.jsGPS + IP geolocation (3 fallback APIs)
publicHolidayService.jsPublic holiday calendar (67 holidays loaded)

Communications

ServicePurpose
memoService.jsMemo CRUD, read tracking
notificationIntegrationService.jsMulti-channel notification dispatch
emailPriorityService.jsEmail queue priority
ntfyService.jsntfy push notification (dah provisioned)

AI Layer (multi-provider)

ServicePurpose
aiService.jsGeneric AI client (semasa)
aiServiceLocal.jsLocal LLM fallback
aiServiceSecure.jsServer-side AI (elak expose API key client-side)
multiAIService.jsProvider abstraction layer
openaiDirect.js.disabledDisabled โ€” direct OpenAI client (security risk)
enhancedSofiaService.jsSofia AI assistant features
sofiaDataService.jsSofia data layer
sofiaInsightsService.jsSofia analytics
sofiaKnowledgeService.jsSofia RAG knowledge base
thrSystemKnowledge.jsSystem knowledge untuk AI context

Agent Routing System

ServicePurpose
agentEnforcement.jsEnforce agent boundaries
agentRoutingSystem.jsRoute requests ke AI agent yang sesuai
autoRouter.js ยท autoRouterTest.jsAuto-routing logic + tests
memoryAgentService.jsAgent memory layer
taskAnalyzer.jsAnalyze task type untuk routing
AGENT_ROUTING_README.mdRouting documentation

Infrastructure

ServicePurpose
auditService.jsWrite ke thr_audit_trail
errorLoggingService.jsError tracking centralized
storageService.jsGeneric file upload/download
secureStorageService.jsEncrypted storage
featureFlagsService.jsFeature flag toggles
hookManager.jsWebhook/event manager
queueManager.jsBackground job queue
dashboardService.jsDashboard 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

DateCheck-insDay
2026-04-2158Tue
2026-04-2059Mon
2026-04-199Sun (low)
2026-04-186Sat (low)
2026-04-1756Fri
2026-04-1655Thu
2026-04-1555Wed

~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)

CodeName
ALAnnual Leave
MCMedical Certificate
ELEmergency Leave
CLCompassionate Leave
MATMaternity Leave
PATPaternity Leave
HLHospitalization Leave
MLMarriage Leave
UPLUnpaid Leave
RLReplacement 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

CountryComponentRate
๐Ÿ‡ฒ๐Ÿ‡พ MalaysiaEPF (Employees Provident Fund)Employee 11%, Employer 12-13%
SOCSOTiered ikut salary
EIS (Employment Insurance)0.2% kedua-dua belah
PCB (Income Tax)Progressive โ€” calc via calculate_pcb_simple()
๐Ÿ‡ฎ๐Ÿ‡ฉ IndonesiaBPJS Ketenagakerjaan TK JHTEmployee 2%, Employer 3.7%
JP (Pension)1% / 2%
Kesehatan (Health)1% / 4%

Payroll Tables

TableRowsPurpose
thr_payroll_batches13Pay period grouping
thr_payroll_transactions723Per-employee per-component line items
thr_payroll_recordsโ€”Records yang detailed
thr_payroll_periodsโ€”Period definitions
thr_employee_salary514Basic salary rates aktif
thr_employee_salary_items115Salary item details
thr_employee_allowances62Recurring allowances
thr_employee_deductions98Recurring 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

FunctionPurpose
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) / _filteredBaca 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

ChannelStatusNotes
In-appโœ… Activethr_notifications + NotificationCenter UI
Email (SMTP)โœ… ActiveFrom: noreply@dm.todak.io
Push (ntfy)๐Ÿ”ง Provisionedthr_employees ada ntfy_topic, push_notifications_enabled
WhatsApp๐Ÿ”ง Via RPCsTriggered oleh OpenClaw bot yang berasingan

Anti-Spam Architecture (4-layer dedup)

  1. Frontend dedupe โ€” UI elak double-submit
  2. Service dedupe โ€” service layer check recent sends
  3. UNIQUE INDEX โ€” DB constraint atas (recipient, content_hash, date)
  4. 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

FieldValue
Total queued (lifetime)743 emails
Last queued2026-03-18 (idle 30+ hari)
Test mode flagthr_email_settings.test_mode
DeliveryEdge 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)

  1. Letak semua code specific ATLAS dalam module ni
  2. Guna shared services untuk authentication
  3. Communicate dengan THR modules via events/services
  4. 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:

FunctionPurpose
attendance-check-in.jsCheck-in server-side (validate geolocation, upload selfie, write ke thr_attendance)
attendance-check-out.jsCheck-out server-side
attendance-qr.jsAttendance berasaskan QR code (scan QR kat office)
attendance-status.jsDapatkan current attendance status untuk employee
attendance-team.jsTeam attendance overview untuk managers
ai-chat.jsAI chat endpoint (kemungkinan OpenAI proxy)
sofia-ai.jsSofia 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:

FunctionPurpose
send-emailSend emails via SMTP, process thr_email_queue
process-document-aiAI document processing (OCR, extraction)
sofia-multi-aiMulti-provider AI chat (OpenAI / Gemini / dll.)
ai-intentClassify user intent untuk routing
ai-generate-viewAI-generated dashboard views/widgets
auto-checkoutAuto-checkout attendance yang pending (cron @ */15min)
cleanup-attendance-selfiesPrune attendance selfies lama (cron @ daily 02:00)
leave-year-endYear-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)

NameScheduleAction
cleanup-attendance-selfies-daily0 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.

TableRowsApparent Purpose
thr_activity_logs11,464REST API hits / activity general (partitioned)
thr_audit_logs10,353Audit log table (legacy?)
thr_audit_trail7,817Change tracking detailed (semasa via triggers)
thr_audit_trail_archive1,397Rows audit_trail lama yang diarkib
thr_change_audit_log606Change tracking specific
thr_organization_changes_audit125Changes org-specific
thr_system_checkup_logs63System 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

SettingValue
Build toolVite 4.5
Outputdist/
Node version18 (Netlify default)
Install commandnpm ci
Build commandnpm run build
Pre-deploynpm run pre-deploy (security audit)

Environment Variables yang Diperlukan

VariablePurpose
VITE_SUPABASE_URLhttps://ftbtsxlujsnobujwekwx.supabase.co
VITE_SUPABASE_ANON_KEY atau VITE_SUPABASE_PUBLISHABLE_KEYClient API key
VITE_GOOGLE_CLIENT_IDGoogle 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

MetricLast 30d
Activity log entries3,309
Attendance check-ins1,103
Audit trail events634
Audit log entries516
Notifications sent14
Change requests7
Leave applications4
Claims submitted0 โš ๏ธ
Emails queued0 โš ๏ธ
Messages sent0 โš ๏ธ
Memos created0 โš ๏ธ

User Login Activity

MetricCount
Total registered (auth.users)245
Login sejam lepas2
Login hari ni15
Login 7 hari lepas40 (~16% users)

Daily Active Users (7 hari)

DayUsers
Tue 04-2115 โฌ†๏ธ
Mon 04-202
Sun 04-191
Sat 04-181
Fri 04-175
Thu 04-166
Wed 04-1510

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

FileIssue
modules/employees/pages/AttendanceManagement.jsxRe-enable team attendance filtering untuk direct reports
modules/employees/components/CareerDevelopment.jsxDapatkan current user ID sebenar
modules/employees/services/employeeUpdateService.jsModule sync queue belum dibuat
modules/shared/pages/CustomDashboard.jsxWidget configuration dialog belum implement
modules/shared/pages/ClaimsFixed.jsxEdit functionality pending

Incidents Lepas (dari CLAUDE.md)

DateIncidentRoot CauseFix
2025-12-17509 employees ada access_level yang salahJSONB/column desync โ€” code baca dari JSONB instead of columnCleanup scripts + rule: selalu guna COLUMN access_level
2025-12-19Employee dapat 10+ duplicate memo emails (multi-org)Employee yang sama dalam multiple orgs trigger N notifications4-layer dedup architecture
2025-12-29HR Admin org assignment lookup gagalQuery column salah (employee_id vs admin_user_id)Fixed dalam hrOrganizationService

Pending Backlog

IssueSeverity
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

  1. Security โ€” Enable RLS atas critical tables, asingkan staging DB, rotate service JWT
  2. Roll out manager tier โ€” assign Levels 4-6 kepada managers sebenar (sekarang kosong)
  3. Siasat adoption โ€” usage claims/leave sangat rendah, cari friction points
  4. Consolidate audit โ€” pilih satu audit table canonical, deprecate yang lain
  5. Email queue debug โ€” cari kenapa takde emails sejak 2026-03-18
  6. Launch Sofia AI โ€” infrastructure ready, populate knowledge base
  7. Tambah BM i18n โ€” user base utama adalah Malaysian
  8. Mobile app โ€” sekarang web-only je, attendance check-in akan lebih baik dengan native app
  9. 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

๐Ÿ“Š Auto-Generated Summary

Last updated: 2026-04-23 08:02:51 UTC by THRCC docs-updater

Project Stats

Branchstaging
Components205
Modulesadmin, atlas, attendance, auth, claims, documents, employees, leave, messaging, notifications, payroll
Netlify Functionsai-chat.js, attendance-check-in.js, attendance-check-out.js, attendance-qr.js, attendance-status.js, attendance-team.js, sofia-ai.js, utils
Edge FunctionsN/A

Recent Commits

HashAuthorMessageWhen
c76bb64Kamiera THRCCfeat(directory): Add Person View toggle to Employee Directory7 seconds ago
c9679e8Kamiera THRCCstyle(nav): Revert font size, increase section header container padding4 hours ago
d523887Kamiera THRCCchore: add THRCC Claude instructions for staging workflow4 hours ago
d4b819fKamiera THRCCstyle(nav): Increase side menu section header font size5 hours ago
4a43ccebroneotodakfix(email): Auto-process queue after memo email queuing9 weeks ago
e9598f7broneotodakfeat(admin): Add Send Test Email button to Email Queue Dashboard9 weeks ago
4bc9ea9broneotodakfix(email): Remove auto_send_emails trigger to fix duplicate delivery9 weeks ago
fc6dc7abroneotodakfix(email): Process 1 email per Edge Function invocation9 weeks ago
0e6ced1broneotodakfix(email): Replace denomailer with raw SMTP to eliminate duplicates9 weeks ago
7d5ff98broneotodakfix(email): Isolate SMTP sends + add claim lock to prevent duplicates9 weeks ago