Files
boc/backend/handlers/hr.go
T
Bernt (LandveX AI) 67a69ab073 feat(boc): v1.0 - Complete Business Operations Center
- Go backend API with full CRUD for all modules
- Rust analytics service with parallel processing
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables
- Redis cache, Kafka event streaming
- WebSocket hub, automation engine
- PDF generation, Resend email integration
- JWT auth, multi-tenant
- Docker Compose deployment
- Nginx reverse proxy

Refs: BOC-001
2026-07-12 13:21:10 +00:00

277 lines
7.7 KiB
Go

package handlers
import (
"database/sql"
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
)
type HRHandler struct {
DB *sql.DB
}
func NewHRHandler(db *sql.DB) *HRHandler {
return &HRHandler{DB: db}
}
type Employee struct {
ID string `json:"id"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Email string `json:"email"`
Phone string `json:"phone"`
Department string `json:"department"`
Position string `json:"position"`
EmploymentType string `json:"employment_type"`
Salary float64 `json:"salary"`
Currency string `json:"currency"`
StartDate *time.Time `json:"start_date"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
type Leave struct {
ID string `json:"id"`
EmployeeID string `json:"employee_id"`
Type string `json:"type"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
Days float64 `json:"days"`
Status string `json:"status"`
ApprovedBy *string `json:"approved_by"`
ApprovedAt *time.Time `json:"approved_at"`
}
type Timesheet struct {
ID string `json:"id"`
EmployeeID string `json:"employee_id"`
Date time.Time `json:"date"`
Hours float64 `json:"hours"`
Project string `json:"project"`
Task string `json:"task"`
Description string `json:"description"`
Status string `json:"status"`
}
func (h *HRHandler) ListEmployees(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query(`
SELECT id, first_name, last_name, email, phone, department, position,
employment_type, salary, currency, start_date, status, created_at
FROM boc_employees
WHERE status = 'active'
ORDER BY created_at DESC
`)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
employees := []Employee{}
for rows.Next() {
var e Employee
if err := rows.Scan(&e.ID, &e.FirstName, &e.LastName, &e.Email, &e.Phone,
&e.Department, &e.Position, &e.EmploymentType, &e.Salary, &e.Currency,
&e.StartDate, &e.Status, &e.CreatedAt); err != nil {
continue
}
employees = append(employees, e)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"employees": employees,
"total": len(employees),
})
}
func (h *HRHandler) CreateEmployee(w http.ResponseWriter, r *http.Request) {
var req Employee
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
var id string
err := h.DB.QueryRow(`
INSERT INTO boc_employees (first_name, last_name, email, phone, department, position,
employment_type, salary, currency, start_date, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'active')
RETURNING id
`, req.FirstName, req.LastName, req.Email, req.Phone, req.Department, req.Position,
req.EmploymentType, req.Salary, req.Currency, req.StartDate).Scan(&id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create employee")
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"id": id,
"message": "Employee created",
})
}
func (h *HRHandler) GetEmployee(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var e Employee
err := h.DB.QueryRow(`
SELECT id, first_name, last_name, email, phone, department, position,
employment_type, salary, currency, start_date, status, created_at
FROM boc_employees WHERE id = $1
`, id).Scan(&e.ID, &e.FirstName, &e.LastName, &e.Email, &e.Phone,
&e.Department, &e.Position, &e.EmploymentType, &e.Salary, &e.Currency,
&e.StartDate, &e.Status, &e.CreatedAt)
if err == sql.ErrNoRows {
writeError(w, http.StatusNotFound, "employee not found")
return
}
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
writeJSON(w, http.StatusOK, e)
}
func (h *HRHandler) UpdateEmployee(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
var req Employee
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
_, err := h.DB.Exec(`
UPDATE boc_employees
SET first_name = $1, last_name = $2, email = $3, phone = $4,
department = $5, position = $6, employment_type = $7,
salary = $8, currency = $9, start_date = $10, status = $11
WHERE id = $12
`, req.FirstName, req.LastName, req.Email, req.Phone, req.Department,
req.Position, req.EmploymentType, req.Salary, req.Currency,
req.StartDate, req.Status, id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to update employee")
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Employee updated",
})
}
func (h *HRHandler) ListLeaves(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query(`
SELECT id, employee_id, type, start_date, end_date, days, status, approved_by, approved_at
FROM boc_leaves
ORDER BY start_date DESC
LIMIT 100
`)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
leaves := []Leave{}
for rows.Next() {
var l Leave
if err := rows.Scan(&l.ID, &l.EmployeeID, &l.Type, &l.StartDate, &l.EndDate,
&l.Days, &l.Status, &l.ApprovedBy, &l.ApprovedAt); err != nil {
continue
}
leaves = append(leaves, l)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"leaves": leaves,
"total": len(leaves),
})
}
func (h *HRHandler) CreateLeave(w http.ResponseWriter, r *http.Request) {
var req Leave
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
var id string
err := h.DB.QueryRow(`
INSERT INTO boc_leaves (employee_id, type, start_date, end_date, days, status)
VALUES ($1, $2, $3, $4, $5, 'pending')
RETURNING id
`, req.EmployeeID, req.Type, req.StartDate, req.EndDate, req.Days).Scan(&id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create leave")
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"id": id,
"message": "Leave request created",
})
}
func (h *HRHandler) ListTimesheets(w http.ResponseWriter, r *http.Request) {
rows, err := h.DB.Query(`
SELECT id, employee_id, date, hours, project, task, description, status
FROM boc_timesheets
ORDER BY date DESC
LIMIT 100
`)
if err != nil {
writeError(w, http.StatusInternalServerError, "database error")
return
}
defer rows.Close()
timesheets := []Timesheet{}
for rows.Next() {
var t Timesheet
if err := rows.Scan(&t.ID, &t.EmployeeID, &t.Date, &t.Hours, &t.Project,
&t.Task, &t.Description, &t.Status); err != nil {
continue
}
timesheets = append(timesheets, t)
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"timesheets": timesheets,
"total": len(timesheets),
})
}
func (h *HRHandler) CreateTimesheet(w http.ResponseWriter, r *http.Request) {
var req Timesheet
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid request")
return
}
var id string
err := h.DB.QueryRow(`
INSERT INTO boc_timesheets (employee_id, date, hours, project, task, description, status)
VALUES ($1, $2, $3, $4, $5, $6, 'draft')
RETURNING id
`, req.EmployeeID, req.Date, req.Hours, req.Project, req.Task, req.Description).Scan(&id)
if err != nil {
writeError(w, http.StatusInternalServerError, "failed to create timesheet")
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"id": id,
"message": "Timesheet created",
})
}