Skip to main content

Database Schema

BonardaHR uses PostgreSQL 15+ as its database with Flyway for version-controlled schema migrations.


Core Design Principles​

  1. Hybrid Data Model — Fixed columns for core fields + JSONB for configurable employee sections
  2. Audit Trail — All main tables include created_at, updated_at, created_by, updated_by
  3. Soft Deletes — Where appropriate, status flags are used instead of hard deletes
  4. Referential Integrity — Foreign keys with appropriate CASCADE / SET NULL actions
  5. Performance — Indexes on foreign keys, search fields, and JSONB queries (GIN)
  6. Public IDs — Every entity exposes a UUID public_id to clients; the internal BIGSERIAL id is never returned by the API

Entity Relationship Overview​

employees (1) ──── (N) employee_field_values
│ │
│ (self-ref) section_fields
└── reports_to_id │
employee_sections
│
ā”œā”€ā”€ (N) ──── (N) roles (via employee_roles)
│ │
│ roles (N) ──── (N) permissions (via role_permissions)
│
ā”œā”€ā”€ (1) ──── (N) time_off_balances ──── time_off_types
│
└── (1) ──── (N) time_off_requests ──── time_off_types
│
reviewer_id ──── employees

Core Tables​

employees​

Core employee information with a fixed schema.

ColumnTypeNotes
idBIGSERIAL PKInternal identifier — never exposed via API
public_idUUID NOT NULL UNIQUEAPI-facing identifier (added in V3)
first_nameVARCHAR(100) NOT NULL
last_nameVARCHAR(100) NOT NULL
emailVARCHAR(255) UNIQUE NOT NULLCompany email
phone_numberVARCHAR(20)
positionVARCHAR(100)Job title
locationVARCHAR(100)Office, city, or remote
birthdayDATE
hire_dateDATE NOT NULL
statusVARCHAR(20) NOT NULLACTIVE, INACTIVE, ON_LEAVE, TERMINATED
reports_to_idBIGINT FKSelf-referencing — manager
microsoft_user_idVARCHAR(255) UNIQUEAzure AD user ID for SSO
Audit fieldsversion, created_at, updated_at, created_by, updated_by

Indexes: public_id (unique), reports_to_id, email, status, hire_date, microsoft_user_id

Constraints:

  • fk_employees_reports_to — self-referencing FK ON DELETE SET NULL
  • chk_employee_status — status must be one of (ACTIVE, INACTIVE, ON_LEAVE, TERMINATED)

Application-computed fields:

  • tenure — Period between hire_date and today
  • fullName — concatenation of first_name + ' ' + last_name
  • effectiveStatus — if stored status is ACTIVE and employee has approved time-off today → displayed as ON_LEAVE

employee_sections​

Defines configurable section categories that can appear on employee profiles.

ColumnTypeNotes
idBIGSERIAL PK
nameVARCHAR(100) UNIQUE NOT NULLInternal key (e.g. "payroll")
display_nameVARCHAR(100) NOT NULLUser-facing label (e.g. "Payroll Information")
descriptionTEXT
display_orderINTEGER NOT NULL DEFAULT 0
is_activeBOOLEAN NOT NULL DEFAULT true
required_permissionVARCHAR(100)Permission to view this section on another profile; NULL = visible to all

section_fields​

Field definitions within a section (the schema for dynamic fields).

Each field has a field_type (TEXT, NUMBER, DATE, BOOLEAN, SELECT, MULTI_SELECT), validation_rules (JSONB), and editable_by (SYSTEM, HR_ONLY, EMPLOYEE).


employee_field_values​

Stores the actual dynamic field values for each employee as JSONB.

ColumnTypeNotes
employee_idBIGINT FKWhich employee
field_idBIGINT FKWhich field definition
valueJSONBThe stored value

GIN index on value for efficient JSONB queries.


Time Off Tables​

time_off_types​

Defines leave categories (e.g. Annual Leave, Sick Leave, Maternity).

Key columns: name, is_unlimited (boolean), default_days, attachment_requirement (NEVER/ALWAYS/CONDITIONAL), attachment_required_after_days, counts_towards_bradford (boolean — see ADR-026).


time_off_balances​

Denormalised per-employee, per-type, per-year balance records.

ColumnTypeNotes
employee_idBIGINT FK NOT NULL
time_off_type_idBIGINT FK NOT NULL
yearINTEGER NOT NULLCalendar year
total_allocatedNUMERIC(5,1) DEFAULT 0
usedNUMERIC(5,1) DEFAULT 0Approved and consumed days
pendingNUMERIC(5,1) DEFAULT 0Days in pending requests (denormalised)
carry_overNUMERIC(5,1) DEFAULT 0Days carried from previous year

Constraints: UNIQUE(employee_id, time_off_type_id, year); CHECK(used + pending <= total_allocated + carry_over)

Application-computed: remaining = total_allocated + carry_over - used - pending


time_off_requests​

Leave requests with full approval workflow. See HR Workflows for lifecycle details.

ColumnTypeNotes
employee_idBIGINT FK NOT NULLRequester
time_off_type_idBIGINT FK NOT NULLLeave type
start_dateDATE NOT NULL
end_dateDATE NOT NULL
half_dayBOOLEAN DEFAULT false
half_day_periodVARCHAR(20)MORNING or AFTERNOON
business_daysNUMERIC(5,1) NOT NULLCalculated weekday count; half-day = 0.5
statusVARCHAR(20) DEFAULT 'PENDING'PENDING, APPROVED, REJECTED, CANCELLED
reviewer_idBIGINT FKApproving/rejecting employee
review_noteTEXTReviewer's comment
reviewed_atTIMESTAMP

Indexes: employee_id, status, (start_date, end_date)


Reports & Analytics Tables​

bradford_settings​

Single-row configuration table for Bradford Factor risk thresholds.

ColumnDefaultDescription
low_threshold50Scores below this are LOW risk
medium_threshold200Scores below this (and ≄ low) are MEDIUM risk
high_threshold500Scores below this (and ≄ medium) are HIGH; ≄ high are CRITICAL

Bradford Factor Formula: S² Ɨ D

  • S = number of separate absence spells (approved requests where counts_towards_bradford = true)
  • D = total days absent
  • Rolling 52-week window

Reports Performance Indexes (V12)​

IndexTablePurpose
idx_time_off_requests_bradfordtime_off_requestsBradford queries (approved requests)
idx_employees_exit_dateemployeesTurnover analysis
idx_time_off_requests_employee_approvedtime_off_requestsPer-employee approved requests
idx_timesheets_status_weektimesheetsTimesheet compliance queries

Migration History​

All schema changes go through Flyway migrations in src/main/resources/db/migration/.

Naming convention: V{version}__{description}.sql

VersionFilePurpose
V1create_employee_tables.sqlCore schema: employees, sections, fields, roles, permissions, RBAC, audit
V2create_time_off_tables.sqlTime off types, balances, requests, unlimited leave, attachments
V3create_timesheet_tables.sqlTimesheet management with weekly entries
V4create_departments_and_positions.sqlDepartments and positions
V5create_document_tables.sqlDocument management with signatures
V6create_company_events.sqlCompany-wide events for dashboard
V7create_sites.sqlSite/location management
V8–V10VariousNotifications, folder conditions, HR oversight
V11add_reports_feature.sqlcounts_towards_bradford flag, REPORT_READ permission
V12add_reports_indexes.sqlPerformance indexes for Bradford Factor and reports queries
V13add_bradford_settings.sqlConfigurable Bradford Factor thresholds
V14–V19VariousTime-off attachments, folder conditions, additional features
caution

Never modify an existing migration file. Always create a new V{next}__description.sql for any schema change.


Backup & Recovery​

Daily Backups​

pg_dump bonarda_hr > backup_$(date +%Y%m%d).sql

Point-in-Time Recovery​

PostgreSQL is configured with WAL archiving for PITR capability in production.


Security Considerations​

  1. Audit Logging — All changes tracked via created_by / updated_by audit fields
  2. Connection Pooling — HikariCP with prepared statements to prevent SQL injection
  3. Row-Level Security (planned) — For future multi-tenant support
  4. Encrypted Columns (planned) — Sensitive data (SSN, salary) to be encrypted at rest

Monitoring & Maintenance​

Key Metrics to Monitor​

  • Table sizes and growth rates
  • Index usage and bloat
  • Slow query log analysis
  • Connection pool utilisation
  • JSONB field value sizes

Regular Maintenance Schedule​

TaskFrequency
VACUUM ANALYZEWeekly
Index rebuildQuarterly
Statistics updateAfter bulk operations
Slow query reviewMonthly

Time Off Permissions Reference (V4)​

PermissionAssigned To
TIME_OFF_TYPE_CREATEADMIN, HR_MANAGER
TIME_OFF_TYPE_READADMIN, HR_MANAGER, MANAGER, EMPLOYEE
TIME_OFF_TYPE_UPDATEADMIN, HR_MANAGER
TIME_OFF_TYPE_DELETEADMIN
TIME_OFF_REQUEST_CREATEADMIN, HR_MANAGER, MANAGER, EMPLOYEE
TIME_OFF_REQUEST_READ_OWNADMIN, HR_MANAGER, MANAGER, EMPLOYEE
TIME_OFF_REQUEST_READ_TEAMADMIN, HR_MANAGER, MANAGER
TIME_OFF_REQUEST_READ_ALLADMIN, HR_MANAGER
TIME_OFF_REQUEST_APPROVEADMIN, HR_MANAGER, MANAGER
TIME_OFF_BALANCE_READ_OWNADMIN, HR_MANAGER, MANAGER, EMPLOYEE
TIME_OFF_BALANCE_READ_ALLADMIN, HR_MANAGER
TIME_OFF_BALANCE_ADJUSTADMIN, HR_MANAGER