Files
backend_fiesta/controllers/productController.go
Suriya a9c13292f0 Make subcategory optional and fix category picker for real tenant data
subcategoryid is no longer required to import a catalogue product —
it's a display/grouping hint elsewhere (the codebase already has an
"Uncategorized" fallback for subcategoryid=0), so requiring it was
pure friction with no correctness payoff.

Fixes the category picker at the root: categoryid 2, which tenant
1135's real products actually use, has no row in productcategories at
all (not a filter bug — the master data is genuinely missing it).
Rather than inventing category master data, adds
GET /products/gettenantcategories, which lists categories a tenant's
own products actually use (falling back to a synthesized label when
the master table has no name), so the import category picker always
offers something real instead of an incomplete global list.

Also relaxes the subcategory lookup's tenant filter to include
unowned/global rows (tenantid NULL or 0), not just exact tenant
matches — categoryid 2's real subcategories carry no tenant at all,
so the strict filter was hiding them even when a tenant wanted one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 18:18:51 +05:30

657 lines
16 KiB
Go

package controllers
import (
"net/http"
"strconv"
"nearle/models"
"nearle/services"
"github.com/gofiber/fiber/v2"
)
type ProductController struct {
productService services.ProductService
}
func NewProductController(productService services.ProductService) *ProductController {
return &ProductController{productService: productService}
}
func (ctl *ProductController) GetProductSubCategory(c *fiber.Ctx) error {
categoryID, _ := strconv.Atoi(c.Query("categoryid", "0"))
tenantID, _ := strconv.Atoi(c.Query("tenantid", "0"))
data, err := ctl.productService.GetProductSubCategory(categoryID, tenantID)
if err != nil {
return c.JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": 200,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetProductCount(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
categoryID, _ := strconv.Atoi(c.Query("categoryid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
// locationID, _ := strconv.Atoi(c.Query("locationid"))
approve := c.Query("approve", "")
data, err := ctl.productService.GetProductCount(tenantID, categoryID, subcategoryID, approve)
if err != nil {
return c.JSON(fiber.Map{
"code": http.StatusConflict,
"message": "failed",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetProductCategory(c *fiber.Ctx) error {
data, err := ctl.productService.GetProductCategory()
if err != nil {
return c.JSON(fiber.Map{
"code": 500,
"message": "Failed to fetch categories",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetProductVariants(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
data, err := ctl.productService.GetProductVariants(tenantID, subcategoryID)
if err != nil {
return c.JSON(fiber.Map{
"code": 500,
"message": "Failed to fetch Product varients",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetCatalougeProducts(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
locationID, _ := strconv.Atoi(c.Query("locationid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
keyword := c.Query("keyword")
pageno, _ := strconv.Atoi(c.Query("pageno"))
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
data, err := ctl.productService.GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
if err != nil {
return c.JSON(fiber.Map{
"code": 500,
"message": "Failed to fetch Catalouge Product",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetProductStocks(c *fiber.Ctx) error {
tenantID := c.Query("tenantid")
locationID := c.Query("locationid")
stocks, err := ctl.productService.GetProductStocks(tenantID, locationID)
if err != nil {
return c.JSON(fiber.Map{
"code": 500,
"message": "Failed to fetch product stocks",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": 200,
"message": "Product stocks fetched successfully",
"status": true,
"data": stocks,
})
}
func (ctl *ProductController) CreateProductStock(c *fiber.Ctx) error {
var stocks []models.Productstock
if err := c.BodyParser(&stocks); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": 400,
"message": "Invalid request body",
"status": false,
})
}
if err := ctl.productService.CreateProductStock(stocks); err != nil {
return c.JSON(fiber.Map{
"code": 500,
"message": "Failed to create product stocks",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": 201,
"message": "Product stocks created successfully",
"status": true,
"details": stocks,
})
}
func (ctl *ProductController) CreateProduct(c *fiber.Ctx) error {
var product models.Products
if err := c.BodyParser(&product); err != nil {
return c.JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if err := ctl.productService.CreateProduct(product); err != nil {
return c.JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": "Failed to create product",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusCreated,
"message": "Product created successfully",
"status": true,
"data": product,
})
}
func (ctl *ProductController) UpdateProduct(c *fiber.Ctx) error {
var product models.Products
if err := c.BodyParser(&product); err != nil {
return c.JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if err := ctl.productService.UpdateProduct(product); err != nil {
return c.JSON(fiber.Map{
"code": http.StatusConflict,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusAccepted,
"message": "Product updated successfully",
"status": true,
"data": product,
})
}
func (ctl *ProductController) DeleteProduct(c *fiber.Ctx) error {
pidStr := c.Query("productid")
pid, err := strconv.Atoi(pidStr)
if err != nil || pid <= 0 {
return c.JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid product ID",
"status": false,
})
}
if err := ctl.productService.DeleteProduct(pid); err != nil {
return c.JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Product deleted successfully",
"status": true,
})
}
func (ctl *ProductController) GetStockStatement(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
locationID, _ := strconv.Atoi(c.Query("locationid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
pageno, _ := strconv.Atoi(c.Query("pageno"))
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
keyword := c.Query("keyword")
data, err := ctl.productService.GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
if err != nil {
return c.JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": data,
})
}
func (ctl *ProductController) GetLocationProducts(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
locationID, _ := strconv.Atoi(c.Query("locationid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
keyword := c.Query("keyword")
pageno, _ := strconv.Atoi(c.Query("pageno"))
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
result, err := ctl.productService.GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize, keyword)
if err != nil {
return c.JSON(fiber.Map{
"status": false,
"code": http.StatusInternalServerError,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"status": true,
"code": http.StatusOK,
"message": "Success",
"details": result,
})
}
func (ctl *ProductController) GetLocationProductSummary(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
locationID, _ := strconv.Atoi(c.Query("locationid"))
result, err := ctl.productService.GetLocationProductSummary(tenantID, locationID)
if err != nil {
return c.JSON(fiber.Map{
"status": false,
"code": http.StatusInternalServerError,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"status": true,
"code": http.StatusOK,
"message": "Success",
"details": result,
})
}
func (ctl *ProductController) GetAllProducts(c *fiber.Ctx) error {
categoryID, _ := strconv.Atoi(c.Query("categoryid"))
subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid"))
productID, _ := strconv.Atoi(c.Query("productid"))
applocationID, _ := strconv.Atoi(c.Query("applocationid"))
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
locationID, _ := strconv.Atoi(c.Query("locationid"))
keyword := c.Query("keyword", "")
productStatus := c.Query("productstatus", "")
approve := c.Query("approve", "")
pageno, _ := strconv.Atoi(c.Query("pageno"))
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
details, err := ctl.productService.FetchFilteredProducts(
categoryID, subcategoryID, productID, applocationID, tenantID,
locationID, keyword, productStatus, approve, pageno, pagesize,
)
if err != nil {
return c.JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.JSON(fiber.Map{
"status": true,
"code": http.StatusOK,
"message": "Success",
"data": details,
})
}
func (ctl *ProductController) GetProductByVariant(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
variantid, _ := strconv.Atoi(c.Query("variantid"))
result, err := ctl.productService.GetProductByVariant(tenantID, variantid)
if err != nil {
return c.JSON(fiber.Map{
"code": http.StatusConflict,
"message": "failed",
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": result,
})
}
func (ctl *ProductController) GetProductsBySubcategory(c *fiber.Ctx) error {
categoryID, err := strconv.Atoi(c.Query("categoryid"))
if err != nil || categoryID == 0 {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"code": 400,
"status": false,
"message": "Valid categoryid is required",
"data": fiber.Map{},
})
}
params := models.ProductFilter{
CategoryID: categoryID,
TenantID: parseInt(c.Query("tenantid")),
AppLocationID: parseInt(c.Query("applocationid")),
ProductID: parseInt(c.Query("productid")),
Keyword: c.Query("keyword"),
LocationID: parseInt(c.Query("locationid")),
}
result, err := ctl.productService.GetProductsBySubcategory(params)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": 500,
"status": false,
"message": err.Error(),
"data": fiber.Map{},
})
}
return c.JSON(fiber.Map{
"code": 200,
"status": true,
"message": "Success",
"data": result,
})
}
func parseInt(s string) int {
if s == "" {
return 0
}
val, _ := strconv.Atoi(s)
return val
}
func (ctl *ProductController) UpdateProductLocation(c *fiber.Ctx) error {
var data models.Productlocations
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"status": false,
"code": http.StatusBadRequest,
"message": "Invalid request body",
})
}
if err := ctl.productService.UpdateProductLocation(data); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.Status(http.StatusAccepted).JSON(fiber.Map{
"status": true,
"code": http.StatusAccepted,
"message": "Product update successful",
})
}
func (ctl *ProductController) CreateProductLocation(c *fiber.Ctx) error {
var data []models.Productlocations
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if err := ctl.productService.CreateProductLocation(data); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.Status(http.StatusCreated).JSON(fiber.Map{
"status": true,
"code": http.StatusCreated,
"message": "Success",
})
}
func (ctl *ProductController) CreateProductVariant(c *fiber.Ctx) error {
var input models.Productvariant
// Parse request body
if err := c.BodyParser(&input); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
// Call service layer
if err := ctl.productService.CreateProductVariant(input); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"code": http.StatusConflict,
"message": err.Error(),
"status": false,
})
}
return c.Status(http.StatusOK).JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
})
}
func (ctl *ProductController) ImportCatalogueProduct(c *fiber.Ctx) error {
var data []models.ImportCatalogueProductRequest
if err := c.BodyParser(&data); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if len(data) == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Request body must contain at least one product",
"status": false,
})
}
for _, req := range data {
if req.Tenantid == 0 || req.Locationid == 0 || req.Brand == "" || req.Catalogueid == 0 || req.Categoryid == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "tenantid, locationid, brand, catalogueid, and categoryid are required",
"status": false,
})
}
}
if err := ctl.productService.ImportCatalogueProduct(data); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"status": false,
"code": http.StatusConflict,
"message": err.Error(),
})
}
return c.Status(http.StatusCreated).JSON(fiber.Map{
"status": true,
"code": http.StatusCreated,
"message": "Success",
})
}
func (ctl *ProductController) GetImportedCatalogueProducts(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
// brand is optional: omit it to check imported status across every
// brand at once, matching the all-brands catalogue browse view.
brand := c.Query("brand")
if tenantID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "tenantid is required",
"status": false,
})
}
refs, err := ctl.productService.GetImportedCatalogueRefs(tenantID, brand)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": refs,
})
}
func (ctl *ProductController) GetTenantCategories(c *fiber.Ctx) error {
tenantID, _ := strconv.Atoi(c.Query("tenantid"))
if tenantID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "tenantid is required",
"status": false,
})
}
categories, err := ctl.productService.GetTenantCategories(tenantID)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"code": http.StatusInternalServerError,
"message": err.Error(),
"status": false,
})
}
return c.JSON(fiber.Map{
"code": http.StatusOK,
"message": "Success",
"status": true,
"details": categories,
})
}
func (ctl *ProductController) DeleteProductLocation(c *fiber.Ctx) error {
var input struct {
Tenantid int `json:"tenantid"`
Locationid int `json:"locationid"`
Productid int `json:"productid"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "Invalid request body",
"status": false,
})
}
if input.Tenantid == 0 || input.Locationid == 0 || input.Productid == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{
"code": http.StatusBadRequest,
"message": "tenantid, locationid, and productid are required",
"status": false,
})
}
if err := ctl.productService.DeleteProductLocation(input.Tenantid, input.Locationid, input.Productid); err != nil {
return c.Status(http.StatusConflict).JSON(fiber.Map{
"code": http.StatusConflict,
"message": err.Error(),
"status": false,
})
}
return c.Status(http.StatusOK).JSON(fiber.Map{
"code": http.StatusOK,
"message": "Product location deleted successfully",
"status": true,
})
}