From ebb33186b82b7c9b300b822bb760debe6273cee4 Mon Sep 17 00:00:00 2001 From: "aditya.siregar" Date: Sat, 14 Jun 2025 21:17:13 +0700 Subject: [PATCH] Add refund order --- internal/entity/casheer_session.go | 27 + internal/entity/category.go | 9 + internal/entity/order.go | 2 + internal/entity/order_inquiry.go | 36 +- internal/entity/product.go | 48 +- internal/handlers/http/cashier.go | 123 +++++ internal/handlers/http/categories.go | 139 ++++++ internal/handlers/http/customerorder/order.go | 268 ---------- internal/handlers/http/discovery/discover.go | 229 --------- internal/handlers/http/event/event.go | 205 -------- internal/handlers/http/menu.go | 2 +- internal/handlers/http/order.go | 35 ++ internal/handlers/http/order/order.go | 461 ------------------ internal/handlers/http/product/product.go | 13 +- internal/handlers/http/studio/studio.go | 232 --------- internal/handlers/request/cashier.go | 18 + internal/handlers/request/category.go | 14 + internal/handlers/request/product.go | 34 +- internal/handlers/response/cashier.go | 62 +++ internal/handlers/response/category.go | 29 ++ internal/handlers/response/product.go | 20 +- internal/repository/casheer_seasion.go | 137 ++++++ internal/repository/categories_repo.go | 94 ++++ internal/repository/events/event.go | 86 ---- internal/repository/models/casheer_seasion.go | 20 + internal/repository/models/categories.go | 16 + internal/repository/models/order.go | 39 +- internal/repository/orde_repo.go | 94 ++-- internal/repository/products/product.go | 12 +- internal/repository/repository.go | 18 +- internal/repository/studios/studio.go | 89 ---- internal/routes/customer_routes.go | 2 - internal/routes/routes.go | 6 +- internal/services/discovery/discovery.go | 168 ------- internal/services/event/event.go | 88 ---- internal/services/service.go | 82 +--- internal/services/studio/studio.go | 70 --- .../v2/cashier_session/casheer_session.go | 99 ++++ internal/services/v2/categories/categories.go | 88 ++++ .../services/v2/order/create_order_inquiry.go | 8 + internal/services/v2/order/execute_order.go | 15 + internal/services/v2/order/order.go | 9 + 42 files changed, 1152 insertions(+), 2094 deletions(-) create mode 100644 internal/entity/casheer_session.go create mode 100644 internal/entity/category.go create mode 100644 internal/handlers/http/cashier.go create mode 100644 internal/handlers/http/categories.go delete mode 100644 internal/handlers/http/customerorder/order.go delete mode 100644 internal/handlers/http/discovery/discover.go delete mode 100644 internal/handlers/http/event/event.go delete mode 100644 internal/handlers/http/order/order.go delete mode 100644 internal/handlers/http/studio/studio.go create mode 100644 internal/handlers/request/cashier.go create mode 100644 internal/handlers/request/category.go create mode 100644 internal/handlers/response/cashier.go create mode 100644 internal/handlers/response/category.go create mode 100644 internal/repository/casheer_seasion.go create mode 100644 internal/repository/categories_repo.go delete mode 100644 internal/repository/events/event.go create mode 100644 internal/repository/models/casheer_seasion.go create mode 100644 internal/repository/models/categories.go delete mode 100644 internal/repository/studios/studio.go delete mode 100644 internal/services/discovery/discovery.go delete mode 100644 internal/services/event/event.go delete mode 100644 internal/services/studio/studio.go create mode 100644 internal/services/v2/cashier_session/casheer_session.go create mode 100644 internal/services/v2/categories/categories.go diff --git a/internal/entity/casheer_session.go b/internal/entity/casheer_session.go new file mode 100644 index 0000000..75c3b79 --- /dev/null +++ b/internal/entity/casheer_session.go @@ -0,0 +1,27 @@ +package entity + +import "time" + +type CashierSession struct { + ID int64 + CashierID int64 + OpenedAt time.Time + ClosedAt *time.Time + OpeningAmount float64 + ClosingAmount *float64 + ExpectedAmount *float64 + Status string +} + +type PaymentSummary struct { + PaymentType string + PaymentProvider string + TotalAmount float64 +} + +type CashierSessionReport struct { + SessionID int64 + ExpectedAmount float64 + ClosingAmount float64 + Payments []PaymentSummary +} diff --git a/internal/entity/category.go b/internal/entity/category.go new file mode 100644 index 0000000..a73aae2 --- /dev/null +++ b/internal/entity/category.go @@ -0,0 +1,9 @@ +package entity + +type Category struct { + ID int64 + PartnerID int64 + Name string + CreatedAt int64 + UpdatedAt int64 +} diff --git a/internal/entity/order.go b/internal/entity/order.go index 7be9262..51ff13b 100644 --- a/internal/entity/order.go +++ b/internal/entity/order.go @@ -26,6 +26,7 @@ type Order struct { User User `gorm:"foreignKey:CreatedBy;constraint:OnDelete:CASCADE;"` Source string `gorm:"type:varchar;column:source"` OrderType string `gorm:"type:varchar;column:order_type"` + CashierSessionID int64 `gorm:"type:varchar;column:cashier_session_id"` TableNumber string InProgressOrderID int64 } @@ -113,6 +114,7 @@ type OrderRequest struct { PaymentProvider string OrderType string ID int64 + CashierSessionID int64 } type OrderItemRequest struct { diff --git a/internal/entity/order_inquiry.go b/internal/entity/order_inquiry.go index 79aff0e..5cc40e0 100644 --- a/internal/entity/order_inquiry.go +++ b/internal/entity/order_inquiry.go @@ -26,6 +26,7 @@ type OrderInquiry struct { PaymentProvider string `json:"payment_provider"` TableNumber string `json:"table_number"` OrderType string `json:"order_type"` + CashierSessionID int64 `json:"cashier_session_id"` } type OrderCalculation struct { @@ -54,6 +55,7 @@ func NewOrderInquiry( paymentProvider string, tableNumber string, orderType string, + cashierSessionID int64, ) *OrderInquiry { return &OrderInquiry{ ID: constants.GenerateUUID(), @@ -75,6 +77,7 @@ func NewOrderInquiry( PaymentProvider: paymentProvider, TableNumber: tableNumber, OrderType: orderType, + CashierSessionID: cashierSessionID, } } @@ -95,22 +98,23 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order { now := time.Now() order := &Order{ - PartnerID: i.PartnerID, - CustomerID: &i.CustomerID, - InquiryID: &i.ID, - Status: constants.StatusPaid, - Amount: i.Amount, - Tax: i.Tax, - Total: i.Total, - PaymentType: paymentMethod, - PaymentProvider: paymentProvider, - Source: i.Source, - CreatedBy: i.CreatedBy, - CreatedAt: now, - OrderItems: make([]OrderItem, len(i.OrderItems)), - OrderType: i.OrderType, - CustomerName: i.CustomerName, - TableNumber: i.TableNumber, + PartnerID: i.PartnerID, + CustomerID: &i.CustomerID, + InquiryID: &i.ID, + Status: constants.StatusPaid, + Amount: i.Amount, + Tax: i.Tax, + Total: i.Total, + PaymentType: paymentMethod, + PaymentProvider: paymentProvider, + Source: i.Source, + CreatedBy: i.CreatedBy, + CreatedAt: now, + OrderItems: make([]OrderItem, len(i.OrderItems)), + OrderType: i.OrderType, + CustomerName: i.CustomerName, + TableNumber: i.TableNumber, + CashierSessionID: i.CashierSessionID, } for idx, item := range i.OrderItems { diff --git a/internal/entity/product.go b/internal/entity/product.go index 100e70d..fb029e9 100644 --- a/internal/entity/product.go +++ b/internal/entity/product.go @@ -2,23 +2,26 @@ package entity import ( "enaklo-pos-be/internal/constants/product" + "enaklo-pos-be/internal/repository/models" "time" ) type Product struct { - ID int64 `gorm:"primaryKey;autoIncrement;column:id"` - PartnerID int64 `gorm:"type:int;column:partner_id"` - Name string `gorm:"type:varchar(255);not null;column:name"` - Type string `gorm:"type:varchar;column:type"` - Price float64 `gorm:"type:decimal;column:price"` - Status string `gorm:"type:varchar;column:status"` - Description string `gorm:"type:varchar(255);not null;column:description"` - CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` - UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` - DeletedAt *time.Time `gorm:"column:deleted_at"` - CreatedBy int64 `gorm:"type:int;column:created_by"` - UpdatedBy int64 `gorm:"type:int;column:updated_by"` - Image string `gorm:"type:varchar;column:image"` + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + PartnerID int64 `gorm:"type:int;column:partner_id"` + Name string `gorm:"type:varchar(255);not null;column:name"` + Type string `gorm:"type:varchar;column:type"` + Price float64 `gorm:"type:decimal;column:price"` + Status string `gorm:"type:varchar;column:status"` + Description string `gorm:"type:varchar(255);not null;column:description"` + CreatedAt time.Time `gorm:"autoCreateTime;column:created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime;column:updated_at"` + DeletedAt *time.Time `gorm:"column:deleted_at"` + CreatedBy int64 `gorm:"type:int;column:created_by"` + UpdatedBy int64 `gorm:"type:int;column:updated_by"` + Image string `gorm:"type:varchar;column:image"` + CategoryID *int64 `gorm:"column:category_id"` + Category *models.CategoryDB `gorm:"foreignKey:CategoryID;references:ID"` } func (Product) TableName() string { @@ -26,14 +29,15 @@ func (Product) TableName() string { } type ProductSearch struct { - Search string - Name string - Type product.ProductType - BranchID int64 - PartnerID int64 - Available product.ProductStock - Limit int - Offset int + Search string + Name string + Type product.ProductType + BranchID int64 + PartnerID int64 + Available product.ProductStock + Limit int + Offset int + CategoryID int64 } type ProductPOS struct { @@ -72,6 +76,8 @@ func (e *ProductDB) ToProduct() *Product { CreatedBy: e.CreatedBy, UpdatedBy: e.UpdatedBy, Image: e.Image, + Category: e.Category, + CategoryID: e.CategoryID, } } diff --git a/internal/handlers/http/cashier.go b/internal/handlers/http/cashier.go new file mode 100644 index 0000000..d251021 --- /dev/null +++ b/internal/handlers/http/cashier.go @@ -0,0 +1,123 @@ +package http + +import ( + "enaklo-pos-be/internal/common/errors" + "enaklo-pos-be/internal/handlers/request" + "enaklo-pos-be/internal/handlers/response" + "enaklo-pos-be/internal/services/v2/cashier_session" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type CashierSessionHandler struct { + service cashier_session.Service +} + +func NewCashierSession(service cashier_session.Service) *CashierSessionHandler { + return &CashierSessionHandler{service: service} +} + +func (h *CashierSessionHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/cashier-sessions") + route.Use(jwt) + + route.POST("/open", h.OpenSession) + route.POST("/close/:id", h.CloseSession) + route.GET("/open", h.GetOpenSession) + route.GET("/report/:id", h.GetSessionReport) +} + +func (h *CashierSessionHandler) OpenSession(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.OpenCashierSessionRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + session, err := h.service.OpenSession(ctx, req.ToEntity(ctx.RequestedBy())) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCashierSessionResponse(session), + }) +} + +func (h *CashierSessionHandler) CloseSession(c *gin.Context) { + ctx := request.GetMyContext(c) + idStr := c.Param("id") + sessionID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var body struct { + ClosingAmount float64 `json:"closing_amount"` + } + + if err := c.ShouldBindJSON(&body); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + report, err := h.service.CloseSession(ctx, sessionID, body.ClosingAmount) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCashierSessionReport(report), + }) +} + +func (h *CashierSessionHandler) GetOpenSession(c *gin.Context) { + ctx := request.GetMyContext(c) + + cashierID := ctx.RequestedBy() + + session, err := h.service.GetOpenSession(ctx, cashierID) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCashierSessionResponse(session), + }) +} + +func (h *CashierSessionHandler) GetSessionReport(c *gin.Context) { + ctx := request.GetMyContext(c) + idStr := c.Param("id") + + sessionID, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + report, err := h.service.GetSessionReport(ctx, sessionID) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCashierSessionReport(report), + }) +} diff --git a/internal/handlers/http/categories.go b/internal/handlers/http/categories.go new file mode 100644 index 0000000..9ffcc0d --- /dev/null +++ b/internal/handlers/http/categories.go @@ -0,0 +1,139 @@ +package http + +import ( + "enaklo-pos-be/internal/common/errors" + "enaklo-pos-be/internal/handlers/request" + "enaklo-pos-be/internal/handlers/response" + category "enaklo-pos-be/internal/services/v2/categories" + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type CategoryHandler struct { + service category.Service +} + +func NewCategoryHandler(service category.Service) *CategoryHandler { + return &CategoryHandler{service: service} +} + +func (h *CategoryHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { + route := group.Group("/categories") + route.Use(jwt) + + route.POST("/create", h.Create) + route.GET("/list", h.GetByPartner) + route.GET("/:id", h.GetByID) + route.PUT("/:id", h.Update) + route.DELETE("/:id", h.Delete) +} + +func (h *CategoryHandler) Create(c *gin.Context) { + ctx := request.GetMyContext(c) + + var req request.CategoryRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + category, err := h.service.Create(ctx, req.ToEntity(ctx.RequestedBy())) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCategoryResponse(category), + }) +} + +func (h *CategoryHandler) GetByPartner(c *gin.Context) { + ctx := request.GetMyContext(c) + + categories, err := h.service.GetByPartnerID(ctx, ctx.RequestedBy()) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCategoryListResponse(categories), + }) +} + +func (h *CategoryHandler) GetByID(c *gin.Context) { + ctx := request.GetMyContext(c) + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + category, err := h.service.GetByID(ctx, id) + if err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + Data: response.MapToCategoryResponse(category), + }) +} + +func (h *CategoryHandler) Update(c *gin.Context) { + ctx := request.GetMyContext(c) + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + var req request.CategoryRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + category := req.ToEntity(ctx.RequestedBy()) + category.ID = id + + if err := h.service.Update(ctx, category); err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + }) +} + +func (h *CategoryHandler) Delete(c *gin.Context) { + ctx := request.GetMyContext(c) + + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.ErrorWrapper(c, errors.ErrorBadRequest) + return + } + + if err := h.service.Delete(ctx, id); err != nil { + response.ErrorWrapper(c, err) + return + } + + c.JSON(http.StatusOK, response.BaseResponse{ + Success: true, + Status: http.StatusOK, + }) +} diff --git a/internal/handlers/http/customerorder/order.go b/internal/handlers/http/customerorder/order.go deleted file mode 100644 index f3afba4..0000000 --- a/internal/handlers/http/customerorder/order.go +++ /dev/null @@ -1,268 +0,0 @@ -package customerorder - -import ( - "enaklo-pos-be/internal/common/errors" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/handlers/request" - "enaklo-pos-be/internal/handlers/response" - "enaklo-pos-be/internal/services" - "encoding/json" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" -) - -type Handler struct { - service services.Order -} - -func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { - route := group.Group("/order") - - route.POST("/inquiry", h.Inquiry) - route.POST("/execute", jwt, h.Execute) - route.GET("/history", jwt, h.History) - route.GET("/detail", jwt, h.Detail) -} - -func NewHandler(service services.Order) *Handler { - return &Handler{ - service: service, - } -} - -func (h *Handler) Inquiry(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.CustomerOrder - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if err := request.ValidateAndHandleError(req); err != nil { - response.ErrorWrapper(c, errors.NewErrorMessage(errors.ErrorBadRequest, err.Error())) - return - } - - order, err := h.service.CreateOrder(ctx, req.ToEntity(ctx.RequestedBy())) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToCreateOrderResponse(order, req), - }) -} - -func (h *Handler) Execute(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.Execute - 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 - } - - order, err := h.service.Execute(ctx, req.ToOrderExecuteRequest(ctx.RequestedBy())) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToExecuteOrderResponse(order), - }) -} - -func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse, req request.CustomerOrder) response.CreateOrderResponse { - order := orderResponse.Order - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.CreateOrderResponse{ - ID: order.ID, - PartnerID: order.PartnerID, - Status: order.Status, - Amount: order.Amount, - PaymentType: order.PaymentType, - CreatedAt: order.CreatedAt, - OrderItems: orderItems, - Tax: order.Tax, - Total: order.Total, - } -} - -func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse) response.ExecuteOrderResponse { - order := orderResponse.Order - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.ExecuteOrderResponse{ - ID: order.ID, - PartnerID: order.PartnerID, - Status: order.Status, - Amount: order.Amount, - PaymentType: order.PaymentType, - CreatedAt: order.CreatedAt, - OrderItems: orderItems, - PaymentToken: orderResponse.PaymentToken, - RedirectURL: orderResponse.RedirectURL, - QRcode: orderResponse.QRCode, - VirtualAccount: orderResponse.VirtualAccount, - BankName: orderResponse.BankName, - BankCode: orderResponse.BankCode, - } -} - -func (h *Handler) toHistoryOrderResponse(resp *entity.HistoryOrder) response.HistoryOrder { - return response.HistoryOrder{ - ID: resp.ID, - Employee: resp.Employee, - Site: resp.Site, - Timestamp: resp.Timestamp.Format(time.RFC3339), - BookingTime: resp.BookingTime.Format(time.RFC3339), - Tickets: resp.Tickets, - PaymentType: resp.PaymentType, - Status: resp.GetPaymentStatus(), - Amount: resp.Amount, - } -} - -func (h *Handler) toHistoryOrderList(resp []*entity.HistoryOrder, total int64, req request.OrderParamCustomer) response.HistoryOrderList { - var orders []response.HistoryOrder - for _, b := range resp { - orders = append(orders, h.toHistoryOrderResponse(b)) - } - - return response.HistoryOrderList{ - Orders: orders, - Total: total, - Limit: req.Limit, - Offset: req.Offset, - } -} - -func (h *Handler) History(c *gin.Context) { - var req request.OrderParamCustomer - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - orders, total, err := h.service.GetAllHistoryOrders(ctx, req.ToOrderEntity(ctx)) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toHistoryOrderList(orders, int64(total), req), - }) -} - -func (h *Handler) Detail(c *gin.Context) { - var req request.OrderParamCustomer - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - order, err := h.service.GetByID(ctx, req.ID, req.ReferenceID) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toOrderDetail(order), - }) -} - -func (h *Handler) toOrderDetail(order *entity.Order) *response.OrderDetail { - if order == nil { - return nil - } - - payment := map[string]string{} - paymentLink := "" - paymentToken := "" - - if order.Payment.RequestMetadata != nil && order.Status != "EXPIRED" { - json.Unmarshal(order.Payment.RequestMetadata, &payment) - paymentLink = payment["payment_redirect_url"] - paymentToken = payment["payment_token"] - } - - qrCode := "" - - var siteName string - - if order.Site != nil { - siteName = order.Site.Name - } - - orderDetail := &response.OrderDetail{ - ID: order.ID, - QRCode: qrCode, - FullName: order.User.Name, - Email: order.User.Email, - PhoneNumber: order.User.PhoneNumber, - TotalAmount: order.Total, - CreatedAt: order.CreatedAt, - Status: order.Status, - PaymentLink: paymentLink, - PaymentToken: paymentToken, - SiteName: siteName, - Fee: order.Tax, - } - - orderDetail.OrderItems = make([]response.OrderDetailItem, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderDetail.OrderItems[i] = response.OrderDetailItem{ - Name: item.Product.Name, - ItemType: item.ItemType, - Description: "", - Quantity: int(item.Quantity), - UnitPrice: item.Price, - TotalPrice: float64(item.Quantity) * item.Price, - } - } - - return orderDetail -} diff --git a/internal/handlers/http/discovery/discover.go b/internal/handlers/http/discovery/discover.go deleted file mode 100644 index f751bf8..0000000 --- a/internal/handlers/http/discovery/discover.go +++ /dev/null @@ -1,229 +0,0 @@ -package discovery - -import ( - "enaklo-pos-be/internal/common/errors" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/handlers/request" - "enaklo-pos-be/internal/handlers/response" - "enaklo-pos-be/internal/services" - "github.com/gin-gonic/gin" - "net/http" -) - -type Handler struct { - service services.DiscoverService -} - -func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { - route := group.Group("/discovery") - - route.GET("/home", h.DisoveryHome) - route.GET("/search", h.DisoverySearch) - route.GET("/site/detail", h.DiscoveryGetByID) - route.GET("/site/products", h.DiscoveryProducts) - -} - -func NewHandler(service services.DiscoverService) *Handler { - return &Handler{ - service: service, - } -} - -func (h *Handler) DisoveryHome(c *gin.Context) { - var req request.DiscoveryHomeParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, err := h.service.Home(c.Request.Context(), req.ToEntity()) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: ConvertEntityToResponse(res), - }) -} - -func (h *Handler) DisoverySearch(c *gin.Context) { - var req request.DiscoveryHomeParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, total, err := h.service.Search(c.Request.Context(), req.ToEntity()) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: ConvertEntityToSearchResponse(res, total, req), - }) -} - -func (h *Handler) DiscoveryGetByID(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.DiscoverySearchByID - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, err := h.service.GetByID(ctx, req.ID) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: ConvertEntityToGetByIDResp(res), - }) -} - -func (h *Handler) DiscoveryProducts(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.DiscoverySearchByID - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, err := h.service.GetProductsByID(ctx, req.ID) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: ConvertToProductResp(res), - }) -} - -func ConvertEntityToResponse(entityResp *entity.DiscoverySearchResp) *response.ExploreResponse { - // Convert ExploreRegions - exploreRegions := make([]response.Region, len(entityResp.ExploreRegions)) - for i, region := range entityResp.ExploreRegions { - exploreRegions[i] = response.Region{ - Name: region.Name, - } - } - - // Convert ExploreDestinations - exploreDestinations := make([]response.Destination, len(entityResp.ExploreDestinations)) - for i, destination := range entityResp.ExploreDestinations { - exploreDestinations[i] = response.Destination{ - Name: destination.Name, - ImageURL: destination.ImageURL, - } - } - - mustVisit := make([]response.MustVisit, len(entityResp.MustVisit)) - for i, mv := range entityResp.MustVisit { - mustVisit[i] = response.MustVisit{ - Name: mv.Name, - Region: mv.Region, - Rating: mv.Rating, - ReviewCount: mv.ReviewCount, - Price: mv.Price, - ImageURL: mv.ImageURL, - SiteID: mv.SiteID, - Regency: mv.Regency, - } - } - - return &response.ExploreResponse{ - ExploreRegions: exploreRegions, - ExploreDestinations: exploreDestinations, - MustVisit: mustVisit, - } -} - -func ConvertEntityToSearchResponse(entityResp *entity.DiscoverySearchResp, total int64, req request.DiscoveryHomeParam) *response.SearchResponse { - data := make([]response.SiteSeach, len(entityResp.MustVisit)) - for i, mv := range entityResp.MustVisit { - data[i] = response.SiteSeach{ - Name: mv.Name, - Region: mv.Region, - Rating: mv.Rating, - ReviewCount: mv.ReviewCount, - Price: mv.Price, - ImageURL: mv.ImageURL, - SiteID: mv.SiteID, - Regency: mv.Regency, - } - } - - return &response.SearchResponse{ - Data: data, - Total: int(total), - Limit: req.Limit, - Offset: req.Offset, - } -} - -func ConvertEntityToGetByIDResp(resp *entity.Site) *response.SearchSiteByIDResponse { - if resp == nil { - return nil - } - - return &response.SearchSiteByIDResponse{ - ID: resp.ID, - Name: resp.Name, - Image: resp.Image, - Address: resp.Address, - LocationLink: resp.LocationLink, - Description: resp.Description, - Highlight: resp.Highlight, - ContactPerson: resp.ContactPerson, - TnC: resp.TnC, - AdditionalInfo: resp.AdditionalInfo, - PartnerID: resp.PartnerID, - Regency: resp.Regency, - Region: resp.Region, - } -} - -func ConvertToProductResp(resp []*entity.Product) *response.SearchProductSiteResponse { - if resp == nil { - return nil - } - - var productResp []response.SearchProductSiteByIDResponse - - partnerID := int64(0) - for _, res := range resp { - productResp = append(productResp, response.SearchProductSiteByIDResponse{ - ID: res.ID, - Name: res.Name, - Price: res.Price, - Description: res.Description, - Type: res.Type, - }) - - partnerID = res.PartnerID - } - - return &response.SearchProductSiteResponse{ - Product: productResp, - PartnerID: partnerID, - } -} diff --git a/internal/handlers/http/event/event.go b/internal/handlers/http/event/event.go deleted file mode 100644 index 1b6ba0b..0000000 --- a/internal/handlers/http/event/event.go +++ /dev/null @@ -1,205 +0,0 @@ -package event - -import ( - "net/http" - "strconv" - "time" - - "github.com/gin-gonic/gin" - - "enaklo-pos-be/internal/common/errors" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/handlers/request" - "enaklo-pos-be/internal/handlers/response" - "enaklo-pos-be/internal/services" -) - -type Handler struct { - service services.Event -} - -func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { - route := group.Group("/event") - - route.POST("/", jwt, h.Create) - route.GET("/list", jwt, h.GetAll) - route.PUT("/:id", jwt, h.Update) - route.GET("/:id", jwt, h.GetByID) - route.DELETE("/:id", jwt, h.Delete) -} - -func NewHandler(service services.Event) *Handler { - return &Handler{ - service: service, - } -} - -func (h *Handler) Create(c *gin.Context) { - var req request.Event - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if err := req.Validate(); err != nil { - response.ErrorWrapper(c, errors.ErrorInvalidRequest) - return - } - - res, err := h.service.Create(c.Request.Context(), req.ToEntity()) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toEventResponse(res), - }) -} - -func (h *Handler) Update(c *gin.Context) { - id := c.Param("id") - - eventID, err := strconv.ParseInt(id, 10, 64) - if err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - var req request.Event - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if err := req.Validate(); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - updatedEvent, err := h.service.Update(c.Request.Context(), eventID, req.ToEntity()) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toEventResponse(updatedEvent), - }) -} - -func (h *Handler) GetAll(c *gin.Context) { - var req request.EventParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - events, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toEventResponseList(events, int64(total), req), - }) -} - -func (h *Handler) Delete(c *gin.Context) { - id := c.Param("id") - - // Parse the ID into a uint - eventID, err := strconv.ParseInt(id, 10, 64) - if err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - err = h.service.Delete(c.Request.Context(), eventID) - if err != nil { - c.JSON(http.StatusInternalServerError, response.BaseResponse{ - Success: false, - Status: http.StatusInternalServerError, - Message: err.Error(), - Data: nil, - }) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: nil, - }) -} - -func (h *Handler) GetByID(c *gin.Context) { - id := c.Param("id") - - // Parse the ID into a uint - eventID, err := strconv.ParseInt(id, 10, 64) - if err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, err := h.service.GetByID(c.Request.Context(), eventID) - if err != nil { - c.JSON(http.StatusInternalServerError, response.BaseResponse{ - Success: false, - Status: http.StatusInternalServerError, - Message: err.Error(), - Data: nil, - }) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toEventResponse(res), - }) -} - -func (h *Handler) toEventResponse(resp *entity.Event) response.Event { - return response.Event{ - ID: resp.ID, - Name: resp.Name, - Description: resp.Description, - StartDate: resp.StartDate.Format("2006-01-02"), - EndDate: resp.EndDate.Format("2006-01-02"), - StartTime: resp.StartDate.Format("15:04:05"), - EndTime: resp.EndDate.Format("15:04:05"), - Location: resp.Location, - Level: resp.Level, - Included: resp.Included, - Price: resp.Price, - Paid: resp.Paid, - LocationID: resp.LocationID, - CreatedAt: resp.CreatedAt.Format(time.RFC3339), - UpdatedAt: resp.CreatedAt.Format(time.RFC3339), - Status: string(resp.Status), - } -} - -func (h *Handler) toEventResponseList(resp []*entity.Event, total int64, req request.EventParam) response.EventList { - var events []response.Event - for _, evt := range resp { - events = append(events, h.toEventResponse(evt)) - } - - return response.EventList{ - Events: events, - Total: total, - Limit: req.Limit, - Offset: req.Offset, - } -} diff --git a/internal/handlers/http/menu.go b/internal/handlers/http/menu.go index c58c31e..b9cf4a0 100644 --- a/internal/handlers/http/menu.go +++ b/internal/handlers/http/menu.go @@ -56,7 +56,7 @@ func (h *MenuHandler) GetProducts(c *gin.Context) { return } - searchParam := req.ToEntity() + searchParam := req.ToEntity(partnerID) searchParam.PartnerID = partnerID products, total, err := h.service.GetProductsByPartnerID(ctx, searchParam) diff --git a/internal/handlers/http/order.go b/internal/handlers/http/order.go index 87204a0..b125232 100644 --- a/internal/handlers/http/order.go +++ b/internal/handlers/http/order.go @@ -29,6 +29,7 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { 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) @@ -47,6 +48,7 @@ type InquiryRequest struct { 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 { @@ -70,6 +72,11 @@ type ExecuteRequest struct { 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() @@ -109,6 +116,7 @@ func (h *Handler) Inquiry(c *gin.Context) { OrderType: req.OrderType, PaymentProvider: req.GetPaymentProvider(), TableNumber: req.TableNumber, + CashierSessionID: req.CashierSessionID, } result, err := h.service.CreateOrderInquiry(ctx, orderReq) @@ -152,6 +160,33 @@ func (h *Handler) Execute(c *gin.Context) { }) } +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() diff --git a/internal/handlers/http/order/order.go b/internal/handlers/http/order/order.go deleted file mode 100644 index e5bc974..0000000 --- a/internal/handlers/http/order/order.go +++ /dev/null @@ -1,461 +0,0 @@ -package order - -import ( - "enaklo-pos-be/internal/common/errors" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/handlers/request" - "enaklo-pos-be/internal/handlers/response" - "enaklo-pos-be/internal/services" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" -) - -type Handler struct { - service services.Order -} - -func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { - route := group.Group("/order") - - route.POST("/inquiry", jwt, h.Inquiry) - route.GET("/print-detail", jwt, h.PrintDetail) - route.POST("/execute", jwt, h.Execute) - route.GET("/history", jwt, h.GetAllHistoryOrders) - route.GET("/ticket-sold", jwt, h.CountSoldOfTicket) - route.POST("/checkin/inquiry", jwt, h.CheckInInquiry) - route.POST("/checkin/execute", jwt, h.CheckInExecute) - route.GET("/sum-amount", jwt, h.SumAmount) - route.GET("/daily-sales", jwt, h.GetDailySalesTicket) - route.GET("/payment-distribution", jwt, h.GetPaymentDistributionChart) -} - -func NewHandler(service services.Order) *Handler { - return &Handler{ - service: service, - } -} - -func (h *Handler) Inquiry(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.Order - 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 - } - - orderRequest := req.ToEntity(*ctx.GetPartnerID(), ctx.RequestedBy()) - - order, err := h.service.CreateOrder(ctx, orderRequest) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToCreateOrderResponse(order), - }) -} - -func (h *Handler) PrintDetail(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.OrderPrintDetail - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - order, err := h.service.GetPrintDetail(ctx, req.ID) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToPrintDetailResponse(order, ctx.GetName()), - }) -} - -func (h *Handler) Execute(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.Execute - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if !ctx.IsCasheer() { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - req.PartnerID = *ctx.GetPartnerID() - - validate := validator.New() - if err := validate.Struct(req); err != nil { - response.ErrorWrapper(c, err) - return - } - - order, err := h.service.Execute(ctx, req.ToOrderExecuteRequest(ctx.RequestedBy())) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToExecuteOrderResponse(order), - }) -} - -func (h *Handler) CheckInInquiry(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.Checkin - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if !ctx.IsCasheer() || req.QRCode == "" { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - partnerID := ctx.GetPartnerID() - - resp, err := h.service.CheckInInquiry(ctx, req.QRCode, partnerID) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: response.CheckingInquiryResponse{ - Token: resp.Token, - }, - }) -} - -func (h *Handler) CheckInExecute(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.CheckinExecute - if err := c.ShouldBindJSON(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - if !ctx.IsCasheer() || req.Token == "" { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - partnerID := ctx.GetPartnerID() - - resp, err := h.service.CheckInExecute(ctx, req.Token, partnerID) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: MapOrderToExecuteCheckinResponse(resp.Order), - }) -} - -func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response.CreateOrderResponse { - order := orderResponse.Order - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.CreateOrderResponse{ - ID: order.ID, - PartnerID: order.PartnerID, - Status: order.Status, - Amount: order.Amount, - Total: order.Total, - Tax: order.Tax, - PaymentType: order.PaymentType, - CreatedAt: order.CreatedAt, - OrderItems: orderItems, - } -} - -func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse) response.ExecuteOrderResponse { - order := orderResponse.Order - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.ExecuteOrderResponse{ - ID: order.ID, - PartnerID: order.PartnerID, - Status: order.Status, - Amount: order.Amount, - PaymentType: order.PaymentType, - CreatedAt: order.CreatedAt, - OrderItems: orderItems, - PaymentToken: orderResponse.PaymentToken, - RedirectURL: orderResponse.RedirectURL, - QRcode: orderResponse.QRCode, - } -} - -func MapOrderToExecuteCheckinResponse(order *entity.Order) response.ExecuteCheckinResponse { - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.ExecuteCheckinResponse{ - ID: order.ID, - PartnerID: order.PartnerID, - Status: order.Status, - Amount: order.Amount, - PaymentType: order.PaymentType, - CreatedAt: order.CreatedAt, - OrderItems: orderItems, - } -} - -func (h *Handler) toHistoryOrderResponse(resp *entity.HistoryOrder) response.HistoryOrder { - return response.HistoryOrder{ - ID: resp.ID, - Employee: resp.Employee, - Site: resp.Site, - Timestamp: resp.Timestamp.Format("2006-01-02 15:04:05"), - BookingTime: resp.BookingTime.Format("2006-01-02 15:04:05"), - Tickets: resp.Tickets, - PaymentType: resp.PaymentType, - Status: resp.Status, - Amount: resp.Amount, - VisitDate: resp.VisitDate.Format("2006-01-02"), - TicketStatus: resp.TicketStatus, - Source: resp.Source, - } -} - -func (h *Handler) SumAmount(c *gin.Context) { - var req request.OrderParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - order, err := h.service.SumAmount(ctx, req.ToOrderEntity(ctx)) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: response.OrderAmount{ - Amount: order.Amount, - }, - }) -} - -func (h *Handler) GetAllHistoryOrders(c *gin.Context) { - var req request.OrderParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - orders, total, err := h.service.GetAllHistoryOrders(ctx, req.ToOrderEntity(ctx)) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toHistoryOrderList(orders, int64(total), req), - }) -} - -func (h *Handler) CountSoldOfTicket(c *gin.Context) { - var req request.OrderParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - - res, err := h.service.CountSoldOfTicket(ctx, req.ToOrderEntity(ctx)) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: response.TicketSold{ - Count: res.Count, - }, - }) -} - -func (h *Handler) GetDailySalesTicket(c *gin.Context) { - var req request.OrderParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - - resp, err := h.service.GetDailySales(ctx, req.ToOrderEntity(ctx)) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toDailySales(resp), - }) -} - -func (h *Handler) GetPaymentDistributionChart(c *gin.Context) { - var req request.OrderParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - ctx := request.GetMyContext(c) - - resp, err := h.service.GetPaymentDistribution(ctx, req.ToOrderEntity(ctx)) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toPaymentDistributionChart(resp), - }) -} - -func (h *Handler) toHistoryOrderList(resp []*entity.HistoryOrder, total int64, req request.OrderParam) response.HistoryOrderList { - var orders []response.HistoryOrder - for _, b := range resp { - orders = append(orders, h.toHistoryOrderResponse(b)) - } - - return response.HistoryOrderList{ - Orders: orders, - Total: total, - Limit: req.Limit, - Offset: req.Offset, - } -} - -func (h *Handler) toDailySales(resp []entity.ProductDailySales) []response.ProductDailySales { - var dailySales []response.ProductDailySales - for _, b := range resp { - dailySales = append(dailySales, response.ProductDailySales{ - Day: b.Day, - SiteID: b.SiteID, - Total: b.Total, - SiteName: b.SiteName, - PaymentType: b.PaymentType, - }) - } - return dailySales -} - -func (h *Handler) toPaymentDistributionChart(resp []entity.PaymentTypeDistribution) []response.PaymentDistribution { - var dailySales []response.PaymentDistribution - for _, b := range resp { - dailySales = append(dailySales, response.PaymentDistribution{ - PaymentType: b.PaymentType, - Count: b.Count, - }) - } - return dailySales -} - -func MapOrderToPrintDetailResponse(order *entity.OrderPrintDetail, casherName string) response.PrintDetailResponse { - orderItems := make([]response.CreateOrderItemResponse, len(order.OrderItems)) - for i, item := range order.OrderItems { - orderItems[i] = response.CreateOrderItemResponse{ - ID: item.ID, - ItemID: item.ItemID, - Quantity: item.Quantity, - Price: item.Price, - Name: item.Product.Name, - } - } - - return response.PrintDetailResponse{ - ID: order.ID, - OrderID: order.OrderID, - Total: order.Total, - Fee: order.Fee, - PaymentType: order.GetPaymanetType(), - Source: order.Source, - VisitDateAt: order.VisitDate.Format("2006-01-02"), - VisitTime: time.Now().Format("15:04:05"), - OrderItems: orderItems, - CasheerName: casherName, - PartnerName: order.SiteName, - Logo: order.Logo, - } -} diff --git a/internal/handlers/http/product/product.go b/internal/handlers/http/product/product.go index d0e15d8..5f69ab5 100644 --- a/internal/handlers/http/product/product.go +++ b/internal/handlers/http/product/product.go @@ -55,7 +55,6 @@ func (h *Handler) Create(c *gin.Context) { } req.PartnerID = *ctx.GetPartnerID() - req.SiteID = *ctx.GetSiteID() res, err := h.service.Create(ctx, req.ToEntity()) @@ -140,7 +139,9 @@ func (h *Handler) GetAll(c *gin.Context) { return } - products, total, err := h.service.GetAll(c.Request.Context(), req.ToEntity()) + ctx := request.GetMyContext(c) + + products, total, err := h.service.GetAll(ctx, req.ToEntity(*ctx.GetPartnerID())) if err != nil { response.ErrorWrapper(c, err) return @@ -266,6 +267,13 @@ func (h *Handler) GetByID(c *gin.Context) { } func (h *Handler) toProductResponse(resp *entity.Product) response.Product { + category := response.Category{} + + if resp.Category != nil { + category.ID = resp.Category.ID + category.Name = resp.Category.Name + } + return response.Product{ ID: resp.ID, Name: resp.Name, @@ -274,6 +282,7 @@ func (h *Handler) toProductResponse(resp *entity.Product) response.Product { Status: resp.Status, Description: resp.Description, Image: resp.Image, + Category: category, } } diff --git a/internal/handlers/http/studio/studio.go b/internal/handlers/http/studio/studio.go deleted file mode 100644 index ed8208c..0000000 --- a/internal/handlers/http/studio/studio.go +++ /dev/null @@ -1,232 +0,0 @@ -package studio - -import ( - "enaklo-pos-be/internal/common/errors" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/handlers/request" - "enaklo-pos-be/internal/handlers/response" - "enaklo-pos-be/internal/services" - "encoding/json" - "net/http" - "strconv" - "time" - - "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" -) - -type StudioHandler struct { - service services.Studio -} - -func (h *StudioHandler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { - route := group.Group("/studio") - - route.POST("/", jwt, h.Create) - route.PUT("/:id", jwt, h.Update) - route.GET("/:id", jwt, h.GetByID) - route.GET("/search", jwt, h.Search) -} - -func NewStudioHandler(service services.Studio) *StudioHandler { - return &StudioHandler{ - service: service, - } -} - -// Create handles the creation of a new studio. -// @Summary Create a new studio -// @Description Create a new studio based on the provided details. -// @Accept json -// @Produce json -// @Param Authorization header string true "JWT token" -// @Param req body request.Studio true "New studio details" -// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio created successfully" -// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" -// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" -// @Router /api/v1/studio [post] -// @Tags Studio APIs -func (h *StudioHandler) Create(c *gin.Context) { - ctx := request.GetMyContext(c) - - var req request.Studio - 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 - } - - res, err := h.service.Create(ctx, req.ToEntity()) - - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toStudioResponse(res), - }) -} - -// Update handles the update of an existing studio. -// @Summary Update an existing studio -// @Description Update the details of an existing studio based on the provided ID. -// @Accept json -// @Produce json -// @Param Authorization header string true "JWT token" -// @Param id path int64 true "Studio ID to update" -// @Param req body request.Studio true "Updated studio details" -// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio updated successfully" -// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" -// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" -// @Router /api/v1/studio/{id} [put] -// @Tags Studio APIs -func (h *StudioHandler) Update(c *gin.Context) { - ctx := request.GetMyContext(c) - - id := c.Param("id") - - studioID, err := strconv.ParseInt(id, 10, 64) - if err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - var req request.Studio - 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 - } - - updatedStudio, err := h.service.Update(ctx, studioID, req.ToEntity()) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toStudioResponse(updatedStudio), - }) -} - -// Search retrieves a list of studios based on search criteria. -// @Summary Search for studios -// @Description Search for studios based on query parameters. -// @Accept json -// @Produce json -// @Param Authorization header string true "JWT token" -// @Param Name query string false "Studio name for search" -// @Param Status query string false "Studio status for search" -// @Param Limit query int false "Number of items to retrieve (default 10)" -// @Param Offset query int false "Offset for pagination (default 0)" -// @Success 200 {object} response.BaseResponse{data=response.StudioList} "List of studios" -// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" -// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" -// @Router /api/v1/studio/search [get] -// @Tags Studio APIs -func (h *StudioHandler) Search(c *gin.Context) { - var req request.StudioParam - if err := c.ShouldBindQuery(&req); err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - studios, total, err := h.service.Search(c.Request.Context(), req.ToEntity()) - if err != nil { - response.ErrorWrapper(c, err) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toStudioResponseList(studios, int64(total), req), - }) -} - -// GetByID retrieves details of a specific studio by ID. -// @Summary Get details of a studio by ID -// @Description Get details of a studio based on the provided ID. -// @Accept json -// @Produce json -// @Param Authorization header string true "JWT token" -// @Param id path int64 true "Studio ID to retrieve" -// @Success 200 {object} response.BaseResponse{data=response.Studio} "Studio details" -// @Failure 400 {object} response.BaseResponse{data=errors.Error} "Bad request" -// @Failure 401 {object} response.BaseResponse{data=errors.Error} "Unauthorized" -// @Router /api/v1/studio/{id} [get] -// @Tags Studio APIs -func (h *StudioHandler) GetByID(c *gin.Context) { - id := c.Param("id") - - studioID, err := strconv.ParseInt(id, 10, 64) - if err != nil { - response.ErrorWrapper(c, errors.ErrorBadRequest) - return - } - - res, err := h.service.GetByID(c.Request.Context(), studioID) - if err != nil { - c.JSON(http.StatusInternalServerError, response.BaseResponse{ - Success: false, - Status: http.StatusInternalServerError, - Message: err.Error(), - Data: nil, - }) - return - } - - c.JSON(http.StatusOK, response.BaseResponse{ - Success: true, - Status: http.StatusOK, - Data: h.toStudioResponse(res), - }) -} - -func (h *StudioHandler) toStudioResponse(resp *entity.Studio) response.Studio { - metadata := make(map[string]interface{}) - if err := json.Unmarshal(resp.Metadata, &metadata); err != nil { - //TODO taufanvps - // Handle the error if the metadata cannot be unmarshaled. - } - - return response.Studio{ - ID: &resp.ID, - BranchId: &resp.BranchId, - Name: resp.Name, - Status: string(resp.Status), - Price: resp.Price, - Metadata: metadata, - CreatedAt: resp.CreatedAt.Format(time.RFC3339), - UpdatedAt: resp.CreatedAt.Format(time.RFC3339), - } -} - -func (h *StudioHandler) toStudioResponseList(resp []*entity.Studio, total int64, req request.StudioParam) response.StudioList { - var studios []response.Studio - for _, b := range resp { - studios = append(studios, h.toStudioResponse(b)) - } - - return response.StudioList{ - Studios: studios, - Total: total, - Limit: req.Limit, - Offset: req.Offset, - } -} diff --git a/internal/handlers/request/cashier.go b/internal/handlers/request/cashier.go new file mode 100644 index 0000000..346113b --- /dev/null +++ b/internal/handlers/request/cashier.go @@ -0,0 +1,18 @@ +package request + +import "enaklo-pos-be/internal/entity" + +type OpenCashierSessionRequest struct { + OpeningAmount float64 `json:"opening_amount" validate:"required,gt=0"` +} + +type CloseCashierSessionRequest struct { + ClosingAmount float64 `json:"closing_amount" validate:"required"` +} + +func (o *OpenCashierSessionRequest) ToEntity(cashierID int64) *entity.CashierSession { + return &entity.CashierSession{ + CashierID: cashierID, + OpeningAmount: o.OpeningAmount, + } +} diff --git a/internal/handlers/request/category.go b/internal/handlers/request/category.go new file mode 100644 index 0000000..0d83701 --- /dev/null +++ b/internal/handlers/request/category.go @@ -0,0 +1,14 @@ +package request + +import "enaklo-pos-be/internal/entity" + +type CategoryRequest struct { + Name string `json:"name" binding:"required"` +} + +func (r *CategoryRequest) ToEntity(partnerID int64) *entity.Category { + return &entity.Category{ + PartnerID: partnerID, + Name: r.Name, + } +} diff --git a/internal/handlers/request/product.go b/internal/handlers/request/product.go index c6272d2..896634c 100644 --- a/internal/handlers/request/product.go +++ b/internal/handlers/request/product.go @@ -6,24 +6,26 @@ import ( ) type ProductParam struct { - Search string `form:"search" json:"search" example:"Nasi Goreng"` - Name string `form:"name" json:"name" example:"Nasi Goreng"` - Type product.ProductType `form:"type" json:"type" example:"FOOD/BEVERAGE"` - BranchID int64 `form:"branch_id" json:"branch_id" example:"1"` - Available product.ProductStock `form:"available" json:"available" example:"1" example:"AVAILABLE/UNAVAILABLE"` - Limit int `form:"limit" json:"limit" example:"10"` - Offset int `form:"offset" json:"offset" example:"0"` + Search string `form:"search" json:"search" example:"Nasi Goreng"` + Name string `form:"name" json:"name" example:"Nasi Goreng"` + Type product.ProductType `form:"type" json:"type" example:"FOOD/BEVERAGE"` + BranchID int64 `form:"branch_id" json:"branch_id" example:"1"` + Available product.ProductStock `form:"available" json:"available" example:"1" example:"AVAILABLE/UNAVAILABLE"` + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` + CategoryID int64 `form:"category_id" json:"category_id" example:"1"` } -func (p *ProductParam) ToEntity() entity.ProductSearch { +func (p *ProductParam) ToEntity(partnerID int64) entity.ProductSearch { return entity.ProductSearch{ - Search: p.Search, - Name: p.Name, - Type: p.Type, - BranchID: p.BranchID, - Available: p.Available, - Limit: p.Limit, - Offset: p.Offset, + Search: p.Search, + Name: p.Name, + Type: p.Type, + PartnerID: partnerID, + Available: p.Available, + Limit: p.Limit, + Offset: p.Offset, + CategoryID: p.CategoryID, } } @@ -40,6 +42,7 @@ type Product struct { Description string `json:"description"` Stock int64 `json:"stock"` Image string `json:"image"` + CategoryID int64 `json:"category_id"` } func (e *Product) ToEntity() *entity.Product { @@ -51,5 +54,6 @@ func (e *Product) ToEntity() *entity.Product { Description: e.Description, PartnerID: e.PartnerID, Image: e.Image, + CategoryID: &e.CategoryID, } } diff --git a/internal/handlers/response/cashier.go b/internal/handlers/response/cashier.go new file mode 100644 index 0000000..777932f --- /dev/null +++ b/internal/handlers/response/cashier.go @@ -0,0 +1,62 @@ +package response + +import ( + "enaklo-pos-be/internal/entity" + "time" +) + +type CashierSessionResponse struct { + ID int64 `json:"id"` + CashierID int64 `json:"cashier_id"` + OpenedAt time.Time `json:"opened_at"` + ClosedAt *time.Time `json:"closed_at,omitempty"` + OpeningAmount float64 `json:"opening_amount"` + ClosingAmount *float64 `json:"closing_amount,omitempty"` + Status string `json:"status"` +} + +type PaymentSummaryResponse struct { + PaymentType string `json:"payment_type"` + PaymentProvider string `json:"payment_provider"` + TotalAmount float64 `json:"total_amount"` +} + +type CashierSessionReportResponse struct { + SessionID int64 `json:"session_id"` + ExpectedAmount float64 `json:"expected_amount"` + ClosingAmount float64 `json:"closing_amount"` + Payments []PaymentSummaryResponse `json:"payments"` +} + +func MapToCashierSessionResponse(e *entity.CashierSession) *CashierSessionResponse { + if e == nil { + return nil + } + + return &CashierSessionResponse{ + ID: e.ID, + CashierID: e.CashierID, + OpenedAt: e.OpenedAt, + ClosedAt: e.ClosedAt, + OpeningAmount: e.OpeningAmount, + ClosingAmount: e.ClosingAmount, + Status: e.Status, + } +} + +func MapToCashierSessionReport(e *entity.CashierSessionReport) *CashierSessionReportResponse { + payments := make([]PaymentSummaryResponse, len(e.Payments)) + for i, p := range e.Payments { + payments[i] = PaymentSummaryResponse{ + PaymentType: p.PaymentType, + PaymentProvider: p.PaymentProvider, + TotalAmount: p.TotalAmount, + } + } + return &CashierSessionReportResponse{ + SessionID: e.SessionID, + ExpectedAmount: e.ExpectedAmount, + ClosingAmount: e.ClosingAmount, + Payments: payments, + } +} diff --git a/internal/handlers/response/category.go b/internal/handlers/response/category.go new file mode 100644 index 0000000..48ee45c --- /dev/null +++ b/internal/handlers/response/category.go @@ -0,0 +1,29 @@ +package response + +import "enaklo-pos-be/internal/entity" + +type CategoryResponse struct { + ID int64 `json:"id"` + Name string `json:"name"` + PartnerID int64 `json:"partner_id"` +} + +func MapToCategoryResponse(cat *entity.Category) *CategoryResponse { + if cat == nil { + return nil + } + + return &CategoryResponse{ + ID: cat.ID, + Name: cat.Name, + PartnerID: cat.PartnerID, + } +} + +func MapToCategoryListResponse(cats []*entity.Category) []*CategoryResponse { + result := make([]*CategoryResponse, len(cats)) + for i, c := range cats { + result[i] = MapToCategoryResponse(c) + } + return result +} diff --git a/internal/handlers/response/product.go b/internal/handlers/response/product.go index 33d26cb..e726679 100644 --- a/internal/handlers/response/product.go +++ b/internal/handlers/response/product.go @@ -1,13 +1,19 @@ package response type Product struct { - ID int64 `json:"id"` - Name string `json:"name"` - Type string `json:"type"` - Price float64 `json:"price"` - Status string `json:"status"` - Description string `json:"description"` - Image string `json:"image"` + ID int64 `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Price float64 `json:"price"` + Status string `json:"status"` + Description string `json:"description"` + Image string `json:"image"` + Category Category `json:"category"` +} + +type Category struct { + ID int64 `json:"id"` + Name string `json:"name"` } type ProductList struct { diff --git a/internal/repository/casheer_seasion.go b/internal/repository/casheer_seasion.go new file mode 100644 index 0000000..efceda2 --- /dev/null +++ b/internal/repository/casheer_seasion.go @@ -0,0 +1,137 @@ +package repository + +import ( + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "enaklo-pos-be/internal/repository/models" + "github.com/pkg/errors" + "gorm.io/gorm" + "time" +) + +type CashierSessionRepository interface { + CreateSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) + CloseSession(ctx mycontext.Context, sessionID int64, closingAmount, expectedAmount float64) error + GetOpenSessionByCashierID(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) + GetSessionByID(ctx mycontext.Context, sessionID int64) (*entity.CashierSession, error) + GetPaymentSummaryBySessionID(ctx mycontext.Context, sessionID int64) ([]entity.PaymentSummary, error) +} + +type cashierSessionRepository struct { + db *gorm.DB +} + +func NewCashierSessionRepository(db *gorm.DB) CashierSessionRepository { + return &cashierSessionRepository{db: db} +} + +func (r *cashierSessionRepository) CreateSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) { + dbModel := models.CashierSessionDB{ + CashierID: session.CashierID, + OpenedAt: time.Now(), + OpeningAmount: session.OpeningAmount, + Status: "open", + CreatedAt: time.Now(), + } + + if err := r.db.Create(&dbModel).Error; err != nil { + return nil, errors.Wrap(err, "failed to create cashier session") + } + + session.ID = dbModel.ID + session.Status = dbModel.Status + session.OpenedAt = dbModel.OpenedAt + + return session, nil +} + +func (r *cashierSessionRepository) CloseSession(ctx mycontext.Context, sessionID int64, closingAmount, expectedAmount float64) error { + result := r.db.Model(&models.CashierSessionDB{}). + Where("id = ?", sessionID). + Updates(map[string]interface{}{ + "closed_at": time.Now(), + "closing_amount": closingAmount, + "expected_amount": expectedAmount, + "status": "closed", + }) + + if result.Error != nil { + return errors.Wrap(result.Error, "failed to close session") + } + + if result.RowsAffected == 0 { + return errors.New("no session updated") + } + + return nil +} + +func (r *cashierSessionRepository) GetOpenSessionByCashierID(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) { + var dbModel models.CashierSessionDB + if err := r.db.Where("cashier_id = ? AND status = 'open'", cashierID). + Order("opened_at DESC").First(&dbModel).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, errors.Wrap(err, "failed to get open session") + } + + return r.toEntity(&dbModel), nil +} + +func (r *cashierSessionRepository) GetSessionByID(ctx mycontext.Context, sessionID int64) (*entity.CashierSession, error) { + var dbModel models.CashierSessionDB + if err := r.db.First(&dbModel, sessionID).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, errors.Wrap(err, "failed to get session by ID") + } + + return r.toEntity(&dbModel), nil +} + +func (r *cashierSessionRepository) toEntity(db *models.CashierSessionDB) *entity.CashierSession { + return &entity.CashierSession{ + ID: db.ID, + CashierID: db.CashierID, + OpenedAt: db.OpenedAt, + ClosedAt: db.ClosedAt, + OpeningAmount: db.OpeningAmount, + ClosingAmount: db.ClosingAmount, + ExpectedAmount: db.ExpectedAmount, + Status: db.Status, + } +} + +func (r *cashierSessionRepository) GetPaymentSummaryBySessionID(ctx mycontext.Context, sessionID int64) ([]entity.PaymentSummary, error) { + type result struct { + PaymentType string + PaymentProvider string + TotalAmount float64 + } + + var rows []result + + err := r.db.WithContext(ctx). + Table("orders"). + Select("payment_type, payment_provider, SUM(total) AS total_amount"). + Where("cashier_session_id = ?", sessionID). + Group("payment_type, payment_provider"). + Scan(&rows).Error + + if err != nil { + return nil, errors.Wrap(err, "failed to summarize payments from orders") + } + + summary := make([]entity.PaymentSummary, len(rows)) + for i, row := range rows { + summary[i] = entity.PaymentSummary{ + PaymentType: row.PaymentType, + PaymentProvider: row.PaymentProvider, + TotalAmount: row.TotalAmount, + } + } + + return summary, nil +} diff --git a/internal/repository/categories_repo.go b/internal/repository/categories_repo.go new file mode 100644 index 0000000..b02af20 --- /dev/null +++ b/internal/repository/categories_repo.go @@ -0,0 +1,94 @@ +package repository + +import ( + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "enaklo-pos-be/internal/repository/models" + "github.com/pkg/errors" + "gorm.io/gorm" + "time" +) + +type CategoryRepository interface { + Create(ctx mycontext.Context, category *entity.Category) (*entity.Category, error) + GetByPartnerID(ctx mycontext.Context, partnerID int64) ([]*entity.Category, error) + GetByID(ctx mycontext.Context, id int64) (*entity.Category, error) + Update(ctx mycontext.Context, category *entity.Category) error + Delete(ctx mycontext.Context, id int64) error +} + +type categoryRepository struct { + db *gorm.DB +} + +func NewCategoryRepository(db *gorm.DB) CategoryRepository { + return &categoryRepository{db: db} +} + +func (r *categoryRepository) Create(ctx mycontext.Context, category *entity.Category) (*entity.Category, error) { + dbModel := &models.CategoryDB{ + PartnerID: category.PartnerID, + Name: category.Name, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if err := r.db.WithContext(ctx).Create(dbModel).Error; err != nil { + return nil, errors.Wrap(err, "failed to create category") + } + category.ID = dbModel.ID + return category, nil +} + +func (r *categoryRepository) GetByPartnerID(ctx mycontext.Context, partnerID int64) ([]*entity.Category, error) { + var dbModels []models.CategoryDB + if err := r.db.WithContext(ctx). + Where("partner_id = ? AND deleted_at IS NULL", partnerID). + Find(&dbModels).Error; err != nil { + return nil, errors.Wrap(err, "failed to fetch categories by partner ID") + } + + var result []*entity.Category + for _, db := range dbModels { + result = append(result, r.toEntity(&db)) + } + return result, nil +} + +func (r *categoryRepository) GetByID(ctx mycontext.Context, id int64) (*entity.Category, error) { + var db models.CategoryDB + if err := r.db.WithContext(ctx). + Where("id = ? AND deleted_at IS NULL", id). + First(&db).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, errors.Wrap(err, "failed to get category by ID") + } + return r.toEntity(&db), nil +} + +func (r *categoryRepository) Update(ctx mycontext.Context, category *entity.Category) error { + return r.db.WithContext(ctx).Model(&models.CategoryDB{}). + Where("id = ?", category.ID). + Updates(map[string]interface{}{ + "name": category.Name, + "updated_at": time.Now(), + }).Error +} + +func (r *categoryRepository) Delete(ctx mycontext.Context, id int64) error { + return r.db.WithContext(ctx). + Model(&models.CategoryDB{}). + Where("id = ?", id). + Update("deleted_at", time.Now()).Error +} + +func (r *categoryRepository) toEntity(db *models.CategoryDB) *entity.Category { + return &entity.Category{ + ID: db.ID, + PartnerID: db.PartnerID, + Name: db.Name, + CreatedAt: db.CreatedAt.Unix(), + UpdatedAt: db.UpdatedAt.Unix(), + } +} diff --git a/internal/repository/events/event.go b/internal/repository/events/event.go deleted file mode 100644 index 680b892..0000000 --- a/internal/repository/events/event.go +++ /dev/null @@ -1,86 +0,0 @@ -package event - -import ( - "context" - - "go.uber.org/zap" - "gorm.io/gorm" - - "enaklo-pos-be/internal/common/logger" - "enaklo-pos-be/internal/entity" -) - -type EventRepoImpl struct { - DB *gorm.DB -} - -func NewEventRepo(db *gorm.DB) *EventRepoImpl { - return &EventRepoImpl{DB: db} -} - -func (e *EventRepoImpl) CreateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) { - err := e.DB.Create(event).Error - if err != nil { - logger.ContextLogger(ctx).Error("error when create event", zap.Error(err)) - return nil, err - } - return event, nil -} - -func (e *EventRepoImpl) UpdateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) { - if err := e.DB.Save(event).Error; err != nil { - logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) - return nil, err - } - return event, nil -} - -func (e *EventRepoImpl) GetEventByID(ctx context.Context, id int64) (*entity.EventDB, error) { - event := new(entity.EventDB) - if err := e.DB.First(event, id).Error; err != nil { - logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) - return nil, err - } - return event, nil -} - -func (e *EventRepoImpl) GetAllEvents(ctx context.Context, nameFilter string, limit, offset int) (entity.EventList, int, error) { - var events []*entity.EventDB - var total int64 - - query := e.DB - query = query.Where("deleted_at is null") - - if nameFilter != "" { - query = query.Where("name LIKE ?", "%"+nameFilter+"%") - } - - if limit > 0 { - query = query.Limit(limit) - } - if offset > 0 { - query = query.Offset(offset) - } - - if err := query.Find(&events).Error; err != nil { - logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) - return nil, 0, err - } - - if err := e.DB.Model(&entity.EventDB{}).Where(query).Count(&total).Error; err != nil { - logger.ContextLogger(ctx).Error("error when count event", zap.Error(err)) - return nil, 0, err - } - - return events, int(total), nil -} - -func (e *EventRepoImpl) DeleteEvent(ctx context.Context, id int64) error { - event := new(entity.EventDB) - event.ID = id - if err := e.DB.Delete(event).Error; err != nil { - logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) - return err - } - return nil -} diff --git a/internal/repository/models/casheer_seasion.go b/internal/repository/models/casheer_seasion.go new file mode 100644 index 0000000..4a5d78e --- /dev/null +++ b/internal/repository/models/casheer_seasion.go @@ -0,0 +1,20 @@ +package models + +import "time" + +type CashierSessionDB struct { + ID int64 `gorm:"primaryKey"` + CashierID int64 `gorm:"not null"` + OpenedAt time.Time `gorm:"not null"` + ClosedAt *time.Time + OpeningAmount float64 `gorm:"not null"` + ClosingAmount *float64 + ExpectedAmount *float64 + Notes *string + Status string `gorm:"not null"` + CreatedAt time.Time +} + +func (CashierSessionDB) TableName() string { + return "cashier_sessions" +} diff --git a/internal/repository/models/categories.go b/internal/repository/models/categories.go new file mode 100644 index 0000000..ce2ebb8 --- /dev/null +++ b/internal/repository/models/categories.go @@ -0,0 +1,16 @@ +package models + +import "time" + +type CategoryDB struct { + ID int64 `gorm:"primaryKey;autoIncrement"` + PartnerID int64 `gorm:"not null"` + Name string `gorm:"type:varchar(255);not null"` + CreatedAt time.Time `gorm:"autoCreateTime"` + UpdatedAt time.Time `gorm:"autoUpdateTime"` + DeletedAt *time.Time +} + +func (CategoryDB) TableName() string { + return "categories" +} diff --git a/internal/repository/models/order.go b/internal/repository/models/order.go index 4753374..db1eb3f 100644 --- a/internal/repository/models/order.go +++ b/internal/repository/models/order.go @@ -5,24 +5,26 @@ import ( ) type OrderDB struct { - ID int64 `gorm:"primaryKey;column:id"` - PartnerID int64 `gorm:"column:partner_id"` - CustomerID *int64 `gorm:"column:customer_id"` - InquiryID *string `gorm:"column:inquiry_id"` - Status string `gorm:"column:status"` - Amount float64 `gorm:"column:amount"` - Tax float64 `gorm:"column:tax"` - Total float64 `gorm:"column:total"` - PaymentType string `gorm:"column:payment_type"` - Source string `gorm:"column:source"` - CreatedBy int64 `gorm:"column:created_by"` - CreatedAt time.Time `gorm:"column:created_at"` - UpdatedAt time.Time `gorm:"column:updated_at"` - OrderItems []OrderItemDB `gorm:"foreignKey:OrderID"` - OrderType string `gorm:"column:order_type"` - TableNumber string `gorm:"column:table_number"` - PaymentProvider string `gorm:"column:payment_provider"` - CustomerName string `gorm:"column:customer_name"` + ID int64 `gorm:"primaryKey;column:id"` + PartnerID int64 `gorm:"column:partner_id"` + CustomerID *int64 `gorm:"column:customer_id"` + InquiryID *string `gorm:"column:inquiry_id"` + Status string `gorm:"column:status"` + Amount float64 `gorm:"column:amount"` + Tax float64 `gorm:"column:tax"` + Total float64 `gorm:"column:total"` + PaymentType string `gorm:"column:payment_type"` + Source string `gorm:"column:source"` + CreatedBy int64 `gorm:"column:created_by"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` + OrderItems []OrderItemDB `gorm:"foreignKey:OrderID"` + OrderType string `gorm:"column:order_type"` + TableNumber string `gorm:"column:table_number"` + PaymentProvider string `gorm:"column:payment_provider"` + CustomerName string `gorm:"column:customer_name"` + CashierSessionID int64 `gorm:"column:cashier_session_id"` + Description string `gorm:"column:description"` } func (OrderDB) TableName() string { @@ -68,6 +70,7 @@ type OrderInquiryDB struct { PaymentProvider string `gorm:"column:payment_provider"` TableNumber string `gorm:"column:table_number"` OrderType string `gorm:"column:order_type"` + CashierSessionID int64 `gorm:"column:cashier_session_id"` } func (OrderInquiryDB) TableName() string { diff --git a/internal/repository/orde_repo.go b/internal/repository/orde_repo.go index 2d25a2a..cc7f653 100644 --- a/internal/repository/orde_repo.go +++ b/internal/repository/orde_repo.go @@ -40,6 +40,7 @@ type OrderRepository interface { FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error) GetOrderHistoryByUserID(ctx mycontext.Context, userID int64, req entity.SearchRequest) ([]*entity.Order, int64, error) FindByIDAndCustomerID(ctx mycontext.Context, id int64, customerID int64) (*entity.Order, error) + UpdateOrder(ctx mycontext.Context, id int64, status string, description string) error } type orderRepository struct { @@ -232,25 +233,48 @@ func (r *orderRepository) UpdateInquiryStatus(ctx mycontext.Context, id string, return nil } +func (r *orderRepository) UpdateOrder(ctx mycontext.Context, id int64, status string, description string) error { + now := time.Now() + + result := r.db.Model(&models.OrderDB{}). + Where("id = ?", id). + Updates(map[string]interface{}{ + "status": status, + "updated_at": now, + "description": description, + }) + + if result.Error != nil { + return errors.Wrap(result.Error, "failed to update order status") + } + + if result.RowsAffected == 0 { + logger.ContextLogger(ctx).Warn("no order updated") + } + + return nil +} + func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB { return models.OrderDB{ - ID: order.ID, - PartnerID: order.PartnerID, - CustomerID: order.CustomerID, - InquiryID: order.InquiryID, - Status: order.Status, - Amount: order.Amount, - Tax: order.Tax, - Total: order.Total, - PaymentType: order.PaymentType, - Source: order.Source, - CreatedBy: order.CreatedBy, - CreatedAt: order.CreatedAt, - UpdatedAt: order.UpdatedAt, - OrderType: order.OrderType, - TableNumber: order.TableNumber, - PaymentProvider: order.PaymentProvider, - CustomerName: order.CustomerName, + ID: order.ID, + PartnerID: order.PartnerID, + CustomerID: order.CustomerID, + InquiryID: order.InquiryID, + Status: order.Status, + Amount: order.Amount, + Tax: order.Tax, + Total: order.Total, + PaymentType: order.PaymentType, + Source: order.Source, + CreatedBy: order.CreatedBy, + CreatedAt: order.CreatedAt, + UpdatedAt: order.UpdatedAt, + OrderType: order.OrderType, + TableNumber: order.TableNumber, + PaymentProvider: order.PaymentProvider, + CustomerName: order.CustomerName, + CashierSessionID: order.CashierSessionID, } } @@ -346,27 +370,29 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo PaymentProvider: inquiry.PaymentProvider, OrderType: inquiry.OrderType, TableNumber: inquiry.TableNumber, + CashierSessionID: inquiry.CashierSessionID, } } func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry { inquiry := &entity.OrderInquiry{ - ID: dbModel.ID, - PartnerID: dbModel.PartnerID, - Status: dbModel.Status, - Amount: dbModel.Amount, - Tax: dbModel.Tax, - Total: dbModel.Total, - PaymentType: dbModel.PaymentType, - Source: dbModel.Source, - CreatedBy: dbModel.CreatedBy, - CreatedAt: dbModel.CreatedAt, - ExpiresAt: dbModel.ExpiresAt, - OrderItems: []entity.OrderItem{}, - OrderType: dbModel.OrderType, - CustomerName: dbModel.CustomerName, - PaymentProvider: dbModel.PaymentProvider, - TableNumber: dbModel.TableNumber, + ID: dbModel.ID, + PartnerID: dbModel.PartnerID, + Status: dbModel.Status, + Amount: dbModel.Amount, + Tax: dbModel.Tax, + Total: dbModel.Total, + PaymentType: dbModel.PaymentType, + Source: dbModel.Source, + CreatedBy: dbModel.CreatedBy, + CreatedAt: dbModel.CreatedAt, + ExpiresAt: dbModel.ExpiresAt, + OrderItems: []entity.OrderItem{}, + OrderType: dbModel.OrderType, + CustomerName: dbModel.CustomerName, + PaymentProvider: dbModel.PaymentProvider, + TableNumber: dbModel.TableNumber, + CashierSessionID: dbModel.CashierSessionID, } if dbModel.CustomerID != nil { @@ -718,7 +744,7 @@ func (r *orderRepository) GetRevenueOverview( ctx mycontext.Context, req entity.RevenueOverviewRequest, ) ([]entity.RevenueOverviewItem, error) { - var overview []entity.RevenueOverviewItem + overview := []entity.RevenueOverviewItem{} baseQuery := r.db.Model(&models.OrderDB{}). Where("partner_id = ?", req.PartnerID). diff --git a/internal/repository/products/product.go b/internal/repository/products/product.go index 2b8c631..fe82abe 100644 --- a/internal/repository/products/product.go +++ b/internal/repository/products/product.go @@ -38,7 +38,7 @@ func (b *ProductRepository) UpdateProduct(ctx context.Context, product *entity.P func (b *ProductRepository) GetProductByID(ctx context.Context, id int64) (*entity.ProductDB, error) { product := new(entity.ProductDB) - if err := b.db.First(product, id).Error; err != nil { + if err := b.db.Preload("Category").First(product, id).Error; err != nil { logger.ContextLogger(ctx).Error("error when get by id product", zap.Error(err)) return nil, err } @@ -84,8 +84,12 @@ func (b *ProductRepository) GetAllProducts(ctx context.Context, req entity.Produ query = query.Where("type = ? ", req.Type) } - if req.BranchID > 0 { - query = query.Where("branch_id = ? ", req.BranchID) + if req.CategoryID > 0 { + query = query.Where("category_id = ? ", req.CategoryID) + } + + if req.PartnerID > 0 { + query = query.Where("partner_id = ? ", req.PartnerID) } if req.Limit > 0 { @@ -96,7 +100,7 @@ func (b *ProductRepository) GetAllProducts(ctx context.Context, req entity.Produ query = query.Offset(req.Offset) } - if err := query.Find(&products).Error; err != nil { + if err := query.Preload("Category").Find(&products).Order("id ASC").Error; err != nil { logger.ContextLogger(ctx).Error("error when get all products", zap.Error(err)) return nil, 0, err } diff --git a/internal/repository/repository.go b/internal/repository/repository.go index df9346a..372497f 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -15,7 +15,6 @@ import ( pg "enaklo-pos-be/internal/repository/payment_gateway" "enaklo-pos-be/internal/repository/products" "enaklo-pos-be/internal/repository/sites" - "enaklo-pos-be/internal/repository/studios" transactions "enaklo-pos-be/internal/repository/transaction" "enaklo-pos-be/internal/repository/trx" "enaklo-pos-be/internal/repository/users" @@ -28,15 +27,12 @@ import ( "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/repository/auth" "enaklo-pos-be/internal/repository/crypto" - event "enaklo-pos-be/internal/repository/events" ) type RepoManagerImpl struct { Crypto Crypto Auth Auth - Event Event User User - Studio Studio Product Product Order Order OSS OSSRepository @@ -60,15 +56,15 @@ type RepoManagerImpl struct { MemberRepository MemberRepository PartnerSetting PartnerSettingsRepository UndianRepository UndianRepo + CashierSeasionRepo CashierSessionRepository + CategoryRepository CategoryRepository } func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { return &RepoManagerImpl{ Crypto: crypto.NewCrypto(cfg.Auth()), Auth: auth.NewAuthRepository(db), - Event: event.NewEventRepo(db), User: users.NewUserRepository(db), - Studio: studios.NewStudioRepository(db), Product: products.NewProductRepository(db), Order: orders.NewOrderRepository(db), OSS: oss.NewOssRepositoryImpl(cfg.OSSConfig), @@ -92,6 +88,8 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl { InProgressOrderRepo: NewInProgressOrderRepository(db), PartnerSetting: NewPartnerSettingsRepository(db), UndianRepository: NewUndianRepository(db), + CashierSeasionRepo: NewCashierSessionRepository(db), + CategoryRepository: NewCategoryRepository(db), } } @@ -101,14 +99,6 @@ type Auth interface { UpdatePassword(ctx context.Context, trx *gorm.DB, newHashedPassword string, userID int64, resetPassword bool) error } -type Event interface { - CreateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) - UpdateEvent(ctx context.Context, event *entity.EventDB) (*entity.EventDB, error) - GetEventByID(ctx context.Context, id int64) (*entity.EventDB, error) - GetAllEvents(ctx context.Context, nameFilter string, limit, offset int) (entity.EventList, int, error) - DeleteEvent(ctx context.Context, id int64) error -} - type Crypto interface { CompareHashAndPassword(hash string, password string) bool ValidateWT(tokenString string) (*jwt.Token, error) diff --git a/internal/repository/studios/studio.go b/internal/repository/studios/studio.go deleted file mode 100644 index 35d83b0..0000000 --- a/internal/repository/studios/studio.go +++ /dev/null @@ -1,89 +0,0 @@ -package studios - -import ( - "context" - "enaklo-pos-be/internal/common/logger" - "enaklo-pos-be/internal/entity" - - "go.uber.org/zap" - "gorm.io/gorm" -) - -type StudioRepository struct { - db *gorm.DB -} - -func NewStudioRepository(db *gorm.DB) *StudioRepository { - return &StudioRepository{ - db: db, - } -} - -func (s *StudioRepository) CreateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) { - err := s.db.Omit("ID").Create(studio).Error - if err != nil { - logger.ContextLogger(ctx).Error("error when creating studio", zap.Error(err)) - return nil, err - } - return studio, nil -} - -func (s *StudioRepository) UpdateStudio(ctx context.Context, studio *entity.StudioDB) (*entity.StudioDB, error) { - if err := s.db.Save(studio).Error; err != nil { - logger.ContextLogger(ctx).Error("error when updating studio", zap.Error(err)) - return nil, err - } - return studio, nil -} - -func (s *StudioRepository) GetStudioByID(ctx context.Context, id int64) (*entity.StudioDB, error) { - studio := new(entity.StudioDB) - if err := s.db.First(studio, id).Error; err != nil { - logger.ContextLogger(ctx).Error("error when getting studio by ID", zap.Error(err)) - return nil, err - } - return studio, nil -} - -func (s *StudioRepository) SearchStudios(ctx context.Context, req entity.StudioSearch) (entity.StudioList, int, error) { - var studios []*entity.StudioDB - var total int64 - - query := s.db - - if req.Id > 0 { - query = query.Where("id = ?", req.Id) - } - - if req.Name != "" { - query = query.Where("name ILIKE ?", "%"+req.Name+"%") - } - - if req.Status != "" { - query = query.Where("status = ?", req.Status) - } - - if req.BranchId > 0 { - query = query.Where("branch_id = ?", req.BranchId) - } - - if req.Limit > 0 { - query = query.Limit(req.Limit) - } - - if req.Offset > 0 { - query = query.Offset(req.Offset) - } - - if err := query.Find(&studios).Error; err != nil { - logger.ContextLogger(ctx).Error("error when getting all studios", zap.Error(err)) - return nil, 0, err - } - - if err := s.db.Model(&entity.StudioDB{}).Where(query).Count(&total).Error; err != nil { - logger.ContextLogger(ctx).Error("error when counting studios", zap.Error(err)) - return nil, 0, err - } - - return studios, int(total), nil -} diff --git a/internal/routes/customer_routes.go b/internal/routes/customer_routes.go index 7ba563a..6abcfc6 100644 --- a/internal/routes/customer_routes.go +++ b/internal/routes/customer_routes.go @@ -3,7 +3,6 @@ package routes import ( "enaklo-pos-be/internal/handlers/http" "enaklo-pos-be/internal/handlers/http/customerauth" - "enaklo-pos-be/internal/handlers/http/discovery" "enaklo-pos-be/internal/middlewares" "enaklo-pos-be/internal/app" @@ -19,7 +18,6 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan optionlMiddleWare := middlewares.OptionalCustomerAuthorizationMiddleware(repoManager.Crypto) serverRoutes := []HTTPHandlerRoutes{ - discovery.NewHandler(serviceManager.DiscoverService), customerauth.NewAuthHandler(serviceManager.AuthV2Svc, serviceManager.MemberRegistrationSvc), http.NewMenuHandler(serviceManager.ProductV2Svc, serviceManager.InProgressSvc), http.NewCustomerOrderHandler(serviceManager.OrderV2Svc), diff --git a/internal/routes/routes.go b/internal/routes/routes.go index 12c9c52..6493f27 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -8,14 +8,12 @@ import ( "enaklo-pos-be/internal/handlers/http/partner" "enaklo-pos-be/internal/handlers/http/product" site "enaklo-pos-be/internal/handlers/http/sites" - "enaklo-pos-be/internal/handlers/http/studio" "enaklo-pos-be/internal/handlers/http/transaction" "enaklo-pos-be/internal/handlers/http/user" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "net/http" - "enaklo-pos-be/internal/handlers/http/event" "enaklo-pos-be/internal/middlewares" "github.com/gin-gonic/gin" @@ -46,9 +44,7 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana authMiddleware := middlewares.AuthorizationMiddleware(repoManager.Crypto) serverRoutes := []HTTPHandlerRoutes{ auth.NewAuthHandler(serviceManager.AuthSvc), - event.NewHandler(serviceManager.EventSvc), user.NewHandler(serviceManager.UserSvc), - studio.NewStudioHandler(serviceManager.StudioSvc), product.NewHandler(serviceManager.ProductSvc), oss.NewOssHandler(serviceManager.OSSSvc), partner.NewHandler(serviceManager.PartnerSvc), @@ -74,6 +70,8 @@ func RegisterPrivateRoutesV2(app *app.Server, serviceManager *services.ServiceMa http2.NewMemberRegistrationHandler(serviceManager.MemberRegistrationSvc), http2.NewCustomerHandler(serviceManager.CustomerV2Svc), http2.NewInProgressOrderHandler(serviceManager.InProgressSvc), + http2.NewCashierSession(serviceManager.CashierSvc), + http2.NewCategoryHandler(serviceManager.CategorySvc), } for _, handler := range serverRoutes { diff --git a/internal/services/discovery/discovery.go b/internal/services/discovery/discovery.go deleted file mode 100644 index c9fbb4b..0000000 --- a/internal/services/discovery/discovery.go +++ /dev/null @@ -1,168 +0,0 @@ -package discovery - -import ( - "context" - "enaklo-pos-be/config" - "errors" - "gorm.io/gorm" - - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/repository" -) - -const ( - defaultLatitude = -6.2088 - defaultLongitude = 106.8456 - radius = 10000 -) - -type DiscoveryService struct { - repo repository.SiteRepository - cfg config.Discovery - product repository.Product -} - -func NewDiscoveryService(repo repository.SiteRepository, cfg config.Discovery, product repository.Product) *DiscoveryService { - return &DiscoveryService{ - repo: repo, - cfg: cfg, - product: product, - } -} - -func (s *DiscoveryService) Home(ctx context.Context, search *entity.DiscoverySearch) (*entity.DiscoverySearchResp, error) { - if search.Lat == 0 || search.Long == 0 { - search.Lat = defaultLatitude - search.Long = defaultLongitude - } - - siteProducts, err := s.repo.GetNearestSites(ctx, search.Lat, search.Long, radius) - if err != nil { - return nil, err - } - exploreDestinations := []entity.ExploreDestination{} - for _, exploreDestination := range s.cfg.ExploreDestinations { - exploreDestinations = append(exploreDestinations, entity.ExploreDestination{ - Name: exploreDestination.Name, - ImageURL: exploreDestination.ImageURL, - }) - } - - exploreRegions := []entity.ExploreRegion{} - for _, exploreRegion := range s.cfg.ExploreRegions { - exploreRegions = append(exploreRegions, entity.ExploreRegion{ - Name: exploreRegion.Name, - }) - } - - mustVisits := []entity.MustVisit{} - - for _, siteProduct := range siteProducts { - if siteProduct.Status == "Active" { - mustVisits = append(mustVisits, entity.MustVisit{ - Name: siteProduct.SiteName, - Price: siteProduct.ProductPrice, - Region: siteProduct.Region, - SiteID: siteProduct.SiteID, - ImageURL: siteProduct.Image, - }) - } - } - - response := &entity.DiscoverySearchResp{ - ExploreRegions: exploreRegions, - ExploreDestinations: exploreDestinations, - MustVisit: mustVisits, - } - - return response, nil -} - -func (s *DiscoveryService) Search(ctx context.Context, search *entity.DiscoverySearch) (*entity.DiscoverySearchResp, int64, error) { - if search.Lat == 0 || search.Long == 0 { - search.Lat = defaultLatitude - search.Long = defaultLongitude - search.Radius = radius - } - - search.Status = "Active" - siteProducts, total, err := s.repo.SearchSites(ctx, search) - if err != nil { - return nil, 0, err - } - exploreDestinations := []entity.ExploreDestination{} - for _, exploreDestination := range s.cfg.ExploreDestinations { - exploreDestinations = append(exploreDestinations, entity.ExploreDestination{ - Name: exploreDestination.Name, - ImageURL: exploreDestination.ImageURL, - }) - } - - exploreRegions := []entity.ExploreRegion{} - for _, exploreRegion := range s.cfg.ExploreRegions { - exploreRegions = append(exploreRegions, entity.ExploreRegion{ - Name: exploreRegion.Name, - }) - } - - mustVisits := []entity.MustVisit{} - - for _, siteProduct := range siteProducts { - mustVisits = append(mustVisits, entity.MustVisit{ - Name: siteProduct.SiteName, - Price: siteProduct.ProductPrice, - Region: siteProduct.Region, - SiteID: siteProduct.SiteID, - ImageURL: siteProduct.Image, - Regency: siteProduct.Regency, - }) - } - - response := &entity.DiscoverySearchResp{ - ExploreRegions: exploreRegions, - ExploreDestinations: exploreDestinations, - MustVisit: mustVisits, - } - - return response, total, nil -} - -func (s *DiscoveryService) GetByID(ctx context.Context, id int64) (*entity.Site, error) { - site, err := s.repo.GetByID(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil - } - return nil, err - } - - if site.Status != "Active" { - return nil, nil - } - - return site.ToSite(), nil -} - -func (s *DiscoveryService) GetProductsByID(ctx context.Context, id int64) ([]*entity.Product, error) { - site, err := s.repo.GetByID(ctx, id) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil - } - return nil, err - } - - if site.Status == "Inactive" { - return nil, nil - } - - product, err := s.product.GetProductsBySiteID(ctx, site.ID) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil - } - return nil, err - } - - return product.ToProductList(), nil -} diff --git a/internal/services/event/event.go b/internal/services/event/event.go deleted file mode 100644 index d547848..0000000 --- a/internal/services/event/event.go +++ /dev/null @@ -1,88 +0,0 @@ -package event - -import ( - "context" - - "go.uber.org/zap" - - "enaklo-pos-be/internal/common/logger" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/repository" -) - -type EventService struct { - repo repository.Event -} - -func NewEventService(repo repository.Event) *EventService { - return &EventService{ - repo: repo, - } -} - -func (s *EventService) Create(ctx context.Context, eventReq *entity.Event) (*entity.Event, error) { - eventDB := eventReq.ToEventDB() - - eventDB, err := s.repo.CreateEvent(ctx, eventDB) - if err != nil { - logger.ContextLogger(ctx).Error("error when create event", zap.Error(err)) - return nil, err - } - - return eventDB.ToEvent(), nil -} - -func (s *EventService) Update(ctx context.Context, id int64, eventReq *entity.Event) (*entity.Event, error) { - existingEvent, err := s.repo.GetEventByID(ctx, id) - if err != nil { - return nil, err - } - - existingEvent.ToUpdatedEvent(*eventReq) - - updatedEventDB, err := s.repo.UpdateEvent(ctx, existingEvent.ToEventDB()) - if err != nil { - logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) - return nil, err - } - - return updatedEventDB.ToEvent(), nil -} - -func (s *EventService) GetByID(ctx context.Context, id int64) (*entity.Event, error) { - eventDB, err := s.repo.GetEventByID(ctx, id) - if err != nil { - logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) - return nil, err - } - - return eventDB.ToEvent(), nil -} - -func (s *EventService) GetAll(ctx context.Context, search entity.EventSearch) ([]*entity.Event, int, error) { - events, total, err := s.repo.GetAllEvents(ctx, search.Name, search.Limit, search.Offset) - if err != nil { - logger.ContextLogger(ctx).Error("error when get all events", zap.Error(err)) - return nil, 0, err - } - - return events.ToEventList(), total, nil -} - -func (s *EventService) Delete(ctx context.Context, id int64) error { - eventDB, err := s.repo.GetEventByID(ctx, id) - if err != nil { - logger.ContextLogger(ctx).Error("error when get event by id", zap.Error(err)) - return err - } - - eventDB.SetDeleted() - - _, err = s.repo.UpdateEvent(ctx, eventDB) - if err != nil { - logger.ContextLogger(ctx).Error("error when update event", zap.Error(err)) - return err - } - - return nil -} diff --git a/internal/services/service.go b/internal/services/service.go index d313b31..bf4b8b8 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -4,17 +4,17 @@ import ( "context" "enaklo-pos-be/internal/common/mycontext" "enaklo-pos-be/internal/services/balance" - "enaklo-pos-be/internal/services/discovery" service "enaklo-pos-be/internal/services/license" "enaklo-pos-be/internal/services/member" "enaklo-pos-be/internal/services/oss" "enaklo-pos-be/internal/services/partner" "enaklo-pos-be/internal/services/product" site "enaklo-pos-be/internal/services/sites" - "enaklo-pos-be/internal/services/studio" "enaklo-pos-be/internal/services/transaction" "enaklo-pos-be/internal/services/users" authSvc "enaklo-pos-be/internal/services/v2/auth" + "enaklo-pos-be/internal/services/v2/cashier_session" + category "enaklo-pos-be/internal/services/v2/categories" customerSvc "enaklo-pos-be/internal/services/v2/customer" "enaklo-pos-be/internal/services/v2/inprogress_order" orderSvc "enaklo-pos-be/internal/services/v2/order" @@ -29,22 +29,18 @@ import ( "enaklo-pos-be/internal/entity" "enaklo-pos-be/internal/repository" "enaklo-pos-be/internal/services/auth" - "enaklo-pos-be/internal/services/event" ) type ServiceManagerImpl struct { - AuthSvc Auth - EventSvc Event - UserSvc User - StudioSvc Studio - ProductSvc Product - OSSSvc OSSService - PartnerSvc Partner - SiteSvc Site - LicenseSvc License - Transaction Transaction - Balance Balance - DiscoverService DiscoverService + AuthSvc Auth + UserSvc User + ProductSvc Product + OSSSvc OSSService + PartnerSvc Partner + SiteSvc Site + LicenseSvc License + Transaction Transaction + Balance Balance OrderV2Svc orderSvc.Service CustomerV2Svc customerSvc.Service @@ -53,6 +49,8 @@ type ServiceManagerImpl struct { InProgressSvc inprogress_order.InProgressOrderService AuthV2Svc authSvc.Service UndianSvc undian.Service + CashierSvc cashier_session.Service + CategorySvc category.Service } func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) *ServiceManagerImpl { @@ -60,14 +58,16 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService) productSvcV2 := productSvc.New(repo.ProductRepo) partnerSettings := partner_settings.NewPartnerSettingsService(repo.PartnerSetting) - - orderService := orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, repo.UndianRepository) + cashierSvc := cashier_session.New(repo.CashierSeasionRepo) + orderService := orderSvc.New(repo.OrderRepo, + productSvcV2, custSvcV2, repo.TransactionRepo, + repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, + repo.UndianRepository, cashierSvc) inprogressOrder := inprogress_order.NewInProgressOrderService(repo.OrderRepo, orderService, productSvcV2) + categorySvc := category.New(repo.CategoryRepository) return &ServiceManagerImpl{ AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License), - EventSvc: event.NewEventService(repo.Event), UserSvc: users.NewUserService(repo.User), - StudioSvc: studio.NewStudioService(repo.Studio), ProductSvc: product.NewProductService(repo.Product), OSSSvc: oss.NewOSSService(repo.OSS), PartnerSvc: partner.NewPartnerService( @@ -76,14 +76,15 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) LicenseSvc: service.NewLicenseService(repo.License), Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx), Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction), - DiscoverService: discovery.NewDiscoveryService(repo.Site, cfg.Discovery, repo.Product), - OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, repo.UndianRepository), + OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings, repo.UndianRepository, cashierSvc), MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2, repo.Crypto), CustomerV2Svc: custSvcV2, InProgressSvc: inprogressOrder, ProductV2Svc: productSvcV2, AuthV2Svc: authSvc.New(repo.CustomerRepo, repo.Crypto), UndianSvc: undian.New(repo.UndianRepository), + CashierSvc: cashierSvc, + CategorySvc: categorySvc, } } @@ -93,14 +94,6 @@ type Auth interface { ResetPassword(ctx mycontext.Context, oldPassword, newPassword string) error } -type Event interface { - Create(ctx context.Context, eventReq *entity.Event) (*entity.Event, error) - Update(ctx context.Context, id int64, eventReq *entity.Event) (*entity.Event, error) - GetByID(ctx context.Context, id int64) (*entity.Event, error) - GetAll(ctx context.Context, search entity.EventSearch) ([]*entity.Event, int, error) - Delete(ctx context.Context, id int64) error -} - type User interface { Create(ctx mycontext.Context, userReq *entity.User) (*entity.User, error) CreateWithTx(ctx mycontext.Context, tx *gorm.DB, userReq *entity.User) (*entity.User, error) @@ -112,13 +105,6 @@ type User interface { Delete(ctx mycontext.Context, id int64) error } -type Studio interface { - Create(ctx mycontext.Context, studioReq *entity.Studio) (*entity.Studio, error) - Update(ctx mycontext.Context, id int64, studioReq *entity.Studio) (*entity.Studio, error) - GetByID(ctx context.Context, id int64) (*entity.Studio, error) - Search(ctx context.Context, search entity.StudioSearch) ([]*entity.Studio, int, error) -} - type Product interface { Create(ctx mycontext.Context, productReq *entity.Product) (*entity.Product, error) Update(ctx mycontext.Context, id int64, productReq *entity.Product) (*entity.Product, error) @@ -128,23 +114,6 @@ type Product interface { Delete(ctx mycontext.Context, id int64) error } -type Order interface { - CreateOrder(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderResponse, error) - CheckInInquiry(ctx mycontext.Context, qrCode string, partnerID *int64) (*entity.CheckinResponse, error) - CheckInExecute(ctx mycontext.Context, - token string, partnerID *int64) (*entity.CheckinExecute, error) - Execute(ctx mycontext.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) - ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error - GetAllHistoryOrders(ctx mycontext.Context, req entity.OrderSearch) ([]*entity.HistoryOrder, int, error) - CountSoldOfTicket(ctx mycontext.Context, req entity.OrderSearch) (*entity.TicketSold, error) - SumAmount(ctx mycontext.Context, req entity.OrderSearch) (*entity.Order, error) - GetDailySales(ctx mycontext.Context, req entity.OrderSearch) ([]entity.ProductDailySales, error) - GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error) - GetByID(ctx mycontext.Context, id int64, referenceID string) (*entity.Order, error) - GetPrintDetail(ctx mycontext.Context, id int64) (*entity.OrderPrintDetail, error) - ProcessLinkQuCallback(ctx context.Context, req *entity.LinkQuCallback) error -} - type OSSService interface { UploadFile(ctx context.Context, req *entity.UploadFileRequest) (*entity.UploadFileResponse, error) } @@ -183,10 +152,3 @@ type Balance interface { WithdrawInquiry(ctx context.Context, req *entity.BalanceWithdrawInquiry) (*entity.BalanceWithdrawInquiryResponse, error) WithdrawExecute(ctx mycontext.Context, req *entity.WalletWithdrawRequest) (*entity.WalletWithdrawResponse, error) } - -type DiscoverService interface { - Home(ctx context.Context, search *entity.DiscoverySearch) (*entity.DiscoverySearchResp, error) - Search(ctx context.Context, search *entity.DiscoverySearch) (*entity.DiscoverySearchResp, int64, error) - GetByID(ctx context.Context, id int64) (*entity.Site, error) - GetProductsByID(ctx context.Context, id int64) ([]*entity.Product, error) -} diff --git a/internal/services/studio/studio.go b/internal/services/studio/studio.go deleted file mode 100644 index 14a9156..0000000 --- a/internal/services/studio/studio.go +++ /dev/null @@ -1,70 +0,0 @@ -package studio - -import ( - "context" - "enaklo-pos-be/internal/common/logger" - "enaklo-pos-be/internal/common/mycontext" - "enaklo-pos-be/internal/entity" - "enaklo-pos-be/internal/repository" - "go.uber.org/zap" -) - -type StudioService struct { - repo repository.Studio -} - -func NewStudioService(repo repository.Studio) *StudioService { - return &StudioService{ - repo: repo, - } -} - -func (s *StudioService) Create(ctx mycontext.Context, studioReq *entity.Studio) (*entity.Studio, error) { - newStudioDB := studioReq.NewStudiosDB() - newStudioDB.CreatedBy = ctx.RequestedBy() - - newStudioDB, err := s.repo.CreateStudio(ctx, newStudioDB) - if err != nil { - logger.ContextLogger(ctx).Error("error when creating studio", zap.Error(err)) - return nil, err - } - - return newStudioDB.ToStudio(), nil -} - -func (s *StudioService) Update(ctx mycontext.Context, id int64, studioReq *entity.Studio) (*entity.Studio, error) { - existingStudio, err := s.repo.GetStudioByID(ctx, id) - if err != nil { - return nil, err - } - - existingStudio.ToUpdatedStudio(ctx.RequestedBy(), *studioReq) - - updatedStudioDB, err := s.repo.UpdateStudio(ctx, existingStudio.ToStudioDB()) - if err != nil { - logger.ContextLogger(ctx).Error("error when updating studio", zap.Error(err)) - return nil, err - } - - return updatedStudioDB.ToStudio(), nil -} - -func (s *StudioService) GetByID(ctx context.Context, id int64) (*entity.Studio, error) { - studioDB, err := s.repo.GetStudioByID(ctx, id) - if err != nil { - logger.ContextLogger(ctx).Error("error when getting studio by id", zap.Error(err)) - return nil, err - } - - return studioDB.ToStudio(), nil -} - -func (s *StudioService) Search(ctx context.Context, search entity.StudioSearch) ([]*entity.Studio, int, error) { - studios, total, err := s.repo.SearchStudios(ctx, search) - if err != nil { - logger.ContextLogger(ctx).Error("error when getting all studios", zap.Error(err)) - return nil, 0, err - } - - return studios.ToStudioList(), total, nil -} diff --git a/internal/services/v2/cashier_session/casheer_session.go b/internal/services/v2/cashier_session/casheer_session.go new file mode 100644 index 0000000..9c1ad00 --- /dev/null +++ b/internal/services/v2/cashier_session/casheer_session.go @@ -0,0 +1,99 @@ +package cashier_session + +import ( + "enaklo-pos-be/internal/common/logger" + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type Service interface { + OpenSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) + CloseSession(ctx mycontext.Context, sessionID int64, closingAmount float64) (*entity.CashierSessionReport, error) + GetOpenSession(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) + GetSessionReport(ctx mycontext.Context, sessionID int64) (*entity.CashierSessionReport, error) +} + +type Repository interface { + CreateSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) + CloseSession(ctx mycontext.Context, sessionID int64, closingAmount, expectedAmount float64) error + GetOpenSessionByCashierID(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) + GetSessionByID(ctx mycontext.Context, sessionID int64) (*entity.CashierSession, error) + GetPaymentSummaryBySessionID(ctx mycontext.Context, sessionID int64) ([]entity.PaymentSummary, error) +} + +type cashierSessionSvc struct { + repo Repository +} + +func New(repo Repository) Service { + return &cashierSessionSvc{repo: repo} +} + +func (s *cashierSessionSvc) OpenSession(ctx mycontext.Context, session *entity.CashierSession) (*entity.CashierSession, error) { + openSession, err := s.repo.GetOpenSessionByCashierID(ctx, session.CashierID) + if err != nil { + return nil, errors.Wrap(err, "failed to check existing open session") + } + if openSession != nil { + return nil, errors.New("cashier already has an open session") + } + + newSession, err := s.repo.CreateSession(ctx, session) + if err != nil { + logger.ContextLogger(ctx).Error("failed to create cashier session", zap.Error(err)) + return nil, errors.Wrap(err, "failed to create cashier session") + } + + return newSession, nil +} + +func (s *cashierSessionSvc) CloseSession(ctx mycontext.Context, sessionID int64, closingAmount float64) (*entity.CashierSessionReport, error) { + report, err := s.repo.GetPaymentSummaryBySessionID(ctx, sessionID) + if err != nil { + return nil, errors.Wrap(err, "failed to get payment summary") + } + + var expectedAmount float64 + for _, r := range report { + expectedAmount += r.TotalAmount + } + + if err := s.repo.CloseSession(ctx, sessionID, closingAmount, expectedAmount); err != nil { + return nil, errors.Wrap(err, "failed to close session") + } + + return &entity.CashierSessionReport{ + SessionID: sessionID, + ClosingAmount: closingAmount, + ExpectedAmount: expectedAmount, + Payments: report, + }, nil +} + +func (s *cashierSessionSvc) GetOpenSession(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) { + session, err := s.repo.GetOpenSessionByCashierID(ctx, cashierID) + if err != nil { + return nil, errors.Wrap(err, "failed to get open session") + } + return session, nil +} + +func (s *cashierSessionSvc) GetSessionReport(ctx mycontext.Context, sessionID int64) (*entity.CashierSessionReport, error) { + report, err := s.repo.GetPaymentSummaryBySessionID(ctx, sessionID) + if err != nil { + return nil, errors.Wrap(err, "failed to get payment summary") + } + + var expectedAmount float64 + for _, r := range report { + expectedAmount += r.TotalAmount + } + + return &entity.CashierSessionReport{ + SessionID: sessionID, + ExpectedAmount: expectedAmount, + Payments: report, + }, nil +} diff --git a/internal/services/v2/categories/categories.go b/internal/services/v2/categories/categories.go new file mode 100644 index 0000000..f526870 --- /dev/null +++ b/internal/services/v2/categories/categories.go @@ -0,0 +1,88 @@ +package category + +import ( + "enaklo-pos-be/internal/common/logger" + "enaklo-pos-be/internal/common/mycontext" + "enaklo-pos-be/internal/entity" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +type Service interface { + Create(ctx mycontext.Context, category *entity.Category) (*entity.Category, error) + GetByPartnerID(ctx mycontext.Context, partnerID int64) ([]*entity.Category, error) + Update(ctx mycontext.Context, category *entity.Category) error + Delete(ctx mycontext.Context, id int64) error + GetByID(ctx mycontext.Context, id int64) (*entity.Category, error) +} + +type Repository interface { + Create(ctx mycontext.Context, category *entity.Category) (*entity.Category, error) + GetByPartnerID(ctx mycontext.Context, partnerID int64) ([]*entity.Category, error) + Update(ctx mycontext.Context, category *entity.Category) error + Delete(ctx mycontext.Context, id int64) error + GetByID(ctx mycontext.Context, id int64) (*entity.Category, error) +} + +type categorySvc struct { + repo Repository +} + +func New(repo Repository) Service { + return &categorySvc{repo: repo} +} + +func (s *categorySvc) Create(ctx mycontext.Context, category *entity.Category) (*entity.Category, error) { + existing, err := s.repo.GetByPartnerID(ctx, category.PartnerID) + if err != nil { + return nil, errors.Wrap(err, "failed to fetch categories") + } + + for _, cat := range existing { + if cat.Name == category.Name { + return nil, errors.New("category name already exists for this partner") + } + } + + newCategory, err := s.repo.Create(ctx, category) + if err != nil { + logger.ContextLogger(ctx).Error("failed to create category", zap.Error(err)) + return nil, errors.Wrap(err, "failed to create category") + } + + return newCategory, nil +} + +func (s *categorySvc) GetByPartnerID(ctx mycontext.Context, partnerID int64) ([]*entity.Category, error) { + categories, err := s.repo.GetByPartnerID(ctx, partnerID) + if err != nil { + return nil, errors.Wrap(err, "failed to get categories by partner") + } + return categories, nil +} + +func (s *categorySvc) Update(ctx mycontext.Context, category *entity.Category) error { + err := s.repo.Update(ctx, category) + if err != nil { + logger.ContextLogger(ctx).Error("failed to update category", zap.Error(err)) + return errors.Wrap(err, "failed to update category") + } + return nil +} + +func (s *categorySvc) Delete(ctx mycontext.Context, id int64) error { + err := s.repo.Delete(ctx, id) + if err != nil { + logger.ContextLogger(ctx).Error("failed to delete category", zap.Error(err)) + return errors.Wrap(err, "failed to delete category") + } + return nil +} + +func (s *categorySvc) GetByID(ctx mycontext.Context, id int64) (*entity.Category, error) { + category, err := s.repo.GetByID(ctx, id) + if err != nil { + return nil, errors.Wrap(err, "failed to get category by ID") + } + return category, nil +} diff --git a/internal/services/v2/order/create_order_inquiry.go b/internal/services/v2/order/create_order_inquiry.go index 9ae09f7..330e732 100644 --- a/internal/services/v2/order/create_order_inquiry.go +++ b/internal/services/v2/order/create_order_inquiry.go @@ -12,6 +12,13 @@ import ( func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) { + + cashierSession, err := s.cashierSvc.GetOpenSession(ctx, ctx.RequestedBy()) + if err != nil { + logger.ContextLogger(ctx).Error("no open session found for cashier", zap.Error(err)) + return nil, err + } + productIDs, filteredItems, err := s.ValidateOrderItems(ctx, req.OrderItems) if err != nil { return nil, err @@ -60,6 +67,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context, req.PaymentProvider, req.TableNumber, req.OrderType, + cashierSession.ID, ) for _, item := range req.OrderItems { diff --git a/internal/services/v2/order/execute_order.go b/internal/services/v2/order/execute_order.go index 42919e8..7b1c67d 100644 --- a/internal/services/v2/order/execute_order.go +++ b/internal/services/v2/order/execute_order.go @@ -6,6 +6,7 @@ import ( "enaklo-pos-be/internal/constants" "enaklo-pos-be/internal/entity" "fmt" + "github.com/pkg/errors" "go.uber.org/zap" "time" ) @@ -36,6 +37,20 @@ func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context, }, nil } +func (s *orderSvc) RefundRequest(ctx mycontext.Context, partnerID, orderID int64, reason string) error { + order, err := s.repo.FindByIDAndPartnerID(ctx, partnerID, orderID) + if err != nil { + logger.ContextLogger(ctx).Error("failed to create order", zap.Error(err)) + return err + } + + if order.Status != "PAID" { + return errors.New("only paid order can be refund") + } + + return s.repo.UpdateOrder(ctx, order.ID, "REFUNDED", reason) +} + func (s *orderSvc) processPostOrderActions( ctx mycontext.Context, order *entity.Order, diff --git a/internal/services/v2/order/order.go b/internal/services/v2/order/order.go index f991e3c..a5cb732 100644 --- a/internal/services/v2/order/order.go +++ b/internal/services/v2/order/order.go @@ -12,6 +12,7 @@ type Repository interface { CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error) FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error) UpdateInquiryStatus(ctx mycontext.Context, id string, status string) error + UpdateOrder(ctx mycontext.Context, id int64, status string, description string) error GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error) GetOrderPaymentMethodBreakdown( ctx mycontext.Context, @@ -65,6 +66,7 @@ type Service interface { req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) ExecuteOrderInquiry(ctx mycontext.Context, token string, paymentMethod, paymentProvider string, inProgressOrderID int64) (*entity.OrderResponse, error) + RefundRequest(ctx mycontext.Context, partnerID, orderID int64, reason string) error GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error) CalculateOrderTotals( ctx mycontext.Context, @@ -122,6 +124,10 @@ type VoucherUndianRepo interface { CreateUndianVouchers(ctx mycontext.Context, vouchers []*entity.UndianVoucherDB) error } +type CashierSvc interface { + GetOpenSession(ctx mycontext.Context, cashierID int64) (*entity.CashierSession, error) +} + type orderSvc struct { repo Repository product ProductService @@ -133,6 +139,7 @@ type orderSvc struct { partnerSetting PartnerSettings inprogressOrder InProgressOrderRepository voucherUndianRepo VoucherUndianRepo + cashierSvc CashierSvc } func New( @@ -145,6 +152,7 @@ func New( notification NotificationService, partnerSetting PartnerSettings, voucherUndianRepo VoucherUndianRepo, + cashierSvc CashierSvc, ) Service { return &orderSvc{ repo: repo, @@ -156,5 +164,6 @@ func New( notification: notification, partnerSetting: partnerSetting, voucherUndianRepo: voucherUndianRepo, + cashierSvc: cashierSvc, } }