CREATE TABLE staff_employees ( id VARCHAR(26) PRIMARY KEY CHECK (id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), tenant_id VARCHAR(26) NOT NULL CHECK (tenant_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), core_user_id VARCHAR(26) CHECK (core_user_id IS NULL OR core_user_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), personnel_number TEXT NOT NULL, display_name TEXT NOT NULL, position TEXT NOT NULL DEFAULT '', gender TEXT NOT NULL DEFAULT '' CHECK (gender IN ('', 'male', 'female')), status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'blocked', 'dismissed')), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, id), UNIQUE (tenant_id, personnel_number), UNIQUE (tenant_id, core_user_id) ); CREATE TABLE staff_attendance_events ( id VARCHAR(26) PRIMARY KEY CHECK (id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), tenant_id VARCHAR(26) NOT NULL CHECK (tenant_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), employee_id VARCHAR(26) NOT NULL, event_type TEXT NOT NULL CHECK (event_type IN ('check_in', 'check_out')), source TEXT NOT NULL CHECK (source IN ('terminal', 'mobile_app', 'manual_entry', 'web_portal')), source_event_id TEXT NOT NULL, occurred_at TIMESTAMPTZ NOT NULL, received_at TIMESTAMPTZ NOT NULL DEFAULT now(), device_id VARCHAR(26) CHECK (device_id IS NULL OR device_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), status TEXT NOT NULL DEFAULT 'accepted' CHECK (status IN ('accepted', 'rejected', 'review_required')), actor_id VARCHAR(26) NOT NULL CHECK (actor_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), correlation_id VARCHAR(26) NOT NULL CHECK (correlation_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, source, source_event_id), FOREIGN KEY (tenant_id, employee_id) REFERENCES staff_employees(tenant_id, id) ); CREATE INDEX staff_attendance_employee_time_idx ON staff_attendance_events (tenant_id, employee_id, occurred_at DESC); CREATE TABLE staff_timesheet_entries ( id VARCHAR(26) PRIMARY KEY CHECK (id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), tenant_id VARCHAR(26) NOT NULL CHECK (tenant_id ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), employee_id VARCHAR(26) NOT NULL, work_date DATE NOT NULL, minutes_worked INTEGER NOT NULL DEFAULT 0 CHECK (minutes_worked BETWEEN 0 AND 1440), status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'submitted', 'approved', 'rejected')), note TEXT NOT NULL DEFAULT '', revision INTEGER NOT NULL DEFAULT 1 CHECK (revision > 0), updated_by VARCHAR(26) NOT NULL CHECK (updated_by ~ '^[0-9A-HJKMNP-TV-Z]{26}$'), created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now(), UNIQUE (tenant_id, employee_id, work_date), FOREIGN KEY (tenant_id, employee_id) REFERENCES staff_employees(tenant_id, id) ); CREATE INDEX staff_timesheet_period_idx ON staff_timesheet_entries (tenant_id, work_date, employee_id); ALTER TABLE staff_employees ENABLE ROW LEVEL SECURITY; ALTER TABLE staff_employees FORCE ROW LEVEL SECURITY; CREATE POLICY staff_employees_tenant_isolation ON staff_employees USING (tenant_id = current_setting('app.tenant_id', true)) WITH CHECK (tenant_id = current_setting('app.tenant_id', true)); ALTER TABLE staff_attendance_events ENABLE ROW LEVEL SECURITY; ALTER TABLE staff_attendance_events FORCE ROW LEVEL SECURITY; CREATE POLICY staff_attendance_events_tenant_isolation ON staff_attendance_events USING (tenant_id = current_setting('app.tenant_id', true)) WITH CHECK ( tenant_id = current_setting('app.tenant_id', true) AND actor_id = current_setting('app.user_id', true) ); ALTER TABLE staff_timesheet_entries ENABLE ROW LEVEL SECURITY; ALTER TABLE staff_timesheet_entries FORCE ROW LEVEL SECURITY; CREATE POLICY staff_timesheet_entries_tenant_isolation ON staff_timesheet_entries USING (tenant_id = current_setting('app.tenant_id', true)) WITH CHECK ( tenant_id = current_setting('app.tenant_id', true) AND updated_by = current_setting('app.user_id', true) ); GRANT SELECT, INSERT, UPDATE ON staff_employees TO krona_staff_app; GRANT SELECT, INSERT ON staff_attendance_events TO krona_staff_app; REVOKE UPDATE, DELETE, TRUNCATE ON staff_attendance_events FROM krona_staff_app; GRANT SELECT, INSERT, UPDATE ON staff_timesheet_entries TO krona_staff_app;