2026-02-06 17:35:29 +01:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"erp_system/internal/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderList(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
status := r.URL.Query().Get("status")
|
2026-02-07 07:47:20 +01:00
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
|
|
|
if page < 1 {
|
|
|
|
|
page = 1
|
|
|
|
|
}
|
|
|
|
|
limit := 10
|
|
|
|
|
|
|
|
|
|
orders, total, err := models.OrderGetPaginated(h.DB, status, page, limit)
|
2026-02-06 17:35:29 +01:00
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-07 07:47:20 +01:00
|
|
|
totalPages := (total + limit - 1) / limit
|
|
|
|
|
|
2026-02-06 17:35:29 +01:00
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "Orders",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "orders",
|
|
|
|
|
"Orders": orders,
|
|
|
|
|
"FilterStatus": status,
|
2026-02-07 07:47:20 +01:00
|
|
|
"Page": page,
|
|
|
|
|
"TotalPages": totalPages,
|
|
|
|
|
"HasPrev": page > 1,
|
|
|
|
|
"HasNext": page < totalPages,
|
|
|
|
|
"PrevPage": page - 1,
|
|
|
|
|
"NextPage": page + 1,
|
2026-02-06 17:35:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" && r.URL.Query().Get("partial") == "true" {
|
|
|
|
|
h.renderPartial(w, "orders/list.html", "order-table", data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.render(w, []string{"layout.html", "orders/list.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderNew(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
customers, _ := models.CustomerGetAll(h.DB, "")
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "New Order",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "orders",
|
|
|
|
|
"Order": &models.Order{OrderDate: time.Now().Format("2006-01-02"), Status: "draft"},
|
|
|
|
|
"Customers": customers,
|
|
|
|
|
"IsNew": true,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "orders/form.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderCreate(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
customerID, _ := strconv.Atoi(r.FormValue("customer_id"))
|
|
|
|
|
o := &models.Order{
|
|
|
|
|
CustomerID: customerID,
|
|
|
|
|
Status: "draft",
|
|
|
|
|
OrderDate: r.FormValue("order_date"),
|
|
|
|
|
Notes: r.FormValue("notes"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.CustomerID == 0 {
|
|
|
|
|
customers, _ := models.CustomerGetAll(h.DB, "")
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": "New Order",
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "orders",
|
|
|
|
|
"Order": o,
|
|
|
|
|
"Customers": customers,
|
|
|
|
|
"IsNew": true,
|
|
|
|
|
"Error": "Customer is required",
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "orders/form.html"}, data)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if o.OrderDate == "" {
|
|
|
|
|
o.OrderDate = time.Now().Format("2006-01-02")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := models.OrderInsert(h.DB, o); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", fmt.Sprintf("/orders/%d", o.ID))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/orders/%d", o.ID), http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderDetail(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
order, err := models.OrderGetByID(h.DB, id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Order not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": fmt.Sprintf("Order #%d", order.ID),
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "orders",
|
|
|
|
|
"Order": order,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "orders/detail.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderEdit(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
order, err := models.OrderGetByID(h.DB, id)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, "Order not found", http.StatusNotFound)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if order.Status != "draft" {
|
|
|
|
|
http.Error(w, "Only draft orders can be edited", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
customers, _ := models.CustomerGetAll(h.DB, "")
|
|
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
|
|
|
|
"Title": fmt.Sprintf("Edit Order #%d", order.ID),
|
|
|
|
|
"Username": h.getUsername(r),
|
|
|
|
|
"ActivePage": "orders",
|
|
|
|
|
"Order": order,
|
|
|
|
|
"Customers": customers,
|
|
|
|
|
"IsNew": false,
|
|
|
|
|
}
|
|
|
|
|
h.render(w, []string{"layout.html", "orders/form.html"}, data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderUpdate(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
customerID, _ := strconv.Atoi(r.FormValue("customer_id"))
|
|
|
|
|
|
|
|
|
|
o := &models.Order{
|
|
|
|
|
ID: id,
|
|
|
|
|
CustomerID: customerID,
|
|
|
|
|
OrderDate: r.FormValue("order_date"),
|
|
|
|
|
Notes: r.FormValue("notes"),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := models.OrderUpdate(h.DB, o); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", fmt.Sprintf("/orders/%d", id))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/orders/%d", id), http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderConfirm(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
if err := models.OrderUpdateStatus(h.DB, id, "confirmed"); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", fmt.Sprintf("/orders/%d", id))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/orders/%d", id), http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderFulfill(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
|
|
|
|
|
// Update status
|
|
|
|
|
if err := models.OrderUpdateStatus(h.DB, id, "fulfilled"); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate invoice and GL entries
|
|
|
|
|
if err := models.GenerateInvoiceFromOrder(h.DB, id); err != nil {
|
|
|
|
|
http.Error(w, fmt.Sprintf("Order fulfilled but invoice generation failed: %v", err), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", fmt.Sprintf("/orders/%d", id))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/orders/%d", id), http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderCancel(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
if err := models.OrderUpdateStatus(h.DB, id, "cancelled"); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", fmt.Sprintf("/orders/%d", id))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, fmt.Sprintf("/orders/%d", id), http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderDelete(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
id, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
if err := models.OrderDelete(h.DB, id); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.Header.Get("HX-Request") == "true" {
|
|
|
|
|
w.Header().Set("HX-Redirect", "/orders")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
http.Redirect(w, r, "/orders", http.StatusSeeOther)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Order line handlers (HTMX partials)
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderLineAdd(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
orderID, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
qty, _ := strconv.ParseFloat(r.FormValue("quantity"), 64)
|
|
|
|
|
price, _ := strconv.ParseFloat(r.FormValue("unit_price"), 64)
|
|
|
|
|
|
|
|
|
|
line := &models.OrderLine{
|
|
|
|
|
OrderID: orderID,
|
|
|
|
|
Description: r.FormValue("description"),
|
|
|
|
|
Quantity: qty,
|
|
|
|
|
UnitPrice: price,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if line.Description == "" || line.Quantity <= 0 {
|
|
|
|
|
http.Error(w, "Description and quantity are required", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := models.OrderLineInsert(h.DB, line); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return updated order detail
|
|
|
|
|
order, err := models.OrderGetByID(h.DB, orderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.renderPartial(w, "orders/detail.html", "order-lines", order)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (h *Handler) OrderLineDelete(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
orderID, _ := strconv.Atoi(r.PathValue("id"))
|
|
|
|
|
lineID, _ := strconv.Atoi(r.PathValue("lineID"))
|
|
|
|
|
|
|
|
|
|
if err := models.OrderLineDelete(h.DB, lineID, orderID); err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return updated order detail
|
|
|
|
|
order, err := models.OrderGetByID(h.DB, orderID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
h.renderPartial(w, "orders/detail.html", "order-lines", order)
|
|
|
|
|
}
|