package handlers import ( "net/http" "time" "github.com/jmoiron/sqlx" "workorders/internal/config" "workorders/internal/model" ) type WorkOrderHandler struct { db *sqlx.DB cfg *config.Config } func NewWorkOrderHandler(db *sqlx.DB, cfg *config.Config) *WorkOrderHandler { return &WorkOrderHandler{db: db, cfg: cfg} } func (h *WorkOrderHandler) List(w http.ResponseWriter, r *http.Request) { status := r.URL.Query().Get("status") search := r.URL.Query().Get("search") if search != "" { search = "%" + search + "%" } query := ` SELECT wo.id, wo.wo_number, wo.title, wo.status, wo.priority, wo.site_name, wo.scheduled_start, COUNT(s.id) AS step_count, ISNULL(SUM(CAST(s.completed AS INT)),0) AS steps_done, COUNT(a.id) AS photo_count FROM work_orders wo LEFT JOIN wo_steps s ON s.wo_id = wo.id LEFT JOIN wo_attachments a ON a.wo_id = wo.id WHERE (@p1 = '' OR wo.status = @p1) AND (@p2 = '' OR wo.title LIKE @p2 OR wo.wo_number LIKE @p2 OR wo.site_name LIKE @p2) GROUP BY wo.id, wo.wo_number, wo.title, wo.status, wo.priority, wo.site_name, wo.scheduled_start ORDER BY wo.scheduled_start DESC, wo.id DESC` rows := []model.WorkOrderListItem{} if err := h.db.Select(&rows, query, status, search); err != nil { respondError(w, http.StatusInternalServerError, err.Error()) return } respond(w, http.StatusOK, rows) } func (h *WorkOrderHandler) Get(w http.ResponseWriter, r *http.Request) { id, err := intParam(r, "id") if err != nil { respondError(w, http.StatusBadRequest, "invalid id") return } var wo model.WorkOrder if err = h.db.Get(&wo, `SELECT * FROM work_orders WHERE id = @p1`, id); err != nil { respondError(w, http.StatusNotFound, "not found") return } respond(w, http.StatusOK, wo) } func (h *WorkOrderHandler) Create(w http.ResponseWriter, r *http.Request) { user := userFromCtx(r) var body model.WorkOrder if err := decode(r, &body); err != nil { respondError(w, http.StatusBadRequest, "invalid body") return } if body.Priority == "" { body.Priority = "normal" } var id int err := h.db.QueryRow(` INSERT INTO work_orders (title, description, instructions, status, priority, scheduled_start, scheduled_end, site_name, address, lat, lng, access_notes, parent_type, parent_id, created_by, updated_at) OUTPUT INSERTED.id VALUES (@p1,@p2,@p3,'draft',@p4,@p5,@p6,@p7,@p8,@p9,@p10,@p11,@p12,@p13,@p14,GETUTCDATE())`, body.Title, body.Description, body.Instructions, body.Priority, body.ScheduledStart, body.ScheduledEnd, body.SiteName, body.Address, body.Lat, body.Lng, body.AccessNotes, body.ParentType, body.ParentID, user.Email, ).Scan(&id) if err != nil { respondError(w, http.StatusInternalServerError, err.Error()) return } var wo model.WorkOrder h.db.Get(&wo, `SELECT * FROM work_orders WHERE id = @p1`, id) respond(w, http.StatusCreated, wo) } func (h *WorkOrderHandler) Update(w http.ResponseWriter, r *http.Request) { id, err := intParam(r, "id") if err != nil { respondError(w, http.StatusBadRequest, "invalid id") return } var body model.WorkOrder if err := decode(r, &body); err != nil { respondError(w, http.StatusBadRequest, "invalid body") return } _, err = h.db.Exec(` UPDATE work_orders SET title=@p1, description=@p2, instructions=@p3, priority=@p4, scheduled_start=@p5, scheduled_end=@p6, site_name=@p7, address=@p8, lat=@p9, lng=@p10, access_notes=@p11, updated_at=GETUTCDATE() WHERE id=@p12`, body.Title, body.Description, body.Instructions, body.Priority, body.ScheduledStart, body.ScheduledEnd, body.SiteName, body.Address, body.Lat, body.Lng, body.AccessNotes, id, ) if err != nil { respondError(w, http.StatusInternalServerError, err.Error()) return } var wo model.WorkOrder h.db.Get(&wo, `SELECT * FROM work_orders WHERE id = @p1`, id) respond(w, http.StatusOK, wo) } func (h *WorkOrderHandler) Delete(w http.ResponseWriter, r *http.Request) { id, err := intParam(r, "id") if err != nil { respondError(w, http.StatusBadRequest, "invalid id") return } h.db.Exec(`DELETE FROM work_orders WHERE id = @p1`, id) respond(w, http.StatusOK, map[string]any{"deleted": id}) } func (h *WorkOrderHandler) UpdateStatus(w http.ResponseWriter, r *http.Request) { id, err := intParam(r, "id") if err != nil { respondError(w, http.StatusBadRequest, "invalid id") return } user := userFromCtx(r) var body struct { Status string `json:"status"` } if err := decode(r, &body); err != nil { respondError(w, http.StatusBadRequest, "invalid body") return } now := time.Now().UTC() _, err = h.db.Exec(` UPDATE work_orders SET status=@p1, updated_at=GETUTCDATE(), actual_start = CASE WHEN @p1='in_progress' AND actual_start IS NULL THEN @p2 ELSE actual_start END, actual_end = CASE WHEN @p1='closed' THEN @p2 ELSE actual_end END, closed_at = CASE WHEN @p1='closed' THEN @p2 ELSE closed_at END, closed_by = CASE WHEN @p1='closed' THEN @p3 ELSE closed_by END WHERE id=@p4`, body.Status, now, user.Email, id, ) if err != nil { respondError(w, http.StatusInternalServerError, err.Error()) return } var wo model.WorkOrder h.db.Get(&wo, `SELECT * FROM work_orders WHERE id = @p1`, id) respond(w, http.StatusOK, wo) }