Files
billit/internal/handler/product.go
2025-12-06 15:31:18 +05:30

234 lines
5.7 KiB
Go

package handler
import "billit/internal/models"
import "billit/internal/view"
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 view.RenderServerError(c, "Failed to load products. Please try again.")
}
return view.Render(c, view.ProductListPage(products))
}
// ProductCreatePageHandler renders the /product/create form page
func (h *ProductHandlers) ProductCreatePageHandler(c echo.Context) error {
return view.Render(c, view.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 view.RenderNotFound(c, "Product not found or you don't have access to it.")
}
return view.Render(c, view.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 := models.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 := models.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")
}