234 lines
5.7 KiB
Go
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")
|
|
}
|