package http import ( "enaklo-pos-be/internal/common/errors" order2 "enaklo-pos-be/internal/constants/order" "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/handlers/request" "enaklo-pos-be/internal/handlers/response" "enaklo-pos-be/internal/services/v2/order" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "net/http" "strconv" "time" ) type Handler struct { service order.Service } func NewOrderHandler(service order.Service) *Handler { return &Handler{ service: service, } } func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { route := group.Group("/order") route.POST("/inquiry", jwt, h.Inquiry) route.POST("/execute", jwt, h.Execute) route.POST("/refund", jwt, h.Refund) route.GET("/history", jwt, h.GetOrderHistory) route.GET("/payment-analysis", jwt, h.GetPaymentMethodAnalysis) route.GET("/revenue-overview", jwt, h.GetRevenueOverview) route.GET("/sales-by-category", jwt, h.GetSalesByCategory) route.GET("/popular-products", jwt, h.GetPopularProducts) } type InquiryRequest struct { CustomerID *int64 `json:"customer_id"` CustomerName string `json:"customer_name" validate:"required_without=CustomerID"` CustomerEmail string `json:"customer_email"` CustomerPhoneNumber string `json:"customer_phone_number"` PaymentMethod string `json:"payment_method" validate:"required"` OrderItems []OrderItemRequest `json:"order_items" validate:"required,min=1,dive"` OrderType string `json:"order_type"` PaymentProvider string `json:"payment_provider"` TableNumber string `json:"table_number"` CashierSessionID int64 `json:"cashier_session_id"` } func (o *InquiryRequest) GetPaymentProvider() string { if o.PaymentMethod == "CASH" { return "CASH" } return o.PaymentProvider } type OrderItemRequest struct { ProductID int64 `json:"product_id" validate:"required"` Quantity int `json:"quantity" validate:"required,min=1"` Notes string `json:"notes"` } type ExecuteRequest struct { PaymentMethod string `json:"payment_method" validate:"required"` PaymentProvider string `json:"payment_provider"` InProgressOrderID int64 `json:"in_progress_order_id"` Token string `json:"token"` } type RefundRequest struct { OrderID int64 `json:"order_id" validate:"required"` Reason string `json:"reason" validate:"required"` } func (h *Handler) Inquiry(c *gin.Context) { ctx := request.GetMyContext(c) userID := ctx.RequestedBy() partnerID := ctx.GetPartnerID() var req InquiryRequest if err := c.ShouldBindJSON(&req); err != nil { response.ErrorWrapper(c, errors.ErrorBadRequest) return } validate := validator.New() if err := validate.Struct(req); err != nil { response.ErrorWrapper(c, err) return } orderItems := make([]entity.OrderItemRequest, len(req.OrderItems)) for i, item := range req.OrderItems { orderItems[i] = entity.OrderItemRequest{ ProductID: item.ProductID, Quantity: item.Quantity, Notes: item.Notes, } } orderReq := &entity.OrderRequest{ Source: "POS", CreatedBy: userID, PartnerID: *partnerID, PaymentMethod: req.PaymentMethod, OrderItems: orderItems, CustomerID: req.CustomerID, CustomerName: req.CustomerName, CustomerEmail: req.CustomerEmail, CustomerPhoneNumber: req.CustomerPhoneNumber, OrderType: req.OrderType, PaymentProvider: req.GetPaymentProvider(), TableNumber: req.TableNumber, CashierSessionID: req.CashierSessionID, } result, err := h.service.CreateOrderInquiry(ctx, orderReq) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: response.MapToInquiryResponse(result), }) } func (h *Handler) Execute(c *gin.Context) { ctx := request.GetMyContext(c) var req ExecuteRequest if err := c.ShouldBindJSON(&req); err != nil { response.ErrorWrapper(c, errors.ErrorBadRequest) return } validate := validator.New() if err := validate.Struct(req); err != nil { response.ErrorWrapper(c, err) return } result, err := h.service.ExecuteOrderInquiry(ctx, req.Token, req.PaymentMethod, req.PaymentProvider, req.InProgressOrderID) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: response.MapToOrderResponse(result), }) } func (h *Handler) Refund(c *gin.Context) { ctx := request.GetMyContext(c) var req RefundRequest if err := c.ShouldBindJSON(&req); err != nil { response.ErrorWrapper(c, errors.ErrorBadRequest) return } validate := validator.New() if err := validate.Struct(req); err != nil { response.ErrorWrapper(c, err) return } err := h.service.RefundRequest(ctx, *ctx.GetPartnerID(), req.OrderID, req.Reason) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, }) } func (h *Handler) GetOrderHistory(c *gin.Context) { ctx := request.GetMyContext(c) partnerID := ctx.GetPartnerID() limitStr := c.Query("limit") offsetStr := c.Query("offset") status := c.Query("status") startDateStr := c.Query("start_date") endDateStr := c.Query("end_date") searchReq := entity.SearchRequest{} if status != "" { searchReq.Status = status } limit := 20 if limitStr != "" { parsedLimit, err := strconv.Atoi(limitStr) if err == nil && parsedLimit > 0 { limit = parsedLimit } } if limit > 100 { limit = 100 } searchReq.Limit = limit offset := 0 if offsetStr != "" { parsedOffset, err := strconv.Atoi(offsetStr) if err == nil && parsedOffset >= 0 { offset = parsedOffset } } searchReq.Offset = offset if startDateStr != "" { startDate, err := time.Parse(time.RFC3339, startDateStr) if err == nil { searchReq.Start = startDate } } // Parse end date if provided if endDateStr != "" { endDate, err := time.Parse(time.RFC3339, endDateStr) if err == nil { searchReq.End = endDate } } orders, total, err := h.service.GetOrderHistory(ctx, *partnerID, searchReq) if err != nil { response.ErrorWrapper(c, err) return } responseData := []response.OrderHistoryResponse{} for _, order := range orders { var orderItems []response.OrderItemResponse for _, item := range order.OrderItems { orderItems = append(orderItems, response.OrderItemResponse{ ProductID: item.ItemID, ProductName: item.ItemName, Price: item.Price, Quantity: item.Quantity, Subtotal: item.Price * float64(item.Quantity), }) } responseData = append(responseData, response.OrderHistoryResponse{ ID: order.ID, CustomerName: order.CustomerName, CustomerID: order.CustomerID, IsMember: order.IsMemberOrder(), Status: order.Status, Amount: order.Amount, Total: order.Total, PaymentType: h.formatPayment(order.PaymentType, order.PaymentProvider), TableNumber: order.TableNumber, OrderType: order.OrderType, OrderItems: orderItems, CreatedAt: order.CreatedAt.Format("2006-01-02T15:04:05Z"), Tax: order.Tax, }) } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: responseData, PagingMeta: &response.PagingMeta{ Page: offset + 1, Total: int64(total), Limit: limit, }, }) } func (h *Handler) formatPayment(payment, provider string) string { if payment == "CASH" { return payment } return payment + " " + provider } func (h *Handler) GetPaymentMethodAnalysis(c *gin.Context) { ctx := request.GetMyContext(c) partnerID := ctx.GetPartnerID() // Parse query parameters limitStr := c.Query("limit") offsetStr := c.Query("offset") status := c.Query("status") startDateStr := c.Query("start_date") endDateStr := c.Query("end_date") searchReq := entity.SearchRequest{} limit := 10 if limitStr != "" { parsedLimit, err := strconv.Atoi(limitStr) if err == nil && parsedLimit > 0 { limit = parsedLimit } } if limit > 20 { limit = 20 } searchReq.Limit = limit offset := 0 if offsetStr != "" { parsedOffset, err := strconv.Atoi(offsetStr) if err == nil && parsedOffset >= 0 { offset = parsedOffset } } searchReq.Offset = offset if status != "" { searchReq.Status = status } if startDateStr != "" { startDate, err := time.Parse(time.RFC3339, startDateStr) if err == nil { searchReq.Start = startDate } } if endDateStr != "" { endDate, err := time.Parse(time.RFC3339, endDateStr) if err == nil { searchReq.End = endDate } } paymentAnalysis, err := h.service.GetOrderPaymentAnalysis(ctx, *partnerID, searchReq) if err != nil { response.ErrorWrapper(c, err) return } paymentBreakdown := make([]PaymentMethodBreakdown, len(paymentAnalysis.PaymentMethodBreakdown)) for i, bd := range paymentAnalysis.PaymentMethodBreakdown { paymentBreakdown[i] = PaymentMethodBreakdown{ PaymentMethod: h.formatPayment(bd.PaymentType, bd.PaymentProvider), TotalTransactions: bd.TotalTransactions, TotalAmount: bd.TotalAmount, } } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: PaymentMethodAnalysisResponse{ PaymentMethodBreakdown: paymentBreakdown, TotalAmount: paymentAnalysis.TotalAmount, TotalTransactions: paymentAnalysis.TotalTransactions, }, }) } type PaymentMethodBreakdown struct { PaymentMethod string `json:"payment_method"` TotalTransactions int64 `json:"total_transactions"` TotalAmount float64 `json:"total_amount"` AverageTransactionAmount float64 `json:"average_transaction_amount"` Percentage float64 `json:"percentage"` } type PaymentMethodAnalysisResponse struct { PaymentMethodBreakdown []PaymentMethodBreakdown `json:"payment_method_breakdown"` TotalAmount float64 `json:"total_amount"` TotalTransactions int64 `json:"total_transactions"` MostUsedPaymentMethod string `json:"most_used_payment_method"` HighestRevenueMethod string `json:"highest_revenue_method"` } func (h *Handler) GetRevenueOverview(c *gin.Context) { ctx := request.GetMyContext(c) partnerID := ctx.GetPartnerID() granularity := c.Query("period") year := time.Now().Year() if granularity != "m" && granularity != "w" && granularity != "d" { granularity = "m" } revenueOverview, err := h.service.GetRevenueOverview( ctx, *partnerID, year, granularity, order2.Paid.String(), ) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: revenueOverview, }) } func (h *Handler) GetSalesByCategory(c *gin.Context) { ctx := request.GetMyContext(c) partnerID := ctx.GetPartnerID() period := c.Query("period") status := order2.Paid.String() if period != "d" && period != "w" && period != "m" { period = "d" } salesByCategory, err := h.service.GetSalesByCategory( ctx, *partnerID, period, status, ) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: salesByCategory, }) } func (h *Handler) GetPopularProducts(c *gin.Context) { ctx := request.GetMyContext(c) partnerID := ctx.GetPartnerID() period := c.Query("period") status := order2.Paid.String() sortBy := c.Query("sort_by") limit := 1000 if period != "d" && period != "w" && period != "m" { period = "d" } popularProducts, err := h.service.GetPopularProducts( ctx, *partnerID, period, status, limit, sortBy, ) if err != nil { response.ErrorWrapper(c, err) return } c.JSON(http.StatusOK, response.BaseResponse{ Success: true, Status: http.StatusOK, Data: popularProducts, }) }