-- ===================================================== -- Migration 005: Employee Lifecycle Platform -- ===================================================== -- ========================================== -- 1. CORE TABLES -- ========================================== -- Departments (Avdelningar) CREATE TABLE IF NOT EXISTS departments ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), name VARCHAR(100) NOT NULL, code VARCHAR(20), description TEXT, parent_id UUID REFERENCES departments(id), manager_id UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_departments_tenant ON departments(tenant_id); -- Teams CREATE TABLE IF NOT EXISTS teams ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), name VARCHAR(100) NOT NULL, description TEXT, department_id UUID REFERENCES departments(id), lead_id UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_teams_tenant ON teams(tenant_id); CREATE INDEX IF NOT EXISTS idx_teams_department ON teams(department_id); -- Roles (Rolldefinitioner) CREATE TABLE IF NOT EXISTS roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), name VARCHAR(100) NOT NULL, description TEXT, level INTEGER DEFAULT 1, -- hierarkisk nivå permissions JSONB DEFAULT '[]', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_roles_tenant ON roles(tenant_id); -- Employees (Medarbetare) CREATE TABLE IF NOT EXISTS employees ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), -- Personuppgifter first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, phone VARCHAR(20), personal_number VARCHAR(20), birth_date DATE, -- Profil avatar_url VARCHAR(500), bio TEXT, -- Anställning employment_type VARCHAR(20) DEFAULT 'full_time', -- full_time, part_time, consultant, intern, contractor employment_status VARCHAR(20) DEFAULT 'active', -- active, probation, notice, terminated, suspended start_date DATE, end_date DATE, probation_end_date DATE, notice_period_days INTEGER DEFAULT 30, -- Organisation department_id UUID REFERENCES departments(id), team_id UUID REFERENCES teams(id), manager_id UUID REFERENCES employees(id), role_id UUID REFERENCES roles(id), -- Ekonomi salary DECIMAL(12,2), salary_currency VARCHAR(3) DEFAULT 'SEK', bank_account VARCHAR(50), bank_clearing VARCHAR(10), -- Kontakt address TEXT, postal_code VARCHAR(10), city VARCHAR(100), country VARCHAR(2) DEFAULT 'SE', -- Nödfall emergency_contact_name VARCHAR(100), emergency_contact_phone VARCHAR(20), emergency_contact_relation VARCHAR(50), -- Metadata created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID, version INTEGER DEFAULT 1 ); CREATE INDEX IF NOT EXISTS idx_employees_tenant ON employees(tenant_id); CREATE INDEX IF NOT EXISTS idx_employees_department ON employees(department_id); CREATE INDEX IF NOT EXISTS idx_employees_team ON employees(team_id); CREATE INDEX IF NOT EXISTS idx_employees_manager ON employees(manager_id); CREATE INDEX IF NOT EXISTS idx_employees_status ON employees(employment_status); CREATE INDEX IF NOT EXISTS idx_employees_email ON employees(email); -- Employee Timeline Events CREATE TABLE IF NOT EXISTS employee_timeline_events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, event_type VARCHAR(50) NOT NULL, -- hired, promoted, review, leave, termination, role_change, salary_change, etc. event_date DATE NOT NULL, title VARCHAR(255) NOT NULL, description TEXT, metadata JSONB DEFAULT '{}', created_by UUID, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_timeline_employee ON employee_timeline_events(employee_id); CREATE INDEX IF NOT EXISTS idx_timeline_date ON employee_timeline_events(event_date); CREATE INDEX IF NOT EXISTS idx_timeline_type ON employee_timeline_events(event_type); -- ========================================== -- 2. COMPETENCE SYSTEM -- ========================================== -- Competences (Kompetenser) CREATE TABLE IF NOT EXISTS competences ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), name VARCHAR(100) NOT NULL, category VARCHAR(50), -- technical, soft_skill, language, leadership, domain description TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_competences_tenant ON competences(tenant_id); -- Employee Competences CREATE TABLE IF NOT EXISTS employee_competences ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, competence_id UUID NOT NULL REFERENCES competences(id), level INTEGER CHECK (level BETWEEN 1 AND 5), -- 1=beginner, 2=intermediate, 3=advanced, 4=expert, 5=master acquired_date DATE, validated_by UUID REFERENCES employees(id), validation_date DATE, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(employee_id, competence_id) ); CREATE INDEX IF NOT EXISTS idx_emp_comp_employee ON employee_competences(employee_id); CREATE INDEX IF NOT EXISTS idx_emp_comp_competence ON employee_competences(competence_id); -- ========================================== -- 3. DOCUMENT SYSTEM -- ========================================== -- Document Templates CREATE TABLE IF NOT EXISTS document_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), name VARCHAR(255) NOT NULL, document_type VARCHAR(50) NOT NULL, -- contract, nda, policy, handbook, equipment_agreement description TEXT, content JSONB, -- strukturerat innehåll med variabler variables JSONB DEFAULT '[]', -- lista över variabler default_terms JSONB, language VARCHAR(5) DEFAULT 'sv', version INTEGER DEFAULT 1, is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_doc_templates_tenant ON document_templates(tenant_id); CREATE INDEX IF NOT EXISTS idx_doc_templates_type ON document_templates(document_type); -- Employee Documents CREATE TABLE IF NOT EXISTS employee_documents ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, template_id UUID REFERENCES document_templates(id), document_type VARCHAR(50) NOT NULL, title VARCHAR(255) NOT NULL, -- Innehåll content JSONB, file_url VARCHAR(500), file_type VARCHAR(50), file_size INTEGER, -- Signering signature_status VARCHAR(20) DEFAULT 'draft', -- draft, sent, partially_signed, signed, expired signed_by_employee_at TIMESTAMP WITH TIME ZONE, signed_by_company_at TIMESTAMP WITH TIME ZONE, employee_signature_data JSONB, company_signature_data JSONB, signature_method VARCHAR(20), -- digital, manual, bankid -- Versionering version INTEGER DEFAULT 1, parent_id UUID REFERENCES employee_documents(id), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_by UUID ); CREATE INDEX IF NOT EXISTS idx_emp_docs_employee ON employee_documents(employee_id); CREATE INDEX IF NOT EXISTS idx_emp_docs_type ON employee_documents(document_type); CREATE INDEX IF NOT EXISTS idx_emp_docs_status ON employee_documents(signature_status); -- ========================================== -- 4. TRAINING SYSTEM -- ========================================== -- Trainings (Utbildningar) CREATE TABLE IF NOT EXISTS trainings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), title VARCHAR(255) NOT NULL, description TEXT, category VARCHAR(50), -- onboarding, compliance, skill, safety, leadership format VARCHAR(20), -- video, text, quiz, workshop, mixed content JSONB, -- modulär struktur duration_minutes INTEGER, mandatory BOOLEAN DEFAULT false, valid_for_days INTEGER, -- hur länge certifiering gäller passing_score INTEGER DEFAULT 80, -- procent för godkänt is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_trainings_tenant ON trainings(tenant_id); CREATE INDEX IF NOT EXISTS idx_trainings_category ON trainings(category); -- Employee Trainings CREATE TABLE IF NOT EXISTS employee_trainings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, training_id UUID NOT NULL REFERENCES trainings(id), status VARCHAR(20) DEFAULT 'assigned', -- assigned, in_progress, completed, expired, failed progress_percent INTEGER DEFAULT 0, started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE, expires_at TIMESTAMP WITH TIME ZONE, score INTEGER, certificate_url VARCHAR(500), certificate_number VARCHAR(100), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(employee_id, training_id) ); CREATE INDEX IF NOT EXISTS idx_emp_trainings_employee ON employee_trainings(employee_id); CREATE INDEX IF NOT EXISTS idx_emp_trainings_status ON employee_trainings(status); CREATE INDEX IF NOT EXISTS idx_emp_trainings_expires ON employee_trainings(expires_at); -- ========================================== -- 5. TASK SYSTEM -- ========================================== -- Task Templates CREATE TABLE IF NOT EXISTS task_templates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), title VARCHAR(255) NOT NULL, description TEXT, category VARCHAR(50), -- onboarding, role, compliance, project, recurring priority VARCHAR(20) DEFAULT 'medium', -- low, medium, high, critical estimated_duration_minutes INTEGER, checklist JSONB DEFAULT '[]', required_role_ids UUID[], required_competence_ids UUID[], instructions TEXT, resources JSONB DEFAULT '[]', -- länkar, dokument, etc. is_active BOOLEAN DEFAULT true, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_task_templates_tenant ON task_templates(tenant_id); -- Employee Tasks CREATE TABLE IF NOT EXISTS employee_tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, template_id UUID REFERENCES task_templates(id), title VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(20) DEFAULT 'pending', -- pending, in_progress, completed, cancelled, overdue priority VARCHAR(20) DEFAULT 'medium', due_date DATE, completed_at TIMESTAMP WITH TIME ZONE, completed_by UUID REFERENCES employees(id), checklist_progress JSONB DEFAULT '[]', assigned_by UUID REFERENCES employees(id), assigned_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), source VARCHAR(50), -- onboarding, role, manual, automation, performance source_id UUID, notes TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_emp_tasks_employee ON employee_tasks(employee_id); CREATE INDEX IF NOT EXISTS idx_emp_tasks_status ON employee_tasks(status); CREATE INDEX IF NOT EXISTS idx_emp_tasks_due ON employee_tasks(due_date); -- ========================================== -- 6. PERFORMANCE SYSTEM -- ========================================== -- Performance Reviews CREATE TABLE IF NOT EXISTS performance_reviews ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, reviewer_id UUID NOT NULL REFERENCES employees(id), review_period_start DATE NOT NULL, review_period_end DATE NOT NULL, review_type VARCHAR(20), -- annual, quarterly, probation, ad_hoc, 360 -- Bedömningar overall_rating INTEGER CHECK (overall_rating BETWEEN 1 AND 5), ratings JSONB DEFAULT '{}', -- {competence_id: score, ...} goals JSONB DEFAULT '[]', -- [{goal, target, achievement, status}] strengths TEXT[], improvements TEXT[], -- Feedback employee_comments TEXT, reviewer_comments TEXT, development_plan TEXT, status VARCHAR(20) DEFAULT 'draft', -- draft, submitted, acknowledged, disputed acknowledged_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_perf_reviews_employee ON performance_reviews(employee_id); CREATE INDEX IF NOT EXISTS idx_perf_reviews_status ON performance_reviews(status); -- Performance KPIs CREATE TABLE IF NOT EXISTS performance_kpis ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), employee_id UUID NOT NULL REFERENCES employees(id) ON DELETE CASCADE, kpi_name VARCHAR(100) NOT NULL, kpi_value DECIMAL(10,2), target_value DECIMAL(10,2), unit VARCHAR(20), period VARCHAR(20), -- weekly, monthly, quarterly, yearly period_start DATE, period_end DATE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_perf_kpis_employee ON performance_kpis(employee_id); CREATE INDEX IF NOT EXISTS idx_perf_kpis_period ON performance_kpis(period_start); -- ========================================== -- 7. ONBOARDING SYSTEM -- ========================================== -- Onboardings CREATE TABLE IF NOT EXISTS onboardings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES boc_tenants(id), employee_id UUID NOT NULL REFERENCES employees(id), -- Status status VARCHAR(20) DEFAULT 'in_progress', -- in_progress, completed, cancelled progress_percent INTEGER DEFAULT 0, -- Steg steps JSONB DEFAULT '[]', -- [{step, status, completed_at, data}] -- Tidslinje started_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), completed_at TIMESTAMP WITH TIME ZONE, target_completion_date DATE, -- Ansvarig hr_manager_id UUID REFERENCES employees(id), buddy_id UUID REFERENCES employees(id), -- mentor/kompis created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_onboardings_tenant ON onboardings(tenant_id); CREATE INDEX IF NOT EXISTS idx_onboardings_employee ON onboardings(employee_id); CREATE INDEX IF NOT EXISTS idx_onboardings_status ON onboardings(status); -- ========================================== -- 8. AUDIT LOG -- ========================================== CREATE TABLE IF NOT EXISTS employee_audit_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), table_name VARCHAR(50) NOT NULL, record_id UUID NOT NULL, action VARCHAR(20) NOT NULL, -- create, update, delete old_values JSONB, new_values JSONB, changed_by UUID, changed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), ip_address INET, user_agent TEXT ); CREATE INDEX IF NOT EXISTS idx_audit_table ON employee_audit_log(table_name); CREATE INDEX IF NOT EXISTS idx_audit_record ON employee_audit_log(record_id); CREATE INDEX IF NOT EXISTS idx_audit_changed_at ON employee_audit_log(changed_at); -- ========================================== -- 9. TRIGGERS -- ========================================== -- Auto-update updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ language 'plpgsql'; -- Apply triggers DROP TRIGGER IF EXISTS update_employees_updated_at ON employees; CREATE TRIGGER update_employees_updated_at BEFORE UPDATE ON employees FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_employee_documents_updated_at ON employee_documents; CREATE TRIGGER update_employee_documents_updated_at BEFORE UPDATE ON employee_documents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_trainings_updated_at ON trainings; CREATE TRIGGER update_trainings_updated_at BEFORE UPDATE ON trainings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_employee_trainings_updated_at ON employee_trainings; CREATE TRIGGER update_employee_trainings_updated_at BEFORE UPDATE ON employee_trainings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_employee_tasks_updated_at ON employee_tasks; CREATE TRIGGER update_employee_tasks_updated_at BEFORE UPDATE ON employee_tasks FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_performance_reviews_updated_at ON performance_reviews; CREATE TRIGGER update_performance_reviews_updated_at BEFORE UPDATE ON performance_reviews FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); DROP TRIGGER IF EXISTS update_onboardings_updated_at ON onboardings; CREATE TRIGGER update_onboardings_updated_at BEFORE UPDATE ON onboardings FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();