package controllers import ( "nearle/db" "nearle/domain" "nearle/models" "nearle/utils" "net/http" "strconv" "strings" "time" "github.com/gofiber/fiber/v2" ) func GetProductInfo(c *fiber.Ctx) error { pid, _ := strconv.Atoi(c.Query("productid")) utils.Logger.Debugf("productid: %d", pid) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, }) } func GetProducts(c *fiber.Ctx) error { return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, }) } func GetProductCategory(c *fiber.Ctx) error { data := domain.GetProductCategory() return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func GetProductSubCategory(c *fiber.Ctx) error { categoryID, _ := strconv.Atoi(c.Query("categoryid")) tenantID, _ := strconv.Atoi(c.Query("tenantid")) data := domain.GetProductSubCategory(categoryID, tenantID) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func CreateProductVariant(c *fiber.Ctx) error { var input models.Productvariant 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 err := domain.CreateProductVariant(input); 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, }) } func CreateProduct(c *fiber.Ctx) error { var product models.Products if err := c.BodyParser(&product); err != nil { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "code": http.StatusBadRequest, "message": "Invalid request body", "status": false, }) } if err := domain.CreateProduct(&product); err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": "Failed to create product", "status": false, }) } return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Product created successfully", "status": true, }) } func 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 := domain.FetchFilteredProducts( categoryID, subcategoryID, productID, applocationID, tenantID, locationID, keyword, productStatus, approve, pageno, pagesize, ) 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, "data": details, }) } func UpdateProduct(c *fiber.Ctx) error { var data models.Products if err := c.BodyParser(&data); err != nil { return err } err := domain.UpdateProduct(data) if err != nil { return c.JSON(fiber.Map{ "status": false, "code": http.StatusConflict, "message": err, }) } return c.JSON(fiber.Map{ "status": true, "code": http.StatusAccepted, "message": "Product update successful", }) } func DeleteProduct(c *fiber.Ctx) error { // Get product ID from query 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, }) } // Start transaction tx := db.DB.Begin() if tx.Error != nil { return c.JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": "Failed to start transaction", "status": false, }) } // Delete product if err := tx.Table("products").Where("productid = ?", pid).Delete(&models.Products{}).Error; err != nil { tx.Rollback() return c.JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": "Error deleting product", "status": false, }) } // Commit transaction if err := tx.Commit().Error; err != nil { return c.JSON(fiber.Map{ "code": http.StatusInternalServerError, "message": "Failed to commit transaction", "status": false, }) } // Success response return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, }) } func GetProductsBySubcategory(c *fiber.Ctx) error { // log.Println("Handler: GetProductsBySubcategory called") // Required query param: categoryid categoryIDStr := c.Query("categoryid") if categoryIDStr == "" { utils.Error("Error: categoryid is missing") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "status": false, "message": "categoryid is required", "data": fiber.Map{}, }) } categoryID, err := strconv.Atoi(categoryIDStr) if err != nil { utils.Logger.Errorf("Error: Invalid categoryid: %s", categoryIDStr) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "status": false, "message": "Invalid categoryid", "data": fiber.Map{}, }) } // Optional tenantid tenantIDStr := c.Query("tenantid") tenantID := 0 if tenantIDStr != "" { tenantID, err = strconv.Atoi(tenantIDStr) if err != nil { utils.Logger.Errorf("Error: Invalid tenantid: %s", tenantIDStr) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "status": false, "message": "Invalid tenantid", "data": fiber.Map{}, }) } } // Optional query params applocationIDStr := c.Query("applocationid", "0") applocationID, _ := strconv.Atoi(applocationIDStr) productIDStr := c.Query("productid") keyword := c.Query("keyword") locationIDStr := c.Query("locationid") locationID := 0 if locationIDStr != "" { locationID, _ = strconv.Atoi(locationIDStr) } productID := 0 if productIDStr != "" { productID, err = strconv.Atoi(productIDStr) if err != nil { utils.Logger.Errorf("Error: Invalid productid: %s", productIDStr) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "status": false, "message": "Invalid productid", "data": fiber.Map{}, }) } } // Fetch subcategories var subcategories []models.Subcategory utils.Logger.Infof("Fetching subcategories for categoryid: %d", categoryID) if err := db.DB.Table("productsubcategories"). Where("categoryid = ?", categoryID). Find(&subcategories).Error; err != nil { utils.Logger.Errorf("Error fetching subcategories: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "status": false, "message": "Error fetching subcategories", "data": fiber.Map{}, }) } // Build product query var allProducts []models.Products query := db.DB. Table("products a"). Joins("LEFT JOIN productlocations pl ON pl.productid = a.productid"). Where("a.categoryid = ?", categoryID) if tenantID > 0 { query = query.Where("a.tenantid = ?", tenantID) } if locationID > 0 { utils.Logger.Infof("Filtering by locationid: %d", locationID) query = query.Where("pl.locationid = ?", locationID) } else { utils.Info("No locationid provided or value is 0 — showing all locations") } if applocationID > 0 { query = query.Where("a.applocationid = ?", applocationID) } if productID > 0 { query = query.Where("a.productid = ?", productID) } if keyword != "" { utils.Info("Applying keyword filter") like := "%" + strings.ToLower(keyword) + "%" query = query.Where( db.DB.Where("LOWER(a.productname) LIKE ?", like). Or("LOWER(a.unitvalue) LIKE ?", like). Or("LOWER(CAST(a.productcost AS CHAR)) LIKE ?", like), ) } if err := query.Select("a.*").Find(&allProducts).Error; err != nil { utils.Logger.Errorf("Error fetching products: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "status": false, "message": "Error fetching products", "data": fiber.Map{}, }) } // Group products under subcategories var details []models.SubcategoryProductResponse var uncategorizedProducts []models.Products for _, subcat := range subcategories { var subcatProducts []models.Products for _, prod := range allProducts { if prod.Subcategoryid != nil && *prod.Subcategoryid == subcat.Subcategoryid { subcatProducts = append(subcatProducts, prod) } } if len(subcatProducts) > 0 { details = append(details, models.SubcategoryProductResponse{ SubcategoryID: subcat.Subcategoryid, SubcategoryName: subcat.Subcategoryname, Products: subcatProducts, }) } } for _, prod := range allProducts { if prod.Subcategoryid == nil { uncategorizedProducts = append(uncategorizedProducts, prod) } } if len(uncategorizedProducts) > 0 { details = append(details, models.SubcategoryProductResponse{ SubcategoryID: 0, SubcategoryName: "Uncategorized", Products: uncategorizedProducts, }) } // Tenant info block only if tenantID is passed if tenantID > 0 { var tenantInfo = fiber.Map{} var tenantFound bool for _, p := range allProducts { if p.Tenantid != nil { var tenant struct { Tenantname string Address string Licenseno string Primaryemail string Primarycontact string Locationname string Pickuplocationid int Suburb string City string Latitude string Longitude string Postcode string } err := db.DB.Raw(` SELECT t.tenantname, t.address, t.licenseno, t.primaryemail, t.primarycontact, l.locationid AS pickuplocationid, l.suburb, l.city, l.latitude, l.longitude, l.postcode, a.locationname FROM tenants t LEFT JOIN tenantlocations l ON t.tenantid = l.tenantid LEFT JOIN app_location a ON l.applocationid = a.applocationid WHERE t.tenantid = ? AND t.applocationid = ? LIMIT 1 `, *p.Tenantid, applocationID).Scan(&tenant).Error if err == nil { tenantInfo = fiber.Map{ "tenantname": tenant.Tenantname, "address": tenant.Address, "licenseno": tenant.Licenseno, "primaryemail": tenant.Primaryemail, "primarycontact": tenant.Primarycontact, "locationname": tenant.Locationname, "pickuplocationid": tenant.Pickuplocationid, "suburb": tenant.Suburb, "city": tenant.City, "pickuplat": tenant.Latitude, "pickuplong": tenant.Longitude, "postcode": tenant.Postcode, "details": details, } tenantFound = true } break } } if tenantFound { return c.Status(fiber.StatusOK).JSON(fiber.Map{ "code": 200, "status": true, "message": "Success", "data": tenantInfo, }) } } // Return only details if tenantid is not provided return c.Status(fiber.StatusOK).JSON(fiber.Map{ "code": 200, "status": true, "message": "Success", "data": fiber.Map{ "details": details, }, }) } func GetProductCount(c *fiber.Ctx) error { tenantID, _ := strconv.Atoi(c.Query("tenantid")) categoryID, _ := strconv.Atoi(c.Query("categoryid")) subcategoryID, _ := strconv.Atoi(c.Query("subcategoryid")) approve := c.Query("approve", "") // New line result, err := domain.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": result, }) } func GetProductByVariant(c *fiber.Ctx) error { tenantID, _ := strconv.Atoi(c.Query("tenantid")) variantid, _ := strconv.Atoi(c.Query("variantid")) result, err := domain.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 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 := domain.GetCatalougeProducts(tenantId, locationid, subcategoryid, pageno, pagesize, keyword) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func 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 := domain.GetStockStatement(tenantId, locationid, subcategoryid, pageno, pagesize, keyword) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func GetProductVariants(c *fiber.Ctx) error { tenantId, _ := strconv.Atoi(c.Query("tenantid")) subcategoryId, _ := strconv.Atoi(c.Query("subcategoryid")) data := domain.GetProductVariants(tenantId, subcategoryId) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func CreateProductLocation(c *fiber.Ctx) error { var data []models.Productlocations if err := c.BodyParser(&data); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "message": "Invalid request body", "status": false, }) } err := domain.CreateProductLocation(data) if err != nil { return c.JSON(fiber.Map{ "status": false, "code": http.StatusConflict, "message": err, }) } return c.JSON(fiber.Map{ "status": true, "code": http.StatusCreated, "message": "Success", }) } func UpdateProductLocation(c *fiber.Ctx) error { var data models.Productlocations if err := c.BodyParser(&data); err != nil { return err } err := domain.UpdateProductLocation(data) if err != nil { return c.JSON(fiber.Map{ "status": false, "code": http.StatusConflict, "message": err, }) } return c.JSON(fiber.Map{ "status": true, "code": http.StatusAccepted, "message": "Product update successful", }) } func 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, }) } for i := range stocks { stocks[i].Stockdate = time.Now() } // Insert all stock records if err := db.DB.Table("productstocks").Create(&stocks).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "message": "Failed to create product stocks", "status": false, }) } // Collect and dedupe product IDs to update their status idMap := make(map[int]struct{}) var productIDs []int for _, s := range stocks { if s.Productid <= 0 { continue } if _, ok := idMap[s.Productid]; !ok { idMap[s.Productid] = struct{}{} productIDs = append(productIDs, s.Productid) } } // Only update if we have product ids if len(productIDs) > 0 { if err := db.DB.Table("products"). Where("productid IN ?", productIDs). Update("productstatus", "available").Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "message": "Failed to update product status", "status": false, }) } } return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "code": 201, "message": "Product stocks created successfully", "status": true, "details": stocks, }) } func UpdateProductStock(c *fiber.Ctx) error { var stock models.Productstock // Parse body if err := c.BodyParser(&stock); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "message": "Invalid request body", "status": false, }) } // Validate required ID if stock.Productstockid == 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "code": 400, "message": "Missing productstockid", "status": false, }) } // Attempt update if err := domain.UpdateProductStock(&stock); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "message": "Failed to update product stock", "status": false, }) } return c.Status(fiber.StatusOK).JSON(fiber.Map{ "code": 200, "message": "Product stock updated successfully", "status": true, }) } func GetProductStocks(c *fiber.Ctx) error { tenantID := c.Query("tenantid") locationID := c.Query("locationid") stocks, err := domain.GetProductStocks(tenantID, locationID) if err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "message": "Failed to fetch product stocks", "status": false, }) } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": 200, "message": "Product stocks fetched successfully", "status": true, "data": stocks, }) } func 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 := domain.GetLocationProducts(tenantid, locationid, subcategoryid, pageno, pagesize, keyword) return c.JSON(fiber.Map{ "status": true, "code": http.StatusOK, "message": "Success", "details": result, }) } func GetSubCategoryWiseSummary(c *fiber.Ctx) error { tenantId, _ := strconv.Atoi(c.Query("tenantid")) locationid, _ := strconv.Atoi(c.Query("locationid")) subcategoryid, _ := strconv.Atoi(c.Query("subcategoryid")) data, err := domain.GetSubCategoryWiseSummary(tenantId, locationid, subcategoryid) if err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "code": 500, "status": false, "message": "Failed to fetch subcategory-wise summary", }) } if data == nil { data = []models.SubCategorySummary{} } return c.Status(http.StatusOK).JSON(fiber.Map{ "code": http.StatusOK, "message": "Subcategory-wise summary fetched successfully", "status": true, "details": data, }) } func GetLocationProductSummary(c *fiber.Ctx) error { tenantid, _ := strconv.Atoi(c.Query("tenantid")) locationid, _ := strconv.Atoi(c.Query("locationid")) result := domain.GetLocationProductSummary(tenantid, locationid) return c.JSON(fiber.Map{ "status": true, "code": http.StatusOK, "message": "Success", "details": result, }) } func GetStockStatementSummary(c *fiber.Ctx) error { tenantId, _ := strconv.Atoi(c.Query("tenantid")) locationid, _ := strconv.Atoi(c.Query("locationid")) data := domain.GetStockStatementSummary(tenantId, locationid) return c.JSON(fiber.Map{ "code": http.StatusOK, "message": "Success", "status": true, "details": data, }) } func CreateProductDiscount(c *fiber.Ctx) error { var data models.ProductDiscount if err := c.BodyParser(&data); err != nil { return c.Status(http.StatusBadRequest).JSON(fiber.Map{ "status": false, "code": http.StatusBadRequest, "message": "Invalid JSON body", }) } if len(data.Locationid) > 0 { data.LocationidStr = strings.Join(data.Locationid, ",") } data.Status = "Active" if err := domain.CreateProductDiscount(&data); err != nil { return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ "status": false, "code": http.StatusInternalServerError, "message": err.Error(), }) } return c.Status(http.StatusCreated).JSON(fiber.Map{ "status": true, "code": http.StatusCreated, "message": "Product discount created successfully", "data": data, }) } func GetProductDiscounts(c *fiber.Ctx) error { tid, _ := strconv.Atoi(c.Query("tenantid")) lid := c.Query("locationid") data := domain.GetProductDiscounts(tid, lid) return c.JSON(fiber.Map{ "status": true, "code": 200, "message": "Success", "details": data, }) }