package repositories import ( "fmt" "log" "nearle/models" "strings" "time" "gorm.io/gorm" ) type OrderRepository interface { GetTenantOrders(q models.DeliveryQuery) ([]models.OrderInfo, error) GetTenantLocationOrders(input models.DeliveryQuery) ([]models.OrderInfo, error) GetPartnerOrders(stat, fdate, tdate string, pid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) GetCustomerOrders(stat, fdate, tdate string, cid, mid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) GetAdminOrders(stat, fdate, tdate string, aid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) GetUserOrders(stat, fdate, tdate string, uid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) GetAllOrders(stat, fdate, tdate string, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) GetOrderSummary(tid, pid, cid, aid int, fdate, tdate string) ([]models.Ordersummarydaily, error) GetLocationOrderSummary(tenantID int) ([]models.Ordersummarylocation, error) GetOrderInsights(tenantID int) ([]models.OrderInsightv1, error) GetOrderDetails(orderHeaderID int) ([]models.OrderDetails, error) UpdateOrder(order *models.Orders) error CreateOrder(order models.Orders) (models.Orders, error) GetCustomerOrdersv3(customerID, tenantID, moduleID, fromDate, toDate, orderStatus, keyword string, pageSize, offset int) ([]models.CustomerOrder, error) GetRevenueSummary(tid, lid int, fdate, tdate string) (*models.TenantRevenueSummary, error) GetSalesSummary(tid, lid int, fdate, tdate string) (*models.SalesSummaryResponse, error) GetTimeSeries(tenantID, locationID int, granularity, fromDate, toDate string) ([]models.TimeSeriesData, error) } type orderRepository struct { db *gorm.DB } func NewOrderRepository(db *gorm.DB) OrderRepository { return &orderRepository{db: db} } const ( base = `SELECT DISTINCT a.orderheaderid, a.applocationid, h.locationname AS applocation, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, a.deliverycharge, a.kms, a.customerid, a.pickuplocationid, a.pickupaddress, a.pickuplat, a.pickuplong, a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, a.deliverycustomer,a.deliverycontactno,a.deliverylocation as deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, b.customertoken, c.tenantname, c.tenanttoken, c.primarycontact AS tenantcontactno, c.postcode AS tenantpostcode, c.suburb AS tenantsuburb, c.city AS tenantcity, d.locationname, d.contactno AS locationcontactno, d.postcode AS locationpostcode, d.suburb AS locationsuburb, d.city AS locationcity FROM orders a INNER JOIN customers b ON a.customerid = b.customerid INNER JOIN tenants c ON a.tenantid = c.tenantid INNER JOIN tenantlocations d ON a.locationid = d.locationid INNER JOIN app_location h ON a.applocationid = h.applocationid INNER JOIN app_locationconfig i ON a.applocationid = i.applocationid` orderdetails = `SELECT DISTINCT a.orderheaderid, a.applocationid, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime AS deliverydate, a.pending, a.processing, a.ready, a.delivered AS completed, a.cancelled, a.deliverycharge, a.kms, a.customerid, a.pickupaddress, a.pickuplat, a.pickuplong, a.pickupcustomer, a.pickupcontactno, a.pickuplocation as pickupsuburb, a.pickupcity, a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, a.deliverycustomer,a.deliverycontactno,a.deliverylocation as deliverysuburb, a.deliverycity,a.paymenttype, a.smsdelivery, a.orderamount, b.tenantname, b.tenanttoken, b.primarycontact AS tenantcontactno, b.postcode AS tenantpostcode, b.suburb AS tenantsuburb,b.city AS tenantcity, c.locationname, c.contactno AS locationcontactno, c.postcode AS locationpostcode, c.suburb AS locationsuburb, c.city AS locationcity, d.locationname AS applocation FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid` ) func (r *orderRepository) GetTenantOrders(input models.DeliveryQuery) ([]models.OrderInfo, error) { var data []models.OrderInfo var query string var params []interface{} offset := (input.Pageno - 1) * input.Pagesize baseQuery := base + ` WHERE a.tenantid = ?` params = append(params, input.Tenantid) if input.Status == "ongoing" { query = baseQuery + ` AND a.orderstatus IN ('pending','processing','ready')` } else { query = baseQuery + ` AND a.orderstatus = ?` params = append(params, input.Status) } query += ` AND a.deliverytime::date BETWEEN ? AND ?` params = append(params, input.Fromdate, input.ToDate) if input.Keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR c.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + input.Keyword + "%" params = append(params, like, like, like, like, like, like) } if input.Configid != 0 { query += ` AND a.configid = ?` params = append(params, input.Configid) } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, input.Pagesize, offset) fmt.Println("Executing:", query) res := r.db.Raw(query, params...).Find(&data) return data, res.Error } func (r *orderRepository) GetPartnerOrders(stat, fdate, tdate string, pid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) { var data []models.OrderInfo if pageno <= 0 { pageno = 1 } if pagesize <= 0 { pagesize = 10 } offset := (pageno - 1) * pagesize fmt.Println("Getting partner order details") query := orderdetails + ` WHERE a.partnerid = ?` params := []interface{}{pid} if fdate != "" && tdate != "" { query += ` AND a.deliverytime::date BETWEEN ? AND ?` params = append(params, fdate, tdate) } if stat != "" { query += ` AND a.orderstatus = ?` params = append(params, stat) } if keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" params = append(params, like, like, like, like, like, like) } var total int64 countQuery := ` SELECT COUNT(DISTINCT a.orderheaderid) FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid LEFT JOIN app_users g ON f.userid = g.userid WHERE a.partnerid = ?` countParams := []interface{}{pid} if fdate != "" && tdate != "" { countQuery += ` AND a.deliverytime::date BETWEEN ? AND ?` countParams = append(countParams, fdate, tdate) } if stat != "" { countQuery += ` AND a.orderstatus = ?` countParams = append(countParams, stat) } if keyword != "" { countQuery += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" countParams = append(countParams, like, like, like, like, like, like) } r.db.Raw(countQuery, countParams...).Scan(&total) if int64(offset) >= total { offset = 0 pageno = 1 } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) fmt.Println("QUERY:", query) fmt.Println("PARAMS:", params) r.db.Raw(query, params...).Find(&data) fmt.Println("RESULT COUNT:", len(data)) return data, nil } func (r *orderRepository) GetCustomerOrders(stat, fdate, tdate string, cid, mid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) { var data []models.OrderInfo if pageno <= 0 { pageno = 1 } if pagesize <= 0 { pagesize = 10 } offset := (pageno - 1) * pagesize fmt.Println("Getting customer order details") query := orderdetails + ` WHERE a.customerid = ?` params := []interface{}{cid} if mid != 0 { query += ` AND a.moduleid = ?` params = append(params, mid) } if fdate != "" && tdate != "" { query += ` AND a.orderdate::date BETWEEN ? AND ?` params = append(params, fdate, tdate) } if stat != "" { query += ` AND a.orderstatus = ?` params = append(params, stat) } if keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" params = append(params, like, like, like, like, like, like) } var total int64 countQuery := ` SELECT COUNT(DISTINCT a.orderheaderid) FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid WHERE a.customerid = ?` countParams := []interface{}{cid} if mid != 0 { countQuery += ` AND a.moduleid = ?` countParams = append(countParams, mid) } if fdate != "" && tdate != "" { countQuery += ` AND a.orderdate::date BETWEEN ? AND ?` countParams = append(countParams, fdate, tdate) } if stat != "" { countQuery += ` AND a.orderstatus = ?` countParams = append(countParams, stat) } if keyword != "" { countQuery += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" countParams = append(countParams, like, like, like, like, like, like) } r.db.Raw(countQuery, countParams...).Scan(&total) if int64(offset) >= total { offset = 0 pageno = 1 } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) fmt.Println("QUERY:", query) fmt.Println("PARAMS:", params) res := r.db.Raw(query, params...).Find(&data) if res.Error != nil { fmt.Println("ERROR:", res.Error) } fmt.Println("RESULT COUNT:", len(data)) return data, nil } func (r *orderRepository) GetAdminOrders(stat, fdate, tdate string, aid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) { var data []models.OrderInfo if pageno <= 0 { pageno = 1 } if pagesize <= 0 { pagesize = 10 } offset := (pageno - 1) * pagesize fmt.Println("Getting admin order details") query := orderdetails + ` WHERE 1=1` params := []interface{}{} if aid != 0 { query += ` AND a.applocationid = ?` params = append(params, aid) } if fdate != "" && tdate != "" { query += ` AND a.deliverytime::date BETWEEN ? AND ?` params = append(params, fdate, tdate) } if stat != "" { query += ` AND a.orderstatus = ?` params = append(params, stat) } if keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" params = append(params, like, like, like, like, like, like) } var total int64 countQuery := ` SELECT COUNT(DISTINCT a.orderheaderid) FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid LEFT JOIN app_users g ON f.userid = g.userid WHERE 1=1 ` countParams := []interface{}{} if aid != 0 { countQuery += ` AND a.applocationid = ?` countParams = append(countParams, aid) } if fdate != "" && tdate != "" { countQuery += ` AND a.deliverytime::date BETWEEN ? AND ?` countParams = append(countParams, fdate, tdate) } if stat != "" { countQuery += ` AND a.orderstatus = ?` countParams = append(countParams, stat) } if keyword != "" { countQuery += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" countParams = append(countParams, like, like, like, like, like, like) } r.db.Raw(countQuery, countParams...).Scan(&total) if int64(offset) >= total { offset = 0 pageno = 1 } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) fmt.Println("QUERY:", query) fmt.Println("PARAMS:", params) r.db.Raw(query, params...).Find(&data) fmt.Println("RESULT COUNT:", len(data)) return data, nil } func (r *orderRepository) GetUserOrders(stat, fdate, tdate string, uid, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) { var data []models.OrderInfo if pageno <= 0 { pageno = 1 } if pagesize <= 0 { pagesize = 10 } offset := (pageno - 1) * pagesize fmt.Println("Getting user order details") query := orderdetails + ` WHERE e.status = 'Active' AND e.userid = ?` params := []interface{}{uid} if fdate != "" && tdate != "" { query += ` AND a.deliverytime::date BETWEEN ? AND ?` params = append(params, fdate, tdate) } if stat != "" { query += ` AND a.orderstatus = ?` params = append(params, stat) } if keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" params = append(params, like, like, like, like, like, like) } var total int64 countQuery := ` SELECT COUNT(DISTINCT a.orderheaderid) FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid LEFT JOIN app_users g ON f.userid = g.userid WHERE e.status = 'Active' AND e.userid = ? ` countParams := []interface{}{uid} if fdate != "" && tdate != "" { countQuery += ` AND a.deliverytime::date BETWEEN ? AND ?` countParams = append(countParams, fdate, tdate) } if stat != "" { countQuery += ` AND a.orderstatus = ?` countParams = append(countParams, stat) } if keyword != "" { countQuery += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" countParams = append(countParams, like, like, like, like, like, like) } r.db.Raw(countQuery, countParams...).Scan(&total) if int64(offset) >= total { offset = 0 pageno = 1 } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) fmt.Println("QUERY:", query) fmt.Println("PARAMS:", params) r.db.Raw(query, params...).Find(&data) fmt.Println("RESULT COUNT:", len(data)) return data, nil } func (r *orderRepository) GetAllOrders(stat, fdate, tdate string, pageno, pagesize int, keyword string) ([]models.OrderInfo, error) { var data []models.OrderInfo if pageno <= 0 { pageno = 1 } if pagesize <= 0 { pagesize = 10 } offset := (pageno - 1) * pagesize fmt.Println("Getting all orders") query := orderdetails + ` WHERE 1=1` params := []interface{}{} if fdate != "" && tdate != "" { query += ` AND a.deliverytime::date BETWEEN ? AND ?` params = append(params, fdate, tdate) } if stat != "" { query += ` AND a.orderstatus = ?` params = append(params, stat) } if keyword != "" { query += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" params = append(params, like, like, like, like, like, like) } var total int64 countQuery := ` SELECT COUNT(DISTINCT a.orderheaderid) FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid INNER JOIN app_locationconfig e ON d.applocationid = e.applocationid LEFT JOIN deliveries f ON a.orderheaderid = f.orderheaderid LEFT JOIN app_users g ON f.userid = g.userid WHERE 1=1 ` countParams := []interface{}{} if fdate != "" && tdate != "" { countQuery += ` AND a.deliverytime::date BETWEEN ? AND ?` countParams = append(countParams, fdate, tdate) } if stat != "" { countQuery += ` AND a.orderstatus = ?` countParams = append(countParams, stat) } if keyword != "" { countQuery += ` AND ( a.pickupcustomer LIKE ? OR b.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` like := "%" + keyword + "%" countParams = append(countParams, like, like, like, like, like, like) } r.db.Raw(countQuery, countParams...).Scan(&total) if int64(offset) >= total { offset = 0 pageno = 1 } query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, pagesize, offset) fmt.Println("QUERY:", query) fmt.Println("PARAMS:", params) r.db.Raw(query, params...).Find(&data) fmt.Println("RESULT COUNT:", len(data)) return data, nil } func (r *orderRepository) GetOrderSummary(tid, pid, cid, lid int, fdate, tdate string) ([]models.Ordersummarydaily, error) { var data []models.Ordersummarydaily // Base SELECT const base = ` SELECT COUNT(*) AS total, SUM(CASE WHEN o.orderstatus = 'created' THEN 1 ELSE 0 END) AS created, SUM(CASE WHEN o.orderstatus = 'pending' THEN 1 ELSE 0 END) AS pending, SUM(CASE WHEN o.orderstatus = 'processing' THEN 1 ELSE 0 END) AS processing, SUM(CASE WHEN o.orderstatus = 'delivered' THEN 1 ELSE 0 END) AS delivered, SUM(CASE WHEN o.orderstatus = 'cancelled' THEN 1 ELSE 0 END) AS cancelled, t.tenantid, t.tenantname FROM orders o INNER JOIN tenants t ON o.tenantid = t.tenantid ` var params []interface{} var q1 string // Apply filters (at least one scoping id is required — enforced by the caller) switch { case tid != 0: q1 = base + " WHERE o.configid = 1 AND o.tenantid = ?" params = append(params, tid) case pid != 0: q1 = base + " WHERE o.configid = 1 AND o.partnerid = ?" params = append(params, pid) case cid != 0: q1 = base + " WHERE o.configid = 1 AND o.customerid = ?" params = append(params, cid) case lid != 0: q1 = base + " WHERE o.configid = 1 AND o.locationid = ?" params = append(params, lid) default: return nil, fmt.Errorf("at least one of tenantid, partnerid, customerid or locationid is required") } // Date filter if fdate != "" && tdate != "" { q1 += " AND o.orderdate::date BETWEEN ? AND ?" params = append(params, fdate, tdate) } // Group by tenant q1 += " GROUP BY t.tenantid, t.tenantname" // Debug fmt.Println("Executing GetOrderSummary query:", q1) if err := r.db.Raw(q1, params...).Scan(&data).Error; err != nil { return nil, err } return data, nil } func (r *orderRepository) GetLocationOrderSummary(tenantID int) ([]models.Ordersummarylocation, error) { var data []models.Ordersummarylocation var params []interface{} q1 := ` SELECT l.locationid, l.locationname, COALESCE(COUNT(o.orderid), 0) AS total, COALESCE(SUM(CASE WHEN o.orderstatus = 'created' THEN 1 ELSE 0 END), 0) AS created, COALESCE(SUM(CASE WHEN o.orderstatus = 'pending' THEN 1 ELSE 0 END), 0) AS pending, COALESCE(SUM(CASE WHEN o.orderstatus = 'processing' THEN 1 ELSE 0 END), 0) AS processing, COALESCE(SUM(CASE WHEN o.orderstatus = 'delivered' THEN 1 ELSE 0 END), 0) AS delivered, COALESCE(SUM(CASE WHEN o.orderstatus = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled FROM tenantlocations l LEFT JOIN orders o ON l.locationid = o.locationid AND l.tenantid = o.tenantid AND o.configid = 1 ` if tenantID != 0 { q1 += " WHERE l.tenantid = ?" params = append(params, tenantID) } q1 += " GROUP BY l.locationid, l.locationname ORDER BY l.locationid" fmt.Println("Executing query:", q1) if err := r.db.Raw(q1, params...).Scan(&data).Error; err != nil { return nil, err } return data, nil } func (r *orderRepository) GetRevenueSummary(tid, lid int, fdate, tdate string) (*models.TenantRevenueSummary, error) { var summary models.TenantRevenueSummary // 1. If tid is 0 and lid is not 0, lookup the tenantid from tenantlocations if tid == 0 && lid != 0 { var tenantLoc struct { Tenantid int } if err := r.db.Table("tenantlocations").Select("tenantid").Where("locationid = ?", lid).Scan(&tenantLoc).Error; err != nil { return nil, err } tid = tenantLoc.Tenantid } if tid == 0 { return nil, fmt.Errorf("tenant ID is required or could not be determined") } summary.Tenantid = tid // 2. Fetch the tenant name var tenant struct { Tenantname string } if err := r.db.Table("tenants").Select("tenantname").Where("tenantid = ?", tid).Scan(&tenant).Error; err != nil { return nil, err } summary.Tenantname = tenant.Tenantname // 3. Fetch overall revenue for this tenant overallQuery := ` SELECT COALESCE(SUM(orderamount), 0) AS overall_revenue FROM orders WHERE tenantid = ? AND orderstatus = 'delivered' AND configid = 1 ` var overallParams []interface{} overallParams = append(overallParams, tid) if lid != 0 { overallQuery += " AND locationid = ?" overallParams = append(overallParams, lid) } if fdate != "" && tdate != "" { overallQuery += " AND orderdate::date BETWEEN ? AND ?" overallParams = append(overallParams, fdate, tdate) } var overallRev float64 if err := r.db.Raw(overallQuery, overallParams...).Scan(&overallRev).Error; err != nil { return nil, err } summary.OverallRevenue = overallRev // 4. Fetch revenue details by location locationQuery := ` SELECT l.locationid, l.locationname, COALESCE(SUM(o.orderamount), 0) AS revenue FROM tenantlocations l LEFT JOIN orders o ON l.locationid = o.locationid AND o.orderstatus = 'delivered' AND o.configid = 1 ` var locParams []interface{} if fdate != "" && tdate != "" { locationQuery += " AND o.orderdate::date BETWEEN ? AND ?" locParams = append(locParams, fdate, tdate) } locationQuery += " WHERE l.tenantid = ?" locParams = append(locParams, tid) if lid != 0 { locationQuery += " AND l.locationid = ?" locParams = append(locParams, lid) } locationQuery += " GROUP BY l.locationid, l.locationname ORDER BY l.locationid" var locRevenues []models.LocationRevenueDetails if err := r.db.Raw(locationQuery, locParams...).Scan(&locRevenues).Error; err != nil { return nil, err } if locRevenues == nil { locRevenues = []models.LocationRevenueDetails{} } summary.LocationRevenue = locRevenues return &summary, nil } func (r *orderRepository) GetDistinctLocations() ([]models.OrderInsight, error) { var locations []models.OrderInsight q1 := ` SELECT DISTINCT a.applocationid, b.locationname FROM orders a INNER JOIN app_location b ON a.applocationid = b.applocationid WHERE b.status = 'Active'` if err := r.db.Raw(q1).Scan(&locations).Error; err != nil { return nil, err } return locations, nil } func (r *orderRepository) GetSalesSummary(tid, lid int, fdate, tdate string) (*models.SalesSummaryResponse, error) { var summary models.SalesSummaryResponse whereClause := "tenantid = ? AND orderstatus IN ('delivered', 'completed') AND configid = 1" var params []interface{} params = append(params, tid) if lid != 0 { whereClause += " AND locationid = ?" params = append(params, lid) } if fdate != "" && tdate != "" { whereClause += " AND orderdate::date BETWEEN ? AND ?" params = append(params, fdate, tdate) } totalsQuery := fmt.Sprintf(` SELECT COALESCE(SUM(COALESCE(ordervalue, 0) + COALESCE(orderamount, 0) + COALESCE(deliveryamt, 0)), 0) AS total_revenue, COUNT(orderheaderid) AS total_orders FROM orders WHERE %s`, whereClause) var result struct { TotalRevenue float64 TotalOrders int } if err := r.db.Raw(totalsQuery, params...).Scan(&result).Error; err != nil { return nil, err } summary.TotalRevenue = result.TotalRevenue summary.TotalOrders = result.TotalOrders if summary.TotalOrders > 0 { summary.AverageOrderValue = summary.TotalRevenue / float64(summary.TotalOrders) } chartQuery := fmt.Sprintf(` SELECT CAST(orderdate AS DATE) AS date, COALESCE(SUM(COALESCE(ordervalue, 0) + COALESCE(orderamount, 0) + COALESCE(deliveryamt, 0)), 0) AS revenue, COUNT(orderheaderid) AS orders FROM orders WHERE %s GROUP BY CAST(orderdate AS DATE) ORDER BY date ASC`, whereClause) var chartData []models.SalesSummaryChartData if err := r.db.Raw(chartQuery, params...).Scan(&chartData).Error; err != nil { return nil, err } if chartData == nil { chartData = []models.SalesSummaryChartData{} } summary.ChartData = chartData var topLocations []models.SalesSummaryTopLocation if lid == 0 { topWhere := "o.tenantid = ? AND o.orderstatus IN ('delivered', 'completed') AND o.configid = 1" var topParams []interface{} topParams = append(topParams, tid) if fdate != "" && tdate != "" { topWhere += " AND o.orderdate::date BETWEEN ? AND ?" topParams = append(topParams, fdate, tdate) } cleanTopLocQuery := fmt.Sprintf(` SELECT COALESCE(l.locationname, 'Unknown') AS locationname, COALESCE(SUM(COALESCE(o.ordervalue, 0) + COALESCE(o.orderamount, 0) + COALESCE(o.deliveryamt, 0)), 0) AS revenue FROM orders o LEFT JOIN tenantlocations l ON o.locationid = l.locationid WHERE %s GROUP BY l.locationid, l.locationname ORDER BY revenue DESC LIMIT 5`, topWhere) if err := r.db.Raw(cleanTopLocQuery, topParams...).Scan(&topLocations).Error; err != nil { return nil, err } } if topLocations == nil { topLocations = []models.SalesSummaryTopLocation{} } summary.TopLocations = topLocations return &summary, nil } func (r *orderRepository) GetMonthlyOrders(applocationid string) (*models.Ordermonths, error) { var orderMonths models.Ordermonths q2 := ` SELECT COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 1 THEN 1 ELSE 0 END), 0) AS jan, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 2 THEN 1 ELSE 0 END), 0) AS feb, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 3 THEN 1 ELSE 0 END), 0) AS mar, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 4 THEN 1 ELSE 0 END), 0) AS apr, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 5 THEN 1 ELSE 0 END), 0) AS may, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 6 THEN 1 ELSE 0 END), 0) AS jun, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 7 THEN 1 ELSE 0 END), 0) AS jul, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 8 THEN 1 ELSE 0 END), 0) AS aug, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 9 THEN 1 ELSE 0 END), 0) AS sep, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 10 THEN 1 ELSE 0 END), 0) AS oct, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 11 THEN 1 ELSE 0 END), 0) AS nov, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 12 THEN 1 ELSE 0 END), 0) AS dece FROM orders a WHERE a.applocationid = ? AND EXTRACT(YEAR FROM a.orderdate) = EXTRACT(YEAR FROM CURRENT_DATE)` if err := r.db.Raw(q2, applocationid).Scan(&orderMonths).Error; err != nil { return nil, err } return &orderMonths, nil } func (r *orderRepository) GetOrderInsights(tenantID int) ([]models.OrderInsightv1, error) { var locations []models.OrderInsightv1 var params []interface{} // ✅ Query 1: Get all locations (even without orders) q1 := `SELECT b.locationid, b.locationname FROM tenantlocations b WHERE b.status = 'Active'` if tenantID != 0 { q1 += " AND b.tenantid = ?" params = append(params, tenantID) } if err := r.db.Raw(q1, params...).Scan(&locations).Error; err != nil { return nil, err } // ✅ Query 2: Monthly order counts per location q2 := `SELECT COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 1 THEN 1 ELSE 0 END), 0) AS jan, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 2 THEN 1 ELSE 0 END), 0) AS feb, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 3 THEN 1 ELSE 0 END), 0) AS mar, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 4 THEN 1 ELSE 0 END), 0) AS apr, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 5 THEN 1 ELSE 0 END), 0) AS may, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 6 THEN 1 ELSE 0 END), 0) AS jun, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 7 THEN 1 ELSE 0 END), 0) AS jul, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 8 THEN 1 ELSE 0 END), 0) AS aug, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 9 THEN 1 ELSE 0 END), 0) AS sep, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 10 THEN 1 ELSE 0 END), 0) AS oct, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 11 THEN 1 ELSE 0 END), 0) AS nov, COALESCE(SUM(CASE WHEN EXTRACT(MONTH FROM a.orderdate) = 12 THEN 1 ELSE 0 END), 0) AS dece FROM orders a WHERE a.locationid = ? AND EXTRACT(YEAR FROM a.orderdate) = EXTRACT(YEAR FROM CURRENT_DATE)` if tenantID != 0 { q2 += " AND a.tenantid = ?" } // ✅ Attach monthly order counts for each location for i := range locations { var orderMonths models.Ordermonths if tenantID != 0 { if err := r.db.Raw(q2, locations[i].Locationid, tenantID).Scan(&orderMonths).Error; err != nil { return nil, err } } else { if err := r.db.Raw(q2, locations[i].Locationid).Scan(&orderMonths).Error; err != nil { return nil, err } } locations[i].Ordermonths = &orderMonths } return locations, nil } func (r *orderRepository) GetOrderDetails(orderHeaderID int) ([]models.OrderDetails, error) { var details []models.OrderDetails query := ` SELECT a.orderdetailid, a.orderheaderid, a.tenantid, a.locationid, a.productid, a.productname, a.productdescription, a.supplyqty, a.balanceqty, a.orderqty, a.price, a.unitid, a.unitname, a.productaddonid, a.addontypeid, a.productmapid, a.productvariantid, a.productaddondescription, a.discountid, a.discountname, a.discountcode, a.discountterms, a.discountpercentage, a.discountamount, a.landingamount, a.taxpercentage, a.taxamount, a.productsumprice, a.itemstatus, a.delivered, COALESCE(b.orderamount, 0) as orderamount, COALESCE(b.taxamount, 0) as totaltaxamount, c.productimage FROM orderdetails a LEFT JOIN orders b ON b.orderheaderid = a.orderheaderid LEFT JOIN products c ON a.productid = c.productid WHERE a.orderheaderid = ? ` if err := r.db.Raw(query, orderHeaderID).Scan(&details).Error; err != nil { return nil, err } return details, nil } func (r *orderRepository) UpdateOrder(order *models.Orders) error { tx := r.db.Begin() // Handle stock restoration on order cancellation var existingOrder models.Orders if err := tx.Where("orderheaderid = ?", order.Orderheaderid).First(&existingOrder).Error; err == nil { newStatus := strings.ToLower(strings.TrimSpace(order.Orderstatus)) oldStatus := strings.ToLower(strings.TrimSpace(existingOrder.Orderstatus)) if newStatus == "cancelled" && oldStatus != "cancelled" { var items []models.OrderDetail if err := tx.Table("orderdetails").Where("orderheaderid = ?", order.Orderheaderid).Find(&items).Error; err == nil { for _, item := range items { itemLocID := item.Locationid if itemLocID == 0 { itemLocID = existingOrder.Locationid } qty := int(item.Orderqty) if qty <= 0 { qty = 1 } restoredStock := models.Productstock{ Tenantid: existingOrder.Tenantid, Stockdate: time.Now(), Locationid: itemLocID, Productid: item.Productid, Quantity: qty, Stocktype: "in", Status: "Active", } if err := tx.Table("productstocks").Create(&restoredStock).Error; err != nil { tx.Rollback() return err } // Update productlocation status back to available if stock is restored tx.Table("productlocations"). Where("productid = ? AND tenantid = ? AND locationid = ?", item.Productid, existingOrder.Tenantid, itemLocID). Update("status", "available") } } } } if err := tx.Where("orderheaderid = ?", order.Orderheaderid).Updates(order).Error; err != nil { tx.Rollback() return err } return tx.Commit().Error } func (r *orderRepository) getSequenceno(tid int, prefix string) string { type SeqResult struct { Orderseqno string } var q1 string // Formats the ID as tenantid-subprefix+seqno (e.g., 908-20245189) switch prefix { case "ORD": q1 = `SELECT CONCAT(tenantid, '-', CASE WHEN subprefix IS NULL OR CAST(subprefix AS TEXT) IN ('0', '0.0', '') THEN '' ELSE CAST(subprefix AS TEXT) END, COALESCE(MAX(orderseqno) + 1, 1)) AS orderseqno FROM ordersequences WHERE tenantid = ? GROUP BY tenantid, subprefix` case "INV": q1 = `SELECT CONCAT(tenantid, '-', CASE WHEN subprefix IS NULL OR CAST(subprefix AS TEXT) IN ('0', '0.0', '') THEN '' ELSE CAST(subprefix AS TEXT) END, COALESCE(MAX(invoiceseqno) + 1, 1)) AS orderseqno FROM ordersequences WHERE tenantid = ? GROUP BY tenantid, subprefix` } var result SeqResult r.db.Raw(q1, tid).Scan(&result) // Fallback if no row exists in the database if result.Orderseqno == "" { return fmt.Sprintf("%d-1", tid) } return result.Orderseqno } func (r *orderRepository) updateSeqno(tid int, prefix string) error { var field string switch prefix { case "ORD": field = "orderseqno" case "INV": field = "invoiceseqno" default: return fmt.Errorf("invalid prefix: %s", prefix) } // 🛠️ Improved: Check if row exists, if not, create it var count int64 r.db.Table("ordersequences").Where("tenantid = ?", tid).Count(&count) if count == 0 { newSeq := map[string]interface{}{ "tenantid": tid, field: 1, "subprefix": nil, // Use NULL so CONCAT ignores it or set default here } return r.db.Table("ordersequences").Create(&newSeq).Error } // If it exists, perform the update return r.db.Table("ordersequences"). Where("tenantid = ?", tid). Update(field, gorm.Expr(fmt.Sprintf("%s + 1", field))).Error } func (r *orderRepository) CreateOrder(data models.Orders) (models.Orders, error) { tx := r.db.Begin() locID := data.Locationid if locID == 0 { locID = data.Applocationid } // 🛠️ Step 1: Pre-validate stock availability for all items before placing order for _, item := range data.Items { itemLocID := item.Locationid if itemLocID == 0 { itemLocID = locID } requestedQty := int(item.Orderqty) if requestedQty <= 0 { requestedQty = 1 } var availableStock int stockQuery := ` SELECT COALESCE( SUM(CASE WHEN LOWER(stocktype) = 'in' THEN quantity ELSE 0 END) - SUM(CASE WHEN LOWER(stocktype) = 'out' THEN quantity ELSE 0 END), 0 ) FROM productstocks WHERE productid = ? AND tenantid = ? AND locationid = ? ` if err := tx.Raw(stockQuery, item.Productid, data.Tenantid, itemLocID).Scan(&availableStock).Error; err != nil { tx.Rollback() return models.Orders{}, fmt.Errorf("failed to verify stock for product %d: %w", item.Productid, err) } // If stock tracking exists and available stock is less than requested quantity, block order placement if availableStock < requestedQty { tx.Rollback() pName := item.Productname if pName == "" { pName = fmt.Sprintf("ID %d", item.Productid) } return models.Orders{}, fmt.Errorf("insufficient stock for product '%s': requested %d, available %d", pName, requestedQty, availableStock) } } // 🛠️ Step 2: Create Order Header data.Orderid = r.getSequenceno(data.Tenantid, "ORD") if err := tx.Create(&data).Error; err != nil { tx.Rollback() return models.Orders{}, err } // 🛠️ Step 3: Insert Order Details & Record "out" stock deduction for _, item := range data.Items { item.Orderheaderid = data.Orderheaderid item.Tenantid = data.Tenantid itemLocID := item.Locationid if itemLocID == 0 { itemLocID = locID } item.Locationid = itemLocID if err := tx.Table("orderdetails").Create(&item).Error; err != nil { tx.Rollback() return models.Orders{}, err } qty := int(item.Orderqty) if qty <= 0 { qty = 1 } stock := models.Productstock{ Tenantid: data.Tenantid, Stockdate: time.Now(), Locationid: itemLocID, Productid: item.Productid, Quantity: qty, Stocktype: "out", Status: "Active", } if err := tx.Table("productstocks").Create(&stock).Error; err != nil { tx.Rollback() return models.Orders{}, err } // Update productlocation status to 'outofstock' if remaining stock drops to 0 or below var remainingStock int remStockQuery := ` SELECT COALESCE( SUM(CASE WHEN LOWER(stocktype) = 'in' THEN quantity ELSE 0 END) - SUM(CASE WHEN LOWER(stocktype) = 'out' THEN quantity ELSE 0 END), 0 ) FROM productstocks WHERE productid = ? AND tenantid = ? AND locationid = ? ` if err := tx.Raw(remStockQuery, item.Productid, data.Tenantid, itemLocID).Scan(&remainingStock).Error; err == nil { if remainingStock <= 0 { tx.Table("productlocations"). Where("productid = ? AND tenantid = ? AND locationid = ?", item.Productid, data.Tenantid, itemLocID). Update("status", "outofstock") } } } if err := r.updateSeqno(data.Tenantid, "ORD"); err != nil { log.Println("updateSeqno error:", err) } if err := tx.Commit().Error; err != nil { return models.Orders{}, err } var order models.Orders if err := r.db.Where("orderheaderid = ?", data.Orderheaderid).First(&order).Error; err != nil { return models.Orders{}, err } var items []models.OrderDetail if err := r.db.Table("orderdetails").Where("orderheaderid = ?", data.Orderheaderid).Find(&items).Error; err == nil { order.Items = items } return order, nil } func (r *orderRepository) getOrderDetailsByHeaderID(orderHeaderID int) ([]models.OrderDetails, float64, float64, error) { var details []models.OrderDetails var orderAmount float64 var totalTaxAmount float64 query := ` SELECT a.orderdetailid,a.orderheaderid,a.tenantid,a.locationid,a.productid,a.productname,a.productdescription,a.supplyqty,a.balanceqty,a.orderqty,a.price,a.unitid, a.unitname,a.productaddonid,a.addontypeid,a.productmapid,a.productvariantid,a.productaddondescription,a.discountid,a.discountname,a.discountcode,a.discountterms, a.discountpercentage,a.discountamount,a.landingamount,a.taxpercentage,a.taxamount,a.productsumprice,a.itemstatus,a.delivered,COALESCE(b.orderamount, 0) as orderamount, COALESCE(b.taxamount, 0) as totaltaxamount,c.productimage FROM orderdetails a LEFT JOIN orders b ON b.orderheaderid = a.orderheaderid LEFT JOIN products c ON a.productid = c.productid WHERE a.orderheaderid = ?` err := r.db.Raw(query, orderHeaderID).Scan(&details).Error if err != nil { return nil, 0, 0, err } if len(details) > 0 { orderAmount = details[0].Orderamount totalTaxAmount = details[0].Totaltaxamount } return details, orderAmount, totalTaxAmount, nil } func (r *orderRepository) GetCustomerOrdersv3(customerID, tenantID, moduleID, fromDate, toDate, orderStatus, keyword string, pageSize, offset int) ([]models.CustomerOrder, error) { orders := make([]models.CustomerOrder, 0) baseQuery := ` SELECT a.orderheaderid, a.applocationid, a.tenantid, a.locationid, a.partnerid, a.configid, a.categoryid, a.subcategoryid, a.moduleid, a.orderid, a.orderstatus, a.orderdate, a.ordernotes, a.itemcount, a.deliverytime, a.pending, a.processing, a.ready, a.delivered, a.cancelled, a.deliverycharge, a.kms, a.customerid, a.pickupaddress, a.pickuplat, a.pickuplong, a.pickupcustomer, a.pickupcontactno, a.pickuplocation AS pickupsuburb, a.pickupcity, a.deliveryid AS deliverycustomerid, a.deliveryaddress, a.deliverylat, a.deliverylong, a.deliverytype, a.deliverycustomer, a.deliverycontactno, a.deliverylocation AS deliverysuburb, a.deliverycity, a.paymenttype, a.smsdelivery, a.taxamount, b.tenantname, b.tenanttoken, b.primarycontact AS tenantcontactno, b.postcode AS tenantpostcode, b.suburb AS tenantsuburb, b.city AS tenantcity, b.registrationno, c.locationname, c.contactno AS locationcontactno, c.postcode AS locationpostcode, c.suburb AS locationsuburb, c.city AS locationcity, d.locationname AS applocation FROM orders a INNER JOIN tenants b ON a.tenantid = b.tenantid INNER JOIN tenantlocations c ON a.locationid = c.locationid INNER JOIN app_location d ON a.applocationid = d.applocationid WHERE 1=1 ` params := []interface{}{} if customerID != "" { baseQuery += " AND a.customerid = ?" params = append(params, customerID) } if tenantID != "" && tenantID != "0" { baseQuery += " AND a.tenantid = ?" params = append(params, tenantID) } if moduleID != "" { baseQuery += " AND a.moduleid = ?" params = append(params, moduleID) } if fromDate != "" && toDate != "" { baseQuery += " AND DATE(a.orderdate) BETWEEN ? AND ?" params = append(params, fromDate, toDate) } if orderStatus != "" { baseQuery += " AND a.orderstatus = ?" params = append(params, orderStatus) } if keyword != "" { baseQuery += " AND EXISTS (SELECT 1 FROM orderdetails od WHERE od.orderheaderid = a.orderheaderid AND od.productname LIKE ?)" params = append(params, "%"+keyword+"%") } baseQuery += " ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?" params = append(params, pageSize, offset) if err := r.db.Raw(baseQuery, params...).Scan(&orders).Error; err != nil { return nil, err } // 🚀 NO LOOPING YET — Now fetch all details in one query return r.attachOrderDetails(orders) } func (r *orderRepository) attachOrderDetails(orders []models.CustomerOrder) ([]models.CustomerOrder, error) { if len(orders) == 0 { return orders, nil } orderIDs := make([]int, 0, len(orders)) for _, o := range orders { orderIDs = append(orderIDs, o.Orderheaderid) } var details []models.OrderDetails query := ` SELECT a.*, p.productimage FROM orderdetails a LEFT JOIN products p ON a.productid = p.productid WHERE a.orderheaderid IN ? ` if err := r.db.Raw(query, orderIDs).Scan(&details).Error; err != nil { return nil, err } // map orderheaderid → []details detailMap := map[int][]models.OrderDetails{} for _, d := range details { detailMap[d.Orderheaderid] = append(detailMap[d.Orderheaderid], d) } for i := range orders { orders[i].OrderDetails = detailMap[orders[i].Orderheaderid] if len(orders[i].OrderDetails) > 0 { orders[i].Orderamount = orders[i].OrderDetails[0].Orderamount orders[i].Totaltaxamount = orders[i].OrderDetails[0].Totaltaxamount } } return orders, nil } func (r *orderRepository) GetTenantLocationOrders(input models.DeliveryQuery) ([]models.OrderInfo, error) { var data []models.OrderInfo var params []interface{} offset := (input.Pageno - 1) * input.Pagesize // Start building SQL query := base + ` WHERE a.tenantid = ? AND a.locationid = ? ` params = append(params, input.Tenantid, input.Locationid) // Status filter if input.Status == "ongoing" { query += ` AND a.orderstatus IN ('pending','processing','ready')` } else if input.Status != "" { query += ` AND a.orderstatus = ?` params = append(params, input.Status) } // Applocation filter if input.Applocationid != 0 { query += ` AND a.applocationid = ?` params = append(params, input.Applocationid) } // Config filter if input.Configid != 0 { query += ` AND a.configid = ?` params = append(params, input.Configid) } // Date filter query += ` AND DATE(a.deliverytime) BETWEEN ? AND ?` params = append(params, input.Fromdate, input.ToDate) // Keyword filter if input.Keyword != "" { like := "%" + input.Keyword + "%" query += ` AND ( a.pickupcustomer LIKE ? OR c.tenantname LIKE ? OR a.deliverycustomer LIKE ? OR a.pickupcontactno LIKE ? OR a.deliverycontactno LIKE ? OR a.orderid LIKE ? )` params = append(params, like, like, like, like, like, like) } // Sorting + pagination query += ` ORDER BY a.orderheaderid DESC LIMIT ? OFFSET ?` params = append(params, input.Pagesize, offset) fmt.Println("Executing Tenant+Location SQL:", query) result := r.db.Raw(query, params...).Find(&data) return data, result.Error } func (r *orderRepository) GetTimeSeries(tenantID, locationID int, granularity, fromDate, toDate string) ([]models.TimeSeriesData, error) { var data []models.TimeSeriesData var dateFunc, dateFuncO2 string switch granularity { case "month": dateFunc = "TO_CHAR(o.orderdate::date, 'YYYY-MM')" dateFuncO2 = "TO_CHAR(o2.orderdate::date, 'YYYY-MM')" case "year": dateFunc = "TO_CHAR(o.orderdate::date, 'YYYY')" dateFuncO2 = "TO_CHAR(o2.orderdate::date, 'YYYY')" case "day": fallthrough default: dateFunc = "TO_CHAR(o.orderdate::date, 'YYYY-MM-DD')" dateFuncO2 = "TO_CHAR(o2.orderdate::date, 'YYYY-MM-DD')" } var locFilter, dateFilter string var params []interface{} // subquery params params = append(params, tenantID) if locationID != 0 { locFilter = " AND o2.locationid = ?" params = append(params, locationID) } if fromDate != "" && toDate != "" { dateFilter = " AND o2.orderdate::date BETWEEN ? AND ?" params = append(params, fromDate, toDate) } // main query params params = append(params, tenantID) if locationID != 0 { params = append(params, locationID) } if fromDate != "" && toDate != "" { params = append(params, fromDate, toDate) } query := fmt.Sprintf(` SELECT %s AS label, COUNT(o.orderheaderid) AS orders, COALESCE(SUM(o.orderamount), 0) AS revenue, COALESCE(SUM(CASE WHEN o.orderstatus = 'cancelled' THEN 1 ELSE 0 END), 0) AS cancelled, COALESCE(SUM(CASE WHEN o.orderstatus IN ('delivered', 'completed') THEN 1 ELSE 0 END), 0) AS delivered, COALESCE(MAX(sku_counts.activeskus), 0) AS activeskus FROM orders o LEFT JOIN ( SELECT %s AS label, COUNT(DISTINCT d.productid) AS activeskus FROM orders o2 JOIN orderdetails d ON o2.orderheaderid = d.orderheaderid WHERE o2.tenantid = ? AND o2.configid = 1 %s %s GROUP BY %s ) sku_counts ON %s = sku_counts.label WHERE o.tenantid = ? AND o.configid = 1 `, dateFunc, dateFuncO2, locFilter, dateFilter, dateFuncO2, dateFunc) if locationID != 0 { query += " AND o.locationid = ?" } if fromDate != "" && toDate != "" { query += " AND o.orderdate::date BETWEEN ? AND ?" } query += fmt.Sprintf(" GROUP BY %s ORDER BY %s", dateFunc, dateFunc) if err := r.db.Raw(query, params...).Scan(&data).Error; err != nil { return nil, err } if data == nil { data = []models.TimeSeriesData{} } return data, nil }