package repositories import ( "fmt" "strconv" "strings" "time" "nearle/models" "gorm.io/gorm" "gorm.io/gorm/clause" ) type ProductRepository interface { GetProductSubCategory(categoryID, tenantID int) ([]models.ProductSubCategory, error) GetProductCount(tenantID, categoryID, subcategoryID int, approve string) ([]models.Productcount, error) GetProductCategory() ([]models.ProductCategory, error) GetProductVariants(tenantID, subcategoryID int) ([]models.Productvariant, error) GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Products, error) GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error) CreateProductStock(stocks []models.Productstock) error UpdateProductStatus(productIDs []int, status string) error CreateProduct(product models.Products) error UpdateProduct(product models.Products) error DeleteProduct(productID int) error GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Productstockstatement, error) GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Locationproducts, error) GetLocationProductSummary(tenantID, locationID int) ([]models.ProductSummary, error) FetchFilteredProducts(categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus, approve string, pageno, pagesize int) ([]models.Tenantproducts, error) GetProductByVariant(tenantid, variantid int) ([]models.Products, error) GetSubcategories(categoryID int) ([]models.Subcategory, error) GetProducts(params models.ProductFilter) ([]models.Products, error) GetTenantInfo(tenantID, applocationID int) (map[string]interface{}, error) UpdateProductLocation(input models.Productlocations) error CreateProductLocation(input []models.Productlocations) error CreateProductVariant(input models.Productvariant) error } type productRepository struct { db *gorm.DB } func NewProductRepository(db *gorm.DB) ProductRepository { return &productRepository{db: db} } func (r *productRepository) GetProductSubCategory(categoryID, tenantID int) ([]models.ProductSubCategory, error) { var data []models.ProductSubCategory var query strings.Builder var args []interface{} query.WriteString("SELECT * FROM productsubcategories WHERE 1=1") if tenantID != 0 { query.WriteString(" AND tenantid = ?") args = append(args, tenantID) } if categoryID != 0 { query.WriteString(" AND categoryid = ?") args = append(args, categoryID) } if err := r.db.Raw(query.String(), args...).Scan(&data).Error; err != nil { return nil, err } // print() return data, nil } func (r *productRepository) GetProductCount(tenantid, categoryid, subcategory int, approve string) ([]models.Productcount, error) { var data []models.Productcount baseQuery := ` SELECT COUNT(*) AS total, SUM(CASE WHEN a.productstatus = 'available' THEN 1 ELSE 0 END) AS available, SUM(CASE WHEN a.productstatus = 'outofstock' THEN 1 ELSE 0 END) AS outofstock FROM products a WHERE 1 = 1 ` var conditions []string var params []interface{} if tenantid != 0 { conditions = append(conditions, "a.tenantid = ?") params = append(params, tenantid) } if categoryid != 0 { conditions = append(conditions, "a.categoryid = ?") params = append(params, categoryid) } if subcategory != 0 { conditions = append(conditions, "a.subcategoryid = ?") params = append(params, subcategory) } if approve != "" { conditions = append(conditions, "a.approve = ?") params = append(params, approve) } if len(conditions) > 0 { baseQuery += " AND " + strings.Join(conditions, " AND ") } if err := r.db.Raw(baseQuery, params...).Scan(&data).Error; err != nil { return nil, err } print(baseQuery) return data, nil } func (r *productRepository) GetProductCategory() ([]models.ProductCategory, error) { var data []models.ProductCategory q1 := `SELECT * FROM productcategories WHERE moduleid = 2 AND status = 'Active'` r.db.Raw(q1).Scan(&data) print(q1) return data, nil } func (r *productRepository) GetProductVariants(tenantID int, subcategoryID int) ([]models.Productvariant, error) { var data []models.Productvariant var query string var params []interface{} query = ` SELECT a.*, b.categoryname FROM productvariants a JOIN app_category b ON a.categoryid = b.categoryid WHERE a.tenantid = ? ` params = append(params, tenantID) if subcategoryID != 0 { query += " AND a.subcategoryid = ?" params = append(params, subcategoryID) } r.db.Raw(query, params...).Scan(&data) //print(query) return data, nil } func (r *productRepository) GetCatalougeProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Products, error) { var data []models.Products if pageno < 1 { pageno = 1 } if pagesize < 1 { pagesize = 10 } offset := (pageno - 1) * pagesize params := []interface{}{locationID, tenantID} // Base query query := ` SELECT a.* FROM products a LEFT JOIN productlocations b ON a.productid = b.productid AND b.locationid = ? AND b.tenantid = a.tenantid WHERE a.approve = 1 AND a.tenantid = ? AND b.productid IS NULL ` // Optional filters if subcategoryID != 0 { query += " AND a.subcategoryid = ?" params = append(params, subcategoryID) } if keyword != "" { query += " AND LOWER(a.productname) LIKE ?" params = append(params, "%"+strings.ToLower(keyword)+"%") } // Pagination query += " ORDER BY a.productid DESC LIMIT " + strconv.Itoa(pagesize) + " OFFSET " + strconv.Itoa(offset) // Debug logs fmt.Println("Executing query:", query) fmt.Println("Params:", params) // Execute query if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { return nil, err } return data, nil } func (r *productRepository) GetProductStocks(tenantID, locationID string) ([]models.Productstocks, error) { var stocks []models.Productstocks var params []interface{} var conditions []string query := ` SELECT a.productid, a.tenantid, MAX(a.stockdate) AS stockdate, a.locationid, a.stocktype, a.maxquantity, a.minquantity, a.status, b.applocationid, b.categoryid, b.subcategoryid, b.catalogueid, b.addonid, b.discountid, b.pricingid, b.productname, b.productimage, b.productdesc, b.productsku, b.brandid, b.productbrand, b.productunit, b.unitvalue, b.toppicks, b.productcost, b.taxamount, b.taxpercent, b.producttax, b.productstock, b.productcombo, b.variants, b.retailprice, b.diffprice, b.diffpercent, b.othercost, b.approve, b.productstatus, b.created, b.updated, c.subcatname AS subcategoryname, SUM(CASE WHEN a.stocktype = 'in' THEN a.quantity ELSE 0 END) - SUM(CASE WHEN a.stocktype = 'out' THEN a.quantity ELSE 0 END) AS quantity FROM productstocks a JOIN products b ON a.productid = b.productid INNER JOIN productsubcategories c ON c.subcatid = b.subcategoryid ` if tenantID != "" { conditions = append(conditions, "a.tenantid = ?") params = append(params, tenantID) } if locationID != "" { conditions = append(conditions, "a.locationid = ?") params = append(params, locationID) } if len(conditions) > 0 { query += " WHERE " + strings.Join(conditions, " AND ") } query += " GROUP BY a.productid" if err := r.db.Raw(query, params...).Scan(&stocks).Error; err != nil { return nil, err } return stocks, nil } func (r *productRepository) CreateProductStock(stocks []models.Productstock) error { return r.db.Table("productstocks").Create(&stocks).Error } func (r *productRepository) UpdateProductStatus(productIDs []int, status string) error { return r.db.Table("products"). Where("productid IN ?", productIDs). Update("productstatus", status).Error } func (r *productRepository) CreateProduct(product models.Products) error { tx := r.db.Begin() if err := tx.Create(&product).Error; err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { return err } return nil } func (r *productRepository) UpdateProduct(product models.Products) error { tx := r.db.Begin() if err := tx.Table("productlocations"). Where("productid = ?", product.Productid). Select("status"). // only update 'approve' field Updates(product).Error; err != nil { tx.Rollback() return err } return tx.Commit().Error } func (r *productRepository) DeleteProduct(productID int) error { tx := r.db.Begin() if err := tx.Table("products").Where("productid = ?", productID).Delete(&models.Products{}).Error; err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { return err } return nil } func (r *productRepository) GetStockStatement(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Productstockstatement, error) { data := make([]models.Productstockstatement, 0) if pageno < 1 { pageno = 1 } if pagesize < 1 { pagesize = 10 } offset := (pageno - 1) * pagesize params := []interface{}{tenantID, locationID} query := `SELECT a.productid,a.productname,a.productimage,a.categoryid,a.subcategoryid,a.productunit,a.unitvalue,a.productcost,a.taxpercent,a.taxamount,a.retailprice,b.tenantid,b.locationid, COALESCE( SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date <= CURRENT_DATE THEN c.quantity ELSE 0 END) - SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date <= CURRENT_DATE THEN c.quantity ELSE 0 END),0 ) AS opening, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END), 0) AS credit, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END), 0) AS debit, COALESCE( ( SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END) - SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date < CURRENT_DATE THEN c.quantity ELSE 0 END) ) + SUM(CASE WHEN UPPER(c.stocktype) = 'IN' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END) - SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' AND c.stockdate::date = CURRENT_DATE THEN c.quantity ELSE 0 END), 0 ) AS closing FROM products a JOIN productlocations b ON a.productid = b.productid AND a.tenantid = b.tenantid LEFT JOIN productstocks c ON a.productid = c.productid AND b.locationid = c.locationid AND b.tenantid = c.tenantid WHERE b.tenantid = ? AND b.locationid = ?` if subcategoryID != 0 { query += " AND a.subcategoryid = ?" params = append(params, subcategoryID) } if keyword != "" { query += " AND (CAST(a.productid AS TEXT) LIKE ? OR LOWER(a.productname) LIKE ?)" likeParam := "%" + strings.ToLower(keyword) + "%" params = append(params, likeParam, likeParam) } query += ` GROUP BY a.productid, a.productname, a.productimage, a.categoryid, a.subcategoryid, a.productunit, a.productcost, a.taxpercent, a.taxamount, a.retailprice, b.tenantid, b.locationid ORDER BY a.productid DESC LIMIT ` + strconv.Itoa(pagesize) + ` OFFSET ` + strconv.Itoa(offset) if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { return nil, err } print(query) return data, nil } func (r *productRepository) GetLocationProducts(tenantID, locationID, subcategoryID, pageno, pagesize int, keyword string) ([]models.Locationproducts, error) { var data []models.Locationproducts if pageno < 1 { pageno = 1 } if pagesize < 1 { pagesize = 10 } offset := (pageno - 1) * pagesize params := []interface{}{tenantID, locationID} query := `SELECT a.*, b.productlocationid, b.status, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' THEN c.quantity ELSE 0 END), 0) AS total_in, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' THEN c.quantity ELSE 0 END), 0) AS total_out, COALESCE(SUM(CASE WHEN UPPER(c.stocktype) = 'IN' THEN c.quantity ELSE 0 END) - SUM(CASE WHEN UPPER(c.stocktype) = 'OUT' THEN c.quantity ELSE 0 END), 0) AS productstock FROM products a INNER JOIN productlocations b ON a.productid = b.productid AND a.tenantid = b.tenantid LEFT JOIN productstocks c ON a.productid = c.productid AND b.locationid = c.locationid AND a.tenantid = c.tenantid WHERE a.approve=1 AND a.tenantid = ? AND b.locationid = ?` if subcategoryID != 0 { query += " AND a.subcategoryid = ?" params = append(params, subcategoryID) } if keyword != "" { query += " AND LOWER(a.productname) LIKE ?" params = append(params, "%"+strings.ToLower(keyword)+"%") } query += ` GROUP BY a.productid, a.productname, a.productimage, a.categoryid, a.subcategoryid, a.productunit, a.productcost, a.taxpercent, a.taxamount, a.retailprice, b.tenantid, b.locationid, b.productlocationid, b.status ORDER BY a.productid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { return nil, err } print(query) return data, nil } func (r *productRepository) GetLocationProductSummary(tenantID, locationID int) ([]models.ProductSummary, error) { data := make([]models.ProductSummary, 0) query := ` SELECT a.subcatid AS subcategoryid, a.subcatname AS subcategroyname, a.image, COUNT(DISTINCT c.productid) AS productcount FROM productsubcategories a LEFT JOIN products b ON a.subcatid = b.subcategoryid AND b.approve = 1 AND b.tenantid = ? LEFT JOIN productlocations c ON b.productid = c.productid AND c.tenantid = ? AND c.locationid = ? WHERE a.categoryid = 2 GROUP BY a.subcatid, a.subcatname, a.image ORDER BY a.subcatid; ` // Only 3 params: tenantID for products, tenantID for locations, locationID params := []interface{}{tenantID, tenantID, locationID} if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { return nil, err } // Correct "All" count: sum of only products that exist in this location total := 0 for _, d := range data { total += d.Productcount } all := models.ProductSummary{ Subcategoryid: 0, Subcategroyname: "All", Productcount: total, } data = append([]models.ProductSummary{all}, data...) return data, nil } func (r *productRepository) FetchFilteredProducts( categoryID, subcategoryID, productID, applocationID, tenantID, locationID int, keyword, productStatus, approve string, pageno, pagesize int, ) ([]models.Tenantproducts, error) { offset := (pageno - 1) * pagesize results := make([]models.Tenantproducts, 0) if tenantID == 0 { return results, nil } // Fetch tenant info var tenant models.TenantInfo if err := r.db.Table("tenants").Where("tenantid = ?", tenantID).First(&tenant).Error; err != nil { return nil, err } // Build product query var products []models.Products query := r.db. Table("products a"). Select(` a.*, b.status, c.categoryname, d.subcatname AS subcategoryname, ps.locationid, COALESCE(ps.quantity, 0) AS quantity `). Joins("LEFT JOIN productlocations b ON a.productid = b.productid"). Joins("LEFT JOIN productcategories c ON a.categoryid = c.categoryid"). Joins("LEFT JOIN productsubcategories d ON a.subcategoryid = d.subcatid"). Joins(` LEFT JOIN ( SELECT productid, locationid, SUM(CASE WHEN stocktype = 'in' THEN quantity ELSE 0 END) - SUM(CASE WHEN stocktype = 'out' THEN quantity ELSE 0 END) AS quantity FROM productstocks GROUP BY productid, locationid ) ps ON ps.productid = a.productid `). Where("a.tenantid = ?", tenantID). Order("a.productid DESC") if categoryID != 0 { query = query.Where("a.categoryid = ?", categoryID) } if subcategoryID != 0 { query = query.Where("a.subcategoryid = ?", subcategoryID) } if productID != 0 { query = query.Where("a.productid = ?", productID) } if productStatus != "" { query = query.Where("a.productstatus = ?", productStatus) } if locationID != 0 { query = query.Where("e.locationid = ?", locationID) } if approve != "" { query = query.Where("a.approve = ?", approve) } if keyword != "" { like := "%" + strings.ToLower(keyword) + "%" query = query.Where( r.db.Where("LOWER(a.productname) LIKE ?", like). Or("LOWER(a.unitvalue) LIKE ?", like). Or("LOWER(CAST(a.productcost AS TEXT)) LIKE ?", like), ) } if pagesize > 0 && offset >= 0 { query = query.Limit(pagesize).Offset(offset) } if err := query.Scan(&products).Error; err != nil { return nil, err } if products == nil { products = []models.Products{} } results = append(results, models.Tenantproducts{ Tenant: tenant, Products: products, }) print(query) return results, nil } func (r *productRepository) GetProductByVariant(tenantid, variantid int) ([]models.Products, error) { var data []models.Products err := r.db. Table("products p"). Select(` p.*, c.categoryname, d.subcatname AS subcategoryname, COALESCE(pd.discountvalue, 0) AS discountvalue, pd.discountid `). Joins("LEFT JOIN productcategories c ON p.categoryid = c.categoryid"). Joins("LEFT JOIN productsubcategories d ON p.subcategoryid = d.subcatid"). Joins("LEFT JOIN productdiscounts pd ON pd.productid = p.productid"). Where("p.tenantid = ? AND p.variants = ?", tenantid, variantid). Order("p.productid DESC"). Scan(&data).Error if err != nil { return nil, err } return data, nil } func (r *productRepository) GetSubcategories(categoryID int) ([]models.Subcategory, error) { var subcats []models.Subcategory err := r.db.Table("productsubcategories"). Where("categoryid = ?", categoryID). Find(&subcats).Error return subcats, err } func (r *productRepository) GetProducts(params models.ProductFilter) ([]models.Products, error) { var products []models.Products q := r.db.Table("products a"). Joins("LEFT JOIN productlocations pl ON pl.productid = a.productid"). Joins("LEFT JOIN productdiscounts pd ON pd.productid = a.productid"). Joins("LEFT JOIN productcategories c ON a.categoryid = c.categoryid"). Where("a.categoryid = ?", params.CategoryID) if params.TenantID > 0 { q = q.Where("a.tenantid = ?", params.TenantID) } if params.LocationID > 0 { q = q.Where("pl.locationid = ?", params.LocationID) } if params.AppLocationID > 0 { q = q.Where("a.applocationid = ?", params.AppLocationID) } if params.ProductID > 0 { q = q.Where("a.productid = ?", params.ProductID) } if params.Keyword != "" { like := "%" + strings.ToLower(params.Keyword) + "%" q = q.Where( r.db.Where("LOWER(a.productname) LIKE ?", like). Or("LOWER(a.unitvalue) LIKE ?", like). Or("LOWER(CAST(a.productcost AS TEXT)) LIKE ?", like), ) } err := q.Select(` a.*, COALESCE(pd.discountvalue, 0) AS discountvalue `).Find(&products).Error return products, err } func (r *productRepository) GetTenantInfo(tenantID, applocationID int) (map[string]interface{}, error) { 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 := r.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 `, tenantID, applocationID).Scan(&tenant).Error if err != nil { return nil, err } return map[string]interface{}{ "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, }, nil } func (r *productRepository) UpdateProductLocation(input models.Productlocations) error { tx := r.db.Begin() t1 := tx.Where("productlocationid = ?", input.Productlocationid).Updates(&input) if t1.Error != nil { tx.Rollback() return t1.Error } if err := tx.Commit().Error; err != nil { return err } return nil } func (r *productRepository) CreateProductLocation(input []models.Productlocations) error { var stk []models.Productstock tx := r.db.Begin() // Insert or update product location if err := tx.Clauses(clause.OnConflict{ Columns: []clause.Column{{Name: "tenantid"}, {Name: "locationid"}, {Name: "productid"}}, DoUpdates: clause.AssignmentColumns([]string{"price", "minquantity", "maxquantity"}), }).Create(&input).Error; err != nil { tx.Rollback() return err } // Prepare product stock entries for _, loc := range input { if loc.Quantity > 0 { stk = append(stk, models.Productstock{ Tenantid: loc.Tenantid, Stockdate: time.Now(), Locationid: loc.Locationid, Productid: loc.Productid, Quantity: loc.Quantity, Stocktype: loc.Stocktype, }) } } // Insert stock records if available if len(stk) > 0 { if err := tx.Create(&stk).Error; err != nil { tx.Rollback() return err } } // Commit transaction if err := tx.Commit().Error; err != nil { return err } return nil } func (r *productRepository) CreateProductVariant(input models.Productvariant) error { tx := r.db.Begin() if err := tx.Create(&input).Error; err != nil { tx.Rollback() return err } if err := tx.Commit().Error; err != nil { return err } return nil }