diff --git a/go.sum b/go.sum index f7afa6b..1e41251 100644 --- a/go.sum +++ b/go.sum @@ -84,8 +84,6 @@ github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uq github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getbrevo/brevo-go v1.0.0 h1:E/pRCsQeExvZeTCJU5vy+xHWcLaL5axWQ9QkxjlFke4= github.com/getbrevo/brevo-go v1.0.0/go.mod h1:2TBMEnaDqq/oiAXUYtn6eykiEdHcEoS7tc63+YoFibw= -github.com/getbrevo/brevo-go v1.1.1 h1:6/SXEQ7ZfUjetPnJ4EncfLSUgXjQv4qUj1EQgLXnDto= -github.com/getbrevo/brevo-go v1.1.1/go.mod h1:ExhytIoPxt/cOBl6ZEMeEZNLUKrWEYA5U3hM/8WP2bg= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= diff --git a/internal/common/mycontext/kinoscontext.go b/internal/common/mycontext/kinoscontext.go index fb19d02..f0fb057 100644 --- a/internal/common/mycontext/kinoscontext.go +++ b/internal/common/mycontext/kinoscontext.go @@ -13,6 +13,7 @@ type Context interface { RequestedBy() int64 IsSuperAdmin() bool + IsAdmin() bool IsCasheer() bool GetPartnerID() *int64 GetSiteID() *int64 @@ -36,6 +37,10 @@ func (m *MyContextImpl) IsSuperAdmin() bool { return m.roleID == int(role.SuperAdmin) } +func (m *MyContextImpl) IsAdmin() bool { + return m.roleID == int(role.SuperAdmin) || m.roleID == int(role.Admin) +} + func (m *MyContextImpl) IsCasheer() bool { return m.roleID == int(role.Casheer) } diff --git a/internal/entity/order.go b/internal/entity/order.go index 57a1b21..462929f 100644 --- a/internal/entity/order.go +++ b/internal/entity/order.go @@ -81,3 +81,58 @@ type CallbackRequest struct { TransactionStatus string `json:"transaction_status"` TransactionID string `json:"transaction_id"` } + +type HistoryOrder struct { + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + Employee string `gorm:"type:varchar;column:employee"` + Site string `gorm:"type:varchar;column:site"` + Timestamp time.Time `gorm:"autoCreateTime;column:timestamp"` + BookingTime time.Time `gorm:"autoCreateTime;column:booking_time"` + Tickets []string `gorm:"-"` + RawTickets string `gorm:"type:text;column:tickets"` + PaymentType string `gorm:"type:varchar;column:payment_type"` + Status string `gorm:"type:varchar;column:status"` + Amount float64 `gorm:"type:numeric;column:amount"` +} + +type HistoryOrderDB struct { + HistoryOrder +} + +type HistoryOrderSearch struct { + PartnerID *int64 + IsAdmin bool + Limit int + Offset int +} + +type HistoryOrderList []*HistoryOrderDB + +func (b *HistoryOrder) ToHistoryOrderDB() *HistoryOrderDB { + return &HistoryOrderDB{ + HistoryOrder: *b, + } +} + +func (e *HistoryOrderDB) ToHistoryOrder() *HistoryOrder { + return &HistoryOrder{ + ID: e.ID, + Employee: e.Employee, + Site: e.Site, + Timestamp: e.Timestamp, + BookingTime: e.BookingTime, + Tickets: e.Tickets, + RawTickets: e.RawTickets, + PaymentType: e.PaymentType, + Status: e.Status, + Amount: e.Amount, + } +} + +func (b *HistoryOrderList) ToHistoryOrderList() []*HistoryOrder { + var HistoryOrders []*HistoryOrder + for _, historyOrder := range *b { + HistoryOrders = append(HistoryOrders, historyOrder.ToHistoryOrder()) + } + return HistoryOrders +} diff --git a/internal/handlers/http/order/order.go b/internal/handlers/http/order/order.go index fcd1ee0..63c4f51 100644 --- a/internal/handlers/http/order/order.go +++ b/internal/handlers/http/order/order.go @@ -6,9 +6,11 @@ import ( "furtuna-be/internal/handlers/request" "furtuna-be/internal/handlers/response" "furtuna-be/internal/services" + "net/http" + "time" + "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" - "net/http" ) type Handler struct { @@ -20,6 +22,7 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) { route.POST("/inquiry", jwt, h.Inquiry) route.POST("/execute", jwt, h.Execute) + route.GET("/history", jwt, h.GetAllHistoryOrders) } func NewHandler(service services.Order) *Handler { @@ -151,3 +154,52 @@ func MapOrderToExecuteOrderResponse(orderResponse *entity.ExecuteOrderResponse) RedirectURL: orderResponse.RedirectURL, } } + +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.Status, + Amount: resp.Amount, + } +} + +func (h *Handler) GetAllHistoryOrders(c *gin.Context) { + var req request.HistoryOrderParam + 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.ToEntity(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) toHistoryOrderList(resp []*entity.HistoryOrder, total int64, req request.HistoryOrderParam) 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, + } +} diff --git a/internal/handlers/request/order.go b/internal/handlers/request/order.go index ed653a7..0183119 100644 --- a/internal/handlers/request/order.go +++ b/internal/handlers/request/order.go @@ -1,6 +1,7 @@ package request import ( + "furtuna-be/internal/common/mycontext" "furtuna-be/internal/constants/transaction" "furtuna-be/internal/entity" ) @@ -11,6 +12,20 @@ type Order struct { OrderItems []OrderItem `json:"order_items" validate:"required"` } +type HistoryOrderParam struct { + Limit int `form:"limit" json:"limit" example:"10"` + Offset int `form:"offset" json:"offset" example:"0"` +} + +func (o *HistoryOrderParam) ToEntity(ctx mycontext.Context) entity.HistoryOrderSearch { + return entity.HistoryOrderSearch{ + PartnerID: ctx.GetPartnerID(), + IsAdmin: ctx.IsAdmin(), + Limit: o.Limit, + Offset: o.Offset, + } +} + type OrderItem struct { ProductID int64 `json:"product_id" validate:"required"` Quantity int64 `json:"quantity" validate:"required"` diff --git a/internal/handlers/response/order.go b/internal/handlers/response/order.go index 419a867..940bd7d 100644 --- a/internal/handlers/response/order.go +++ b/internal/handlers/response/order.go @@ -21,6 +21,18 @@ type Order struct { UpdatedAt string `json:"updated_at"` } +type HistoryOrder struct { + ID int64 `json:"id"` + Employee string `json:"employee"` + Site string `json:"site"` + Timestamp string `json:"timestamp"` + BookingTime string `json:"booking_time"` + Tickets []string `json:"tickets"` + PaymentType string `json:"payment_type"` + Status string `json:"status"` + Amount float64 `json:"amount"` +} + type OrderItem struct { OrderItemID int64 `json:"order_item_id" ` ItemID int64 `json:"item_id" ` @@ -39,6 +51,13 @@ type OrderList struct { Offset int `json:"offset"` } +type HistoryOrderList struct { + Orders []HistoryOrder `json:"history_orders"` + Total int64 `json:"total"` + Limit int `json:"limit"` + Offset int `json:"offset"` +} + type OrderMonthlyRevenue struct { TotalRevenue float64 `json:"total_revenue"` TotalTransaction int64 `json:"total_transaction"` diff --git a/internal/repository/orders/order.go b/internal/repository/orders/order.go index 24757bb..d58c206 100644 --- a/internal/repository/orders/order.go +++ b/internal/repository/orders/order.go @@ -4,6 +4,8 @@ import ( "context" "furtuna-be/internal/common/logger" "furtuna-be/internal/entity" + "strings" + "go.uber.org/zap" "gorm.io/gorm" ) @@ -80,3 +82,49 @@ func (r *OrderRepository) Update(ctx context.Context, order *entity.Order) (*ent } return order, nil } + +func (b *OrderRepository) GetAllHystoryOrders(ctx context.Context, req entity.HistoryOrderSearch) (entity.HistoryOrderList, int, error) { + var orders []*entity.HistoryOrderDB + var total int64 + + query := b.db.Table("orders"). + Select("orders.id as id, users.name as employee, sites.name as site, orders.created_at as timestamp, orders.created_at as booking_time, STRING_AGG(ticket_summary.name || ' x' || ticket_summary.total_qty, ', ') AS tickets, orders.payment_type as payment_type, orders.status as status, orders.amount as amount"). + Joins("left join (SELECT items.order_id, products.name, SUM(items.qty) AS total_qty FROM order_items items LEFT JOIN products ON items.item_id = products.id GROUP BY items.order_id, products.name) AS ticket_summary ON orders.id = ticket_summary.order_id"). + Joins("left join users on orders.created_by = users.id"). + Joins("left join partners on orders.partner_id = partners.id"). + Joins("left join sites on partners.id = sites.partner_id") + + if !req.IsAdmin { + query = query.Where("orders.partner_id = ?", req.PartnerID) + } + + query = query.Group("orders.id, users.name, sites.name, orders.created_at, orders.payment_type, orders.status") + + if err := query.Count(&total).Error; err != nil { + logger.ContextLogger(ctx).Error("error when count history orders", zap.Error(err)) + return nil, 0, err + } + + page := (req.Offset - 1) * req.Limit + + if req.Offset > 0 { + query = query.Offset(page) + } + + if req.Limit > 0 { + query = query.Limit(req.Limit) + } + + if err := query.Scan(&orders).Error; err != nil { + logger.ContextLogger(ctx).Error("error when get all history orders", zap.Error(err)) + return nil, 0, err + } + + for i, order := range orders { + if order.RawTickets != "" { + orders[i].Tickets = strings.Split(order.RawTickets, ", ") + } + } + + return orders, int(total), nil +} diff --git a/internal/repository/repository.go b/internal/repository/repository.go index 309aeef..18ce638 100644 --- a/internal/repository/repository.go +++ b/internal/repository/repository.go @@ -130,6 +130,7 @@ type Order interface { FindByID(ctx context.Context, id int64) (*entity.Order, error) Update(ctx context.Context, order *entity.Order) (*entity.Order, error) SetOrderStatus(ctx context.Context, db *gorm.DB, orderID int64, status string) error + GetAllHystoryOrders(ctx context.Context, req entity.HistoryOrderSearch) (entity.HistoryOrderList, int, error) } type OSSRepository interface { diff --git a/internal/services/order/order.go b/internal/services/order/order.go index 2b075e2..1ca88d8 100644 --- a/internal/services/order/order.go +++ b/internal/services/order/order.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "furtuna-be/internal/common/logger" + "furtuna-be/internal/common/mycontext" order2 "furtuna-be/internal/constants/order" "furtuna-be/internal/entity" "furtuna-be/internal/repository" @@ -296,3 +297,15 @@ func (s *OrderService) updateWalletBalance(ctx context.Context, tx *gorm.DB, par _, err = s.wallet.Update(ctx, tx, wallet) return err } + +func (s *OrderService) GetAllHistoryOrders(ctx mycontext.Context, req entity.HistoryOrderSearch) ([]*entity.HistoryOrder, int, error) { + historyOrders, total, err := s.repo.GetAllHystoryOrders(ctx, req) + if err != nil { + logger.ContextLogger(ctx).Error("error when get all history orders", zap.Error(err)) + return nil, 0, err + } + + data := historyOrders.ToHistoryOrderList() + + return data, total, nil +} diff --git a/internal/services/service.go b/internal/services/service.go index 679d847..539f519 100644 --- a/internal/services/service.go +++ b/internal/services/service.go @@ -8,9 +8,10 @@ import ( "furtuna-be/internal/services/oss" "furtuna-be/internal/services/partner" "furtuna-be/internal/services/product" - "furtuna-be/internal/services/sites" + site "furtuna-be/internal/services/sites" "furtuna-be/internal/services/studio" "furtuna-be/internal/services/users" + "gorm.io/gorm" "furtuna-be/config" @@ -41,9 +42,8 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl) BranchSvc: branch.NewBranchService(repo.Branch), StudioSvc: studio.NewStudioService(repo.Studio), ProductSvc: product.NewProductService(repo.Product), - OrderSvc: order.NewOrderService(repo.Order, repo.Product, - repo.Crypto, repo.Midtrans, repo.Payment, repo.Trx, repo.Wallet), - OSSSvc: oss.NewOSSService(repo.OSS), + OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Crypto, repo.Midtrans, repo.Payment, repo.Trx, repo.Wallet), + OSSSvc: oss.NewOSSService(repo.OSS), PartnerSvc: partner.NewPartnerService( repo.Partner, users.NewUserService(repo.User, repo.Branch), repo.Trx, repo.Wallet), SiteSvc: site.NewSiteService(repo.Site), @@ -101,6 +101,7 @@ type Order interface { CreateOrder(ctx context.Context, req *entity.OrderRequest) (*entity.OrderResponse, error) Execute(ctx context.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error + GetAllHistoryOrders(ctx mycontext.Context, req entity.HistoryOrderSearch) ([]*entity.HistoryOrder, int, error) } type OSSService interface {