diff --git a/controllers/orderController.go b/controllers/orderController.go index 6484f6d..d03e6ba 100644 --- a/controllers/orderController.go +++ b/controllers/orderController.go @@ -415,3 +415,43 @@ func (ctl *OrderController) GetSalesSummary(c *fiber.Ctx) error { "details": data, }) } + +func (ctl *OrderController) GetTimeSeries(c *fiber.Ctx) error { + tid, _ := strconv.Atoi(c.Query("tenantid")) + lid, _ := strconv.Atoi(c.Query("locationid")) + granularity := c.Query("granularity") + fdate := c.Query("fromdate") + tdate := c.Query("todate") + + if tid == 0 { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "tenantid query parameter is required", + "status": false, + }) + } + + if granularity == "" { + return c.Status(http.StatusBadRequest).JSON(fiber.Map{ + "code": http.StatusBadRequest, + "message": "granularity query parameter is required (day, month, year)", + "status": false, + }) + } + + data, err := ctl.orderService.GetTimeSeries(tid, lid, granularity, fdate, tdate) + if err != nil { + return c.Status(http.StatusInternalServerError).JSON(fiber.Map{ + "code": http.StatusInternalServerError, + "message": err.Error(), + "status": false, + }) + } + + return c.Status(http.StatusOK).JSON(fiber.Map{ + "code": http.StatusOK, + "message": "Success", + "status": true, + "details": data, + }) +} diff --git a/controllers/stockrequestController.go b/controllers/stockrequestController.go index 2684b27..2db4278 100644 --- a/controllers/stockrequestController.go +++ b/controllers/stockrequestController.go @@ -40,10 +40,11 @@ func (ctl *StockRequestController) GetStockRequests(c *fiber.Ctx) error { tenantID, _ := strconv.Atoi(c.Query("tenantid", "0")) locationID, _ := strconv.Atoi(c.Query("locationid", "0")) status := c.Query("status", "") + date := c.Query("date", "") pageNo, _ := strconv.Atoi(c.Query("pageno", "1")) pageSize, _ := strconv.Atoi(c.Query("pagesize", "50")) - data, err := ctl.stockRequestService.GetStockRequests(tenantID, locationID, status, pageNo, pageSize) + data, err := ctl.stockRequestService.GetStockRequests(tenantID, locationID, status, date, pageNo, pageSize) if err != nil { return c.JSON(fiber.Map{"code": http.StatusInternalServerError, "message": err.Error(), "status": false}) } diff --git a/main.go b/main.go index 0c6ecb6..bd2b607 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "log" "nearle/db" "nearle/facade" + "nearle/models" "nearle/routes" "os" "os/signal" @@ -37,6 +38,9 @@ func main() { fmt.Println("🌐 Connecting to databases...") db.Connect() fmt.Println("✅ Database connections established!") + + // Ensure schema is updated + db.DB.AutoMigrate(&models.StockRequest{}) f := facade.NewFacade(db.DB) diff --git a/models/order.go b/models/order.go index 5eaa2c6..73b8d67 100644 --- a/models/order.go +++ b/models/order.go @@ -470,3 +470,12 @@ type SalesSummaryResponse struct { ChartData []SalesSummaryChartData `json:"chartData"` TopLocations []SalesSummaryTopLocation `json:"topLocations"` } + +type TimeSeriesData struct { + Label string `json:"label"` + Orders int `json:"orders"` + Revenue float64 `json:"revenue"` + Cancelled int `json:"cancelled"` + Delivered int `json:"delivered"` + Activeskus int `json:"activeskus"` +} diff --git a/models/stockrequest.go b/models/stockrequest.go index 4798236..53fafde 100644 --- a/models/stockrequest.go +++ b/models/stockrequest.go @@ -5,10 +5,12 @@ import "time" type StockRequest struct { Requestid int `json:"requestid" gorm:"primaryKey;autoIncrement;column:requestid"` Tenantid int `json:"tenantid" gorm:"column:tenantid"` + Tenantname string `json:"tenantname" gorm:"column:tenantname"` Locationid int `json:"locationid" gorm:"column:locationid"` + Locationname string `json:"locationname" gorm:"column:locationname"` Productid int `json:"productid" gorm:"column:productid"` - Productname string `json:"productname" gorm:"->"` - Productimage string `json:"productimage" gorm:"->"` + Productname string `json:"productname" gorm:"column:productname"` + Productimage string `json:"productimage" gorm:"column:productimage"` Qty int `json:"qty" gorm:"column:qty"` Status string `json:"status" gorm:"column:status;default:Pending"` Created time.Time `json:"created" gorm:"column:created;autoCreateTime"` diff --git a/repositories/orderRepository.go b/repositories/orderRepository.go index 33e1995..46f79ac 100644 --- a/repositories/orderRepository.go +++ b/repositories/orderRepository.go @@ -27,6 +27,7 @@ type OrderRepository interface { 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 { @@ -1325,3 +1326,87 @@ func (r *orderRepository) GetTenantLocationOrders(input models.DeliveryQuery) ([ 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 +} diff --git a/repositories/stockrequest.go b/repositories/stockrequest.go index 2658e67..25136e9 100644 --- a/repositories/stockrequest.go +++ b/repositories/stockrequest.go @@ -8,7 +8,7 @@ import ( type StockRequestRepository interface { CreateStockRequest(req *models.StockRequest) error - GetStockRequests(tenantID int, locationID int, status string, pageNo int, pageSize int) ([]models.StockRequest, error) + GetStockRequests(tenantID int, locationID int, status string, date string, pageNo int, pageSize int) ([]models.StockRequest, error) GetStockRequestByID(requestID int) (*models.StockRequest, error) UpdateStockRequest(requestID int, status string) error } @@ -22,10 +22,30 @@ func NewStockRequestRepository(db *gorm.DB) StockRequestRepository { } func (r *stockRequestRepository) CreateStockRequest(req *models.StockRequest) error { + // Fetch product details + var prod struct { + Productname string + Productimage string + } + r.db.Table("products").Select("productname, productimage").Where("productid = ?", req.Productid).Scan(&prod) + if req.Productname == "" { + req.Productname = prod.Productname + } + if req.Productimage == "" { + req.Productimage = prod.Productimage + } + + // Fetch tenant name + var tenantName string + r.db.Table("tenants").Select("tenantname").Where("tenantid = ?", req.Tenantid).Scan(&tenantName) + if req.Tenantname == "" { + req.Tenantname = tenantName + } + return r.db.Create(req).Error } -func (r *stockRequestRepository) GetStockRequests(tenantID int, locationID int, status string, pageNo int, pageSize int) ([]models.StockRequest, error) { +func (r *stockRequestRepository) GetStockRequests(tenantID int, locationID int, status string, date string, pageNo int, pageSize int) ([]models.StockRequest, error) { var requests []models.StockRequest query := r.db.Table("stockrequests"). Select("stockrequests.*, products.productname, products.productimage"). @@ -40,6 +60,10 @@ func (r *stockRequestRepository) GetStockRequests(tenantID int, locationID int, query = query.Where("stockrequests.status = ?", status) } + if date != "" { + query = query.Where("DATE(stockrequests.created) = ?", date) + } + offset := (pageNo - 1) * pageSize err := query.Order("stockrequests.created DESC").Offset(offset).Limit(pageSize).Find(&requests).Error return requests, err @@ -47,7 +71,12 @@ func (r *stockRequestRepository) GetStockRequests(tenantID int, locationID int, func (r *stockRequestRepository) GetStockRequestByID(requestID int) (*models.StockRequest, error) { var req models.StockRequest - err := r.db.Table("stockrequests").Where("requestid = ?", requestID).First(&req).Error + err := r.db.Table("stockrequests"). + Select("stockrequests.*, products.productname, products.productimage, tenants.tenantname, tenantlocations.locationname"). + Joins("left join products on products.productid = stockrequests.productid"). + Joins("left join tenants on tenants.tenantid = stockrequests.tenantid"). + Joins("left join tenantlocations on tenantlocations.locationid = stockrequests.locationid"). + Where("requestid = ?", requestID).First(&req).Error return &req, err } diff --git a/routes/orderroutes.go b/routes/orderroutes.go index 9cd0ca0..a44451c 100644 --- a/routes/orderroutes.go +++ b/routes/orderroutes.go @@ -20,6 +20,7 @@ func RegisterOrderRoutes(api fiber.Router, f *facade.Facade) { orders.Get("/getlocationsummary", f.OrderController.GetlocationOrderSummary) orders.Get("/getorderinsight", f.OrderController.GetOrderInsights) orders.Get("/getrevenuesummary", f.OrderController.GetRevenueSummary) + orders.Get("/gettimeseries", f.OrderController.GetTimeSeries) orders.Get("/getorderdetails", f.OrderController.GetOrderDetails) orders.Put("/updateorder", f.OrderController.UpdateOrder) orders.Post("/createorder", f.OrderController.CreateOrderv3) diff --git a/services/orderService.go b/services/orderService.go index 18b5569..e35426d 100644 --- a/services/orderService.go +++ b/services/orderService.go @@ -22,6 +22,7 @@ type OrderService interface { GetTenantLocationOrders(input models.DeliveryQuery) ([]models.OrderInfo, 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 orderService struct { @@ -96,3 +97,7 @@ func (s *orderService) GetSalesSummary(tid, lid int, fdate, tdate string) (*mode return s.repo.GetSalesSummary(tid, lid, fdate, tdate) } +func (s *orderService) GetTimeSeries(tenantID, locationID int, granularity, fromDate, toDate string) ([]models.TimeSeriesData, error) { + return s.repo.GetTimeSeries(tenantID, locationID, granularity, fromDate, toDate) +} + diff --git a/services/stockrequestService.go b/services/stockrequestService.go index 969b916..497fb9e 100644 --- a/services/stockrequestService.go +++ b/services/stockrequestService.go @@ -8,7 +8,7 @@ import ( type StockRequestService interface { CreateStockRequest(req *models.StockRequest) error - GetStockRequests(tenantID int, locationID int, status string, pageNo int, pageSize int) ([]models.StockRequest, error) + GetStockRequests(tenantID int, locationID int, status string, date string, pageNo int, pageSize int) ([]models.StockRequest, error) UpdateStockRequest(requestID int, status string) error } @@ -25,8 +25,8 @@ func (s *stockRequestService) CreateStockRequest(req *models.StockRequest) error return s.repo.CreateStockRequest(req) } -func (s *stockRequestService) GetStockRequests(tenantID int, locationID int, status string, pageNo int, pageSize int) ([]models.StockRequest, error) { - return s.repo.GetStockRequests(tenantID, locationID, status, pageNo, pageSize) +func (s *stockRequestService) GetStockRequests(tenantID int, locationID int, status string, date string, pageNo int, pageSize int) ([]models.StockRequest, error) { + return s.repo.GetStockRequests(tenantID, locationID, status, date, pageNo, pageSize) } func (s *stockRequestService) UpdateStockRequest(requestID int, status string) error { @@ -43,7 +43,7 @@ func (s *stockRequestService) UpdateStockRequest(requestID int, status string) e Locationid: req.Locationid, Productid: req.Productid, Quantity: req.Qty, - Stocktype: "Credit", + Stocktype: "in", Stockdate: time.Now(), }