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
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
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",
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user