package web import ( "billit/internal/database" "net/http" "strconv" "github.com/labstack/echo/v4" ) // ProductHandlers holds db reference for product operations type ProductHandlers struct { db database.Service } // NewProductHandlers creates handlers with db access func NewProductHandlers(db database.Service) *ProductHandlers { return &ProductHandlers{db: db} } // getUserID extracts user ID from context (set by auth middleware) func getUserID(c echo.Context) string { if uid, ok := c.Get("user_id").(string); ok { return uid } return "" } // ProductListHandler renders the /product page with all products func (h *ProductHandlers) ProductListHandler(c echo.Context) error { userID := getUserID(c) products, err := h.db.GetAllProducts(userID) if err != nil { return RenderServerError(c, "Failed to load products. Please try again.") } return Render(c, ProductListPage(products)) } // ProductCreatePageHandler renders the /product/create form page func (h *ProductHandlers) ProductCreatePageHandler(c echo.Context) error { return Render(c, ProductCreatePage()) } // ProductEditPageHandler renders the /product/edit/:sku form page func (h *ProductHandlers) ProductEditPageHandler(c echo.Context) error { sku := c.Param("sku") userID := getUserID(c) product, err := h.db.GetProductBySKU(sku, userID) if err != nil || product == nil { return RenderNotFound(c, "Product not found or you don't have access to it.") } return Render(c, ProductEditPage(*product)) } // ProductCreateHandler handles POST /product/create func (h *ProductHandlers) ProductCreateHandler(c echo.Context) error { userID := getUserID(c) if userID == "" { return c.Redirect(http.StatusFound, "/") } sku := c.FormValue("sku") if sku == "" { return c.String(http.StatusBadRequest, "SKU is required") } name := c.FormValue("name") if name == "" { return c.String(http.StatusBadRequest, "Name is required") } // Check if SKU already exists for this user existing, _ := h.db.GetProductBySKU(sku, userID) if existing != nil { return c.String(http.StatusBadRequest, "A product with this SKU already exists") } hsn := c.FormValue("hsn") baseStr := c.FormValue("base_price") wholesaleStr := c.FormValue("wholesale_price") gstStr := c.FormValue("gst_rate") smallQtyStr := c.FormValue("small_order_qty") base, _ := strconv.ParseFloat(baseStr, 64) wholesale, _ := strconv.ParseFloat(wholesaleStr, 64) if wholesale == 0 { wholesale = base // default wholesale to base price } gstRate := 0.18 // default 18% switch gstStr { case "0": gstRate = 0.0 case "5": gstRate = 0.05 case "12": gstRate = 0.12 case "18": gstRate = 0.18 case "28": gstRate = 0.28 } smallQty := 1 if v, err := strconv.Atoi(smallQtyStr); err == nil && v > 0 { smallQty = v } smallFeeStr := c.FormValue("small_order_fee") smallFee, _ := strconv.ParseFloat(smallFeeStr, 64) unit := c.FormValue("unit") if unit == "" { unit = "pcs" } product := database.Product{ SKU: sku, Name: name, HSNCode: hsn, BasePrice: base, WholesalePrice: wholesale, GSTRate: gstRate, SmallOrderQty: smallQty, SmallOrderFee: smallFee, Unit: unit, } if err := h.db.CreateProduct(product, userID); err != nil { return c.String(http.StatusInternalServerError, "failed to create product") } // Redirect to product list return c.Redirect(http.StatusSeeOther, "/product") } // ProductUpdateHandler handles POST /product/edit/:sku func (h *ProductHandlers) ProductUpdateHandler(c echo.Context) error { userID := getUserID(c) if userID == "" { return c.Redirect(http.StatusFound, "/") } sku := c.Param("sku") // Verify product belongs to user existing, _ := h.db.GetProductBySKU(sku, userID) if existing == nil { return c.String(http.StatusNotFound, "Product not found") } name := c.FormValue("name") if name == "" { return c.String(http.StatusBadRequest, "Name is required") } hsn := c.FormValue("hsn") baseStr := c.FormValue("base_price") wholesaleStr := c.FormValue("wholesale_price") gstStr := c.FormValue("gst_rate") smallQtyStr := c.FormValue("small_order_qty") base, _ := strconv.ParseFloat(baseStr, 64) wholesale, _ := strconv.ParseFloat(wholesaleStr, 64) if wholesale == 0 { wholesale = base } gstRate := 0.18 switch gstStr { case "0": gstRate = 0.0 case "5": gstRate = 0.05 case "12": gstRate = 0.12 case "18": gstRate = 0.18 case "28": gstRate = 0.28 } smallQty := 1 if v, err := strconv.Atoi(smallQtyStr); err == nil && v > 0 { smallQty = v } smallFeeStr := c.FormValue("small_order_fee") smallFee, _ := strconv.ParseFloat(smallFeeStr, 64) unit := c.FormValue("unit") if unit == "" { unit = "pcs" } product := database.Product{ SKU: sku, Name: name, HSNCode: hsn, BasePrice: base, WholesalePrice: wholesale, GSTRate: gstRate, SmallOrderQty: smallQty, SmallOrderFee: smallFee, Unit: unit, } if err := h.db.UpdateProduct(product, userID); err != nil { return c.String(http.StatusInternalServerError, "failed to update product") } return c.Redirect(http.StatusSeeOther, "/product") } // ProductDeleteHandler handles DELETE /product/:sku func (h *ProductHandlers) ProductDeleteHandler(c echo.Context) error { userID := getUserID(c) sku := c.Param("sku") if err := h.db.DeleteProduct(sku, userID); err != nil { return c.String(http.StatusInternalServerError, "failed to delete product") } // For HTMX, return empty to remove the row if c.Request().Header.Get("HX-Request") == "true" { return c.NoContent(http.StatusOK) } return c.Redirect(http.StatusSeeOther, "/product") }