67a69ab073
- 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
277 lines
7.7 KiB
Go
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",
|
|
})
|
|
}
|