update
This commit is contained in:
parent
c642c5c61b
commit
09c9a4d59d
@ -6,6 +6,7 @@ const (
|
||||
New OrderStatus = "NEW"
|
||||
Paid OrderStatus = "PAID"
|
||||
Cancel OrderStatus = "CANCEL"
|
||||
Pending OrderStatus = "PENDING"
|
||||
)
|
||||
|
||||
func (b OrderStatus) toString() string {
|
||||
|
||||
@ -10,7 +10,7 @@ type Order struct {
|
||||
Status string `gorm:"type:varchar;column:status"`
|
||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||
Total float64 `gorm:"type:numeric;not null;column:total"`
|
||||
Fee float64 `gorm:"type:numeric;not null;column:fee"`
|
||||
Tax float64 `gorm:"type:numeric;not null;column:tax"`
|
||||
CustomerID *int64
|
||||
CustomerName string
|
||||
InquiryID *string
|
||||
@ -27,7 +27,7 @@ type Order struct {
|
||||
Source string `gorm:"type:varchar;column:source"`
|
||||
OrderType string `gorm:"type:varchar;column:order_type"`
|
||||
TableNumber string
|
||||
InProgressOrderID string
|
||||
InProgressOrderID int64
|
||||
}
|
||||
|
||||
type OrderDB struct {
|
||||
@ -86,6 +86,7 @@ type OrderItem struct {
|
||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||
Product *Product `gorm:"foreignKey:ItemID;references:ID"`
|
||||
ItemName string `gorm:"type:varchar;column:item_name"`
|
||||
}
|
||||
|
||||
func (OrderItem) TableName() string {
|
||||
@ -105,6 +106,7 @@ type OrderRequest struct {
|
||||
TableNumber string
|
||||
PaymentProvider string
|
||||
OrderType string
|
||||
ID int64
|
||||
}
|
||||
|
||||
type OrderItemRequest struct {
|
||||
|
||||
@ -14,7 +14,7 @@ type OrderInquiry struct {
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
Source string `json:"source"`
|
||||
@ -30,7 +30,7 @@ type OrderInquiry struct {
|
||||
|
||||
type OrderCalculation struct {
|
||||
Subtotal float64 `json:"subtotal"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ func NewOrderInquiry(
|
||||
partnerID int64,
|
||||
customerID int64,
|
||||
amount float64,
|
||||
fee float64,
|
||||
tax float64,
|
||||
total float64,
|
||||
paymentType string,
|
||||
source string,
|
||||
@ -60,7 +60,7 @@ func NewOrderInquiry(
|
||||
PartnerID: partnerID,
|
||||
Status: "PENDING",
|
||||
Amount: amount,
|
||||
Fee: fee,
|
||||
Tax: tax,
|
||||
Total: total,
|
||||
PaymentType: paymentType,
|
||||
CustomerID: customerID,
|
||||
@ -83,6 +83,7 @@ func (oi *OrderInquiry) AddOrderItem(item OrderItemRequest, product *Product) {
|
||||
ItemID: item.ProductID,
|
||||
ItemType: product.Type,
|
||||
Price: product.Price,
|
||||
ItemName: product.Name,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: oi.CreatedBy,
|
||||
Product: product,
|
||||
@ -98,7 +99,7 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
||||
InquiryID: &i.ID,
|
||||
Status: constants.StatusPaid,
|
||||
Amount: i.Amount,
|
||||
Fee: i.Fee,
|
||||
Tax: i.Tax,
|
||||
Total: i.Total,
|
||||
PaymentType: paymentMethod,
|
||||
PaymentProvider: paymentProvider,
|
||||
@ -107,6 +108,8 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
||||
CreatedAt: now,
|
||||
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
||||
OrderType: i.OrderType,
|
||||
CustomerName: i.CustomerName,
|
||||
TableNumber: i.TableNumber,
|
||||
}
|
||||
|
||||
for idx, item := range i.OrderItems {
|
||||
@ -114,6 +117,7 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
||||
ItemID: item.ItemID,
|
||||
ItemType: item.ItemType,
|
||||
Price: item.Price,
|
||||
ItemName: item.ItemName,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: i.CreatedBy,
|
||||
CreatedAt: now,
|
||||
|
||||
56
internal/entity/partner_setting.go
Normal file
56
internal/entity/partner_setting.go
Normal file
@ -0,0 +1,56 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type PartnerSettings struct {
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
TaxEnabled bool `json:"tax_enabled"`
|
||||
TaxPercentage float64 `json:"tax_percentage"`
|
||||
InvoicePrefix string `json:"invoice_prefix"`
|
||||
BusinessHours string `json:"business_hours"`
|
||||
LogoURL string `json:"logo_url"`
|
||||
ThemeColor string `json:"theme_color"`
|
||||
ReceiptFooterText string `json:"receipt_footer_text"`
|
||||
ReceiptHeaderText string `json:"receipt_header_text"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PartnerPaymentMethod struct {
|
||||
ID int64 `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
DisplayName string `json:"display_name"`
|
||||
DisplayOrder int `json:"display_order"`
|
||||
AdditionalInfo string `json:"additional_info"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PartnerFeatureFlag struct {
|
||||
ID int64 `json:"id"`
|
||||
PartnerID int64 `json:"partner_id"`
|
||||
FeatureKey string `json:"feature_key"`
|
||||
IsEnabled bool `json:"is_enabled"`
|
||||
Config string `json:"config"` // JSON string
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type BusinessHoursSetting struct {
|
||||
Monday DayHours `json:"monday"`
|
||||
Tuesday DayHours `json:"tuesday"`
|
||||
Wednesday DayHours `json:"wednesday"`
|
||||
Thursday DayHours `json:"thursday"`
|
||||
Friday DayHours `json:"friday"`
|
||||
Saturday DayHours `json:"saturday"`
|
||||
Sunday DayHours `json:"sunday"`
|
||||
}
|
||||
|
||||
type DayHours struct {
|
||||
Open string `json:"open"` // Format: "HH:MM"
|
||||
Close string `json:"close"` // Format: "HH:MM"
|
||||
}
|
||||
@ -128,3 +128,39 @@ type ProductDetails struct {
|
||||
Products map[int64]*Product // Map for quick lookups by ID
|
||||
PartnerID int64 // Common site ID for all products
|
||||
}
|
||||
|
||||
type PaymentMethodBreakdown struct {
|
||||
PaymentType string `json:"payment_type"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TotalTransactions int64 `json:"total_transactions"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
}
|
||||
|
||||
type OrderPaymentAnalysis struct {
|
||||
TotalTransactions int64 `json:"total"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
PaymentMethodBreakdown []PaymentMethodBreakdown `json:"payment_method_breakdown"`
|
||||
}
|
||||
|
||||
type RevenueOverviewItem struct {
|
||||
Period string `json:"period"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
OrderCount int64 `json:"order_count"`
|
||||
}
|
||||
|
||||
type SalesByCategoryItem struct {
|
||||
Category string `json:"category"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
TotalQuantity int64 `json:"total_quantity"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
type PopularProductItem struct {
|
||||
ProductID int64 `json:"product_id"`
|
||||
ProductName string `json:"product_name"`
|
||||
Category string `json:"category"`
|
||||
TotalSales int64 `json:"total_sales"`
|
||||
TotalRevenue float64 `json:"total_revenue"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
32
internal/entity/search.go
Normal file
32
internal/entity/search.go
Normal file
@ -0,0 +1,32 @@
|
||||
package entity
|
||||
|
||||
import "time"
|
||||
|
||||
type SearchRequest struct {
|
||||
Status string // Filter by order status (e.g., "COMPLETED", "PENDING", etc.)
|
||||
Start time.Time // Start date for filtering orders
|
||||
End time.Time // End date for filtering orders
|
||||
Limit int // Maximum number of records to return
|
||||
Offset int // Number of records to skip for pagination
|
||||
}
|
||||
|
||||
type RevenueOverviewRequest struct {
|
||||
PartnerID int64
|
||||
Year int
|
||||
Granularity string // "monthly" or "weekly"
|
||||
Status string
|
||||
}
|
||||
|
||||
type SalesByCategoryRequest struct {
|
||||
PartnerID int64
|
||||
Period string // "d" (daily), "w" (weekly), "m" (monthly)
|
||||
Status string
|
||||
}
|
||||
|
||||
type PopularProductsRequest struct {
|
||||
PartnerID int64
|
||||
Period string // "d" (daily), "w" (weekly), "m" (monthly)
|
||||
Status string
|
||||
Limit int
|
||||
SortBy string // "sales" or "revenue"
|
||||
}
|
||||
@ -109,7 +109,7 @@ func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse, req requ
|
||||
PaymentType: order.PaymentType,
|
||||
CreatedAt: order.CreatedAt,
|
||||
OrderItems: orderItems,
|
||||
Fee: order.Fee,
|
||||
Tax: order.Tax,
|
||||
Total: order.Total,
|
||||
}
|
||||
}
|
||||
@ -249,7 +249,7 @@ func (h *Handler) toOrderDetail(order *entity.Order) *response.OrderDetail {
|
||||
PaymentLink: paymentLink,
|
||||
PaymentToken: paymentToken,
|
||||
SiteName: siteName,
|
||||
Fee: order.Fee,
|
||||
Fee: order.Tax,
|
||||
}
|
||||
|
||||
orderDetail.OrderItems = make([]response.OrderDetailItem, len(order.OrderItems))
|
||||
|
||||
@ -38,7 +38,7 @@ type CreateInProgressOrderRequest struct {
|
||||
OrderType string `json:"order_type"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
TableNumber string `json:"table_number"`
|
||||
InProgressOrderID string `json:"in_progress_order_id"`
|
||||
InProgressOrderID int64 `json:"in_progress_order_id"`
|
||||
}
|
||||
|
||||
type InProgressOrderItemRequest struct {
|
||||
@ -72,15 +72,15 @@ func (h *InProgressOrderHandler) Save(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
orderItems := make([]entity.InProgressOrderItem, len(req.OrderItems))
|
||||
orderItems := make([]entity.OrderItemRequest, len(req.OrderItems))
|
||||
for i, item := range req.OrderItems {
|
||||
orderItems[i] = entity.InProgressOrderItem{
|
||||
ItemID: item.ProductID,
|
||||
orderItems[i] = entity.OrderItemRequest{
|
||||
ProductID: item.ProductID,
|
||||
Quantity: item.Quantity,
|
||||
}
|
||||
}
|
||||
|
||||
order := &entity.InProgressOrder{
|
||||
order := &entity.OrderRequest{
|
||||
PartnerID: *partnerID,
|
||||
CustomerID: req.CustomerID,
|
||||
CustomerName: req.CustomerName,
|
||||
@ -89,6 +89,7 @@ func (h *InProgressOrderHandler) Save(c *gin.Context) {
|
||||
TableNumber: req.TableNumber,
|
||||
OrderType: req.OrderType,
|
||||
ID: req.InProgressOrderID,
|
||||
Source: "POS",
|
||||
}
|
||||
|
||||
_, err := h.service.Save(ctx, order)
|
||||
@ -103,7 +104,7 @@ func (h *InProgressOrderHandler) Save(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
func mapToInProgressOrderResponse(order *entity.InProgressOrder) map[string]interface{} {
|
||||
func mapToInProgressOrderResponse(order *entity.Order) map[string]interface{} {
|
||||
orderItems := make([]map[string]interface{}, len(order.OrderItems))
|
||||
for i, item := range order.OrderItems {
|
||||
orderItems[i] = map[string]interface{}{
|
||||
|
||||
@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/errors"
|
||||
order2 "enaklo-pos-be/internal/constants/order"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/handlers/request"
|
||||
"enaklo-pos-be/internal/handlers/response"
|
||||
@ -9,6 +10,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@ -26,6 +29,12 @@ 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.GetOrderHistory)
|
||||
route.GET("/payment-analysis", jwt, h.GetPaymentMethodAnalysis)
|
||||
route.GET("/revenue-overview", jwt, h.GetRevenueOverview)
|
||||
route.GET("/sales-by-category", jwt, h.GetSalesByCategory)
|
||||
route.GET("/popular-products", jwt, h.GetPopularProducts)
|
||||
|
||||
}
|
||||
|
||||
type InquiryRequest struct {
|
||||
@ -56,7 +65,7 @@ type OrderItemRequest struct {
|
||||
type ExecuteRequest struct {
|
||||
PaymentMethod string `json:"payment_method" validate:"required"`
|
||||
PaymentProvider string `json:"payment_provider"`
|
||||
InProgressOrderID string `json:"in_progress_order_id"`
|
||||
InProgressOrderID int64 `json:"in_progress_order_id"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
@ -140,3 +149,307 @@ func (h *Handler) Execute(c *gin.Context) {
|
||||
Data: response.MapToOrderResponse(result),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetOrderHistory(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
|
||||
limitStr := c.Query("limit")
|
||||
offsetStr := c.Query("offset")
|
||||
status := c.Query("status")
|
||||
startDateStr := c.Query("start_date")
|
||||
endDateStr := c.Query("end_date")
|
||||
|
||||
// Build search request
|
||||
searchReq := entity.SearchRequest{}
|
||||
|
||||
// Set status if provided
|
||||
if status != "" {
|
||||
searchReq.Status = status
|
||||
}
|
||||
|
||||
// Parse and set limit
|
||||
limit := 10
|
||||
if limitStr != "" {
|
||||
parsedLimit, err := strconv.Atoi(limitStr)
|
||||
if err == nil && parsedLimit > 0 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
if limit > 20 {
|
||||
limit = 20
|
||||
}
|
||||
searchReq.Limit = limit
|
||||
|
||||
// Parse and set offset
|
||||
offset := 0
|
||||
if offsetStr != "" {
|
||||
parsedOffset, err := strconv.Atoi(offsetStr)
|
||||
if err == nil && parsedOffset >= 0 {
|
||||
offset = parsedOffset
|
||||
}
|
||||
}
|
||||
searchReq.Offset = offset
|
||||
|
||||
if startDateStr != "" {
|
||||
startDate, err := time.Parse(time.RFC3339, startDateStr)
|
||||
if err == nil {
|
||||
searchReq.Start = startDate
|
||||
}
|
||||
}
|
||||
|
||||
// Parse end date if provided
|
||||
if endDateStr != "" {
|
||||
endDate, err := time.Parse(time.RFC3339, endDateStr)
|
||||
if err == nil {
|
||||
searchReq.End = endDate
|
||||
}
|
||||
}
|
||||
|
||||
orders, total, err := h.service.GetOrderHistory(ctx, *partnerID, searchReq)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
responseData := []response.OrderHistoryResponse{}
|
||||
for _, order := range orders {
|
||||
var orderItems []response.OrderItemResponse
|
||||
for _, item := range order.OrderItems {
|
||||
orderItems = append(orderItems, response.OrderItemResponse{
|
||||
ProductID: item.ItemID,
|
||||
ProductName: item.ItemName,
|
||||
Price: item.Price,
|
||||
Quantity: item.Quantity,
|
||||
Subtotal: item.Price * float64(item.Quantity),
|
||||
})
|
||||
}
|
||||
|
||||
responseData = append(responseData, response.OrderHistoryResponse{
|
||||
ID: order.ID,
|
||||
CustomerName: order.CustomerName,
|
||||
Status: order.Status,
|
||||
Amount: order.Amount,
|
||||
Total: order.Total,
|
||||
PaymentType: h.formatPayment(order.PaymentType, order.PaymentProvider),
|
||||
TableNumber: order.TableNumber,
|
||||
OrderType: order.OrderType,
|
||||
OrderItems: orderItems,
|
||||
CreatedAt: order.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
Tax: order.Tax,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: responseData,
|
||||
PagingMeta: &response.PagingMeta{
|
||||
Page: offset + 1,
|
||||
Total: int64(total),
|
||||
Limit: limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) formatPayment(payment, provider string) string {
|
||||
if payment == "CASH" {
|
||||
return payment
|
||||
}
|
||||
|
||||
return payment + " " + provider
|
||||
}
|
||||
|
||||
func (h *Handler) GetPaymentMethodAnalysis(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
|
||||
// Parse query parameters
|
||||
limitStr := c.Query("limit")
|
||||
offsetStr := c.Query("offset")
|
||||
status := c.Query("status")
|
||||
startDateStr := c.Query("start_date")
|
||||
endDateStr := c.Query("end_date")
|
||||
|
||||
searchReq := entity.SearchRequest{}
|
||||
|
||||
limit := 10
|
||||
if limitStr != "" {
|
||||
parsedLimit, err := strconv.Atoi(limitStr)
|
||||
if err == nil && parsedLimit > 0 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
if limit > 20 {
|
||||
limit = 20
|
||||
}
|
||||
searchReq.Limit = limit
|
||||
|
||||
offset := 0
|
||||
if offsetStr != "" {
|
||||
parsedOffset, err := strconv.Atoi(offsetStr)
|
||||
if err == nil && parsedOffset >= 0 {
|
||||
offset = parsedOffset
|
||||
}
|
||||
}
|
||||
searchReq.Offset = offset
|
||||
|
||||
if status != "" {
|
||||
searchReq.Status = status
|
||||
}
|
||||
|
||||
if startDateStr != "" {
|
||||
startDate, err := time.Parse(time.RFC3339, startDateStr)
|
||||
if err == nil {
|
||||
searchReq.Start = startDate
|
||||
}
|
||||
}
|
||||
|
||||
if endDateStr != "" {
|
||||
endDate, err := time.Parse(time.RFC3339, endDateStr)
|
||||
if err == nil {
|
||||
searchReq.End = endDate
|
||||
}
|
||||
}
|
||||
|
||||
paymentAnalysis, err := h.service.GetOrderPaymentAnalysis(ctx, *partnerID, searchReq)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
paymentBreakdown := make([]PaymentMethodBreakdown, len(paymentAnalysis.PaymentMethodBreakdown))
|
||||
for i, bd := range paymentAnalysis.PaymentMethodBreakdown {
|
||||
paymentBreakdown[i] = PaymentMethodBreakdown{
|
||||
PaymentMethod: h.formatPayment(bd.PaymentType, bd.PaymentProvider),
|
||||
TotalTransactions: bd.TotalTransactions,
|
||||
TotalAmount: bd.TotalAmount,
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: PaymentMethodAnalysisResponse{
|
||||
PaymentMethodBreakdown: paymentBreakdown,
|
||||
TotalAmount: paymentAnalysis.TotalAmount,
|
||||
TotalTransactions: paymentAnalysis.TotalTransactions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
type PaymentMethodBreakdown struct {
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
TotalTransactions int64 `json:"total_transactions"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
AverageTransactionAmount float64 `json:"average_transaction_amount"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
type PaymentMethodAnalysisResponse struct {
|
||||
PaymentMethodBreakdown []PaymentMethodBreakdown `json:"payment_method_breakdown"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
TotalTransactions int64 `json:"total_transactions"`
|
||||
|
||||
MostUsedPaymentMethod string `json:"most_used_payment_method"`
|
||||
HighestRevenueMethod string `json:"highest_revenue_method"`
|
||||
}
|
||||
|
||||
func (h *Handler) GetRevenueOverview(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
|
||||
granularity := c.Query("period")
|
||||
|
||||
year := time.Now().Year()
|
||||
|
||||
if granularity != "m" && granularity != "w" && granularity != "d" {
|
||||
granularity = "m"
|
||||
}
|
||||
|
||||
revenueOverview, err := h.service.GetRevenueOverview(
|
||||
ctx,
|
||||
*partnerID,
|
||||
year,
|
||||
granularity,
|
||||
order2.Paid.String(),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: revenueOverview,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetSalesByCategory(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
|
||||
period := c.Query("period")
|
||||
status := order2.Paid.String()
|
||||
|
||||
if period != "d" && period != "w" && period != "m" {
|
||||
period = "d"
|
||||
}
|
||||
|
||||
salesByCategory, err := h.service.GetSalesByCategory(
|
||||
ctx,
|
||||
*partnerID,
|
||||
period,
|
||||
status,
|
||||
)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: salesByCategory,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) GetPopularProducts(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
|
||||
period := c.Query("period")
|
||||
status := order2.Paid.String()
|
||||
limitStr := c.Query("limit")
|
||||
sortBy := c.Query("sort_by")
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
limit = 10 // default limit
|
||||
}
|
||||
|
||||
if period != "d" && period != "w" && period != "m" {
|
||||
period = "d"
|
||||
}
|
||||
|
||||
popularProducts, err := h.service.GetPopularProducts(
|
||||
ctx,
|
||||
*partnerID,
|
||||
period,
|
||||
status,
|
||||
limit,
|
||||
sortBy,
|
||||
)
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: popularProducts,
|
||||
})
|
||||
}
|
||||
|
||||
@ -204,7 +204,7 @@ func MapOrderToCreateOrderResponse(orderResponse *entity.OrderResponse) response
|
||||
Status: order.Status,
|
||||
Amount: order.Amount,
|
||||
Total: order.Total,
|
||||
Fee: order.Fee,
|
||||
Tax: order.Tax,
|
||||
PaymentType: order.PaymentType,
|
||||
CreatedAt: order.CreatedAt,
|
||||
OrderItems: orderItems,
|
||||
|
||||
@ -28,6 +28,7 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
||||
route.PUT("/:id", jwt, isSuperAdmin, h.Update)
|
||||
route.GET("/:id", jwt, isSuperAdmin, h.GetByID)
|
||||
route.DELETE("/:id", jwt, isSuperAdmin, h.Delete)
|
||||
route.PUT("/update", jwt, h.UpdateMyStore)
|
||||
}
|
||||
|
||||
func NewHandler(service services.Partner) *Handler {
|
||||
@ -268,3 +269,27 @@ func (h *Handler) toPartnerResponseList(resp []*entity.Partner, total int64, req
|
||||
Offset: req.Offset,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateMyStore(c *gin.Context) {
|
||||
ctx := request.GetMyContext(c)
|
||||
|
||||
PartnerID := ctx.GetPartnerID()
|
||||
|
||||
var req request.Partner
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.ErrorWrapper(c, errors.ErrorBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
updatedPartner, err := h.service.Update(ctx, req.ToEntityUpdate(*PartnerID))
|
||||
if err != nil {
|
||||
response.ErrorWrapper(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response.BaseResponse{
|
||||
Success: true,
|
||||
Status: http.StatusOK,
|
||||
Data: h.toPartnerResponse(updatedPartner),
|
||||
})
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ type CreateOrderResponse struct {
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Total float64 `json:"total"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
OrderItems []CreateOrderItemResponse `json:"order_items"`
|
||||
@ -187,3 +187,17 @@ type OrderDetailItem struct {
|
||||
UnitPrice float64 `json:"unit_price"` // Price per unit
|
||||
TotalPrice float64 `json:"total_price"` // Total price for this item (Quantity * UnitPrice)
|
||||
}
|
||||
|
||||
type OrderHistoryResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Total float64 `json:"total"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
TableNumber string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
OrderItems []OrderItemResponse `json:"order_items"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
Tax float64 `json:"tax"`
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ type OrderInquiryResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
CustomerID int64 `json:"customer_id"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
@ -54,7 +54,7 @@ func MapToInquiryResponse(result *entity.OrderInquiryResponse) OrderInquiryRespo
|
||||
ID: result.OrderInquiry.ID,
|
||||
Status: result.OrderInquiry.Status,
|
||||
Amount: result.OrderInquiry.Amount,
|
||||
Fee: result.OrderInquiry.Fee,
|
||||
Tax: result.OrderInquiry.Tax,
|
||||
Total: result.OrderInquiry.Total,
|
||||
CustomerID: result.OrderInquiry.CustomerID,
|
||||
PaymentType: result.OrderInquiry.PaymentType,
|
||||
@ -74,7 +74,7 @@ type OrderResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee"`
|
||||
Tax float64 `json:"tax"`
|
||||
Total float64 `json:"total"`
|
||||
CustomerName string `json:"customer_name,omitempty"`
|
||||
PaymentType string `json:"payment_type"`
|
||||
@ -89,7 +89,7 @@ func MapToOrderResponse(result *entity.OrderResponse) OrderResponse {
|
||||
ID: result.Order.ID,
|
||||
Status: result.Order.Status,
|
||||
Amount: result.Order.Amount,
|
||||
Fee: result.Order.Fee,
|
||||
Tax: result.Order.Tax,
|
||||
Total: result.Order.Total,
|
||||
PaymentType: result.Order.PaymentType,
|
||||
CreatedAt: result.Order.CreatedAt,
|
||||
|
||||
@ -3,5 +3,5 @@ package response
|
||||
type PagingMeta struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Total int64 `json:"total_data"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package repository
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/repository/models"
|
||||
"github.com/pkg/errors"
|
||||
@ -152,7 +151,7 @@ func (r *inprogressOrderRepository) GetListByPartnerID(ctx mycontext.Context, pa
|
||||
func (r *inprogressOrderRepository) toInProgressOrderDBModel(order *entity.InProgressOrder) models.InProgressOrderDB {
|
||||
now := time2.Now()
|
||||
return models.InProgressOrderDB{
|
||||
ID: constants.GenerateUUID(),
|
||||
ID: order.ID,
|
||||
PartnerID: order.PartnerID,
|
||||
CustomerID: order.CustomerID,
|
||||
CustomerName: order.CustomerName,
|
||||
@ -207,7 +206,7 @@ func (r *inprogressOrderRepository) toOrderInquiryDBModel(inquiry *entity.OrderI
|
||||
CustomerID: &inquiry.CustomerID,
|
||||
Status: inquiry.Status,
|
||||
Amount: inquiry.Amount,
|
||||
Fee: inquiry.Fee,
|
||||
Tax: inquiry.Tax,
|
||||
Total: inquiry.Total,
|
||||
PaymentType: inquiry.PaymentType,
|
||||
Source: inquiry.Source,
|
||||
@ -230,7 +229,7 @@ func (r *inprogressOrderRepository) toDomainOrderInquiryModel(dbModel *models.Or
|
||||
PartnerID: dbModel.PartnerID,
|
||||
Status: dbModel.Status,
|
||||
Amount: dbModel.Amount,
|
||||
Fee: dbModel.Fee,
|
||||
Tax: dbModel.Tax,
|
||||
Total: dbModel.Total,
|
||||
PaymentType: dbModel.PaymentType,
|
||||
Source: dbModel.Source,
|
||||
|
||||
@ -11,7 +11,7 @@ type OrderDB struct {
|
||||
InquiryID *string `gorm:"column:inquiry_id"`
|
||||
Status string `gorm:"column:status"`
|
||||
Amount float64 `gorm:"column:amount"`
|
||||
Fee float64 `gorm:"column:fee"`
|
||||
Tax float64 `gorm:"column:tax"`
|
||||
Total float64 `gorm:"column:total"`
|
||||
PaymentType string `gorm:"column:payment_type"`
|
||||
Source string `gorm:"column:source"`
|
||||
@ -19,6 +19,10 @@ type OrderDB struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
func (OrderDB) TableName() string {
|
||||
@ -29,11 +33,13 @@ type OrderItemDB struct {
|
||||
ID int64 `gorm:"primaryKey;column:order_item_id"`
|
||||
OrderID int64 `gorm:"column:order_id"`
|
||||
ItemID int64 `gorm:"column:item_id"`
|
||||
ItemName string `gorm:"column:item_name"`
|
||||
ItemType string `gorm:"column:item_type"`
|
||||
Price float64 `gorm:"column:price"`
|
||||
Quantity int `gorm:"column:quantity"`
|
||||
CreatedBy int64 `gorm:"column:created_by"`
|
||||
CreatedAt time.Time `gorm:"column:created_at"`
|
||||
Product ProductDB `gorm:"foreignKey:ItemID;references:ID"`
|
||||
}
|
||||
|
||||
func (OrderItemDB) TableName() string {
|
||||
@ -49,7 +55,7 @@ type OrderInquiryDB struct {
|
||||
CustomerPhoneNumber string `gorm:"column:customer_phone_number"`
|
||||
Status string `gorm:"column:status"`
|
||||
Amount float64 `gorm:"column:amount"`
|
||||
Fee float64 `gorm:"column:fee"`
|
||||
Tax float64 `gorm:"column:tax"`
|
||||
Total float64 `gorm:"column:total"`
|
||||
PaymentType string `gorm:"column:payment_type"`
|
||||
Source string `gorm:"column:source"`
|
||||
@ -72,6 +78,7 @@ type InquiryItemDB struct {
|
||||
InquiryID string `gorm:"column:inquiry_id"`
|
||||
ItemID int64 `gorm:"column:item_id"`
|
||||
ItemType string `gorm:"column:item_type"`
|
||||
ItemName string `gorm:"column:item_name"`
|
||||
Price float64 `gorm:"column:price"`
|
||||
Quantity int `gorm:"column:quantity"`
|
||||
CreatedBy int64 `gorm:"column:created_by"`
|
||||
|
||||
53
internal/repository/models/partner_setting.go
Normal file
53
internal/repository/models/partner_setting.go
Normal file
@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type PartnerSettingsDB struct {
|
||||
PartnerID int64 `gorm:"primaryKey;column:partner_id"`
|
||||
TaxEnabled bool `gorm:"column:tax_enabled;default:false"`
|
||||
TaxPercentage float64 `gorm:"column:tax_percentage;default:10.00"`
|
||||
InvoicePrefix string `gorm:"column:invoice_prefix;default:INV"`
|
||||
BusinessHours string `gorm:"column:business_hours;type:json"` // JSON string
|
||||
LogoURL string `gorm:"column:logo_url"`
|
||||
ThemeColor string `gorm:"column:theme_color;default:#000000"`
|
||||
ReceiptFooterText string `gorm:"column:receipt_footer_text;type:text"`
|
||||
ReceiptHeaderText string `gorm:"column:receipt_header_text;type:text"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
func (PartnerSettingsDB) TableName() string {
|
||||
return "partner_settings"
|
||||
}
|
||||
|
||||
type PartnerPaymentMethodDB struct {
|
||||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||||
PartnerID int64 `gorm:"column:partner_id;index:idx_partner_payment"`
|
||||
PaymentMethod string `gorm:"column:payment_method;index:idx_partner_payment"`
|
||||
IsEnabled bool `gorm:"column:is_enabled;default:true"`
|
||||
DisplayName string `gorm:"column:display_name"`
|
||||
DisplayOrder int `gorm:"column:display_order;default:0"`
|
||||
AdditionalInfo string `gorm:"column:additional_info;type:json"` // JSON string
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
func (PartnerPaymentMethodDB) TableName() string {
|
||||
return "partner_payment_methods"
|
||||
}
|
||||
|
||||
type PartnerFeatureFlagDB struct {
|
||||
ID int64 `gorm:"primaryKey;column:id;autoIncrement"`
|
||||
PartnerID int64 `gorm:"column:partner_id;index:idx_partner_feature"`
|
||||
FeatureKey string `gorm:"column:feature_key;index:idx_partner_feature"`
|
||||
IsEnabled bool `gorm:"column:is_enabled;default:true"`
|
||||
Config string `gorm:"column:config;type:json"` // JSON string
|
||||
CreatedAt time.Time `gorm:"column:created_at;default:CURRENT_TIMESTAMP"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;default:CURRENT_TIMESTAMP"`
|
||||
}
|
||||
|
||||
func (PartnerFeatureFlagDB) TableName() string {
|
||||
return "partner_feature_flags"
|
||||
}
|
||||
@ -17,6 +17,26 @@ type OrderRepository 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
|
||||
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
|
||||
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
||||
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error)
|
||||
GetOrderPaymentMethodBreakdown(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
req entity.SearchRequest,
|
||||
) ([]entity.PaymentMethodBreakdown, error)
|
||||
GetRevenueOverview(
|
||||
ctx mycontext.Context,
|
||||
req entity.RevenueOverviewRequest,
|
||||
) ([]entity.RevenueOverviewItem, error)
|
||||
GetSalesByCategory(
|
||||
ctx mycontext.Context,
|
||||
req entity.SalesByCategoryRequest,
|
||||
) ([]entity.SalesByCategoryItem, error)
|
||||
GetPopularProducts(
|
||||
ctx mycontext.Context,
|
||||
req entity.PopularProductsRequest,
|
||||
) ([]entity.PopularProductItem, error)
|
||||
}
|
||||
|
||||
type orderRepository struct {
|
||||
@ -61,13 +81,13 @@ func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*e
|
||||
item.ID = itemDB.ID
|
||||
}
|
||||
|
||||
if order.InProgressOrderID != "" {
|
||||
if err := tx.Where("in_progress_order_id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil {
|
||||
if order.InProgressOrderID != 0 {
|
||||
if err := tx.Where("order_id = ?", order.InProgressOrderID).Delete(&models.OrderItemDB{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to delete in-progress order items")
|
||||
}
|
||||
|
||||
if err := tx.Where("id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderDB{}).Error; err != nil {
|
||||
if err := tx.Where("id = ?", order.InProgressOrderID).Delete(&models.OrderDB{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to delete in-progress order")
|
||||
}
|
||||
@ -109,6 +129,7 @@ func (r *orderRepository) CreateInquiry(ctx mycontext.Context, inquiry *entity.O
|
||||
InquiryID: inquiryDB.ID,
|
||||
ItemID: item.ItemID,
|
||||
ItemType: item.ItemType,
|
||||
ItemName: item.ItemName,
|
||||
Price: item.Price,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: item.CreatedBy,
|
||||
@ -162,6 +183,7 @@ func (r *orderRepository) FindInquiryByID(ctx mycontext.Context, id string) (*en
|
||||
orderItems = append(orderItems, entity.OrderItem{
|
||||
ItemID: itemDB.ItemID,
|
||||
ItemType: itemDB.ItemType,
|
||||
ItemName: itemDB.ItemName,
|
||||
Price: itemDB.Price,
|
||||
Quantity: itemDB.Quantity,
|
||||
CreatedBy: itemDB.CreatedBy,
|
||||
@ -202,13 +224,17 @@ func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
|
||||
InquiryID: order.InquiryID,
|
||||
Status: order.Status,
|
||||
Amount: order.Amount,
|
||||
Fee: order.Fee,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -220,7 +246,7 @@ func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Or
|
||||
InquiryID: dbModel.InquiryID,
|
||||
Status: dbModel.Status,
|
||||
Amount: dbModel.Amount,
|
||||
Fee: dbModel.Fee,
|
||||
Tax: dbModel.Tax,
|
||||
Total: dbModel.Total,
|
||||
PaymentType: dbModel.PaymentType,
|
||||
Source: dbModel.Source,
|
||||
@ -228,6 +254,10 @@ func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Or
|
||||
CreatedAt: dbModel.CreatedAt,
|
||||
UpdatedAt: dbModel.UpdatedAt,
|
||||
OrderItems: []entity.OrderItem{},
|
||||
CustomerName: dbModel.CustomerName,
|
||||
TableNumber: dbModel.TableNumber,
|
||||
OrderType: dbModel.OrderType,
|
||||
PaymentProvider: dbModel.PaymentProvider,
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,6 +267,7 @@ func (r *orderRepository) toOrderItemDBModel(item *entity.OrderItem) models.Orde
|
||||
OrderID: item.OrderID,
|
||||
ItemID: item.ItemID,
|
||||
ItemType: item.ItemType,
|
||||
ItemName: item.ItemName,
|
||||
Price: item.Price,
|
||||
Quantity: item.Quantity,
|
||||
CreatedBy: item.CreatedBy,
|
||||
@ -254,6 +285,7 @@ func (r *orderRepository) toDomainOrderItemModel(dbModel *models.OrderItemDB) *e
|
||||
Quantity: dbModel.Quantity,
|
||||
CreatedBy: dbModel.CreatedBy,
|
||||
CreatedAt: dbModel.CreatedAt,
|
||||
ItemName: dbModel.ItemName,
|
||||
}
|
||||
}
|
||||
|
||||
@ -264,7 +296,7 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo
|
||||
CustomerID: &inquiry.CustomerID,
|
||||
Status: inquiry.Status,
|
||||
Amount: inquiry.Amount,
|
||||
Fee: inquiry.Fee,
|
||||
Tax: inquiry.Tax,
|
||||
Total: inquiry.Total,
|
||||
PaymentType: inquiry.PaymentType,
|
||||
Source: inquiry.Source,
|
||||
@ -287,7 +319,7 @@ func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiry
|
||||
PartnerID: dbModel.PartnerID,
|
||||
Status: dbModel.Status,
|
||||
Amount: dbModel.Amount,
|
||||
Fee: dbModel.Fee,
|
||||
Tax: dbModel.Tax,
|
||||
Total: dbModel.Total,
|
||||
PaymentType: dbModel.PaymentType,
|
||||
Source: dbModel.Source,
|
||||
@ -295,6 +327,10 @@ func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiry
|
||||
CreatedAt: dbModel.CreatedAt,
|
||||
ExpiresAt: dbModel.ExpiresAt,
|
||||
OrderItems: []entity.OrderItem{},
|
||||
OrderType: dbModel.OrderType,
|
||||
CustomerName: dbModel.CustomerName,
|
||||
PaymentProvider: dbModel.PaymentProvider,
|
||||
TableNumber: dbModel.TableNumber,
|
||||
}
|
||||
|
||||
if dbModel.CustomerID != nil {
|
||||
@ -305,3 +341,486 @@ func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiry
|
||||
|
||||
return inquiry
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error) {
|
||||
var ordersDB []models.OrderDB
|
||||
var totalCount int64
|
||||
|
||||
// Build the base query
|
||||
baseQuery := r.db.Model(&models.OrderDB{}).Where("partner_id = ?", partnerID)
|
||||
|
||||
// Apply filters to the base query
|
||||
if req.Status != "" {
|
||||
baseQuery = baseQuery.Where("status = ?", req.Status)
|
||||
}
|
||||
|
||||
if !req.Start.IsZero() {
|
||||
baseQuery = baseQuery.Where("created_at >= ?", req.Start)
|
||||
}
|
||||
|
||||
if !req.End.IsZero() {
|
||||
baseQuery = baseQuery.Where("created_at <= ?", req.End)
|
||||
}
|
||||
|
||||
// Get total count with the current filters before pagination
|
||||
if err := baseQuery.Count(&totalCount).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to count total orders")
|
||||
}
|
||||
|
||||
// Clone the query for fetching the actual data with pagination
|
||||
query := baseQuery.Session(&gorm.Session{})
|
||||
|
||||
// Add ordering and pagination
|
||||
query = query.Order("created_at DESC")
|
||||
|
||||
if req.Limit > 0 {
|
||||
query = query.Limit(req.Limit)
|
||||
}
|
||||
|
||||
if req.Offset > 0 {
|
||||
query = query.Offset(req.Offset)
|
||||
}
|
||||
|
||||
// Execute the query with preloading
|
||||
if err := query.Preload("OrderItems").Find(&ordersDB).Error; err != nil {
|
||||
return nil, 0, errors.Wrap(err, "failed to find order history by partner ID")
|
||||
}
|
||||
|
||||
// Map to domain models
|
||||
orders := make([]*entity.Order, 0, len(ordersDB))
|
||||
for _, orderDB := range ordersDB {
|
||||
order := r.toDomainOrderModel(&orderDB)
|
||||
order.OrderItems = make([]entity.OrderItem, 0, len(orderDB.OrderItems))
|
||||
|
||||
for _, itemDB := range orderDB.OrderItems {
|
||||
item := r.toDomainOrderItemModel(&itemDB)
|
||||
order.OrderItems = append(order.OrderItems, *item)
|
||||
}
|
||||
|
||||
orders = append(orders, order)
|
||||
}
|
||||
|
||||
return orders, totalCount, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error) {
|
||||
isUpdate := order.ID != 0
|
||||
|
||||
tx := r.db.Begin()
|
||||
if tx.Error != nil {
|
||||
return nil, errors.Wrap(tx.Error, "failed to begin transaction")
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
orderDB := r.toInProgressOrderDBModel(order)
|
||||
|
||||
if isUpdate {
|
||||
var existingOrder models.OrderDB
|
||||
if err := tx.First(&existingOrder, order.ID).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "order not found for update")
|
||||
}
|
||||
|
||||
if err := tx.Model(&orderDB).Updates(orderDB).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to update order")
|
||||
}
|
||||
|
||||
if err := tx.Where("order_id = ?", order.ID).Delete(&models.OrderItemDB{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to delete existing order items")
|
||||
}
|
||||
} else {
|
||||
if err := tx.Create(&orderDB).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to insert order")
|
||||
}
|
||||
|
||||
order.ID = orderDB.ID
|
||||
}
|
||||
|
||||
var itemIDs []int64
|
||||
for i := range order.OrderItems {
|
||||
itemIDs = append(itemIDs, order.OrderItems[i].ItemID)
|
||||
}
|
||||
|
||||
var products []models.ProductDB
|
||||
if len(itemIDs) > 0 {
|
||||
if err := tx.Where("id IN ?", itemIDs).Find(&products).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to fetch products")
|
||||
}
|
||||
}
|
||||
|
||||
productMap := make(map[int64]models.ProductDB)
|
||||
for _, product := range products {
|
||||
productMap[product.ID] = product
|
||||
}
|
||||
|
||||
for i := range order.OrderItems {
|
||||
item := &order.OrderItems[i]
|
||||
item.OrderID = orderDB.ID
|
||||
itemDB := r.toOrderItemDBModel(item)
|
||||
|
||||
if err := tx.Create(&itemDB).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return nil, errors.Wrap(err, "failed to insert order item")
|
||||
}
|
||||
|
||||
item.ID = itemDB.ID
|
||||
|
||||
if product, exists := productMap[item.ItemID]; exists {
|
||||
item.Product = r.toDomainProductModel(&product)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) toInProgressOrderDBModel(order *entity.Order) models.OrderDB {
|
||||
now := time.Now()
|
||||
|
||||
return models.OrderDB{
|
||||
ID: order.ID,
|
||||
PartnerID: order.PartnerID,
|
||||
CustomerID: order.CustomerID,
|
||||
CustomerName: order.CustomerName,
|
||||
PaymentType: order.PaymentType,
|
||||
PaymentProvider: order.PaymentProvider,
|
||||
CreatedBy: order.CreatedBy,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
TableNumber: order.TableNumber,
|
||||
OrderType: order.OrderType,
|
||||
Status: order.Status,
|
||||
Amount: order.Amount,
|
||||
Total: order.Total,
|
||||
Tax: order.Tax,
|
||||
Source: order.Source,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *orderRepository) toDomainProductModel(productDB *models.ProductDB) *entity.Product {
|
||||
if productDB == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &entity.Product{
|
||||
ID: productDB.ID,
|
||||
Name: productDB.Name,
|
||||
Description: productDB.Description,
|
||||
Price: productDB.Price,
|
||||
CreatedAt: productDB.CreatedAt,
|
||||
UpdatedAt: productDB.UpdatedAt,
|
||||
Type: productDB.Type,
|
||||
Image: productDB.Image,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error) {
|
||||
var ordersDB []models.OrderDB
|
||||
query := r.db.Where("partner_id = ?", partnerID)
|
||||
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
|
||||
query = query.Order("created_at DESC")
|
||||
|
||||
if limit > 0 {
|
||||
query = query.Limit(limit)
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
query = query.Offset(offset)
|
||||
}
|
||||
|
||||
if err := query.Find(&ordersDB).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find orders by partner ID")
|
||||
}
|
||||
|
||||
orders := make([]*entity.Order, 0, len(ordersDB))
|
||||
for _, orderDB := range ordersDB {
|
||||
order := r.toDomainOrderModel(&orderDB)
|
||||
|
||||
var orderItems []models.OrderItemDB
|
||||
if err := r.db.Where("order_id = ?", orderDB.ID).Find(&orderItems).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to find order items")
|
||||
}
|
||||
|
||||
order.OrderItems = make([]entity.OrderItem, 0, len(orderItems))
|
||||
|
||||
for _, itemDB := range orderItems {
|
||||
item := r.toDomainOrderItemModel(&itemDB)
|
||||
|
||||
orderItem := entity.OrderItem{
|
||||
ID: item.ID,
|
||||
ItemID: item.ItemID,
|
||||
Quantity: item.Quantity,
|
||||
ItemName: item.ItemName,
|
||||
}
|
||||
|
||||
if itemDB.ItemID > 0 {
|
||||
var product models.ProductDB
|
||||
err := r.db.First(&product, itemDB.ItemID).Error
|
||||
|
||||
if err == nil {
|
||||
productDomain := r.toDomainProductModel(&product)
|
||||
orderItem.Product = productDomain
|
||||
}
|
||||
}
|
||||
|
||||
order.OrderItems = append(order.OrderItems, orderItem)
|
||||
}
|
||||
|
||||
orders = append(orders, order)
|
||||
}
|
||||
|
||||
return orders, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetOrderPaymentMethodBreakdown(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
req entity.SearchRequest,
|
||||
) ([]entity.PaymentMethodBreakdown, error) {
|
||||
var breakdown []entity.PaymentMethodBreakdown
|
||||
|
||||
baseQuery := r.db.Model(&models.OrderDB{}).Where("partner_id = ?", partnerID)
|
||||
|
||||
if !req.Start.IsZero() {
|
||||
baseQuery = baseQuery.Where("created_at >= ?", req.Start)
|
||||
}
|
||||
|
||||
if !req.End.IsZero() {
|
||||
baseQuery = baseQuery.Where("created_at <= ?", req.End)
|
||||
}
|
||||
|
||||
if req.Status != "" {
|
||||
baseQuery = baseQuery.Where("status = ?", req.Status)
|
||||
}
|
||||
|
||||
err := baseQuery.Select(
|
||||
"payment_type, " +
|
||||
"payment_provider, " +
|
||||
"COUNT(*) as total_transactions, " +
|
||||
"SUM(total) as total_amount",
|
||||
).Group(
|
||||
"payment_type, payment_provider",
|
||||
).Order("total_amount DESC").Scan(&breakdown).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get payment method breakdown")
|
||||
}
|
||||
|
||||
return breakdown, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetRevenueOverview(
|
||||
ctx mycontext.Context,
|
||||
req entity.RevenueOverviewRequest,
|
||||
) ([]entity.RevenueOverviewItem, error) {
|
||||
var overview []entity.RevenueOverviewItem
|
||||
|
||||
baseQuery := r.db.Model(&models.OrderDB{}).
|
||||
Where("partner_id = ?", req.PartnerID).
|
||||
Where("EXTRACT(YEAR FROM created_at) = ?", req.Year)
|
||||
|
||||
if req.Status != "" {
|
||||
baseQuery = baseQuery.Where("status = ?", req.Status)
|
||||
}
|
||||
|
||||
switch req.Granularity {
|
||||
case "m": // Monthly
|
||||
err := baseQuery.Select(
|
||||
"TO_CHAR(created_at, 'YYYY-MM') as period, " +
|
||||
"SUM(total) as total_amount, " +
|
||||
"COUNT(*) as order_count",
|
||||
).Group("period").
|
||||
Order("period").
|
||||
Scan(&overview).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get monthly revenue overview")
|
||||
}
|
||||
|
||||
case "w": // Weekly
|
||||
err := baseQuery.Select(
|
||||
"CONCAT(EXTRACT(YEAR FROM created_at), '-W', " +
|
||||
"LPAD(EXTRACT(WEEK FROM created_at)::text, 2, '0')) as period, " +
|
||||
"SUM(total) as total_amount, " +
|
||||
"COUNT(*) as order_count",
|
||||
).Group("period").
|
||||
Order("period").
|
||||
Scan(&overview).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get weekly revenue overview")
|
||||
}
|
||||
|
||||
case "d": // Daily
|
||||
err := baseQuery.Select(
|
||||
"TO_CHAR(created_at, 'YYYY-MM-DD') as period, " +
|
||||
"SUM(total) as total_amount, " +
|
||||
"COUNT(*) as order_count",
|
||||
).Group("period").
|
||||
Order("period").
|
||||
Scan(&overview).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get daily revenue overview")
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errors.New("invalid granularity. Use 'm' (monthly), 'w' (weekly), or 'd' (daily)")
|
||||
}
|
||||
|
||||
return overview, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetSalesByCategory(
|
||||
ctx mycontext.Context,
|
||||
req entity.SalesByCategoryRequest,
|
||||
) ([]entity.SalesByCategoryItem, error) {
|
||||
var salesByCategory []entity.SalesByCategoryItem
|
||||
|
||||
baseQuery := r.db.Model(&models.OrderItemDB{}).
|
||||
Joins("JOIN orders ON order_items.order_id = orders.id").
|
||||
Where("orders.partner_id = ?", req.PartnerID)
|
||||
|
||||
if req.Status != "" {
|
||||
baseQuery = baseQuery.Where("orders.status = ?", req.Status)
|
||||
}
|
||||
|
||||
switch req.Period {
|
||||
case "d": // Daily
|
||||
baseQuery = baseQuery.Where("DATE(orders.created_at) = CURRENT_DATE")
|
||||
case "w": // Weekly
|
||||
baseQuery = baseQuery.Where("DATE_TRUNC('week', orders.created_at) = DATE_TRUNC('week', CURRENT_DATE)")
|
||||
case "m": // Monthly
|
||||
baseQuery = baseQuery.Where("DATE_TRUNC('month', orders.created_at) = DATE_TRUNC('month', CURRENT_DATE)")
|
||||
default:
|
||||
return nil, errors.New("invalid period. Use 'd' (daily), 'w' (weekly), or 'm' (monthly)")
|
||||
}
|
||||
|
||||
var totalSales float64
|
||||
err := r.db.Model(&models.OrderItemDB{}).
|
||||
Joins("JOIN orders ON order_items.order_id = orders.id").
|
||||
Where("orders.partner_id = ?", req.PartnerID).
|
||||
Select("COALESCE(SUM(order_items.price * order_items.quantity), 0)").
|
||||
Scan(&totalSales).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to calculate total sales")
|
||||
}
|
||||
|
||||
err = baseQuery.Select(
|
||||
"order_items.item_type AS category, " +
|
||||
"COALESCE(SUM(order_items.price * order_items.quantity), 0) AS total_amount, " +
|
||||
"COALESCE(SUM(order_items.quantity), 0) AS total_quantity",
|
||||
).
|
||||
Group("order_items.item_type").
|
||||
Order("total_amount DESC").
|
||||
Scan(&salesByCategory).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get sales by category")
|
||||
}
|
||||
|
||||
for i := range salesByCategory {
|
||||
if totalSales > 0 {
|
||||
salesByCategory[i].Percentage =
|
||||
(salesByCategory[i].TotalAmount / totalSales) * 100
|
||||
}
|
||||
}
|
||||
|
||||
return salesByCategory, nil
|
||||
}
|
||||
|
||||
func (r *orderRepository) GetPopularProducts(
|
||||
ctx mycontext.Context,
|
||||
req entity.PopularProductsRequest,
|
||||
) ([]entity.PopularProductItem, error) {
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 10
|
||||
}
|
||||
|
||||
if req.SortBy != "sales" && req.SortBy != "revenue" {
|
||||
req.SortBy = "sales" // default to sales
|
||||
}
|
||||
|
||||
// Base query
|
||||
baseQuery := r.db.Model(&models.OrderItemDB{}).
|
||||
Joins("JOIN orders ON order_items.order_id = orders.id").
|
||||
Where("orders.partner_id = ?", req.PartnerID)
|
||||
|
||||
if req.Status != "" {
|
||||
baseQuery = baseQuery.Where("orders.status = ?", req.Status)
|
||||
}
|
||||
|
||||
switch req.Period {
|
||||
case "d": // Daily
|
||||
baseQuery = baseQuery.Where("DATE(orders.created_at) = CURRENT_DATE")
|
||||
case "w": // Weekly
|
||||
baseQuery = baseQuery.Where("DATE_TRUNC('week', orders.created_at) = DATE_TRUNC('week', CURRENT_DATE)")
|
||||
case "m": // Monthly
|
||||
baseQuery = baseQuery.Where("DATE_TRUNC('month', orders.created_at) = DATE_TRUNC('month', CURRENT_DATE)")
|
||||
default:
|
||||
return nil, errors.New("invalid period. Use 'd' (daily), 'w' (weekly), or 'm' (monthly)")
|
||||
}
|
||||
|
||||
// Calculate total sales/revenue for percentage calculation
|
||||
var totalSales struct {
|
||||
TotalAmount float64
|
||||
TotalQuantity int64
|
||||
}
|
||||
|
||||
err := baseQuery.
|
||||
Select("COALESCE(SUM(order_items.price * order_items.quantity), 0) as total_amount, " +
|
||||
"COALESCE(SUM(order_items.quantity), 0) as total_quantity").
|
||||
Scan(&totalSales).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to calculate total sales")
|
||||
}
|
||||
|
||||
// Prepare the query for popular products
|
||||
var popularProducts []entity.PopularProductItem
|
||||
orderClause := "total_sales DESC"
|
||||
if req.SortBy == "revenue" {
|
||||
orderClause = "total_revenue DESC"
|
||||
}
|
||||
|
||||
err = baseQuery.
|
||||
Select(
|
||||
"order_items.item_id AS product_id, " +
|
||||
"order_items.item_name AS product_name, " +
|
||||
"order_items.item_type AS category, " +
|
||||
"COALESCE(SUM(order_items.quantity), 0) AS total_sales, " +
|
||||
"COALESCE(SUM(order_items.price * order_items.quantity), 0) AS total_revenue, " +
|
||||
"COALESCE(AVG(order_items.price), 0) AS average_price",
|
||||
).
|
||||
Group("order_items.item_id, order_items.item_name, order_items.item_type").
|
||||
Order(orderClause).
|
||||
Limit(req.Limit).
|
||||
Scan(&popularProducts).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get popular products")
|
||||
}
|
||||
|
||||
for i := range popularProducts {
|
||||
popularProducts[i].Percentage =
|
||||
(float64(popularProducts[i].TotalSales) / float64(totalSales.TotalQuantity)) * 100
|
||||
}
|
||||
|
||||
return popularProducts, nil
|
||||
}
|
||||
|
||||
225
internal/repository/partner_settings.go
Normal file
225
internal/repository/partner_settings.go
Normal file
@ -0,0 +1,225 @@
|
||||
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 PartnerSettingsRepository interface {
|
||||
GetByPartnerID(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error)
|
||||
Upsert(ctx mycontext.Context, settings *entity.PartnerSettings) error
|
||||
GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error)
|
||||
UpsertPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error
|
||||
DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error
|
||||
UpdatePaymentMethodOrder(ctx mycontext.Context, partnerID int64, methodIDs []int64) error
|
||||
}
|
||||
|
||||
type partnerSettingsRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewPartnerSettingsRepository(db *gorm.DB) PartnerSettingsRepository {
|
||||
return &partnerSettingsRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) GetByPartnerID(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error) {
|
||||
var settingsDB models.PartnerSettingsDB
|
||||
|
||||
err := r.db.Where("partner_id = ?", partnerID).First(&settingsDB).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return &entity.PartnerSettings{
|
||||
PartnerID: partnerID,
|
||||
TaxEnabled: true,
|
||||
TaxPercentage: 10.0,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "failed to get partner settings")
|
||||
}
|
||||
|
||||
return r.toDomainModel(&settingsDB), nil
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) Upsert(ctx mycontext.Context, settings *entity.PartnerSettings) error {
|
||||
settingsDB := r.toDBModel(settings)
|
||||
settingsDB.UpdatedAt = time.Now()
|
||||
|
||||
// Check if record exists
|
||||
var count int64
|
||||
if err := r.db.Model(&models.PartnerSettingsDB{}).Where("partner_id = ?", settings.PartnerID).Count(&count).Error; err != nil {
|
||||
return errors.Wrap(err, "failed to check partner settings existence")
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
// Update existing record
|
||||
if err := r.db.Model(&models.PartnerSettingsDB{}).Where("partner_id = ?", settings.PartnerID).Updates(settingsDB).Error; err != nil {
|
||||
return errors.Wrap(err, "failed to update partner settings")
|
||||
}
|
||||
} else {
|
||||
// Create new record
|
||||
settingsDB.CreatedAt = time.Now()
|
||||
if err := r.db.Create(&settingsDB).Error; err != nil {
|
||||
return errors.Wrap(err, "failed to create partner settings")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error) {
|
||||
var methodsDB []models.PartnerPaymentMethodDB
|
||||
|
||||
if err := r.db.Where("partner_id = ?", partnerID).Order("display_order").Find(&methodsDB).Error; err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get partner payment methods")
|
||||
}
|
||||
|
||||
methods := make([]entity.PartnerPaymentMethod, len(methodsDB))
|
||||
for i, methodDB := range methodsDB {
|
||||
methods[i] = *r.toDomainPaymentMethodModel(&methodDB)
|
||||
}
|
||||
|
||||
return methods, nil
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) UpsertPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error {
|
||||
methodDB := r.toDBPaymentMethodModel(method)
|
||||
methodDB.UpdatedAt = time.Now()
|
||||
|
||||
tx := r.db.Begin()
|
||||
if tx.Error != nil {
|
||||
return errors.Wrap(tx.Error, "failed to begin transaction")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
if method.ID > 0 {
|
||||
// Update existing record
|
||||
if err := tx.Model(&models.PartnerPaymentMethodDB{}).Where("id = ? AND partner_id = ?", method.ID, method.PartnerID).Updates(methodDB).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to update payment method")
|
||||
}
|
||||
} else {
|
||||
// Get the next display order if not specified
|
||||
if method.DisplayOrder == 0 {
|
||||
var maxOrder int
|
||||
if err := tx.Model(&models.PartnerPaymentMethodDB{}).Where("partner_id = ?", method.PartnerID).Select("COALESCE(MAX(display_order), 0)").Row().Scan(&maxOrder); err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to get max display order")
|
||||
}
|
||||
methodDB.DisplayOrder = maxOrder + 1
|
||||
}
|
||||
|
||||
// Create new record
|
||||
methodDB.CreatedAt = time.Now()
|
||||
if err := tx.Create(&methodDB).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to create payment method")
|
||||
}
|
||||
|
||||
method.ID = methodDB.ID
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error {
|
||||
result := r.db.Where("id = ? AND partner_id = ?", id, partnerID).Delete(&models.PartnerPaymentMethodDB{})
|
||||
|
||||
if result.Error != nil {
|
||||
return errors.Wrap(result.Error, "failed to delete payment method")
|
||||
}
|
||||
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.New("payment method not found or not authorized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) UpdatePaymentMethodOrder(ctx mycontext.Context, partnerID int64, methodIDs []int64) error {
|
||||
tx := r.db.Begin()
|
||||
if tx.Error != nil {
|
||||
return errors.Wrap(tx.Error, "failed to begin transaction")
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
for i, id := range methodIDs {
|
||||
if err := tx.Model(&models.PartnerPaymentMethodDB{}).
|
||||
Where("id = ? AND partner_id = ?", id, partnerID).
|
||||
Update("display_order", i+1).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return errors.Wrap(err, "failed to update payment method order")
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) toDomainModel(dbModel *models.PartnerSettingsDB) *entity.PartnerSettings {
|
||||
return &entity.PartnerSettings{
|
||||
PartnerID: dbModel.PartnerID,
|
||||
TaxEnabled: dbModel.TaxEnabled,
|
||||
TaxPercentage: dbModel.TaxPercentage,
|
||||
InvoicePrefix: dbModel.InvoicePrefix,
|
||||
BusinessHours: dbModel.BusinessHours,
|
||||
LogoURL: dbModel.LogoURL,
|
||||
ThemeColor: dbModel.ThemeColor,
|
||||
ReceiptFooterText: dbModel.ReceiptFooterText,
|
||||
ReceiptHeaderText: dbModel.ReceiptHeaderText,
|
||||
CreatedAt: dbModel.CreatedAt,
|
||||
UpdatedAt: dbModel.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) toDBModel(domainModel *entity.PartnerSettings) models.PartnerSettingsDB {
|
||||
return models.PartnerSettingsDB{
|
||||
PartnerID: domainModel.PartnerID,
|
||||
TaxEnabled: domainModel.TaxEnabled,
|
||||
TaxPercentage: domainModel.TaxPercentage,
|
||||
InvoicePrefix: domainModel.InvoicePrefix,
|
||||
BusinessHours: domainModel.BusinessHours,
|
||||
LogoURL: domainModel.LogoURL,
|
||||
ThemeColor: domainModel.ThemeColor,
|
||||
ReceiptFooterText: domainModel.ReceiptFooterText,
|
||||
ReceiptHeaderText: domainModel.ReceiptHeaderText,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) toDomainPaymentMethodModel(dbModel *models.PartnerPaymentMethodDB) *entity.PartnerPaymentMethod {
|
||||
return &entity.PartnerPaymentMethod{
|
||||
ID: dbModel.ID,
|
||||
PartnerID: dbModel.PartnerID,
|
||||
PaymentMethod: dbModel.PaymentMethod,
|
||||
IsEnabled: dbModel.IsEnabled,
|
||||
DisplayName: dbModel.DisplayName,
|
||||
DisplayOrder: dbModel.DisplayOrder,
|
||||
AdditionalInfo: dbModel.AdditionalInfo,
|
||||
CreatedAt: dbModel.CreatedAt,
|
||||
UpdatedAt: dbModel.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *partnerSettingsRepository) toDBPaymentMethodModel(domainModel *entity.PartnerPaymentMethod) models.PartnerPaymentMethodDB {
|
||||
return models.PartnerPaymentMethodDB{
|
||||
ID: domainModel.ID,
|
||||
PartnerID: domainModel.PartnerID,
|
||||
PaymentMethod: domainModel.PaymentMethod,
|
||||
IsEnabled: domainModel.IsEnabled,
|
||||
DisplayName: domainModel.DisplayName,
|
||||
DisplayOrder: domainModel.DisplayOrder,
|
||||
AdditionalInfo: domainModel.AdditionalInfo,
|
||||
}
|
||||
}
|
||||
@ -58,6 +58,7 @@ type RepoManagerImpl struct {
|
||||
ProductRepo ProductRepository
|
||||
TransactionRepo TransactionRepo
|
||||
MemberRepository MemberRepository
|
||||
PartnerSetting PartnerSettingsRepository
|
||||
}
|
||||
|
||||
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
@ -88,6 +89,7 @@ func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
||||
TransactionRepo: NewTransactionRepository(db),
|
||||
MemberRepository: NewMemberRepository(db),
|
||||
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
||||
PartnerSetting: NewPartnerSettingsRepository(db),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ package routes
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/handlers/http/customerauth"
|
||||
"enaklo-pos-be/internal/handlers/http/customerorder"
|
||||
"enaklo-pos-be/internal/handlers/http/discovery"
|
||||
"enaklo-pos-be/internal/middlewares"
|
||||
|
||||
@ -20,7 +19,6 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
|
||||
serverRoutes := []HTTPHandlerRoutes{
|
||||
discovery.NewHandler(serviceManager.DiscoverService),
|
||||
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc),
|
||||
customerorder.NewHandler(serviceManager.OrderSvc),
|
||||
}
|
||||
|
||||
for _, handler := range serverRoutes {
|
||||
|
||||
@ -4,9 +4,6 @@ import (
|
||||
http2 "enaklo-pos-be/internal/handlers/http"
|
||||
"enaklo-pos-be/internal/handlers/http/balance"
|
||||
"enaklo-pos-be/internal/handlers/http/license"
|
||||
linkqu "enaklo-pos-be/internal/handlers/http/linqu"
|
||||
mdtrns "enaklo-pos-be/internal/handlers/http/midtrans"
|
||||
"enaklo-pos-be/internal/handlers/http/order"
|
||||
"enaklo-pos-be/internal/handlers/http/oss"
|
||||
"enaklo-pos-be/internal/handlers/http/partner"
|
||||
"enaklo-pos-be/internal/handlers/http/product"
|
||||
@ -54,15 +51,12 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
|
||||
user.NewHandler(serviceManager.UserSvc),
|
||||
studio.NewStudioHandler(serviceManager.StudioSvc),
|
||||
product.NewHandler(serviceManager.ProductSvc),
|
||||
order.NewHandler(serviceManager.OrderSvc),
|
||||
oss.NewOssHandler(serviceManager.OSSSvc),
|
||||
partner.NewHandler(serviceManager.PartnerSvc),
|
||||
site.NewHandler(serviceManager.SiteSvc),
|
||||
mdtrns.NewHandler(serviceManager.OrderSvc),
|
||||
license.NewHandler(serviceManager.LicenseSvc),
|
||||
transaction.New(serviceManager.Transaction),
|
||||
balance.NewHandler(serviceManager.Balance),
|
||||
linkqu.NewHandler(serviceManager.OrderSvc),
|
||||
}
|
||||
|
||||
for _, handler := range serverRoutes {
|
||||
|
||||
@ -1,623 +0,0 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
errors2 "enaklo-pos-be/internal/common/errors"
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
order2 "enaklo-pos-be/internal/constants/order"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/repository"
|
||||
"enaklo-pos-be/internal/utils/generator"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/context"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config interface {
|
||||
GetOrderFee(source string) float64
|
||||
}
|
||||
|
||||
type OrderService struct {
|
||||
repo repository.Order
|
||||
crypt repository.Crypto
|
||||
product repository.Product
|
||||
pg repository.PaymentGateway
|
||||
payment repository.Payment
|
||||
transaction repository.TransactionRepository
|
||||
txmanager repository.TransactionManager
|
||||
wallet repository.WalletRepository
|
||||
linkquRepo repository.LinkQu
|
||||
cfg Config
|
||||
}
|
||||
|
||||
func NewOrderService(
|
||||
repo repository.Order,
|
||||
product repository.Product, crypt repository.Crypto,
|
||||
pg repository.PaymentGateway, payment repository.Payment,
|
||||
txmanager repository.TransactionManager,
|
||||
wallet repository.WalletRepository, cfg Config,
|
||||
transaction repository.TransactionRepository,
|
||||
linkquRepo repository.LinkQu,
|
||||
) *OrderService {
|
||||
return &OrderService{
|
||||
repo: repo,
|
||||
product: product,
|
||||
crypt: crypt,
|
||||
pg: pg,
|
||||
payment: payment,
|
||||
txmanager: txmanager,
|
||||
wallet: wallet,
|
||||
cfg: cfg,
|
||||
transaction: transaction,
|
||||
linkquRepo: linkquRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OrderService) CreateOrder(ctx mycontext.Context, req *entity.OrderRequest) (*entity.OrderResponse, error) {
|
||||
productIDs, filteredItems := s.filterOrderItems(req.OrderItems)
|
||||
if len(productIDs) == 0 {
|
||||
return nil, errors2.ErrorBadRequest
|
||||
}
|
||||
req.OrderItems = filteredItems
|
||||
|
||||
if len(productIDs) < 1 {
|
||||
return nil, errors2.ErrorBadRequest
|
||||
}
|
||||
|
||||
products, err := s.product.GetProductsByIDs(ctx, productIDs, req.PartnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting products by IDs", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
productMap := make(map[int64]*entity.ProductDB)
|
||||
for _, product := range products {
|
||||
productMap[product.ID] = product
|
||||
}
|
||||
|
||||
totalAmount := 0.0
|
||||
for _, item := range req.OrderItems {
|
||||
product, ok := productMap[item.ProductID]
|
||||
if !ok {
|
||||
logger.ContextLogger(ctx).Error("product not found", zap.Int64("productID", item.ProductID))
|
||||
return nil, errors.New("product not found")
|
||||
}
|
||||
totalAmount += product.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
order := &entity.Order{
|
||||
PartnerID: req.PartnerID,
|
||||
Status: order2.New.String(),
|
||||
Amount: totalAmount,
|
||||
Total: totalAmount + s.cfg.GetOrderFee(req.Source),
|
||||
Fee: s.cfg.GetOrderFee(req.Source),
|
||||
PaymentType: req.PaymentMethod,
|
||||
CreatedBy: req.CreatedBy,
|
||||
OrderItems: []entity.OrderItem{},
|
||||
Source: req.Source,
|
||||
}
|
||||
|
||||
for _, item := range req.OrderItems {
|
||||
order.OrderItems = append(order.OrderItems, entity.OrderItem{
|
||||
ItemID: item.ProductID,
|
||||
ItemType: productMap[item.ProductID].Type,
|
||||
Price: productMap[item.ProductID].Price,
|
||||
Quantity: int(item.Quantity),
|
||||
CreatedBy: req.CreatedBy,
|
||||
Product: productMap[item.ProductID].ToProduct(),
|
||||
})
|
||||
}
|
||||
|
||||
order, err = s.repo.Create(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order, err = s.repo.FindByID(ctx, order.ID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.OrderResponse{
|
||||
Order: order,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) filterOrderItems(items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest) {
|
||||
var productIDs []int64
|
||||
var filteredItems []entity.OrderItemRequest
|
||||
for _, item := range items {
|
||||
if item.Quantity != 0 {
|
||||
productIDs = append(productIDs, item.ProductID)
|
||||
filteredItems = append(filteredItems, item)
|
||||
}
|
||||
}
|
||||
return productIDs, filteredItems
|
||||
}
|
||||
|
||||
func (s *OrderService) CheckInInquiry(ctx mycontext.Context, qrCode string, partnerID *int64) (*entity.CheckinResponse, error) {
|
||||
order, err := s.repo.FindByQRCode(ctx, qrCode)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors2.NewErrorMessage(errors2.ErrorInvalidRequest, "Not Valid QR Code")
|
||||
}
|
||||
|
||||
logger.ContextLogger(ctx).Error("error when getting order by QR code", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if order.PartnerID != *partnerID {
|
||||
return nil, errors2.ErrorBadRequest
|
||||
}
|
||||
|
||||
if order.Status != "PAID" {
|
||||
return nil, errors2.ErrorInvalidRequest
|
||||
}
|
||||
|
||||
token, err := s.crypt.GenerateJWTOrder(order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when generate checkin token", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderResponse := &entity.CheckinResponse{
|
||||
Token: token,
|
||||
}
|
||||
|
||||
return orderResponse, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) CheckInExecute(ctx mycontext.Context,
|
||||
token string, partnerID *int64) (*entity.CheckinExecute, error) {
|
||||
pID, orderID, err := s.crypt.ValidateJWTOrder(token)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when validating JWT order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pID != *partnerID {
|
||||
return nil, errors2.ErrorBadRequest
|
||||
}
|
||||
|
||||
order, err := s.repo.FindByID(ctx, orderID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting order by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &entity.CheckinExecute{
|
||||
Order: order,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) Execute(ctx mycontext.Context, req *entity.OrderExecuteRequest) (*entity.ExecuteOrderResponse, error) {
|
||||
partnerID, orderID, err := s.crypt.ValidateJWTOrder(req.Token)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when validating JWT order", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
order, err := s.repo.FindByID(ctx, orderID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.ContextLogger(ctx).Error("order not found", zap.Int64("orderID", orderID))
|
||||
return nil, errors.New("order not found")
|
||||
}
|
||||
logger.ContextLogger(ctx).Error("error when finding order by ID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payment, err := s.payment.FindByOrderAndPartnerID(ctx, orderID, partnerID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
logger.ContextLogger(ctx).Error("error getting payment data from db", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if payment != nil {
|
||||
return s.createExecuteOrderResponse(order, payment), nil
|
||||
}
|
||||
|
||||
if order.PartnerID != partnerID {
|
||||
logger.ContextLogger(ctx).Error("partner ID mismatch", zap.Int64("orderID", orderID), zap.Int64("tokenPartnerID", partnerID), zap.Int64("orderPartnerID", order.PartnerID))
|
||||
return nil, errors.New("partner ID mismatch")
|
||||
}
|
||||
|
||||
if order.Status != "NEW" {
|
||||
return nil, errors.New("invalid state")
|
||||
}
|
||||
|
||||
resp := &entity.ExecuteOrderResponse{
|
||||
Order: order,
|
||||
}
|
||||
|
||||
order.SetExecutePaymentStatus()
|
||||
order, err = s.repo.Update(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when updating order status", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) createExecuteOrderResponse(order *entity.Order, payment *entity.Payment) *entity.ExecuteOrderResponse {
|
||||
var metadata map[string]string
|
||||
if err := json.Unmarshal(payment.RequestMetadata, &metadata); err != nil {
|
||||
logger.ContextLogger(context.Background()).Error("error unmarshaling request metadata", zap.Error(err))
|
||||
return &entity.ExecuteOrderResponse{
|
||||
Order: order,
|
||||
}
|
||||
}
|
||||
return &entity.ExecuteOrderResponse{
|
||||
Order: order,
|
||||
PaymentToken: metadata["payment_token"],
|
||||
RedirectURL: metadata["payment_redirect_url"],
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OrderService) processNonCashPayment(ctx context.Context, order *entity.Order, partnerID, createdBy int64) (*entity.MidtransResponse, error) {
|
||||
paymentRequest := entity.PaymentRequest{
|
||||
PaymentReferenceID: generator.GenerateUUIDV4(),
|
||||
TotalAmount: int64(order.Total),
|
||||
//OrderItems: order.OrderItems,
|
||||
Provider: order.PaymentType,
|
||||
}
|
||||
|
||||
paymentResponse, err := s.pg.CreatePayment(paymentRequest)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requestMetadata, err := json.Marshal(map[string]string{
|
||||
"partner_id": strconv.FormatInt(partnerID, 10),
|
||||
"created_by": strconv.FormatInt(createdBy, 10),
|
||||
"payment_token": paymentResponse.Token,
|
||||
"payment_redirect_url": paymentResponse.RedirectURL,
|
||||
})
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when marshaling request metadata", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payment := &entity.Payment{
|
||||
PartnerID: partnerID,
|
||||
OrderID: order.ID,
|
||||
ReferenceID: paymentRequest.PaymentReferenceID,
|
||||
Channel: "MIDTRANS",
|
||||
PaymentType: order.PaymentType,
|
||||
Amount: order.Amount,
|
||||
State: "PENDING",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
|
||||
_, err = s.payment.Create(ctx, payment)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment record", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &entity.MidtransResponse{
|
||||
Token: paymentResponse.Token,
|
||||
RedirectURL: paymentResponse.RedirectURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) processQRPayment(ctx mycontext.Context, order *entity.Order, partnerID, createdBy int64) (*entity.PaymentResponse, error) {
|
||||
paymentRequest := entity.PaymentRequest{
|
||||
PaymentReferenceID: generator.GenerateUUIDV4(),
|
||||
TotalAmount: int64(order.Total),
|
||||
Provider: "LINKQU",
|
||||
CustomerID: fmt.Sprintf("POS-%d", ctx.RequestedBy()),
|
||||
CustomerName: fmt.Sprintf("POS-%s", ctx.GetName()),
|
||||
}
|
||||
|
||||
paymentResponse, err := s.pg.CreateQRISPayment(paymentRequest)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requestMetadata, err := json.Marshal(map[string]string{
|
||||
"partner_id": strconv.FormatInt(partnerID, 10),
|
||||
"created_by": strconv.FormatInt(createdBy, 10),
|
||||
"qr_code": paymentResponse.QRCodeURL,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when marshaling request metadata", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payment := &entity.Payment{
|
||||
PartnerID: partnerID,
|
||||
OrderID: order.ID,
|
||||
ReferenceID: paymentRequest.PaymentReferenceID,
|
||||
Channel: "LINKQU",
|
||||
PaymentType: order.PaymentType,
|
||||
Amount: order.Amount,
|
||||
State: "PENDING",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
|
||||
_, err = s.payment.Create(ctx, payment)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment record", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return paymentResponse, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) processVAPayment(ctx mycontext.Context, order *entity.Order, partnerID, createdBy int64) (*entity.PaymentResponse, error) {
|
||||
paymentRequest := entity.PaymentRequest{
|
||||
PaymentReferenceID: generator.GenerateUUIDV4(),
|
||||
TotalAmount: int64(order.Total),
|
||||
Provider: "LINKQU",
|
||||
CustomerID: strconv.FormatInt(order.User.ID, 10),
|
||||
CustomerName: order.User.Name,
|
||||
CustomerEmail: order.User.Email,
|
||||
}
|
||||
|
||||
paymentResponse, err := s.pg.CreatePaymentVA(paymentRequest)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requestMetadata, err := json.Marshal(map[string]string{
|
||||
"virtual_account": paymentResponse.VirtualAccountNumber,
|
||||
"bank_name": paymentResponse.BankName,
|
||||
"bank_code": paymentResponse.BankCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when marshaling request metadata", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payment := &entity.Payment{
|
||||
PartnerID: partnerID,
|
||||
OrderID: order.ID,
|
||||
ReferenceID: paymentRequest.PaymentReferenceID,
|
||||
Channel: "LINKQU",
|
||||
PaymentType: order.PaymentType,
|
||||
Amount: order.Amount,
|
||||
State: "PENDING",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
RequestMetadata: requestMetadata,
|
||||
}
|
||||
|
||||
_, err = s.payment.Create(ctx, payment)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when creating payment record", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return paymentResponse, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) ProcessCallback(ctx context.Context, req *entity.CallbackRequest) error {
|
||||
tx, err := s.txmanager.Begin(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
err = s.processPayment(ctx, tx, req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process payment: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
func (s *OrderService) processPayment(ctx context.Context, tx *gorm.DB, req *entity.CallbackRequest) error {
|
||||
existingPayment, err := s.payment.FindByReferenceID(ctx, tx, req.TransactionID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve payment: %w", err)
|
||||
}
|
||||
|
||||
existingPayment.State = updatePaymentState(req.TransactionStatus)
|
||||
_, err = s.payment.UpdateWithTx(ctx, tx, existingPayment)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update payment: %w", err)
|
||||
}
|
||||
|
||||
order, err := s.repo.FindByID(ctx, existingPayment.OrderID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get order: %w", err)
|
||||
}
|
||||
|
||||
if err := s.updateOrderStatus(ctx, tx, existingPayment.State, existingPayment.OrderID); err != nil {
|
||||
return fmt.Errorf("failed to update order status: %w", err)
|
||||
}
|
||||
|
||||
if existingPayment.State == "PAID" {
|
||||
if err := s.updateWalletBalance(ctx, tx, existingPayment.PartnerID, existingPayment.Amount); err != nil {
|
||||
return fmt.Errorf("failed to update wallet balance: %w", err)
|
||||
}
|
||||
|
||||
transaction := &entity.Transaction{
|
||||
PartnerID: existingPayment.PartnerID,
|
||||
TransactionType: "PAYMENT_RECEIVED",
|
||||
Status: "SUCCESS",
|
||||
CreatedBy: 0,
|
||||
Amount: existingPayment.Amount,
|
||||
Fee: order.Fee,
|
||||
Total: order.Total,
|
||||
}
|
||||
if _, err = s.transaction.Create(ctx, tx, transaction); err != nil {
|
||||
return fmt.Errorf("failed to update transaction: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updatePaymentState(status string) string {
|
||||
switch status {
|
||||
case "settlement", "capture", "paid", "settle":
|
||||
return "PAID"
|
||||
case "expire", "deny", "cancel", "failure", "EXPIRED":
|
||||
return "EXPIRED"
|
||||
default:
|
||||
return status
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OrderService) updateOrderStatus(ctx context.Context, tx *gorm.DB, status string, orderID int64) error {
|
||||
if status != "PENDING" {
|
||||
return s.repo.SetOrderStatus(ctx, tx, orderID, status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OrderService) updateWalletBalance(ctx context.Context, tx *gorm.DB, partnerID int64, amount float64) error {
|
||||
wallet, err := s.wallet.GetByPartnerID(ctx, tx, partnerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get wallet: %w", err)
|
||||
}
|
||||
wallet.Balance += amount
|
||||
_, err = s.wallet.Update(ctx, tx, wallet)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OrderService) GetAllHistoryOrders(ctx mycontext.Context, req entity.OrderSearch) ([]*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
|
||||
}
|
||||
|
||||
func (s *OrderService) CountSoldOfTicket(ctx mycontext.Context, req entity.OrderSearch) (*entity.TicketSold, error) {
|
||||
ticket, err := s.repo.CountSoldOfTicket(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get all history orders", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := ticket.ToTicketSold()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetDailySales(ctx mycontext.Context, req entity.OrderSearch) ([]entity.ProductDailySales, error) {
|
||||
dailySales, err := s.repo.GetDailySalesMetrics(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get all history orders", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dailySales, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetPaymentDistribution(ctx mycontext.Context, req entity.OrderSearch) ([]entity.PaymentTypeDistribution, error) {
|
||||
paymentDistribution, err := s.repo.GetPaymentTypeDistribution(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get all history orders", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return paymentDistribution, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) SumAmount(ctx mycontext.Context, req entity.OrderSearch) (*entity.Order, error) {
|
||||
amount, err := s.repo.SumAmount(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when get amount cash orders", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data := amount.ToSumAmount()
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetByID(ctx mycontext.Context, id int64, referenceID string) (*entity.Order, error) {
|
||||
if referenceID != "" {
|
||||
payment, err := s.payment.FindByReferenceID(ctx, nil, referenceID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting payment by IDs", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
id = payment.OrderID
|
||||
}
|
||||
|
||||
order, err := s.repo.FindByID(ctx, id)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting products by IDs", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ctx.IsCasheer() {
|
||||
return order, nil
|
||||
}
|
||||
|
||||
//if order.CreatedBy != ctx.RequestedBy() {
|
||||
// return nil, errors2.NewError(errors2.ErrorBadRequest.ErrorType(), "order not found")
|
||||
//}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) GetPrintDetail(ctx mycontext.Context, id int64) (*entity.OrderPrintDetail, error) {
|
||||
order, err := s.repo.FindPrintDetailByID(ctx, id)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("error when getting products by IDs", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return order, nil
|
||||
}
|
||||
|
||||
func (s *OrderService) ProcessLinkQuCallback(ctx context.Context, req *entity.LinkQuCallback) error {
|
||||
tx, err := s.txmanager.Begin(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
pay, err := s.linkquRepo.CheckPaymentStatus(req.PaymentReff)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
|
||||
if pay.ResponseCode != "00" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = s.processPayment(ctx, tx, &entity.CallbackRequest{
|
||||
TransactionID: req.PartnerReff,
|
||||
TransactionStatus: pay.Data.StatusPaid,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to process payment: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"enaklo-pos-be/internal/services/discovery"
|
||||
service "enaklo-pos-be/internal/services/license"
|
||||
"enaklo-pos-be/internal/services/member"
|
||||
"enaklo-pos-be/internal/services/order"
|
||||
"enaklo-pos-be/internal/services/oss"
|
||||
"enaklo-pos-be/internal/services/partner"
|
||||
"enaklo-pos-be/internal/services/product"
|
||||
@ -18,6 +17,7 @@ import (
|
||||
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
||||
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
||||
orderSvc "enaklo-pos-be/internal/services/v2/order"
|
||||
"enaklo-pos-be/internal/services/v2/partner_settings"
|
||||
productSvc "enaklo-pos-be/internal/services/v2/product"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@ -35,7 +35,6 @@ type ServiceManagerImpl struct {
|
||||
UserSvc User
|
||||
StudioSvc Studio
|
||||
ProductSvc Product
|
||||
OrderSvc Order
|
||||
OSSSvc OSSService
|
||||
PartnerSvc Partner
|
||||
SiteSvc Site
|
||||
@ -55,15 +54,16 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
||||
|
||||
custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService)
|
||||
productSvcV2 := productSvc.New(repo.ProductRepo)
|
||||
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.InProgressOrderRepo)
|
||||
partnerSettings := partner_settings.NewPartnerSettingsService(repo.PartnerSetting)
|
||||
|
||||
orderService := orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings)
|
||||
inprogressOrder := inprogress_order.NewInProgressOrderService(repo.OrderRepo, orderService, productSvcV2)
|
||||
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),
|
||||
OrderSvc: order.NewOrderService(repo.Order, repo.Product, repo.Crypto, repo.PG, repo.Payment, repo.Trx, repo.Wallet, &cfg.Order, repo.Transaction, repo.LinkQu),
|
||||
OSSSvc: oss.NewOSSService(repo.OSS),
|
||||
PartnerSvc: partner.NewPartnerService(
|
||||
repo.Partner, users.NewUserService(repo.User), repo.Trx, repo.Wallet, repo.User),
|
||||
@ -72,7 +72,7 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
||||
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),
|
||||
OrderV2Svc: orderSvc.New(repo.OrderRepo, productSvcV2, custSvcV2, repo.TransactionRepo, repo.Crypto, &cfg.Order, repo.EmailService, partnerSettings),
|
||||
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2),
|
||||
CustomerV2Svc: custSvcV2,
|
||||
InProgressSvc: inprogressOrder,
|
||||
|
||||
@ -3,28 +3,98 @@ package inprogress_order
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/logger"
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
order2 "enaklo-pos-be/internal/constants/order"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/repository"
|
||||
"enaklo-pos-be/internal/services/v2/order"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type InProgressOrderService interface {
|
||||
Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error)
|
||||
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
|
||||
Save(ctx mycontext.Context, order *entity.OrderRequest) (*entity.Order, error)
|
||||
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error)
|
||||
}
|
||||
|
||||
type OrderRepository interface {
|
||||
CreateOrUpdate(ctx mycontext.Context, order *entity.Order) (*entity.Order, error)
|
||||
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int, status string) ([]*entity.Order, error)
|
||||
}
|
||||
|
||||
type OrderCalculator interface {
|
||||
CalculateOrderTotals(
|
||||
ctx mycontext.Context,
|
||||
items []entity.OrderItemRequest,
|
||||
productDetails *entity.ProductDetails,
|
||||
source string,
|
||||
) (*entity.OrderCalculation, error)
|
||||
ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error)
|
||||
}
|
||||
|
||||
type inProgressOrderSvc struct {
|
||||
repo repository.InProgressOrderRepository
|
||||
repo OrderRepository
|
||||
orderCalculator OrderCalculator
|
||||
product order.ProductService
|
||||
}
|
||||
|
||||
func NewInProgressOrderService(repo repository.InProgressOrderRepository) InProgressOrderService {
|
||||
func NewInProgressOrderService(repo OrderRepository, calculator OrderCalculator, product order.ProductService) InProgressOrderService {
|
||||
return &inProgressOrderSvc{
|
||||
repo: repo,
|
||||
orderCalculator: calculator,
|
||||
product: product,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error) {
|
||||
func (s *inProgressOrderSvc) Save(ctx mycontext.Context, req *entity.OrderRequest) (*entity.Order, error) {
|
||||
productIDs, filteredItems, err := s.orderCalculator.ValidateOrderItems(ctx, req.OrderItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.OrderItems = filteredItems
|
||||
|
||||
productDetails, err := s.product.GetProductDetails(ctx, productIDs, req.PartnerID)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get product details", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderCalculation, err := s.orderCalculator.CalculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderItems := make([]entity.OrderItem, len(req.OrderItems))
|
||||
for i, item := range req.OrderItems {
|
||||
product, exists := productDetails.Products[item.ProductID]
|
||||
productName := ""
|
||||
if exists {
|
||||
productName = product.Name
|
||||
}
|
||||
|
||||
orderItems[i] = entity.OrderItem{
|
||||
ItemID: item.ProductID,
|
||||
ItemName: productName,
|
||||
Quantity: item.Quantity,
|
||||
Price: product.Price,
|
||||
ItemType: product.Type,
|
||||
}
|
||||
}
|
||||
|
||||
order := &entity.Order{
|
||||
ID: req.ID,
|
||||
PartnerID: req.PartnerID,
|
||||
CustomerID: req.CustomerID,
|
||||
CustomerName: req.CustomerName,
|
||||
CreatedBy: req.CreatedBy,
|
||||
OrderItems: orderItems,
|
||||
TableNumber: req.TableNumber,
|
||||
OrderType: req.OrderType,
|
||||
Total: orderCalculation.Total,
|
||||
Tax: orderCalculation.Tax,
|
||||
Amount: orderCalculation.Subtotal,
|
||||
Status: order2.Pending.String(),
|
||||
Source: req.Source,
|
||||
}
|
||||
|
||||
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to create in-progress order",
|
||||
@ -36,8 +106,8 @@ func (s *inProgressOrderSvc) Save(ctx mycontext.Context, order *entity.InProgres
|
||||
return createdOrder, nil
|
||||
}
|
||||
|
||||
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) {
|
||||
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset)
|
||||
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error) {
|
||||
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset, order2.Pending.String())
|
||||
if err != nil {
|
||||
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
|
||||
zap.Error(err),
|
||||
|
||||
@ -7,11 +7,12 @@ import (
|
||||
"enaklo-pos-be/internal/constants"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"go.uber.org/zap"
|
||||
"math"
|
||||
)
|
||||
|
||||
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) {
|
||||
productIDs, filteredItems, err := s.validateOrderItems(ctx, req.OrderItems)
|
||||
productIDs, filteredItems, err := s.ValidateOrderItems(ctx, req.OrderItems)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -23,7 +24,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orderCalculation, err := s.calculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
|
||||
orderCalculation, err := s.CalculateOrderTotals(ctx, req.OrderItems, productDetails, req.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -43,7 +44,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
req.PartnerID,
|
||||
customerID,
|
||||
orderCalculation.Subtotal,
|
||||
orderCalculation.Fee,
|
||||
orderCalculation.Tax,
|
||||
orderCalculation.Total,
|
||||
req.PaymentMethod,
|
||||
req.Source,
|
||||
@ -79,7 +80,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
|
||||
func (s *orderSvc) ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error) {
|
||||
var productIDs []int64
|
||||
var filteredItems []entity.OrderItemRequest
|
||||
|
||||
@ -98,7 +99,7 @@ func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.Orde
|
||||
return productIDs, filteredItems, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) calculateOrderTotals(
|
||||
func (s *orderSvc) CalculateOrderTotals(
|
||||
ctx mycontext.Context,
|
||||
items []entity.OrderItemRequest,
|
||||
productDetails *entity.ProductDetails,
|
||||
@ -114,12 +115,23 @@ func (s *orderSvc) calculateOrderTotals(
|
||||
subtotal += product.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
fee := s.cfg.GetOrderFee(source)
|
||||
partnerID := ctx.GetPartnerID()
|
||||
setting, err := s.partnerSetting.GetSettings(ctx, *partnerID)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.NewError(errors.ErrorInvalidRequest.ErrorType(), "failed to get partner settings")
|
||||
}
|
||||
|
||||
tax := 0.0
|
||||
if setting.TaxEnabled {
|
||||
tax = (setting.TaxPercentage / 100) * subtotal
|
||||
tax = math.Round(tax/100) * 100
|
||||
}
|
||||
|
||||
return &entity.OrderCalculation{
|
||||
Subtotal: subtotal,
|
||||
Fee: fee,
|
||||
Total: subtotal + fee,
|
||||
Tax: tax,
|
||||
Total: subtotal + tax,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -145,3 +157,79 @@ func (s *orderSvc) validateInquiry(ctx mycontext.Context, token string) (*entity
|
||||
|
||||
return inquiry, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) GetOrderPaymentAnalysis(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
req entity.SearchRequest,
|
||||
) (*entity.OrderPaymentAnalysis, error) {
|
||||
paymentBreakdown, err := s.repo.GetOrderPaymentMethodBreakdown(ctx, partnerID, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var totalAmount float64
|
||||
var totalTransactions int64
|
||||
|
||||
for _, breakdown := range paymentBreakdown {
|
||||
totalAmount += breakdown.TotalAmount
|
||||
totalTransactions += breakdown.TotalTransactions
|
||||
}
|
||||
|
||||
return &entity.OrderPaymentAnalysis{
|
||||
TotalAmount: totalAmount,
|
||||
TotalTransactions: totalTransactions,
|
||||
PaymentMethodBreakdown: paymentBreakdown,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *orderSvc) GetRevenueOverview(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
year int,
|
||||
granularity string,
|
||||
status string,
|
||||
) ([]entity.RevenueOverviewItem, error) {
|
||||
req := entity.RevenueOverviewRequest{
|
||||
PartnerID: partnerID,
|
||||
Year: year,
|
||||
Granularity: granularity,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
return s.repo.GetRevenueOverview(ctx, req)
|
||||
}
|
||||
|
||||
func (s *orderSvc) GetSalesByCategory(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
period string,
|
||||
status string,
|
||||
) ([]entity.SalesByCategoryItem, error) {
|
||||
req := entity.SalesByCategoryRequest{
|
||||
PartnerID: partnerID,
|
||||
Period: period,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
return s.repo.GetSalesByCategory(ctx, req)
|
||||
}
|
||||
|
||||
func (s *orderSvc) GetPopularProducts(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
period string,
|
||||
status string,
|
||||
limit int,
|
||||
sortBy string,
|
||||
) ([]entity.PopularProductItem, error) {
|
||||
req := entity.PopularProductsRequest{
|
||||
PartnerID: partnerID,
|
||||
Period: period,
|
||||
Status: status,
|
||||
Limit: limit,
|
||||
SortBy: sortBy,
|
||||
}
|
||||
|
||||
return s.repo.GetPopularProducts(ctx, req)
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func (s *orderSvc) ExecuteOrderInquiry(ctx mycontext.Context,
|
||||
token string, paymentMethod, paymentProvider, inprogressOrderID string) (*entity.OrderResponse, error) {
|
||||
token string, paymentMethod, paymentProvider string, inprogressOrderID int64) (*entity.OrderResponse, error) {
|
||||
inquiry, err := s.validateInquiry(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@ -12,6 +12,24 @@ 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
|
||||
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
|
||||
GetOrderPaymentMethodBreakdown(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
req entity.SearchRequest,
|
||||
) ([]entity.PaymentMethodBreakdown, error)
|
||||
GetRevenueOverview(
|
||||
ctx mycontext.Context,
|
||||
req entity.RevenueOverviewRequest,
|
||||
) ([]entity.RevenueOverviewItem, error)
|
||||
GetSalesByCategory(
|
||||
ctx mycontext.Context,
|
||||
req entity.SalesByCategoryRequest,
|
||||
) ([]entity.SalesByCategoryItem, error)
|
||||
GetPopularProducts(
|
||||
ctx mycontext.Context,
|
||||
req entity.PopularProductsRequest,
|
||||
) ([]entity.PopularProductItem, error)
|
||||
}
|
||||
|
||||
type ProductService interface {
|
||||
@ -42,13 +60,55 @@ type Service interface {
|
||||
CreateOrderInquiry(ctx mycontext.Context,
|
||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
|
||||
ExecuteOrderInquiry(ctx mycontext.Context,
|
||||
token string, paymentMethod, paymentProvider, inProgressOrderID string) (*entity.OrderResponse, error)
|
||||
token string, paymentMethod, paymentProvider string, inProgressOrderID int64) (*entity.OrderResponse, error)
|
||||
GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error)
|
||||
CalculateOrderTotals(
|
||||
ctx mycontext.Context,
|
||||
items []entity.OrderItemRequest,
|
||||
productDetails *entity.ProductDetails,
|
||||
source string,
|
||||
) (*entity.OrderCalculation, error)
|
||||
ValidateOrderItems(ctx mycontext.Context, items []entity.OrderItemRequest) ([]int64, []entity.OrderItemRequest, error)
|
||||
GetOrderPaymentAnalysis(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
req entity.SearchRequest,
|
||||
) (*entity.OrderPaymentAnalysis, error)
|
||||
GetRevenueOverview(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
year int,
|
||||
granularity string,
|
||||
status string,
|
||||
) ([]entity.RevenueOverviewItem, error)
|
||||
GetSalesByCategory(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
period string,
|
||||
status string,
|
||||
) ([]entity.SalesByCategoryItem, error)
|
||||
GetPopularProducts(
|
||||
ctx mycontext.Context,
|
||||
partnerID int64,
|
||||
period string,
|
||||
status string,
|
||||
limit int,
|
||||
sortBy string,
|
||||
) ([]entity.PopularProductItem, error)
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
GetOrderFee(source string) float64
|
||||
}
|
||||
|
||||
type PartnerSettings interface {
|
||||
GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error)
|
||||
}
|
||||
|
||||
type InProgressOrderRepository interface {
|
||||
GetListByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error)
|
||||
}
|
||||
|
||||
type orderSvc struct {
|
||||
repo Repository
|
||||
product ProductService
|
||||
@ -57,6 +117,8 @@ type orderSvc struct {
|
||||
crypt CryptService
|
||||
cfg Config
|
||||
notification NotificationService
|
||||
partnerSetting PartnerSettings
|
||||
inprogressOrder InProgressOrderRepository
|
||||
}
|
||||
|
||||
func New(
|
||||
@ -67,6 +129,7 @@ func New(
|
||||
crypt CryptService,
|
||||
cfg Config,
|
||||
notification NotificationService,
|
||||
partnerSetting PartnerSettings,
|
||||
) Service {
|
||||
return &orderSvc{
|
||||
repo: repo,
|
||||
@ -76,5 +139,6 @@ func New(
|
||||
crypt: crypt,
|
||||
cfg: cfg,
|
||||
notification: notification,
|
||||
partnerSetting: partnerSetting,
|
||||
}
|
||||
}
|
||||
|
||||
10
internal/services/v2/order/order_history.go
Normal file
10
internal/services/v2/order/order_history.go
Normal file
@ -0,0 +1,10 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
)
|
||||
|
||||
func (s *orderSvc) GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error) {
|
||||
return s.repo.GetOrderHistoryByPartnerID(ctx, partnerID, request)
|
||||
}
|
||||
149
internal/services/v2/partner_settings/partner_setting.go
Normal file
149
internal/services/v2/partner_settings/partner_setting.go
Normal file
@ -0,0 +1,149 @@
|
||||
package partner_settings
|
||||
|
||||
import (
|
||||
"enaklo-pos-be/internal/common/mycontext"
|
||||
"enaklo-pos-be/internal/entity"
|
||||
"enaklo-pos-be/internal/repository"
|
||||
"encoding/json"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type PartnerSettingsService interface {
|
||||
GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error)
|
||||
UpdateSettings(ctx mycontext.Context, settings *entity.PartnerSettings) error
|
||||
GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error)
|
||||
AddPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error
|
||||
UpdatePaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error
|
||||
DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error
|
||||
ReorderPaymentMethods(ctx mycontext.Context, partnerID int64, methodIDs []int64) error
|
||||
GetBusinessHours(ctx mycontext.Context, partnerID int64) (*entity.BusinessHoursSetting, error)
|
||||
UpdateBusinessHours(ctx mycontext.Context, partnerID int64, hours *entity.BusinessHoursSetting) error
|
||||
}
|
||||
|
||||
type partnerSettingsService struct {
|
||||
settingsRepo repository.PartnerSettingsRepository
|
||||
}
|
||||
|
||||
func NewPartnerSettingsService(settingsRepo repository.PartnerSettingsRepository) PartnerSettingsService {
|
||||
return &partnerSettingsService{
|
||||
settingsRepo: settingsRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) GetSettings(ctx mycontext.Context, partnerID int64) (*entity.PartnerSettings, error) {
|
||||
return s.settingsRepo.GetByPartnerID(ctx, partnerID)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) UpdateSettings(ctx mycontext.Context, settings *entity.PartnerSettings) error {
|
||||
if settings == nil {
|
||||
return errors.New("settings cannot be nil")
|
||||
}
|
||||
|
||||
// Validate tax percentage
|
||||
if settings.TaxEnabled && (settings.TaxPercentage < 0 || settings.TaxPercentage > 100) {
|
||||
return errors.New("tax percentage must be between 0 and 100")
|
||||
}
|
||||
|
||||
return s.settingsRepo.Upsert(ctx, settings)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) GetPaymentMethods(ctx mycontext.Context, partnerID int64) ([]entity.PartnerPaymentMethod, error) {
|
||||
return s.settingsRepo.GetPaymentMethods(ctx, partnerID)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) AddPaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error {
|
||||
if method == nil {
|
||||
return errors.New("payment method cannot be nil")
|
||||
}
|
||||
|
||||
method.ID = 0
|
||||
|
||||
return s.settingsRepo.UpsertPaymentMethod(ctx, method)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) UpdatePaymentMethod(ctx mycontext.Context, method *entity.PartnerPaymentMethod) error {
|
||||
if method == nil {
|
||||
return errors.New("payment method cannot be nil")
|
||||
}
|
||||
|
||||
if method.ID <= 0 {
|
||||
return errors.New("invalid payment method ID")
|
||||
}
|
||||
|
||||
return s.settingsRepo.UpsertPaymentMethod(ctx, method)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) DeletePaymentMethod(ctx mycontext.Context, id int64, partnerID int64) error {
|
||||
if id <= 0 {
|
||||
return errors.New("invalid payment method ID")
|
||||
}
|
||||
|
||||
return s.settingsRepo.DeletePaymentMethod(ctx, id, partnerID)
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) ReorderPaymentMethods(ctx mycontext.Context, partnerID int64, methodIDs []int64) error {
|
||||
if len(methodIDs) == 0 {
|
||||
return errors.New("method IDs cannot be empty")
|
||||
}
|
||||
|
||||
return s.settingsRepo.UpdatePaymentMethodOrder(ctx, partnerID, methodIDs)
|
||||
}
|
||||
|
||||
// GetBusinessHours retrieves parsed business hours for a partner
|
||||
func (s *partnerSettingsService) GetBusinessHours(ctx mycontext.Context, partnerID int64) (*entity.BusinessHoursSetting, error) {
|
||||
settings, err := s.settingsRepo.GetByPartnerID(ctx, partnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create default hours if not set
|
||||
if settings.BusinessHours == "" {
|
||||
defaultHours := createDefaultBusinessHours()
|
||||
return defaultHours, nil
|
||||
}
|
||||
|
||||
var hours entity.BusinessHoursSetting
|
||||
if err := json.Unmarshal([]byte(settings.BusinessHours), &hours); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse business hours")
|
||||
}
|
||||
|
||||
return &hours, nil
|
||||
}
|
||||
|
||||
func (s *partnerSettingsService) UpdateBusinessHours(ctx mycontext.Context, partnerID int64, hours *entity.BusinessHoursSetting) error {
|
||||
if hours == nil {
|
||||
return errors.New("business hours cannot be nil")
|
||||
}
|
||||
|
||||
settings, err := s.settingsRepo.GetByPartnerID(ctx, partnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize hours to JSON
|
||||
hoursJSON, err := json.Marshal(hours)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to serialize business hours")
|
||||
}
|
||||
|
||||
settings.BusinessHours = string(hoursJSON)
|
||||
|
||||
return s.settingsRepo.Upsert(ctx, settings)
|
||||
}
|
||||
|
||||
func createDefaultBusinessHours() *entity.BusinessHoursSetting {
|
||||
defaultDay := entity.DayHours{
|
||||
Open: "08:00",
|
||||
Close: "22:00",
|
||||
}
|
||||
|
||||
return &entity.BusinessHoursSetting{
|
||||
Monday: defaultDay,
|
||||
Tuesday: defaultDay,
|
||||
Wednesday: defaultDay,
|
||||
Thursday: defaultDay,
|
||||
Friday: defaultDay,
|
||||
Saturday: defaultDay,
|
||||
Sunday: defaultDay,
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user