update
This commit is contained in:
parent
c642c5c61b
commit
09c9a4d59d
@ -3,9 +3,10 @@ package order
|
|||||||
type OrderStatus string
|
type OrderStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
New OrderStatus = "NEW"
|
New OrderStatus = "NEW"
|
||||||
Paid OrderStatus = "PAID"
|
Paid OrderStatus = "PAID"
|
||||||
Cancel OrderStatus = "CANCEL"
|
Cancel OrderStatus = "CANCEL"
|
||||||
|
Pending OrderStatus = "PENDING"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b OrderStatus) toString() string {
|
func (b OrderStatus) toString() string {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ type Order struct {
|
|||||||
Status string `gorm:"type:varchar;column:status"`
|
Status string `gorm:"type:varchar;column:status"`
|
||||||
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
Amount float64 `gorm:"type:numeric;not null;column:amount"`
|
||||||
Total float64 `gorm:"type:numeric;not null;column:total"`
|
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
|
CustomerID *int64
|
||||||
CustomerName string
|
CustomerName string
|
||||||
InquiryID *string
|
InquiryID *string
|
||||||
@ -27,7 +27,7 @@ type Order struct {
|
|||||||
Source string `gorm:"type:varchar;column:source"`
|
Source string `gorm:"type:varchar;column:source"`
|
||||||
OrderType string `gorm:"type:varchar;column:order_type"`
|
OrderType string `gorm:"type:varchar;column:order_type"`
|
||||||
TableNumber string
|
TableNumber string
|
||||||
InProgressOrderID string
|
InProgressOrderID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderDB struct {
|
type OrderDB struct {
|
||||||
@ -86,6 +86,7 @@ type OrderItem struct {
|
|||||||
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
CreatedBy int64 `gorm:"type:int;column:created_by"`
|
||||||
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
UpdatedBy int64 `gorm:"type:int;column:updated_by"`
|
||||||
Product *Product `gorm:"foreignKey:ItemID;references:ID"`
|
Product *Product `gorm:"foreignKey:ItemID;references:ID"`
|
||||||
|
ItemName string `gorm:"type:varchar;column:item_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (OrderItem) TableName() string {
|
func (OrderItem) TableName() string {
|
||||||
@ -105,6 +106,7 @@ type OrderRequest struct {
|
|||||||
TableNumber string
|
TableNumber string
|
||||||
PaymentProvider string
|
PaymentProvider string
|
||||||
OrderType string
|
OrderType string
|
||||||
|
ID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderItemRequest struct {
|
type OrderItemRequest struct {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ type OrderInquiry struct {
|
|||||||
CustomerEmail string `json:"customer_email"`
|
CustomerEmail string `json:"customer_email"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Fee float64 `json:"fee"`
|
Tax float64 `json:"tax"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
PaymentType string `json:"payment_type"`
|
PaymentType string `json:"payment_type"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
@ -30,7 +30,7 @@ type OrderInquiry struct {
|
|||||||
|
|
||||||
type OrderCalculation struct {
|
type OrderCalculation struct {
|
||||||
Subtotal float64 `json:"subtotal"`
|
Subtotal float64 `json:"subtotal"`
|
||||||
Fee float64 `json:"fee"`
|
Tax float64 `json:"tax"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ func NewOrderInquiry(
|
|||||||
partnerID int64,
|
partnerID int64,
|
||||||
customerID int64,
|
customerID int64,
|
||||||
amount float64,
|
amount float64,
|
||||||
fee float64,
|
tax float64,
|
||||||
total float64,
|
total float64,
|
||||||
paymentType string,
|
paymentType string,
|
||||||
source string,
|
source string,
|
||||||
@ -60,7 +60,7 @@ func NewOrderInquiry(
|
|||||||
PartnerID: partnerID,
|
PartnerID: partnerID,
|
||||||
Status: "PENDING",
|
Status: "PENDING",
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Fee: fee,
|
Tax: tax,
|
||||||
Total: total,
|
Total: total,
|
||||||
PaymentType: paymentType,
|
PaymentType: paymentType,
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
@ -83,6 +83,7 @@ func (oi *OrderInquiry) AddOrderItem(item OrderItemRequest, product *Product) {
|
|||||||
ItemID: item.ProductID,
|
ItemID: item.ProductID,
|
||||||
ItemType: product.Type,
|
ItemType: product.Type,
|
||||||
Price: product.Price,
|
Price: product.Price,
|
||||||
|
ItemName: product.Name,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
CreatedBy: oi.CreatedBy,
|
CreatedBy: oi.CreatedBy,
|
||||||
Product: product,
|
Product: product,
|
||||||
@ -98,7 +99,7 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
|||||||
InquiryID: &i.ID,
|
InquiryID: &i.ID,
|
||||||
Status: constants.StatusPaid,
|
Status: constants.StatusPaid,
|
||||||
Amount: i.Amount,
|
Amount: i.Amount,
|
||||||
Fee: i.Fee,
|
Tax: i.Tax,
|
||||||
Total: i.Total,
|
Total: i.Total,
|
||||||
PaymentType: paymentMethod,
|
PaymentType: paymentMethod,
|
||||||
PaymentProvider: paymentProvider,
|
PaymentProvider: paymentProvider,
|
||||||
@ -107,6 +108,8 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
|||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
OrderItems: make([]OrderItem, len(i.OrderItems)),
|
||||||
OrderType: i.OrderType,
|
OrderType: i.OrderType,
|
||||||
|
CustomerName: i.CustomerName,
|
||||||
|
TableNumber: i.TableNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, item := range i.OrderItems {
|
for idx, item := range i.OrderItems {
|
||||||
@ -114,6 +117,7 @@ func (i *OrderInquiry) ToOrder(paymentMethod, paymentProvider string) *Order {
|
|||||||
ItemID: item.ItemID,
|
ItemID: item.ItemID,
|
||||||
ItemType: item.ItemType,
|
ItemType: item.ItemType,
|
||||||
Price: item.Price,
|
Price: item.Price,
|
||||||
|
ItemName: item.ItemName,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
CreatedBy: i.CreatedBy,
|
CreatedBy: i.CreatedBy,
|
||||||
CreatedAt: now,
|
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
|
Products map[int64]*Product // Map for quick lookups by ID
|
||||||
PartnerID int64 // Common site ID for all products
|
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,
|
PaymentType: order.PaymentType,
|
||||||
CreatedAt: order.CreatedAt,
|
CreatedAt: order.CreatedAt,
|
||||||
OrderItems: orderItems,
|
OrderItems: orderItems,
|
||||||
Fee: order.Fee,
|
Tax: order.Tax,
|
||||||
Total: order.Total,
|
Total: order.Total,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ func (h *Handler) toOrderDetail(order *entity.Order) *response.OrderDetail {
|
|||||||
PaymentLink: paymentLink,
|
PaymentLink: paymentLink,
|
||||||
PaymentToken: paymentToken,
|
PaymentToken: paymentToken,
|
||||||
SiteName: siteName,
|
SiteName: siteName,
|
||||||
Fee: order.Fee,
|
Fee: order.Tax,
|
||||||
}
|
}
|
||||||
|
|
||||||
orderDetail.OrderItems = make([]response.OrderDetailItem, len(order.OrderItems))
|
orderDetail.OrderItems = make([]response.OrderDetailItem, len(order.OrderItems))
|
||||||
|
|||||||
@ -38,7 +38,7 @@ type CreateInProgressOrderRequest struct {
|
|||||||
OrderType string `json:"order_type"`
|
OrderType string `json:"order_type"`
|
||||||
PaymentProvider string `json:"payment_provider"`
|
PaymentProvider string `json:"payment_provider"`
|
||||||
TableNumber string `json:"table_number"`
|
TableNumber string `json:"table_number"`
|
||||||
InProgressOrderID string `json:"in_progress_order_id"`
|
InProgressOrderID int64 `json:"in_progress_order_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InProgressOrderItemRequest struct {
|
type InProgressOrderItemRequest struct {
|
||||||
@ -72,15 +72,15 @@ func (h *InProgressOrderHandler) Save(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
orderItems := make([]entity.InProgressOrderItem, len(req.OrderItems))
|
orderItems := make([]entity.OrderItemRequest, len(req.OrderItems))
|
||||||
for i, item := range req.OrderItems {
|
for i, item := range req.OrderItems {
|
||||||
orderItems[i] = entity.InProgressOrderItem{
|
orderItems[i] = entity.OrderItemRequest{
|
||||||
ItemID: item.ProductID,
|
ProductID: item.ProductID,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
order := &entity.InProgressOrder{
|
order := &entity.OrderRequest{
|
||||||
PartnerID: *partnerID,
|
PartnerID: *partnerID,
|
||||||
CustomerID: req.CustomerID,
|
CustomerID: req.CustomerID,
|
||||||
CustomerName: req.CustomerName,
|
CustomerName: req.CustomerName,
|
||||||
@ -89,6 +89,7 @@ func (h *InProgressOrderHandler) Save(c *gin.Context) {
|
|||||||
TableNumber: req.TableNumber,
|
TableNumber: req.TableNumber,
|
||||||
OrderType: req.OrderType,
|
OrderType: req.OrderType,
|
||||||
ID: req.InProgressOrderID,
|
ID: req.InProgressOrderID,
|
||||||
|
Source: "POS",
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := h.service.Save(ctx, order)
|
_, 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))
|
orderItems := make([]map[string]interface{}, len(order.OrderItems))
|
||||||
for i, item := range order.OrderItems {
|
for i, item := range order.OrderItems {
|
||||||
orderItems[i] = map[string]interface{}{
|
orderItems[i] = map[string]interface{}{
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"enaklo-pos-be/internal/common/errors"
|
"enaklo-pos-be/internal/common/errors"
|
||||||
|
order2 "enaklo-pos-be/internal/constants/order"
|
||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
"enaklo-pos-be/internal/handlers/request"
|
"enaklo-pos-be/internal/handlers/request"
|
||||||
"enaklo-pos-be/internal/handlers/response"
|
"enaklo-pos-be/internal/handlers/response"
|
||||||
@ -9,6 +10,8 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
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("/inquiry", jwt, h.Inquiry)
|
||||||
route.POST("/execute", jwt, h.Execute)
|
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 {
|
type InquiryRequest struct {
|
||||||
@ -56,7 +65,7 @@ type OrderItemRequest struct {
|
|||||||
type ExecuteRequest struct {
|
type ExecuteRequest struct {
|
||||||
PaymentMethod string `json:"payment_method" validate:"required"`
|
PaymentMethod string `json:"payment_method" validate:"required"`
|
||||||
PaymentProvider string `json:"payment_provider"`
|
PaymentProvider string `json:"payment_provider"`
|
||||||
InProgressOrderID string `json:"in_progress_order_id"`
|
InProgressOrderID int64 `json:"in_progress_order_id"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,3 +149,307 @@ func (h *Handler) Execute(c *gin.Context) {
|
|||||||
Data: response.MapToOrderResponse(result),
|
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,
|
Status: order.Status,
|
||||||
Amount: order.Amount,
|
Amount: order.Amount,
|
||||||
Total: order.Total,
|
Total: order.Total,
|
||||||
Fee: order.Fee,
|
Tax: order.Tax,
|
||||||
PaymentType: order.PaymentType,
|
PaymentType: order.PaymentType,
|
||||||
CreatedAt: order.CreatedAt,
|
CreatedAt: order.CreatedAt,
|
||||||
OrderItems: orderItems,
|
OrderItems: orderItems,
|
||||||
|
|||||||
@ -28,6 +28,7 @@ func (h *Handler) Route(group *gin.RouterGroup, jwt gin.HandlerFunc) {
|
|||||||
route.PUT("/:id", jwt, isSuperAdmin, h.Update)
|
route.PUT("/:id", jwt, isSuperAdmin, h.Update)
|
||||||
route.GET("/:id", jwt, isSuperAdmin, h.GetByID)
|
route.GET("/:id", jwt, isSuperAdmin, h.GetByID)
|
||||||
route.DELETE("/:id", jwt, isSuperAdmin, h.Delete)
|
route.DELETE("/:id", jwt, isSuperAdmin, h.Delete)
|
||||||
|
route.PUT("/update", jwt, h.UpdateMyStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(service services.Partner) *Handler {
|
func NewHandler(service services.Partner) *Handler {
|
||||||
@ -268,3 +269,27 @@ func (h *Handler) toPartnerResponseList(resp []*entity.Partner, total int64, req
|
|||||||
Offset: req.Offset,
|
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"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
Fee float64 `json:"fee"`
|
Tax float64 `json:"tax"`
|
||||||
PaymentType string `json:"payment_type"`
|
PaymentType string `json:"payment_type"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
OrderItems []CreateOrderItemResponse `json:"order_items"`
|
OrderItems []CreateOrderItemResponse `json:"order_items"`
|
||||||
@ -187,3 +187,17 @@ type OrderDetailItem struct {
|
|||||||
UnitPrice float64 `json:"unit_price"` // Price per unit
|
UnitPrice float64 `json:"unit_price"` // Price per unit
|
||||||
TotalPrice float64 `json:"total_price"` // Total price for this item (Quantity * UnitPrice)
|
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"`
|
ID string `json:"id"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Fee float64 `json:"fee"`
|
Tax float64 `json:"tax"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
CustomerID int64 `json:"customer_id"`
|
CustomerID int64 `json:"customer_id"`
|
||||||
PaymentType string `json:"payment_type"`
|
PaymentType string `json:"payment_type"`
|
||||||
@ -54,7 +54,7 @@ func MapToInquiryResponse(result *entity.OrderInquiryResponse) OrderInquiryRespo
|
|||||||
ID: result.OrderInquiry.ID,
|
ID: result.OrderInquiry.ID,
|
||||||
Status: result.OrderInquiry.Status,
|
Status: result.OrderInquiry.Status,
|
||||||
Amount: result.OrderInquiry.Amount,
|
Amount: result.OrderInquiry.Amount,
|
||||||
Fee: result.OrderInquiry.Fee,
|
Tax: result.OrderInquiry.Tax,
|
||||||
Total: result.OrderInquiry.Total,
|
Total: result.OrderInquiry.Total,
|
||||||
CustomerID: result.OrderInquiry.CustomerID,
|
CustomerID: result.OrderInquiry.CustomerID,
|
||||||
PaymentType: result.OrderInquiry.PaymentType,
|
PaymentType: result.OrderInquiry.PaymentType,
|
||||||
@ -74,7 +74,7 @@ type OrderResponse struct {
|
|||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Fee float64 `json:"fee"`
|
Tax float64 `json:"tax"`
|
||||||
Total float64 `json:"total"`
|
Total float64 `json:"total"`
|
||||||
CustomerName string `json:"customer_name,omitempty"`
|
CustomerName string `json:"customer_name,omitempty"`
|
||||||
PaymentType string `json:"payment_type"`
|
PaymentType string `json:"payment_type"`
|
||||||
@ -89,7 +89,7 @@ func MapToOrderResponse(result *entity.OrderResponse) OrderResponse {
|
|||||||
ID: result.Order.ID,
|
ID: result.Order.ID,
|
||||||
Status: result.Order.Status,
|
Status: result.Order.Status,
|
||||||
Amount: result.Order.Amount,
|
Amount: result.Order.Amount,
|
||||||
Fee: result.Order.Fee,
|
Tax: result.Order.Tax,
|
||||||
Total: result.Order.Total,
|
Total: result.Order.Total,
|
||||||
PaymentType: result.Order.PaymentType,
|
PaymentType: result.Order.PaymentType,
|
||||||
CreatedAt: result.Order.CreatedAt,
|
CreatedAt: result.Order.CreatedAt,
|
||||||
|
|||||||
@ -3,5 +3,5 @@ package response
|
|||||||
type PagingMeta struct {
|
type PagingMeta struct {
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
Total int64 `json:"total_data"`
|
Total int64 `json:"total"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"enaklo-pos-be/internal/common/mycontext"
|
"enaklo-pos-be/internal/common/mycontext"
|
||||||
"enaklo-pos-be/internal/constants"
|
|
||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
"enaklo-pos-be/internal/repository/models"
|
"enaklo-pos-be/internal/repository/models"
|
||||||
"github.com/pkg/errors"
|
"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 {
|
func (r *inprogressOrderRepository) toInProgressOrderDBModel(order *entity.InProgressOrder) models.InProgressOrderDB {
|
||||||
now := time2.Now()
|
now := time2.Now()
|
||||||
return models.InProgressOrderDB{
|
return models.InProgressOrderDB{
|
||||||
ID: constants.GenerateUUID(),
|
ID: order.ID,
|
||||||
PartnerID: order.PartnerID,
|
PartnerID: order.PartnerID,
|
||||||
CustomerID: order.CustomerID,
|
CustomerID: order.CustomerID,
|
||||||
CustomerName: order.CustomerName,
|
CustomerName: order.CustomerName,
|
||||||
@ -207,7 +206,7 @@ func (r *inprogressOrderRepository) toOrderInquiryDBModel(inquiry *entity.OrderI
|
|||||||
CustomerID: &inquiry.CustomerID,
|
CustomerID: &inquiry.CustomerID,
|
||||||
Status: inquiry.Status,
|
Status: inquiry.Status,
|
||||||
Amount: inquiry.Amount,
|
Amount: inquiry.Amount,
|
||||||
Fee: inquiry.Fee,
|
Tax: inquiry.Tax,
|
||||||
Total: inquiry.Total,
|
Total: inquiry.Total,
|
||||||
PaymentType: inquiry.PaymentType,
|
PaymentType: inquiry.PaymentType,
|
||||||
Source: inquiry.Source,
|
Source: inquiry.Source,
|
||||||
@ -230,7 +229,7 @@ func (r *inprogressOrderRepository) toDomainOrderInquiryModel(dbModel *models.Or
|
|||||||
PartnerID: dbModel.PartnerID,
|
PartnerID: dbModel.PartnerID,
|
||||||
Status: dbModel.Status,
|
Status: dbModel.Status,
|
||||||
Amount: dbModel.Amount,
|
Amount: dbModel.Amount,
|
||||||
Fee: dbModel.Fee,
|
Tax: dbModel.Tax,
|
||||||
Total: dbModel.Total,
|
Total: dbModel.Total,
|
||||||
PaymentType: dbModel.PaymentType,
|
PaymentType: dbModel.PaymentType,
|
||||||
Source: dbModel.Source,
|
Source: dbModel.Source,
|
||||||
|
|||||||
@ -5,20 +5,24 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type OrderDB struct {
|
type OrderDB struct {
|
||||||
ID int64 `gorm:"primaryKey;column:id"`
|
ID int64 `gorm:"primaryKey;column:id"`
|
||||||
PartnerID int64 `gorm:"column:partner_id"`
|
PartnerID int64 `gorm:"column:partner_id"`
|
||||||
CustomerID *int64 `gorm:"column:customer_id"`
|
CustomerID *int64 `gorm:"column:customer_id"`
|
||||||
InquiryID *string `gorm:"column:inquiry_id"`
|
InquiryID *string `gorm:"column:inquiry_id"`
|
||||||
Status string `gorm:"column:status"`
|
Status string `gorm:"column:status"`
|
||||||
Amount float64 `gorm:"column:amount"`
|
Amount float64 `gorm:"column:amount"`
|
||||||
Fee float64 `gorm:"column:fee"`
|
Tax float64 `gorm:"column:tax"`
|
||||||
Total float64 `gorm:"column:total"`
|
Total float64 `gorm:"column:total"`
|
||||||
PaymentType string `gorm:"column:payment_type"`
|
PaymentType string `gorm:"column:payment_type"`
|
||||||
Source string `gorm:"column:source"`
|
Source string `gorm:"column:source"`
|
||||||
CreatedBy int64 `gorm:"column:created_by"`
|
CreatedBy int64 `gorm:"column:created_by"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at"`
|
||||||
OrderItems []OrderItemDB `gorm:"foreignKey:OrderID"`
|
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 {
|
func (OrderDB) TableName() string {
|
||||||
@ -29,11 +33,13 @@ type OrderItemDB struct {
|
|||||||
ID int64 `gorm:"primaryKey;column:order_item_id"`
|
ID int64 `gorm:"primaryKey;column:order_item_id"`
|
||||||
OrderID int64 `gorm:"column:order_id"`
|
OrderID int64 `gorm:"column:order_id"`
|
||||||
ItemID int64 `gorm:"column:item_id"`
|
ItemID int64 `gorm:"column:item_id"`
|
||||||
|
ItemName string `gorm:"column:item_name"`
|
||||||
ItemType string `gorm:"column:item_type"`
|
ItemType string `gorm:"column:item_type"`
|
||||||
Price float64 `gorm:"column:price"`
|
Price float64 `gorm:"column:price"`
|
||||||
Quantity int `gorm:"column:quantity"`
|
Quantity int `gorm:"column:quantity"`
|
||||||
CreatedBy int64 `gorm:"column:created_by"`
|
CreatedBy int64 `gorm:"column:created_by"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at"`
|
CreatedAt time.Time `gorm:"column:created_at"`
|
||||||
|
Product ProductDB `gorm:"foreignKey:ItemID;references:ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (OrderItemDB) TableName() string {
|
func (OrderItemDB) TableName() string {
|
||||||
@ -49,7 +55,7 @@ type OrderInquiryDB struct {
|
|||||||
CustomerPhoneNumber string `gorm:"column:customer_phone_number"`
|
CustomerPhoneNumber string `gorm:"column:customer_phone_number"`
|
||||||
Status string `gorm:"column:status"`
|
Status string `gorm:"column:status"`
|
||||||
Amount float64 `gorm:"column:amount"`
|
Amount float64 `gorm:"column:amount"`
|
||||||
Fee float64 `gorm:"column:fee"`
|
Tax float64 `gorm:"column:tax"`
|
||||||
Total float64 `gorm:"column:total"`
|
Total float64 `gorm:"column:total"`
|
||||||
PaymentType string `gorm:"column:payment_type"`
|
PaymentType string `gorm:"column:payment_type"`
|
||||||
Source string `gorm:"column:source"`
|
Source string `gorm:"column:source"`
|
||||||
@ -72,6 +78,7 @@ type InquiryItemDB struct {
|
|||||||
InquiryID string `gorm:"column:inquiry_id"`
|
InquiryID string `gorm:"column:inquiry_id"`
|
||||||
ItemID int64 `gorm:"column:item_id"`
|
ItemID int64 `gorm:"column:item_id"`
|
||||||
ItemType string `gorm:"column:item_type"`
|
ItemType string `gorm:"column:item_type"`
|
||||||
|
ItemName string `gorm:"column:item_name"`
|
||||||
Price float64 `gorm:"column:price"`
|
Price float64 `gorm:"column:price"`
|
||||||
Quantity int `gorm:"column:quantity"`
|
Quantity int `gorm:"column:quantity"`
|
||||||
CreatedBy int64 `gorm:"column:created_by"`
|
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)
|
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
|
||||||
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
|
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
|
||||||
UpdateInquiryStatus(ctx mycontext.Context, id string, status string) 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 {
|
type orderRepository struct {
|
||||||
@ -61,13 +81,13 @@ func (r *orderRepository) Create(ctx mycontext.Context, order *entity.Order) (*e
|
|||||||
item.ID = itemDB.ID
|
item.ID = itemDB.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
if order.InProgressOrderID != "" {
|
if order.InProgressOrderID != 0 {
|
||||||
if err := tx.Where("in_progress_order_id = ?", order.InProgressOrderID).Delete(&models.InProgressOrderItemDB{}).Error; err != nil {
|
if err := tx.Where("order_id = ?", order.InProgressOrderID).Delete(&models.OrderItemDB{}).Error; err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return nil, errors.Wrap(err, "failed to delete in-progress order items")
|
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()
|
tx.Rollback()
|
||||||
return nil, errors.Wrap(err, "failed to delete in-progress order")
|
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,
|
InquiryID: inquiryDB.ID,
|
||||||
ItemID: item.ItemID,
|
ItemID: item.ItemID,
|
||||||
ItemType: item.ItemType,
|
ItemType: item.ItemType,
|
||||||
|
ItemName: item.ItemName,
|
||||||
Price: item.Price,
|
Price: item.Price,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
CreatedBy: item.CreatedBy,
|
CreatedBy: item.CreatedBy,
|
||||||
@ -162,6 +183,7 @@ func (r *orderRepository) FindInquiryByID(ctx mycontext.Context, id string) (*en
|
|||||||
orderItems = append(orderItems, entity.OrderItem{
|
orderItems = append(orderItems, entity.OrderItem{
|
||||||
ItemID: itemDB.ItemID,
|
ItemID: itemDB.ItemID,
|
||||||
ItemType: itemDB.ItemType,
|
ItemType: itemDB.ItemType,
|
||||||
|
ItemName: itemDB.ItemName,
|
||||||
Price: itemDB.Price,
|
Price: itemDB.Price,
|
||||||
Quantity: itemDB.Quantity,
|
Quantity: itemDB.Quantity,
|
||||||
CreatedBy: itemDB.CreatedBy,
|
CreatedBy: itemDB.CreatedBy,
|
||||||
@ -196,38 +218,46 @@ func (r *orderRepository) UpdateInquiryStatus(ctx mycontext.Context, id string,
|
|||||||
|
|
||||||
func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
|
func (r *orderRepository) toOrderDBModel(order *entity.Order) models.OrderDB {
|
||||||
return models.OrderDB{
|
return models.OrderDB{
|
||||||
ID: order.ID,
|
ID: order.ID,
|
||||||
PartnerID: order.PartnerID,
|
PartnerID: order.PartnerID,
|
||||||
CustomerID: order.CustomerID,
|
CustomerID: order.CustomerID,
|
||||||
InquiryID: order.InquiryID,
|
InquiryID: order.InquiryID,
|
||||||
Status: order.Status,
|
Status: order.Status,
|
||||||
Amount: order.Amount,
|
Amount: order.Amount,
|
||||||
Fee: order.Fee,
|
Tax: order.Tax,
|
||||||
Total: order.Total,
|
Total: order.Total,
|
||||||
PaymentType: order.PaymentType,
|
PaymentType: order.PaymentType,
|
||||||
Source: order.Source,
|
Source: order.Source,
|
||||||
CreatedBy: order.CreatedBy,
|
CreatedBy: order.CreatedBy,
|
||||||
CreatedAt: order.CreatedAt,
|
CreatedAt: order.CreatedAt,
|
||||||
UpdatedAt: order.UpdatedAt,
|
UpdatedAt: order.UpdatedAt,
|
||||||
|
OrderType: order.OrderType,
|
||||||
|
TableNumber: order.TableNumber,
|
||||||
|
PaymentProvider: order.PaymentProvider,
|
||||||
|
CustomerName: order.CustomerName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Order {
|
func (r *orderRepository) toDomainOrderModel(dbModel *models.OrderDB) *entity.Order {
|
||||||
return &entity.Order{
|
return &entity.Order{
|
||||||
ID: dbModel.ID,
|
ID: dbModel.ID,
|
||||||
PartnerID: dbModel.PartnerID,
|
PartnerID: dbModel.PartnerID,
|
||||||
CustomerID: dbModel.CustomerID,
|
CustomerID: dbModel.CustomerID,
|
||||||
InquiryID: dbModel.InquiryID,
|
InquiryID: dbModel.InquiryID,
|
||||||
Status: dbModel.Status,
|
Status: dbModel.Status,
|
||||||
Amount: dbModel.Amount,
|
Amount: dbModel.Amount,
|
||||||
Fee: dbModel.Fee,
|
Tax: dbModel.Tax,
|
||||||
Total: dbModel.Total,
|
Total: dbModel.Total,
|
||||||
PaymentType: dbModel.PaymentType,
|
PaymentType: dbModel.PaymentType,
|
||||||
Source: dbModel.Source,
|
Source: dbModel.Source,
|
||||||
CreatedBy: dbModel.CreatedBy,
|
CreatedBy: dbModel.CreatedBy,
|
||||||
CreatedAt: dbModel.CreatedAt,
|
CreatedAt: dbModel.CreatedAt,
|
||||||
UpdatedAt: dbModel.UpdatedAt,
|
UpdatedAt: dbModel.UpdatedAt,
|
||||||
OrderItems: []entity.OrderItem{},
|
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,
|
OrderID: item.OrderID,
|
||||||
ItemID: item.ItemID,
|
ItemID: item.ItemID,
|
||||||
ItemType: item.ItemType,
|
ItemType: item.ItemType,
|
||||||
|
ItemName: item.ItemName,
|
||||||
Price: item.Price,
|
Price: item.Price,
|
||||||
Quantity: item.Quantity,
|
Quantity: item.Quantity,
|
||||||
CreatedBy: item.CreatedBy,
|
CreatedBy: item.CreatedBy,
|
||||||
@ -254,6 +285,7 @@ func (r *orderRepository) toDomainOrderItemModel(dbModel *models.OrderItemDB) *e
|
|||||||
Quantity: dbModel.Quantity,
|
Quantity: dbModel.Quantity,
|
||||||
CreatedBy: dbModel.CreatedBy,
|
CreatedBy: dbModel.CreatedBy,
|
||||||
CreatedAt: dbModel.CreatedAt,
|
CreatedAt: dbModel.CreatedAt,
|
||||||
|
ItemName: dbModel.ItemName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +296,7 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo
|
|||||||
CustomerID: &inquiry.CustomerID,
|
CustomerID: &inquiry.CustomerID,
|
||||||
Status: inquiry.Status,
|
Status: inquiry.Status,
|
||||||
Amount: inquiry.Amount,
|
Amount: inquiry.Amount,
|
||||||
Fee: inquiry.Fee,
|
Tax: inquiry.Tax,
|
||||||
Total: inquiry.Total,
|
Total: inquiry.Total,
|
||||||
PaymentType: inquiry.PaymentType,
|
PaymentType: inquiry.PaymentType,
|
||||||
Source: inquiry.Source,
|
Source: inquiry.Source,
|
||||||
@ -283,18 +315,22 @@ func (r *orderRepository) toOrderInquiryDBModel(inquiry *entity.OrderInquiry) mo
|
|||||||
|
|
||||||
func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry {
|
func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiryDB) *entity.OrderInquiry {
|
||||||
inquiry := &entity.OrderInquiry{
|
inquiry := &entity.OrderInquiry{
|
||||||
ID: dbModel.ID,
|
ID: dbModel.ID,
|
||||||
PartnerID: dbModel.PartnerID,
|
PartnerID: dbModel.PartnerID,
|
||||||
Status: dbModel.Status,
|
Status: dbModel.Status,
|
||||||
Amount: dbModel.Amount,
|
Amount: dbModel.Amount,
|
||||||
Fee: dbModel.Fee,
|
Tax: dbModel.Tax,
|
||||||
Total: dbModel.Total,
|
Total: dbModel.Total,
|
||||||
PaymentType: dbModel.PaymentType,
|
PaymentType: dbModel.PaymentType,
|
||||||
Source: dbModel.Source,
|
Source: dbModel.Source,
|
||||||
CreatedBy: dbModel.CreatedBy,
|
CreatedBy: dbModel.CreatedBy,
|
||||||
CreatedAt: dbModel.CreatedAt,
|
CreatedAt: dbModel.CreatedAt,
|
||||||
ExpiresAt: dbModel.ExpiresAt,
|
ExpiresAt: dbModel.ExpiresAt,
|
||||||
OrderItems: []entity.OrderItem{},
|
OrderItems: []entity.OrderItem{},
|
||||||
|
OrderType: dbModel.OrderType,
|
||||||
|
CustomerName: dbModel.CustomerName,
|
||||||
|
PaymentProvider: dbModel.PaymentProvider,
|
||||||
|
TableNumber: dbModel.TableNumber,
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbModel.CustomerID != nil {
|
if dbModel.CustomerID != nil {
|
||||||
@ -305,3 +341,486 @@ func (r *orderRepository) toDomainOrderInquiryModel(dbModel *models.OrderInquiry
|
|||||||
|
|
||||||
return inquiry
|
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
|
ProductRepo ProductRepository
|
||||||
TransactionRepo TransactionRepo
|
TransactionRepo TransactionRepo
|
||||||
MemberRepository MemberRepository
|
MemberRepository MemberRepository
|
||||||
|
PartnerSetting PartnerSettingsRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepoManagerImpl(db *gorm.DB, cfg *config.Config) *RepoManagerImpl {
|
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),
|
TransactionRepo: NewTransactionRepository(db),
|
||||||
MemberRepository: NewMemberRepository(db),
|
MemberRepository: NewMemberRepository(db),
|
||||||
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
InProgressOrderRepo: NewInProgressOrderRepository(db),
|
||||||
|
PartnerSetting: NewPartnerSettingsRepository(db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"enaklo-pos-be/internal/handlers/http/customerauth"
|
"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/handlers/http/discovery"
|
||||||
"enaklo-pos-be/internal/middlewares"
|
"enaklo-pos-be/internal/middlewares"
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ func RegisterCustomerRoutes(app *app.Server, serviceManager *services.ServiceMan
|
|||||||
serverRoutes := []HTTPHandlerRoutes{
|
serverRoutes := []HTTPHandlerRoutes{
|
||||||
discovery.NewHandler(serviceManager.DiscoverService),
|
discovery.NewHandler(serviceManager.DiscoverService),
|
||||||
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc),
|
customerauth.NewAuthHandler(serviceManager.AuthSvc, serviceManager.UserSvc, serviceManager.CustomerV2Svc),
|
||||||
customerorder.NewHandler(serviceManager.OrderSvc),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, handler := range serverRoutes {
|
for _, handler := range serverRoutes {
|
||||||
|
|||||||
@ -4,9 +4,6 @@ import (
|
|||||||
http2 "enaklo-pos-be/internal/handlers/http"
|
http2 "enaklo-pos-be/internal/handlers/http"
|
||||||
"enaklo-pos-be/internal/handlers/http/balance"
|
"enaklo-pos-be/internal/handlers/http/balance"
|
||||||
"enaklo-pos-be/internal/handlers/http/license"
|
"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/oss"
|
||||||
"enaklo-pos-be/internal/handlers/http/partner"
|
"enaklo-pos-be/internal/handlers/http/partner"
|
||||||
"enaklo-pos-be/internal/handlers/http/product"
|
"enaklo-pos-be/internal/handlers/http/product"
|
||||||
@ -54,15 +51,12 @@ func RegisterPrivateRoutes(app *app.Server, serviceManager *services.ServiceMana
|
|||||||
user.NewHandler(serviceManager.UserSvc),
|
user.NewHandler(serviceManager.UserSvc),
|
||||||
studio.NewStudioHandler(serviceManager.StudioSvc),
|
studio.NewStudioHandler(serviceManager.StudioSvc),
|
||||||
product.NewHandler(serviceManager.ProductSvc),
|
product.NewHandler(serviceManager.ProductSvc),
|
||||||
order.NewHandler(serviceManager.OrderSvc),
|
|
||||||
oss.NewOssHandler(serviceManager.OSSSvc),
|
oss.NewOssHandler(serviceManager.OSSSvc),
|
||||||
partner.NewHandler(serviceManager.PartnerSvc),
|
partner.NewHandler(serviceManager.PartnerSvc),
|
||||||
site.NewHandler(serviceManager.SiteSvc),
|
site.NewHandler(serviceManager.SiteSvc),
|
||||||
mdtrns.NewHandler(serviceManager.OrderSvc),
|
|
||||||
license.NewHandler(serviceManager.LicenseSvc),
|
license.NewHandler(serviceManager.LicenseSvc),
|
||||||
transaction.New(serviceManager.Transaction),
|
transaction.New(serviceManager.Transaction),
|
||||||
balance.NewHandler(serviceManager.Balance),
|
balance.NewHandler(serviceManager.Balance),
|
||||||
linkqu.NewHandler(serviceManager.OrderSvc),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, handler := range serverRoutes {
|
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"
|
"enaklo-pos-be/internal/services/discovery"
|
||||||
service "enaklo-pos-be/internal/services/license"
|
service "enaklo-pos-be/internal/services/license"
|
||||||
"enaklo-pos-be/internal/services/member"
|
"enaklo-pos-be/internal/services/member"
|
||||||
"enaklo-pos-be/internal/services/order"
|
|
||||||
"enaklo-pos-be/internal/services/oss"
|
"enaklo-pos-be/internal/services/oss"
|
||||||
"enaklo-pos-be/internal/services/partner"
|
"enaklo-pos-be/internal/services/partner"
|
||||||
"enaklo-pos-be/internal/services/product"
|
"enaklo-pos-be/internal/services/product"
|
||||||
@ -18,6 +17,7 @@ import (
|
|||||||
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
customerSvc "enaklo-pos-be/internal/services/v2/customer"
|
||||||
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
"enaklo-pos-be/internal/services/v2/inprogress_order"
|
||||||
orderSvc "enaklo-pos-be/internal/services/v2/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"
|
productSvc "enaklo-pos-be/internal/services/v2/product"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -35,7 +35,6 @@ type ServiceManagerImpl struct {
|
|||||||
UserSvc User
|
UserSvc User
|
||||||
StudioSvc Studio
|
StudioSvc Studio
|
||||||
ProductSvc Product
|
ProductSvc Product
|
||||||
OrderSvc Order
|
|
||||||
OSSSvc OSSService
|
OSSSvc OSSService
|
||||||
PartnerSvc Partner
|
PartnerSvc Partner
|
||||||
SiteSvc Site
|
SiteSvc Site
|
||||||
@ -55,15 +54,16 @@ func NewServiceManagerImpl(cfg *config.Config, repo *repository.RepoManagerImpl)
|
|||||||
|
|
||||||
custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService)
|
custSvcV2 := customerSvc.New(repo.CustomerRepo, repo.EmailService)
|
||||||
productSvcV2 := productSvc.New(repo.ProductRepo)
|
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{
|
return &ServiceManagerImpl{
|
||||||
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
|
AuthSvc: auth.New(repo.Auth, repo.Crypto, repo.User, repo.EmailService, cfg.Email, repo.Trx, repo.License),
|
||||||
EventSvc: event.NewEventService(repo.Event),
|
EventSvc: event.NewEventService(repo.Event),
|
||||||
UserSvc: users.NewUserService(repo.User),
|
UserSvc: users.NewUserService(repo.User),
|
||||||
StudioSvc: studio.NewStudioService(repo.Studio),
|
StudioSvc: studio.NewStudioService(repo.Studio),
|
||||||
ProductSvc: product.NewProductService(repo.Product),
|
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),
|
OSSSvc: oss.NewOSSService(repo.OSS),
|
||||||
PartnerSvc: partner.NewPartnerService(
|
PartnerSvc: partner.NewPartnerService(
|
||||||
repo.Partner, users.NewUserService(repo.User), repo.Trx, repo.Wallet, repo.User),
|
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),
|
Transaction: transaction.New(repo.Transaction, repo.Wallet, repo.Trx),
|
||||||
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
|
Balance: balance.NewBalanceService(repo.Wallet, repo.Trx, repo.Crypto, &cfg.Withdraw, repo.Transaction),
|
||||||
DiscoverService: discovery.NewDiscoveryService(repo.Site, cfg.Discovery, repo.Product),
|
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),
|
MemberRegistrationSvc: member.NewMemberRegistrationService(repo.MemberRepository, repo.EmailService, custSvcV2),
|
||||||
CustomerV2Svc: custSvcV2,
|
CustomerV2Svc: custSvcV2,
|
||||||
InProgressSvc: inprogressOrder,
|
InProgressSvc: inprogressOrder,
|
||||||
|
|||||||
@ -3,28 +3,98 @@ package inprogress_order
|
|||||||
import (
|
import (
|
||||||
"enaklo-pos-be/internal/common/logger"
|
"enaklo-pos-be/internal/common/logger"
|
||||||
"enaklo-pos-be/internal/common/mycontext"
|
"enaklo-pos-be/internal/common/mycontext"
|
||||||
|
order2 "enaklo-pos-be/internal/constants/order"
|
||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
"enaklo-pos-be/internal/repository"
|
"enaklo-pos-be/internal/services/v2/order"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InProgressOrderService interface {
|
type InProgressOrderService interface {
|
||||||
Save(ctx mycontext.Context, order *entity.InProgressOrder) (*entity.InProgressOrder, error)
|
Save(ctx mycontext.Context, order *entity.OrderRequest) (*entity.Order, error)
|
||||||
GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, 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 {
|
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{
|
return &inProgressOrderSvc{
|
||||||
repo: repo,
|
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)
|
createdOrder, err := s.repo.CreateOrUpdate(ctx, order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("failed to create in-progress order",
|
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
|
return createdOrder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.InProgressOrder, error) {
|
func (s *inProgressOrderSvc) GetOrdersByPartnerID(ctx mycontext.Context, partnerID int64, limit, offset int) ([]*entity.Order, error) {
|
||||||
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset)
|
orders, err := s.repo.GetListByPartnerID(ctx, partnerID, limit, offset, order2.Pending.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
|
logger.ContextLogger(ctx).Error("failed to get in-progress orders by partner ID",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
|||||||
@ -7,11 +7,12 @@ import (
|
|||||||
"enaklo-pos-be/internal/constants"
|
"enaklo-pos-be/internal/constants"
|
||||||
"enaklo-pos-be/internal/entity"
|
"enaklo-pos-be/internal/entity"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
||||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -23,7 +24,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
|||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -43,7 +44,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
|||||||
req.PartnerID,
|
req.PartnerID,
|
||||||
customerID,
|
customerID,
|
||||||
orderCalculation.Subtotal,
|
orderCalculation.Subtotal,
|
||||||
orderCalculation.Fee,
|
orderCalculation.Tax,
|
||||||
orderCalculation.Total,
|
orderCalculation.Total,
|
||||||
req.PaymentMethod,
|
req.PaymentMethod,
|
||||||
req.Source,
|
req.Source,
|
||||||
@ -79,7 +80,7 @@ func (s *orderSvc) CreateOrderInquiry(ctx mycontext.Context,
|
|||||||
}, nil
|
}, 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 productIDs []int64
|
||||||
var filteredItems []entity.OrderItemRequest
|
var filteredItems []entity.OrderItemRequest
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ func (s *orderSvc) validateOrderItems(ctx mycontext.Context, items []entity.Orde
|
|||||||
return productIDs, filteredItems, nil
|
return productIDs, filteredItems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *orderSvc) calculateOrderTotals(
|
func (s *orderSvc) CalculateOrderTotals(
|
||||||
ctx mycontext.Context,
|
ctx mycontext.Context,
|
||||||
items []entity.OrderItemRequest,
|
items []entity.OrderItemRequest,
|
||||||
productDetails *entity.ProductDetails,
|
productDetails *entity.ProductDetails,
|
||||||
@ -114,12 +115,23 @@ func (s *orderSvc) calculateOrderTotals(
|
|||||||
subtotal += product.Price * float64(item.Quantity)
|
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{
|
return &entity.OrderCalculation{
|
||||||
Subtotal: subtotal,
|
Subtotal: subtotal,
|
||||||
Fee: fee,
|
Tax: tax,
|
||||||
Total: subtotal + fee,
|
Total: subtotal + tax,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,3 +157,79 @@ func (s *orderSvc) validateInquiry(ctx mycontext.Context, token string) (*entity
|
|||||||
|
|
||||||
return inquiry, nil
|
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,
|
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)
|
inquiry, err := s.validateInquiry(ctx, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -12,6 +12,24 @@ type Repository interface {
|
|||||||
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
|
CreateInquiry(ctx mycontext.Context, inquiry *entity.OrderInquiry) (*entity.OrderInquiry, error)
|
||||||
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
|
FindInquiryByID(ctx mycontext.Context, id string) (*entity.OrderInquiry, error)
|
||||||
UpdateInquiryStatus(ctx mycontext.Context, id string, status string) 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 {
|
type ProductService interface {
|
||||||
@ -42,21 +60,65 @@ type Service interface {
|
|||||||
CreateOrderInquiry(ctx mycontext.Context,
|
CreateOrderInquiry(ctx mycontext.Context,
|
||||||
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
|
req *entity.OrderRequest) (*entity.OrderInquiryResponse, error)
|
||||||
ExecuteOrderInquiry(ctx mycontext.Context,
|
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 {
|
type Config interface {
|
||||||
GetOrderFee(source string) float64
|
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 {
|
type orderSvc struct {
|
||||||
repo Repository
|
repo Repository
|
||||||
product ProductService
|
product ProductService
|
||||||
customer CustomerService
|
customer CustomerService
|
||||||
transaction TransactionService
|
transaction TransactionService
|
||||||
crypt CryptService
|
crypt CryptService
|
||||||
cfg Config
|
cfg Config
|
||||||
notification NotificationService
|
notification NotificationService
|
||||||
|
partnerSetting PartnerSettings
|
||||||
|
inprogressOrder InProgressOrderRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@ -67,14 +129,16 @@ func New(
|
|||||||
crypt CryptService,
|
crypt CryptService,
|
||||||
cfg Config,
|
cfg Config,
|
||||||
notification NotificationService,
|
notification NotificationService,
|
||||||
|
partnerSetting PartnerSettings,
|
||||||
) Service {
|
) Service {
|
||||||
return &orderSvc{
|
return &orderSvc{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
product: product,
|
product: product,
|
||||||
customer: customer,
|
customer: customer,
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
crypt: crypt,
|
crypt: crypt,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
notification: notification,
|
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