Fix Split Bill
This commit is contained in:
parent
3696451dc6
commit
93a3b29ae9
@ -135,6 +135,7 @@ type repositories struct {
|
|||||||
orderRepo *repository.OrderRepositoryImpl
|
orderRepo *repository.OrderRepositoryImpl
|
||||||
orderItemRepo *repository.OrderItemRepositoryImpl
|
orderItemRepo *repository.OrderItemRepositoryImpl
|
||||||
paymentRepo *repository.PaymentRepositoryImpl
|
paymentRepo *repository.PaymentRepositoryImpl
|
||||||
|
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
|
||||||
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
||||||
fileRepo *repository.FileRepositoryImpl
|
fileRepo *repository.FileRepositoryImpl
|
||||||
customerRepo *repository.CustomerRepository
|
customerRepo *repository.CustomerRepository
|
||||||
@ -158,6 +159,7 @@ func (a *App) initRepositories() *repositories {
|
|||||||
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
||||||
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
||||||
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
||||||
|
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
|
||||||
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
||||||
fileRepo: repository.NewFileRepositoryImpl(a.db),
|
fileRepo: repository.NewFileRepositoryImpl(a.db),
|
||||||
customerRepo: repository.NewCustomerRepository(a.db),
|
customerRepo: repository.NewCustomerRepository(a.db),
|
||||||
@ -199,7 +201,7 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
|
productProcessor: processor.NewProductProcessorImpl(repos.productRepo, repos.categoryRepo, repos.productVariantRepo, repos.inventoryRepo, repos.outletRepo),
|
||||||
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
||||||
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo),
|
inventoryProcessor: processor.NewInventoryProcessorImpl(repos.inventoryRepo, repos.productRepo, repos.outletRepo),
|
||||||
orderProcessor: processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo),
|
orderProcessor: processor.NewOrderProcessorImpl(repos.orderRepo, repos.orderItemRepo, repos.paymentRepo, repos.paymentOrderItemRepo, repos.productRepo, repos.paymentMethodRepo, repos.inventoryRepo, repos.inventoryMovementRepo, repos.productVariantRepo, repos.outletRepo, repos.customerRepo),
|
||||||
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
|
paymentMethodProcessor: processor.NewPaymentMethodProcessorImpl(repos.paymentMethodRepo),
|
||||||
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
||||||
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
||||||
|
|||||||
@ -67,12 +67,27 @@ type OrderResponse struct {
|
|||||||
TaxAmount float64 `json:"tax_amount"`
|
TaxAmount float64 `json:"tax_amount"`
|
||||||
DiscountAmount float64 `json:"discount_amount"`
|
DiscountAmount float64 `json:"discount_amount"`
|
||||||
TotalAmount float64 `json:"total_amount"`
|
TotalAmount float64 `json:"total_amount"`
|
||||||
|
TotalCost float64 `json:"total_cost"`
|
||||||
|
RemainingAmount float64 `json:"remaining_amount"`
|
||||||
|
PaymentStatus string `json:"payment_status"`
|
||||||
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
|
IsVoid bool `json:"is_void"`
|
||||||
|
IsRefund bool `json:"is_refund"`
|
||||||
|
VoidReason *string `json:"void_reason,omitempty"`
|
||||||
|
VoidedAt *time.Time `json:"voided_at,omitempty"`
|
||||||
|
VoidedBy *uuid.UUID `json:"voided_by,omitempty"`
|
||||||
|
RefundReason *string `json:"refund_reason,omitempty"`
|
||||||
|
RefundedAt *time.Time `json:"refunded_at,omitempty"`
|
||||||
|
RefundedBy *uuid.UUID `json:"refunded_by,omitempty"`
|
||||||
Notes *string `json:"notes"`
|
Notes *string `json:"notes"`
|
||||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
||||||
IsRefund bool `json:"is_refund"`
|
Payments []PaymentResponse `json:"payments,omitempty"`
|
||||||
|
TotalPaid float64 `json:"total_paid"`
|
||||||
|
PaymentCount int `json:"payment_count"`
|
||||||
|
SplitType *string `json:"split_type,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderItemResponse struct {
|
type OrderItemResponse struct {
|
||||||
@ -124,6 +139,7 @@ type ListOrdersRequest struct {
|
|||||||
|
|
||||||
type ListOrdersResponse struct {
|
type ListOrdersResponse struct {
|
||||||
Orders []OrderResponse `json:"orders"`
|
Orders []OrderResponse `json:"orders"`
|
||||||
|
Payments []PaymentResponse `json:"payments"`
|
||||||
TotalCount int `json:"total_count"`
|
TotalCount int `json:"total_count"`
|
||||||
Page int `json:"page"`
|
Page int `json:"page"`
|
||||||
Limit int `json:"limit"`
|
Limit int `json:"limit"`
|
||||||
@ -152,7 +168,6 @@ type SetOrderCustomerResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment-related contracts
|
|
||||||
type CreatePaymentRequest struct {
|
type CreatePaymentRequest struct {
|
||||||
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||||
PaymentMethodID uuid.UUID `json:"payment_method_id" validate:"required"`
|
PaymentMethodID uuid.UUID `json:"payment_method_id" validate:"required"`
|
||||||
@ -160,6 +175,7 @@ type CreatePaymentRequest struct {
|
|||||||
TransactionID *string `json:"transaction_id,omitempty" validate:"omitempty"`
|
TransactionID *string `json:"transaction_id,omitempty" validate:"omitempty"`
|
||||||
SplitNumber int `json:"split_number,omitempty" validate:"omitempty,min=1"`
|
SplitNumber int `json:"split_number,omitempty" validate:"omitempty,min=1"`
|
||||||
SplitTotal int `json:"split_total,omitempty" validate:"omitempty,min=1"`
|
SplitTotal int `json:"split_total,omitempty" validate:"omitempty,min=1"`
|
||||||
|
SplitType *string `json:"split_type,omitempty" validate:"omitempty,oneof=AMOUNT ITEM"`
|
||||||
SplitDescription *string `json:"split_description,omitempty" validate:"omitempty,max=255"`
|
SplitDescription *string `json:"split_description,omitempty" validate:"omitempty,max=255"`
|
||||||
PaymentOrderItems []CreatePaymentOrderItemRequest `json:"payment_order_items,omitempty" validate:"omitempty,dive"`
|
PaymentOrderItems []CreatePaymentOrderItemRequest `json:"payment_order_items,omitempty" validate:"omitempty,dive"`
|
||||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||||
@ -174,11 +190,14 @@ type PaymentResponse struct {
|
|||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
OrderID uuid.UUID `json:"order_id"`
|
OrderID uuid.UUID `json:"order_id"`
|
||||||
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
||||||
|
PaymentMethodName string `json:"payment_method_name"`
|
||||||
|
PaymentMethodType string `json:"payment_method_type"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
TransactionID *string `json:"transaction_id,omitempty"`
|
TransactionID *string `json:"transaction_id,omitempty"`
|
||||||
SplitNumber int `json:"split_number"`
|
SplitNumber int `json:"split_number"`
|
||||||
SplitTotal int `json:"split_total"`
|
SplitTotal int `json:"split_total"`
|
||||||
|
SplitType *string `json:"split_type,omitempty"`
|
||||||
SplitDescription *string `json:"split_description,omitempty"`
|
SplitDescription *string `json:"split_description,omitempty"`
|
||||||
RefundAmount float64 `json:"refund_amount"`
|
RefundAmount float64 `json:"refund_amount"`
|
||||||
RefundReason *string `json:"refund_reason,omitempty"`
|
RefundReason *string `json:"refund_reason,omitempty"`
|
||||||
@ -216,3 +235,33 @@ type RefundPaymentRequest struct {
|
|||||||
RefundAmount float64 `json:"refund_amount" validate:"required,min=0"`
|
RefundAmount float64 `json:"refund_amount" validate:"required,min=0"`
|
||||||
Reason string `json:"reason" validate:"omitempty,max=255"`
|
Reason string `json:"reason" validate:"omitempty,max=255"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SplitBillRequest struct {
|
||||||
|
OrderID uuid.UUID `json:"order_id" validate:"required"`
|
||||||
|
OrganizationID uuid.UUID `json:"organization_id"`
|
||||||
|
PaymentMethodID uuid.UUID `json:"payment_method_id" validate:"required"`
|
||||||
|
CustomerID uuid.UUID `json:"customer_id"`
|
||||||
|
Type string `json:"type" validate:"required,oneof=ITEM AMOUNT"`
|
||||||
|
Items []SplitBillItemRequest `json:"items,omitempty" validate:"required_if=Type ITEM,dive"`
|
||||||
|
Amount float64 `json:"amount,omitempty" validate:"required_if=Type AMOUNT,min=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillItemRequest struct {
|
||||||
|
OrderItemID uuid.UUID `json:"order_item_id" validate:"required"`
|
||||||
|
Amount float64 `json:"amount" validate:"required,min=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillResponse struct {
|
||||||
|
PaymentID uuid.UUID `json:"payment_id"`
|
||||||
|
OrderID uuid.UUID `json:"order_id"`
|
||||||
|
CustomerID uuid.UUID `json:"customer_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Items []SplitBillItemResponse `json:"items,omitempty"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillItemResponse struct {
|
||||||
|
OrderItemID uuid.UUID `json:"order_item_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ const (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
PaymentStatusPending PaymentStatus = "pending"
|
PaymentStatusPending PaymentStatus = "pending"
|
||||||
|
PaymentStatusPartial PaymentStatus = "partial"
|
||||||
PaymentStatusCompleted PaymentStatus = "completed"
|
PaymentStatusCompleted PaymentStatus = "completed"
|
||||||
PaymentStatusFailed PaymentStatus = "failed"
|
PaymentStatusFailed PaymentStatus = "failed"
|
||||||
PaymentStatusRefunded PaymentStatus = "refunded"
|
PaymentStatusRefunded PaymentStatus = "refunded"
|
||||||
@ -49,6 +50,7 @@ type Order struct {
|
|||||||
DiscountAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"discount_amount" validate:"min=0"`
|
DiscountAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"discount_amount" validate:"min=0"`
|
||||||
TotalAmount float64 `gorm:"type:decimal(10,2);not null" json:"total_amount" validate:"required,min=0"`
|
TotalAmount float64 `gorm:"type:decimal(10,2);not null" json:"total_amount" validate:"required,min=0"`
|
||||||
TotalCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"total_cost"`
|
TotalCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"total_cost"`
|
||||||
|
RemainingAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"remaining_amount"`
|
||||||
PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"`
|
PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"`
|
||||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||||
IsVoid bool `gorm:"default:false" json:"is_void"`
|
IsVoid bool `gorm:"default:false" json:"is_void"`
|
||||||
|
|||||||
@ -50,6 +50,13 @@ const (
|
|||||||
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
|
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SplitType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SplitTypeAmount SplitType = "AMOUNT"
|
||||||
|
SplitTypeItem SplitType = "ITEM"
|
||||||
|
)
|
||||||
|
|
||||||
type Payment struct {
|
type Payment struct {
|
||||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
|
OrderID uuid.UUID `gorm:"type:uuid;not null;index" json:"order_id" validate:"required"`
|
||||||
@ -59,6 +66,7 @@ type Payment struct {
|
|||||||
TransactionID *string `gorm:"size:255" json:"transaction_id"`
|
TransactionID *string `gorm:"size:255" json:"transaction_id"`
|
||||||
SplitNumber int `gorm:"default:1" json:"split_number"`
|
SplitNumber int `gorm:"default:1" json:"split_number"`
|
||||||
SplitTotal int `gorm:"default:1" json:"split_total"`
|
SplitTotal int `gorm:"default:1" json:"split_total"`
|
||||||
|
SplitType *SplitType `gorm:"size:20" json:"split_type,omitempty"`
|
||||||
SplitDescription *string `gorm:"size:255" json:"split_description,omitempty"`
|
SplitDescription *string `gorm:"size:255" json:"split_description,omitempty"`
|
||||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||||
|
|||||||
@ -265,7 +265,6 @@ func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
// Parse order ID from URL parameter
|
|
||||||
orderIDStr := c.Param("id")
|
orderIDStr := c.Param("id")
|
||||||
orderID, err := uuid.Parse(orderIDStr)
|
orderID, err := uuid.Parse(orderIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -273,24 +272,47 @@ func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse request body
|
|
||||||
var req contract.SetOrderCustomerRequest
|
var req contract.SetOrderCustomerRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform contract to model
|
|
||||||
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
||||||
|
|
||||||
// Call service
|
|
||||||
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID)
|
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform model to contract
|
|
||||||
contractResp := transformer.SetOrderCustomerModelToContract(response)
|
contractResp := transformer.SetOrderCustomerModelToContract(response)
|
||||||
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::SetOrderCustomer")
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::SetOrderCustomer")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *OrderHandler) SplitBill(c *gin.Context) {
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
|
var req contract.SplitBillRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("invalid_request", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.validator.Validate(&req); err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("validation_failed", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.OrganizationID = contextInfo.OrganizationID
|
||||||
|
modelReq := transformer.SplitBillContractToModel(&req)
|
||||||
|
response, err := h.orderService.SplitBill(c.Request.Context(), modelReq)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SplitBill", err.Error())}), "OrderHandler::SplitBill")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contractResp := transformer.SplitBillModelToContract(response)
|
||||||
|
util.HandleResponse(c.Writer, c.Request, contract.BuildSuccessResponse(contractResp), "OrderHandler::SplitBill")
|
||||||
|
}
|
||||||
|
|||||||
@ -36,7 +36,6 @@ func (h *OutletHandler) ListOutlets(c *gin.Context) {
|
|||||||
OrganizationID: contextInfo.OrganizationID,
|
OrganizationID: contextInfo.OrganizationID,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse query parameters
|
|
||||||
if pageStr := c.Query("page"); pageStr != "" {
|
if pageStr := c.Query("page"); pageStr != "" {
|
||||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||||
req.Page = page
|
req.Page = page
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Entity to Response mappers
|
|
||||||
func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
|
func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
|
||||||
if order == nil {
|
if order == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -29,6 +28,7 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
|
|||||||
DiscountAmount: order.DiscountAmount,
|
DiscountAmount: order.DiscountAmount,
|
||||||
TotalAmount: order.TotalAmount,
|
TotalAmount: order.TotalAmount,
|
||||||
TotalCost: order.TotalCost,
|
TotalCost: order.TotalCost,
|
||||||
|
RemainingAmount: order.RemainingAmount,
|
||||||
PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
|
PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
|
||||||
RefundAmount: order.RefundAmount,
|
RefundAmount: order.RefundAmount,
|
||||||
IsVoid: order.IsVoid,
|
IsVoid: order.IsVoid,
|
||||||
@ -44,6 +44,29 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
|
|||||||
UpdatedAt: order.UpdatedAt,
|
UpdatedAt: order.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate payment summary and determine split type
|
||||||
|
var totalPaid float64
|
||||||
|
var paymentCount int
|
||||||
|
var splitType *string
|
||||||
|
|
||||||
|
if order.Payments != nil {
|
||||||
|
paymentCount = len(order.Payments)
|
||||||
|
for _, payment := range order.Payments {
|
||||||
|
if payment.Status == entities.PaymentTransactionStatusCompleted {
|
||||||
|
totalPaid += payment.Amount
|
||||||
|
}
|
||||||
|
// Determine split type from the first split payment
|
||||||
|
if splitType == nil && payment.SplitType != nil && payment.SplitTotal > 1 {
|
||||||
|
st := string(*payment.SplitType)
|
||||||
|
splitType = &st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.TotalPaid = totalPaid
|
||||||
|
response.PaymentCount = paymentCount
|
||||||
|
response.SplitType = splitType
|
||||||
|
|
||||||
// Map order items
|
// Map order items
|
||||||
if order.OrderItems != nil {
|
if order.OrderItems != nil {
|
||||||
response.OrderItems = make([]models.OrderItemResponse, len(order.OrderItems))
|
response.OrderItems = make([]models.OrderItemResponse, len(order.OrderItems))
|
||||||
@ -119,6 +142,7 @@ func PaymentEntityToResponse(payment *entities.Payment) *models.PaymentResponse
|
|||||||
TransactionID: payment.TransactionID,
|
TransactionID: payment.TransactionID,
|
||||||
SplitNumber: payment.SplitNumber,
|
SplitNumber: payment.SplitNumber,
|
||||||
SplitTotal: payment.SplitTotal,
|
SplitTotal: payment.SplitTotal,
|
||||||
|
SplitType: (*string)(payment.SplitType),
|
||||||
SplitDescription: payment.SplitDescription,
|
SplitDescription: payment.SplitDescription,
|
||||||
RefundAmount: payment.RefundAmount,
|
RefundAmount: payment.RefundAmount,
|
||||||
RefundReason: payment.RefundReason,
|
RefundReason: payment.RefundReason,
|
||||||
@ -129,6 +153,12 @@ func PaymentEntityToResponse(payment *entities.Payment) *models.PaymentResponse
|
|||||||
UpdatedAt: payment.UpdatedAt,
|
UpdatedAt: payment.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add payment method information if available
|
||||||
|
if payment.PaymentMethod.ID != uuid.Nil {
|
||||||
|
response.PaymentMethodName = payment.PaymentMethod.Name
|
||||||
|
response.PaymentMethodType = constants.PaymentMethodType(payment.PaymentMethod.Type)
|
||||||
|
}
|
||||||
|
|
||||||
// Map payment order items
|
// Map payment order items
|
||||||
if payment.PaymentOrderItems != nil {
|
if payment.PaymentOrderItems != nil {
|
||||||
response.PaymentOrderItems = make([]models.PaymentOrderItemResponse, len(payment.PaymentOrderItems))
|
response.PaymentOrderItems = make([]models.PaymentOrderItemResponse, len(payment.PaymentOrderItems))
|
||||||
@ -201,6 +231,12 @@ func CreatePaymentRequestToEntity(req *models.CreatePaymentRequest) *entities.Pa
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var splitType *entities.SplitType
|
||||||
|
if req.SplitType != nil {
|
||||||
|
st := entities.SplitType(*req.SplitType)
|
||||||
|
splitType = &st
|
||||||
|
}
|
||||||
|
|
||||||
payment := &entities.Payment{
|
payment := &entities.Payment{
|
||||||
OrderID: req.OrderID,
|
OrderID: req.OrderID,
|
||||||
PaymentMethodID: req.PaymentMethodID,
|
PaymentMethodID: req.PaymentMethodID,
|
||||||
@ -209,6 +245,7 @@ func CreatePaymentRequestToEntity(req *models.CreatePaymentRequest) *entities.Pa
|
|||||||
TransactionID: req.TransactionID,
|
TransactionID: req.TransactionID,
|
||||||
SplitNumber: req.SplitNumber,
|
SplitNumber: req.SplitNumber,
|
||||||
SplitTotal: req.SplitTotal,
|
SplitTotal: req.SplitTotal,
|
||||||
|
SplitType: splitType,
|
||||||
SplitDescription: req.SplitDescription,
|
SplitDescription: req.SplitDescription,
|
||||||
Metadata: entities.Metadata(req.Metadata),
|
Metadata: entities.Metadata(req.Metadata),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ type Order struct {
|
|||||||
DiscountAmount float64
|
DiscountAmount float64
|
||||||
TotalAmount float64
|
TotalAmount float64
|
||||||
TotalCost float64
|
TotalCost float64
|
||||||
|
RemainingAmount float64
|
||||||
PaymentStatus constants.PaymentStatus
|
PaymentStatus constants.PaymentStatus
|
||||||
RefundAmount float64
|
RefundAmount float64
|
||||||
IsVoid bool
|
IsVoid bool
|
||||||
@ -156,6 +157,7 @@ type OrderResponse struct {
|
|||||||
DiscountAmount float64
|
DiscountAmount float64
|
||||||
TotalAmount float64
|
TotalAmount float64
|
||||||
TotalCost float64
|
TotalCost float64
|
||||||
|
RemainingAmount float64
|
||||||
PaymentStatus constants.PaymentStatus
|
PaymentStatus constants.PaymentStatus
|
||||||
RefundAmount float64
|
RefundAmount float64
|
||||||
IsVoid bool
|
IsVoid bool
|
||||||
@ -172,6 +174,9 @@ type OrderResponse struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
OrderItems []OrderItemResponse
|
OrderItems []OrderItemResponse
|
||||||
Payments []PaymentResponse
|
Payments []PaymentResponse
|
||||||
|
TotalPaid float64
|
||||||
|
PaymentCount int
|
||||||
|
SplitType *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderItemResponse struct {
|
type OrderItemResponse struct {
|
||||||
@ -230,6 +235,7 @@ type ListOrdersRequest struct {
|
|||||||
|
|
||||||
type ListOrdersResponse struct {
|
type ListOrdersResponse struct {
|
||||||
Orders []OrderResponse
|
Orders []OrderResponse
|
||||||
|
Payments []PaymentResponse
|
||||||
TotalCount int
|
TotalCount int
|
||||||
Page int
|
Page int
|
||||||
Limit int
|
Limit int
|
||||||
|
|||||||
@ -16,6 +16,7 @@ type Payment struct {
|
|||||||
TransactionID *string
|
TransactionID *string
|
||||||
SplitNumber int
|
SplitNumber int
|
||||||
SplitTotal int
|
SplitTotal int
|
||||||
|
SplitType *string
|
||||||
SplitDescription *string
|
SplitDescription *string
|
||||||
RefundAmount float64
|
RefundAmount float64
|
||||||
RefundReason *string
|
RefundReason *string
|
||||||
@ -33,6 +34,7 @@ type CreatePaymentRequest struct {
|
|||||||
TransactionID *string `validate:"omitempty"`
|
TransactionID *string `validate:"omitempty"`
|
||||||
SplitNumber int `validate:"omitempty,min=1"`
|
SplitNumber int `validate:"omitempty,min=1"`
|
||||||
SplitTotal int `validate:"omitempty,min=1"`
|
SplitTotal int `validate:"omitempty,min=1"`
|
||||||
|
SplitType *string `validate:"omitempty,oneof=AMOUNT ITEM"`
|
||||||
SplitDescription *string `validate:"omitempty,max=255"`
|
SplitDescription *string `validate:"omitempty,max=255"`
|
||||||
PaymentOrderItems []CreatePaymentOrderItemRequest `validate:"omitempty,dive"`
|
PaymentOrderItems []CreatePaymentOrderItemRequest `validate:"omitempty,dive"`
|
||||||
Metadata map[string]interface{}
|
Metadata map[string]interface{}
|
||||||
@ -48,11 +50,14 @@ type PaymentResponse struct {
|
|||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
OrderID uuid.UUID
|
OrderID uuid.UUID
|
||||||
PaymentMethodID uuid.UUID
|
PaymentMethodID uuid.UUID
|
||||||
|
PaymentMethodName string
|
||||||
|
PaymentMethodType constants.PaymentMethodType
|
||||||
Amount float64
|
Amount float64
|
||||||
Status constants.PaymentTransactionStatus
|
Status constants.PaymentTransactionStatus
|
||||||
TransactionID *string
|
TransactionID *string
|
||||||
SplitNumber int
|
SplitNumber int
|
||||||
SplitTotal int
|
SplitTotal int
|
||||||
|
SplitType *string
|
||||||
SplitDescription *string
|
SplitDescription *string
|
||||||
RefundAmount float64
|
RefundAmount float64
|
||||||
RefundReason *string
|
RefundReason *string
|
||||||
|
|||||||
46
internal/models/split_bill.go
Normal file
46
internal/models/split_bill.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "github.com/google/uuid"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Amount = "AMOUNT"
|
||||||
|
Item = "ITEM"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SplitBillRequest struct {
|
||||||
|
OrderID uuid.UUID `validate:"required"`
|
||||||
|
PaymentMethodID uuid.UUID `validate:"required"`
|
||||||
|
CustomerID uuid.UUID `validate:"required"`
|
||||||
|
Type string `validate:"required,oneof=ITEM AMOUNT"`
|
||||||
|
Items []SplitBillItemRequest `validate:"required_if=Type ITEM,dive"`
|
||||||
|
Amount float64 `validate:"required_if=Type AMOUNT,min=0"`
|
||||||
|
OrganizationID uuid.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplitBillRequest) IsItem() bool {
|
||||||
|
return s.Type == Item
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SplitBillRequest) IsAmount() bool {
|
||||||
|
return s.Type == Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillItemRequest struct {
|
||||||
|
OrderItemID uuid.UUID `validate:"required"`
|
||||||
|
Amount float64 `validate:"required,min=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillResponse struct {
|
||||||
|
PaymentID uuid.UUID `json:"payment_id"`
|
||||||
|
OrderID uuid.UUID `json:"order_id"`
|
||||||
|
CustomerID uuid.UUID `json:"customer_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
Items []SplitBillItemResponse `json:"items,omitempty"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SplitBillItemResponse struct {
|
||||||
|
OrderItemID uuid.UUID `json:"order_item_id"`
|
||||||
|
Amount float64 `json:"amount"`
|
||||||
|
}
|
||||||
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
|
||||||
"apskel-pos-be/internal/constants"
|
"apskel-pos-be/internal/constants"
|
||||||
"apskel-pos-be/internal/entities"
|
"apskel-pos-be/internal/entities"
|
||||||
"apskel-pos-be/internal/mappers"
|
"apskel-pos-be/internal/mappers"
|
||||||
@ -24,6 +26,7 @@ type OrderProcessor interface {
|
|||||||
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
||||||
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
||||||
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
||||||
|
SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderRepository interface {
|
type OrderRepository interface {
|
||||||
@ -67,6 +70,14 @@ type PaymentRepository interface {
|
|||||||
RefundPaymentWithInventoryMovement(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, order *entities.Payment) error
|
RefundPaymentWithInventoryMovement(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID, order *entities.Payment) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaymentOrderItemRepository interface {
|
||||||
|
Create(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentOrderItem, error)
|
||||||
|
GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.PaymentOrderItem, error)
|
||||||
|
Update(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
type PaymentMethodRepository interface {
|
type PaymentMethodRepository interface {
|
||||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
|
||||||
}
|
}
|
||||||
@ -95,6 +106,7 @@ type OrderProcessorImpl struct {
|
|||||||
orderRepo OrderRepository
|
orderRepo OrderRepository
|
||||||
orderItemRepo OrderItemRepository
|
orderItemRepo OrderItemRepository
|
||||||
paymentRepo PaymentRepository
|
paymentRepo PaymentRepository
|
||||||
|
paymentOrderItemRepo PaymentOrderItemRepository
|
||||||
productRepo ProductRepository
|
productRepo ProductRepository
|
||||||
paymentMethodRepo PaymentMethodRepository
|
paymentMethodRepo PaymentMethodRepository
|
||||||
inventoryRepo repository.InventoryRepository
|
inventoryRepo repository.InventoryRepository
|
||||||
@ -108,6 +120,7 @@ func NewOrderProcessorImpl(
|
|||||||
orderRepo OrderRepository,
|
orderRepo OrderRepository,
|
||||||
orderItemRepo OrderItemRepository,
|
orderItemRepo OrderItemRepository,
|
||||||
paymentRepo PaymentRepository,
|
paymentRepo PaymentRepository,
|
||||||
|
paymentOrderItemRepo PaymentOrderItemRepository,
|
||||||
productRepo ProductRepository,
|
productRepo ProductRepository,
|
||||||
paymentMethodRepo PaymentMethodRepository,
|
paymentMethodRepo PaymentMethodRepository,
|
||||||
inventoryRepo repository.InventoryRepository,
|
inventoryRepo repository.InventoryRepository,
|
||||||
@ -120,6 +133,7 @@ func NewOrderProcessorImpl(
|
|||||||
orderRepo: orderRepo,
|
orderRepo: orderRepo,
|
||||||
orderItemRepo: orderItemRepo,
|
orderItemRepo: orderItemRepo,
|
||||||
paymentRepo: paymentRepo,
|
paymentRepo: paymentRepo,
|
||||||
|
paymentOrderItemRepo: paymentOrderItemRepo,
|
||||||
productRepo: productRepo,
|
productRepo: productRepo,
|
||||||
paymentMethodRepo: paymentMethodRepo,
|
paymentMethodRepo: paymentMethodRepo,
|
||||||
inventoryRepo: inventoryRepo,
|
inventoryRepo: inventoryRepo,
|
||||||
@ -216,6 +230,7 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
|
|||||||
DiscountAmount: 0,
|
DiscountAmount: 0,
|
||||||
TotalAmount: totalAmount,
|
TotalAmount: totalAmount,
|
||||||
TotalCost: totalCost,
|
TotalCost: totalCost,
|
||||||
|
RemainingAmount: totalAmount, // Initialize remaining amount equal to total amount
|
||||||
PaymentStatus: entities.PaymentStatusPending,
|
PaymentStatus: entities.PaymentStatusPending,
|
||||||
IsVoid: false,
|
IsVoid: false,
|
||||||
IsRefund: false,
|
IsRefund: false,
|
||||||
@ -325,6 +340,17 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
|
|||||||
order.TaxAmount = order.Subtotal * outlet.TaxRate
|
order.TaxAmount = order.Subtotal * outlet.TaxRate
|
||||||
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
||||||
|
|
||||||
|
// Recalculate remaining amount when items are added
|
||||||
|
totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, orderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get total paid amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
order.RemainingAmount = order.TotalAmount - totalPaid
|
||||||
|
if order.RemainingAmount < 0 {
|
||||||
|
order.RemainingAmount = 0
|
||||||
|
}
|
||||||
|
|
||||||
if req.Metadata != nil {
|
if req.Metadata != nil {
|
||||||
if order.Metadata == nil {
|
if order.Metadata == nil {
|
||||||
order.Metadata = make(entities.Metadata)
|
order.Metadata = make(entities.Metadata)
|
||||||
@ -409,6 +435,17 @@ func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req
|
|||||||
order.DiscountAmount = *req.DiscountAmount
|
order.DiscountAmount = *req.DiscountAmount
|
||||||
// Recalculate total amount
|
// Recalculate total amount
|
||||||
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
||||||
|
|
||||||
|
// Recalculate remaining amount when discount is applied
|
||||||
|
totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get total paid amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
order.RemainingAmount = order.TotalAmount - totalPaid
|
||||||
|
if order.RemainingAmount < 0 {
|
||||||
|
order.RemainingAmount = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if req.Metadata != nil {
|
if req.Metadata != nil {
|
||||||
if order.Metadata == nil {
|
if order.Metadata == nil {
|
||||||
@ -489,16 +526,20 @@ func (p *OrderProcessorImpl) ListOrders(ctx context.Context, req *models.ListOrd
|
|||||||
return nil, fmt.Errorf("failed to list orders: %w", err)
|
return nil, fmt.Errorf("failed to list orders: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to responses
|
|
||||||
orderResponses := make([]models.OrderResponse, len(orders))
|
orderResponses := make([]models.OrderResponse, len(orders))
|
||||||
|
allPayments := make([]models.PaymentResponse, 0)
|
||||||
|
|
||||||
for i, order := range orders {
|
for i, order := range orders {
|
||||||
response := mappers.OrderEntityToResponse(order)
|
response := mappers.OrderEntityToResponse(order)
|
||||||
if response != nil {
|
if response != nil {
|
||||||
orderResponses[i] = *response
|
orderResponses[i] = *response
|
||||||
|
// Add payments from this order to the allPayments list
|
||||||
|
if response.Payments != nil {
|
||||||
|
allPayments = append(allPayments, response.Payments...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total pages
|
|
||||||
totalPages := int(total) / req.Limit
|
totalPages := int(total) / req.Limit
|
||||||
if int(total)%req.Limit > 0 {
|
if int(total)%req.Limit > 0 {
|
||||||
totalPages++
|
totalPages++
|
||||||
@ -506,6 +547,7 @@ func (p *OrderProcessorImpl) ListOrders(ctx context.Context, req *models.ListOrd
|
|||||||
|
|
||||||
return &models.ListOrdersResponse{
|
return &models.ListOrdersResponse{
|
||||||
Orders: orderResponses,
|
Orders: orderResponses,
|
||||||
|
Payments: allPayments,
|
||||||
TotalCount: int(total),
|
TotalCount: int(total),
|
||||||
Page: req.Page,
|
Page: req.Page,
|
||||||
Limit: req.Limit,
|
Limit: req.Limit,
|
||||||
@ -738,6 +780,21 @@ func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.Crea
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update order payment status and remaining amount in processor layer
|
||||||
|
newTotalPaid := totalPaid + req.Amount
|
||||||
|
order.RemainingAmount = order.TotalAmount - newTotalPaid
|
||||||
|
|
||||||
|
if newTotalPaid >= order.TotalAmount {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusCompleted
|
||||||
|
order.RemainingAmount = 0
|
||||||
|
} else {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusPartial
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.orderRepo.Update(ctx, order); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update order payment status: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
paymentWithRelations, err := p.paymentRepo.GetByID(ctx, payment.ID)
|
paymentWithRelations, err := p.paymentRepo.GetByID(ctx, payment.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve created payment: %w", err)
|
return nil, fmt.Errorf("failed to retrieve created payment: %w", err)
|
||||||
@ -769,18 +826,15 @@ func (p *OrderProcessorImpl) RefundPayment(ctx context.Context, paymentID uuid.U
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
|
||||||
// Get the order
|
|
||||||
order, err := p.orderRepo.GetByID(ctx, orderID)
|
order, err := p.orderRepo.GetByID(ctx, orderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("order not found: %w", err)
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify order belongs to the organization
|
|
||||||
if order.OrganizationID != organizationID {
|
if order.OrganizationID != organizationID {
|
||||||
return nil, fmt.Errorf("order does not belong to the organization")
|
return nil, fmt.Errorf("order does not belong to the organization")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if order status is pending (only pending orders can have customer set)
|
|
||||||
if order.Status != entities.OrderStatusPending {
|
if order.Status != entities.OrderStatusPending {
|
||||||
return nil, fmt.Errorf("customer can only be set for pending orders")
|
return nil, fmt.Errorf("customer can only be set for pending orders")
|
||||||
}
|
}
|
||||||
@ -805,3 +859,255 @@ func (p *OrderProcessorImpl) SetOrderCustomer(ctx context.Context, orderID uuid.
|
|||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *OrderProcessorImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
|
||||||
|
order, err := p.orderRepo.GetWithRelations(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("order not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.IsVoid {
|
||||||
|
return nil, fmt.Errorf("cannot split voided order")
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.PaymentStatus == entities.PaymentStatusCompleted {
|
||||||
|
return nil, fmt.Errorf("cannot split fully paid order")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get existing payments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingSplitType *entities.SplitType
|
||||||
|
for _, payment := range existingPayments {
|
||||||
|
if payment.SplitType != nil && payment.SplitTotal > 1 {
|
||||||
|
existingSplitType = payment.SplitType
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingSplitType != nil {
|
||||||
|
requestedSplitType := entities.SplitTypeAmount
|
||||||
|
if req.IsItem() {
|
||||||
|
requestedSplitType = entities.SplitTypeItem
|
||||||
|
}
|
||||||
|
|
||||||
|
if *existingSplitType != requestedSplitType {
|
||||||
|
return nil, fmt.Errorf("order already has %s split payments. Subsequent payments must use the same split type", *existingSplitType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
payment, err := p.paymentMethodRepo.GetByID(ctx, req.PaymentMethodID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("payment method not found: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
customer := &entities.Customer{}
|
||||||
|
if req.CustomerID != uuid.Nil {
|
||||||
|
customer, err = p.customerRepo.GetByIDAndOrganization(ctx, req.CustomerID, order.OrganizationID)
|
||||||
|
|
||||||
|
if err != nil && err != gorm.ErrRecordNotFound {
|
||||||
|
return nil, fmt.Errorf("customer not found or does not belong to the organization: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var response *models.SplitBillResponse
|
||||||
|
if req.IsAmount() {
|
||||||
|
response, err = p.splitBillByAmount(ctx, req, order, payment, customer)
|
||||||
|
} else if req.IsItem() {
|
||||||
|
response, err = p.splitBillByItem(ctx, req, order, payment, customer)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("invalid split type: must be AMOUNT or ITEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderProcessorImpl) splitBillByAmount(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error) {
|
||||||
|
totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get total paid amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingBalance := order.TotalAmount - totalPaid
|
||||||
|
if req.Amount > remainingBalance {
|
||||||
|
return nil, fmt.Errorf("split amount %.2f cannot exceed remaining balance %.2f", req.Amount, remainingBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get existing payments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
splitNumber := len(existingPayments) + 1
|
||||||
|
splitTotal := splitNumber + 1
|
||||||
|
|
||||||
|
splitType := entities.SplitTypeAmount
|
||||||
|
splitPayment := &entities.Payment{
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
PaymentMethodID: payment.ID,
|
||||||
|
Amount: req.Amount,
|
||||||
|
Status: entities.PaymentTransactionStatusCompleted,
|
||||||
|
SplitNumber: splitNumber,
|
||||||
|
SplitTotal: splitTotal,
|
||||||
|
SplitType: &splitType,
|
||||||
|
SplitDescription: stringPtr(fmt.Sprint("Split payment for customer")),
|
||||||
|
Metadata: entities.Metadata{
|
||||||
|
"split_type": "AMOUNT",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.paymentRepo.Create(ctx, splitPayment); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create split payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Metadata == nil {
|
||||||
|
order.Metadata = make(entities.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Metadata["last_split_payment_id"] = splitPayment.ID.String()
|
||||||
|
order.Metadata["last_split_customer_id"] = req.CustomerID.String()
|
||||||
|
order.Metadata["last_split_amount"] = req.Amount
|
||||||
|
order.Metadata["last_split_type"] = "AMOUNT"
|
||||||
|
|
||||||
|
newTotalPaid := totalPaid + req.Amount
|
||||||
|
order.RemainingAmount = order.TotalAmount - newTotalPaid
|
||||||
|
|
||||||
|
if newTotalPaid >= order.TotalAmount {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusCompleted
|
||||||
|
order.Status = entities.OrderStatusCompleted
|
||||||
|
order.RemainingAmount = 0
|
||||||
|
} else {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusPartial
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.orderRepo.Update(ctx, order); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.SplitBillResponse{
|
||||||
|
PaymentID: splitPayment.ID,
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
CustomerID: req.CustomerID,
|
||||||
|
Type: "AMOUNT",
|
||||||
|
Amount: req.Amount,
|
||||||
|
Message: fmt.Sprintf("Successfully split payment by amount %.2f for customer %s. Remaining balance: %.2f", req.Amount, customer.Name, order.RemainingAmount),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OrderProcessorImpl) splitBillByItem(ctx context.Context, req *models.SplitBillRequest, order *entities.Order, payment *entities.PaymentMethod, customer *entities.Customer) (*models.SplitBillResponse, error) {
|
||||||
|
totalSplitAmount := float64(0)
|
||||||
|
for _, item := range req.Items {
|
||||||
|
totalSplitAmount += item.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPaid, err := p.paymentRepo.GetTotalPaidByOrderID(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get total paid amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingBalance := order.TotalAmount - totalPaid
|
||||||
|
|
||||||
|
if totalSplitAmount > remainingBalance {
|
||||||
|
return nil, fmt.Errorf("split amount %.2f cannot exceed remaining balance %.2f", totalSplitAmount, remainingBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range req.Items {
|
||||||
|
orderItem, err := p.orderItemRepo.GetByID(ctx, item.OrderItemID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("order item not found: %w", err)
|
||||||
|
}
|
||||||
|
if orderItem.OrderID != req.OrderID {
|
||||||
|
return nil, fmt.Errorf("order item does not belong to this order")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingPayments, err := p.paymentRepo.GetByOrderID(ctx, req.OrderID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get existing payments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
splitNumber := len(existingPayments) + 1
|
||||||
|
splitTotal := splitNumber + 1
|
||||||
|
|
||||||
|
splitType := entities.SplitTypeItem
|
||||||
|
splitPayment := &entities.Payment{
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
PaymentMethodID: payment.ID,
|
||||||
|
Amount: totalSplitAmount,
|
||||||
|
Status: entities.PaymentTransactionStatusCompleted,
|
||||||
|
SplitNumber: splitNumber,
|
||||||
|
SplitTotal: splitTotal,
|
||||||
|
SplitType: &splitType,
|
||||||
|
SplitDescription: stringPtr(fmt.Sprintf("Split payment by items for customer: %s", customer.Name)),
|
||||||
|
Metadata: entities.Metadata{
|
||||||
|
"split_type": "ITEM",
|
||||||
|
"customer_id": req.CustomerID.String(),
|
||||||
|
"customer_name": customer.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.paymentRepo.Create(ctx, splitPayment); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create split payment: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range req.Items {
|
||||||
|
paymentOrderItem := &entities.PaymentOrderItem{
|
||||||
|
PaymentID: splitPayment.ID,
|
||||||
|
OrderItemID: item.OrderItemID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.paymentOrderItemRepo.Create(ctx, paymentOrderItem); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create payment order item: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if order.Metadata == nil {
|
||||||
|
order.Metadata = make(entities.Metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
order.Metadata["last_split_payment_id"] = splitPayment.ID.String()
|
||||||
|
order.Metadata["last_split_customer_id"] = req.CustomerID.String()
|
||||||
|
order.Metadata["last_split_customer_name"] = customer.Name
|
||||||
|
order.Metadata["last_split_amount"] = totalSplitAmount
|
||||||
|
order.Metadata["last_split_type"] = "ITEM"
|
||||||
|
|
||||||
|
newTotalPaid := totalPaid + totalSplitAmount
|
||||||
|
order.RemainingAmount = order.TotalAmount - newTotalPaid
|
||||||
|
|
||||||
|
if newTotalPaid >= order.TotalAmount {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusCompleted
|
||||||
|
order.Status = entities.OrderStatusCompleted
|
||||||
|
order.RemainingAmount = 0
|
||||||
|
} else {
|
||||||
|
order.PaymentStatus = entities.PaymentStatusPartial
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.orderRepo.Update(ctx, order); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update order: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
responseItems := make([]models.SplitBillItemResponse, len(req.Items))
|
||||||
|
for i, item := range req.Items {
|
||||||
|
responseItems[i] = models.SplitBillItemResponse{
|
||||||
|
OrderItemID: item.OrderItemID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &models.SplitBillResponse{
|
||||||
|
PaymentID: splitPayment.ID,
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
CustomerID: req.CustomerID,
|
||||||
|
Type: "ITEM",
|
||||||
|
Amount: totalSplitAmount,
|
||||||
|
Items: responseItems,
|
||||||
|
Message: fmt.Sprintf("Successfully split payment by items (%.2f) for customer %s. Remaining balance: %.2f", totalSplitAmount, customer.Name, order.RemainingAmount),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -31,10 +31,8 @@ func NewOutletProcessorImpl(outletRepo *repository.OutletRepositoryImpl) *Outlet
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *OutletProcessorImpl) ListOutletsByOrganization(ctx context.Context, organizationID uuid.UUID, page, limit int) ([]*models.OutletResponse, int64, error) {
|
func (p *OutletProcessorImpl) ListOutletsByOrganization(ctx context.Context, organizationID uuid.UUID, page, limit int) ([]*models.OutletResponse, int64, error) {
|
||||||
|
|
||||||
offset := (page - 1) * limit
|
offset := (page - 1) * limit
|
||||||
|
|
||||||
// Get outlets with pagination
|
|
||||||
outlets, total, err := p.outletRepo.GetByOrganizationIDWithPagination(ctx, organizationID, limit, offset)
|
outlets, total, err := p.outletRepo.GetByOrganizationIDWithPagination(ctx, organizationID, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, fmt.Errorf("failed to get outlets: %w", err)
|
return nil, 0, fmt.Errorf("failed to get outlets: %w", err)
|
||||||
|
|||||||
@ -90,7 +90,8 @@ func (r *OrderRepositoryImpl) List(ctx context.Context, filters map[string]inter
|
|||||||
Preload("OrderItems.Product").
|
Preload("OrderItems.Product").
|
||||||
Preload("OrderItems.ProductVariant").
|
Preload("OrderItems.ProductVariant").
|
||||||
Preload("Payments").
|
Preload("Payments").
|
||||||
Preload("Payments.PaymentMethod")
|
Preload("Payments.PaymentMethod").
|
||||||
|
Preload("Payments.PaymentOrderItems")
|
||||||
|
|
||||||
for key, value := range filters {
|
for key, value := range filters {
|
||||||
switch key {
|
switch key {
|
||||||
|
|||||||
62
internal/repository/payment_order_item_repository.go
Normal file
62
internal/repository/payment_order_item_repository.go
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"apskel-pos-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PaymentOrderItemRepository interface {
|
||||||
|
Create(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
|
||||||
|
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentOrderItem, error)
|
||||||
|
GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.PaymentOrderItem, error)
|
||||||
|
Update(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error
|
||||||
|
Delete(ctx context.Context, id uuid.UUID) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaymentOrderItemRepositoryImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPaymentOrderItemRepositoryImpl(db *gorm.DB) *PaymentOrderItemRepositoryImpl {
|
||||||
|
return &PaymentOrderItemRepositoryImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderItemRepositoryImpl) Create(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error {
|
||||||
|
return r.db.WithContext(ctx).Create(paymentOrderItem).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderItemRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentOrderItem, error) {
|
||||||
|
var paymentOrderItem entities.PaymentOrderItem
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Payment").
|
||||||
|
Preload("OrderItem").
|
||||||
|
First(&paymentOrderItem, "id = ?", id).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &paymentOrderItem, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderItemRepositoryImpl) GetByPaymentID(ctx context.Context, paymentID uuid.UUID) ([]*entities.PaymentOrderItem, error) {
|
||||||
|
var paymentOrderItems []*entities.PaymentOrderItem
|
||||||
|
err := r.db.WithContext(ctx).
|
||||||
|
Preload("Payment").
|
||||||
|
Preload("OrderItem").
|
||||||
|
Where("payment_id = ?", paymentID).
|
||||||
|
Find(&paymentOrderItems).Error
|
||||||
|
return paymentOrderItems, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderItemRepositoryImpl) Update(ctx context.Context, paymentOrderItem *entities.PaymentOrderItem) error {
|
||||||
|
return r.db.WithContext(ctx).Save(paymentOrderItem).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *PaymentOrderItemRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
|
||||||
|
return r.db.WithContext(ctx).Delete(&entities.PaymentOrderItem{}, "id = ?", id).Error
|
||||||
|
}
|
||||||
@ -122,17 +122,9 @@ func (r *PaymentRepositoryImpl) CreatePaymentWithInventoryMovement(ctx context.C
|
|||||||
if order.PaymentStatus != entities.PaymentStatusCompleted {
|
if order.PaymentStatus != entities.PaymentStatusCompleted {
|
||||||
orderJustCompleted = true
|
orderJustCompleted = true
|
||||||
}
|
}
|
||||||
if err := tx.Model(&entities.Order{}).Where("id = ?", req.OrderID).Update("payment_status", entities.PaymentStatusCompleted).Error; err != nil {
|
|
||||||
return fmt.Errorf("failed to update order payment status: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tx.Model(&entities.Order{}).Where("id = ?", req.OrderID).Update("status", entities.OrderStatusCompleted).Error; err != nil {
|
if err := tx.Model(&entities.Order{}).Where("id = ?", req.OrderID).Update("status", entities.OrderStatusCompleted).Error; err != nil {
|
||||||
return fmt.Errorf("failed to update order status: %w", err)
|
return fmt.Errorf("failed to update order status: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if err := tx.Model(&entities.Order{}).Where("id = ?", req.OrderID).Update("payment_status", entities.PaymentStatusPartiallyRefunded).Error; err != nil {
|
|
||||||
return fmt.Errorf("failed to update order payment status: %w", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if orderJustCompleted {
|
if orderJustCompleted {
|
||||||
|
|||||||
@ -211,6 +211,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
orders.PUT("/:id/customer", r.orderHandler.SetOrderCustomer)
|
orders.PUT("/:id/customer", r.orderHandler.SetOrderCustomer)
|
||||||
orders.POST("/void", r.orderHandler.VoidOrder)
|
orders.POST("/void", r.orderHandler.VoidOrder)
|
||||||
orders.POST("/:id/refund", r.orderHandler.RefundOrder)
|
orders.POST("/:id/refund", r.orderHandler.RefundOrder)
|
||||||
|
orders.POST("/split-bill", r.orderHandler.SplitBill)
|
||||||
}
|
}
|
||||||
|
|
||||||
payments := protected.Group("/payments")
|
payments := protected.Group("/payments")
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type OrderService interface {
|
|||||||
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error)
|
||||||
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error
|
||||||
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error)
|
||||||
|
SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderServiceImpl struct {
|
type OrderServiceImpl struct {
|
||||||
@ -95,12 +96,10 @@ func (s *OrderServiceImpl) GetOrderByID(ctx context.Context, id uuid.UUID) (*mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OrderServiceImpl) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
|
func (s *OrderServiceImpl) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
|
||||||
// Validate request
|
|
||||||
if err := s.validateListOrdersRequest(req); err != nil {
|
if err := s.validateListOrdersRequest(req); err != nil {
|
||||||
return nil, fmt.Errorf("validation error: %w", err)
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process order listing
|
|
||||||
response, err := s.orderProcessor.ListOrders(ctx, req)
|
response, err := s.orderProcessor.ListOrders(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list orders: %w", err)
|
return nil, fmt.Errorf("failed to list orders: %w", err)
|
||||||
@ -384,3 +383,65 @@ func (s *OrderServiceImpl) validateSetOrderCustomerRequest(req *models.SetOrderC
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *OrderServiceImpl) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
|
||||||
|
if err := s.validateSplitBillRequest(req); err != nil {
|
||||||
|
return nil, fmt.Errorf("validation error: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := s.orderProcessor.SplitBill(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to split bill: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderServiceImpl) validateSplitBillRequest(req *models.SplitBillRequest) error {
|
||||||
|
if req == nil {
|
||||||
|
return fmt.Errorf("request cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.OrderID == uuid.Nil {
|
||||||
|
return fmt.Errorf("order ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.PaymentMethodID == uuid.Nil {
|
||||||
|
return fmt.Errorf("payment ID is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type != "ITEM" && req.Type != "AMOUNT" {
|
||||||
|
return fmt.Errorf("split type must be either ITEM or AMOUNT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == "ITEM" {
|
||||||
|
if len(req.Items) == 0 {
|
||||||
|
return fmt.Errorf("items are required when splitting by ITEM")
|
||||||
|
}
|
||||||
|
|
||||||
|
totalItemAmount := float64(0)
|
||||||
|
for i, item := range req.Items {
|
||||||
|
if item.OrderItemID == uuid.Nil {
|
||||||
|
return fmt.Errorf("order item ID is required for item %d", i+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.Amount <= 0 {
|
||||||
|
return fmt.Errorf("amount must be greater than zero for item %d", i+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
totalItemAmount += item.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalItemAmount <= 0 {
|
||||||
|
return fmt.Errorf("total item amount must be greater than zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Type == "AMOUNT" {
|
||||||
|
if req.Amount <= 0 {
|
||||||
|
return fmt.Errorf("amount must be greater than zero when splitting by AMOUNT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -29,7 +29,6 @@ func NewOutletService(outletProcessor processor.OutletProcessor) *OutletServiceI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *OutletServiceImpl) ListOutlets(ctx context.Context, req *contract.ListOutletsRequest) *contract.Response {
|
func (s *OutletServiceImpl) ListOutlets(ctx context.Context, req *contract.ListOutletsRequest) *contract.Response {
|
||||||
// Validate request
|
|
||||||
if req.Page < 1 {
|
if req.Page < 1 {
|
||||||
req.Page = 1
|
req.Page = 1
|
||||||
}
|
}
|
||||||
@ -51,7 +50,6 @@ func (s *OutletServiceImpl) ListOutlets(ctx context.Context, req *contract.ListO
|
|||||||
contractOutlets[i] = transformer.OutletModelResponseToResponse(outlet)
|
contractOutlets[i] = transformer.OutletModelResponseToResponse(outlet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create paginated response
|
|
||||||
response := transformer.CreateListOutletsResponse(contractOutlets, int(total), req.Page, req.Limit)
|
response := transformer.CreateListOutletsResponse(contractOutlets, int(total), req.Page, req.Limit)
|
||||||
return contract.BuildSuccessResponse(response)
|
return contract.BuildSuccessResponse(response)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,6 +111,12 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
|
|||||||
PrinterType: item.PrinterType,
|
PrinterType: item.PrinterType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Map payments
|
||||||
|
payments := make([]contract.PaymentResponse, len(resp.Payments))
|
||||||
|
for i, payment := range resp.Payments {
|
||||||
|
payments[i] = *PaymentModelToContract(&payment)
|
||||||
|
}
|
||||||
|
|
||||||
return &contract.OrderResponse{
|
return &contract.OrderResponse{
|
||||||
ID: resp.ID,
|
ID: resp.ID,
|
||||||
OrderNumber: resp.OrderNumber,
|
OrderNumber: resp.OrderNumber,
|
||||||
@ -123,12 +129,27 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
|
|||||||
TaxAmount: resp.TaxAmount,
|
TaxAmount: resp.TaxAmount,
|
||||||
DiscountAmount: resp.DiscountAmount,
|
DiscountAmount: resp.DiscountAmount,
|
||||||
TotalAmount: resp.TotalAmount,
|
TotalAmount: resp.TotalAmount,
|
||||||
|
TotalCost: resp.TotalCost,
|
||||||
|
RemainingAmount: resp.RemainingAmount,
|
||||||
|
PaymentStatus: string(resp.PaymentStatus),
|
||||||
|
RefundAmount: resp.RefundAmount,
|
||||||
|
IsVoid: resp.IsVoid,
|
||||||
|
IsRefund: resp.IsRefund,
|
||||||
|
VoidReason: resp.VoidReason,
|
||||||
|
VoidedAt: resp.VoidedAt,
|
||||||
|
VoidedBy: resp.VoidedBy,
|
||||||
|
RefundReason: resp.RefundReason,
|
||||||
|
RefundedAt: resp.RefundedAt,
|
||||||
|
RefundedBy: resp.RefundedBy,
|
||||||
Notes: resp.Notes,
|
Notes: resp.Notes,
|
||||||
Metadata: resp.Metadata,
|
Metadata: resp.Metadata,
|
||||||
CreatedAt: resp.CreatedAt,
|
CreatedAt: resp.CreatedAt,
|
||||||
UpdatedAt: resp.UpdatedAt,
|
UpdatedAt: resp.UpdatedAt,
|
||||||
OrderItems: items,
|
OrderItems: items,
|
||||||
IsRefund: resp.IsRefund,
|
Payments: payments,
|
||||||
|
TotalPaid: resp.TotalPaid,
|
||||||
|
PaymentCount: resp.PaymentCount,
|
||||||
|
SplitType: resp.SplitType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,8 +278,14 @@ func ListOrdersModelToContract(resp *models.ListOrdersResponse) *contract.ListOr
|
|||||||
orders[i] = *OrderModelToContract(&order)
|
orders[i] = *OrderModelToContract(&order)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
payments := make([]contract.PaymentResponse, len(resp.Payments))
|
||||||
|
for i, payment := range resp.Payments {
|
||||||
|
payments[i] = *PaymentModelToContract(&payment)
|
||||||
|
}
|
||||||
|
|
||||||
return &contract.ListOrdersResponse{
|
return &contract.ListOrdersResponse{
|
||||||
Orders: orders,
|
Orders: orders,
|
||||||
|
Payments: payments,
|
||||||
TotalCount: resp.TotalCount,
|
TotalCount: resp.TotalCount,
|
||||||
Page: resp.Page,
|
Page: resp.Page,
|
||||||
Limit: resp.Limit,
|
Limit: resp.Limit,
|
||||||
@ -309,11 +336,14 @@ func PaymentModelToContract(resp *models.PaymentResponse) *contract.PaymentRespo
|
|||||||
ID: resp.ID,
|
ID: resp.ID,
|
||||||
OrderID: resp.OrderID,
|
OrderID: resp.OrderID,
|
||||||
PaymentMethodID: resp.PaymentMethodID,
|
PaymentMethodID: resp.PaymentMethodID,
|
||||||
|
PaymentMethodName: resp.PaymentMethodName,
|
||||||
|
PaymentMethodType: string(resp.PaymentMethodType),
|
||||||
Amount: resp.Amount,
|
Amount: resp.Amount,
|
||||||
Status: string(resp.Status),
|
Status: string(resp.Status),
|
||||||
TransactionID: resp.TransactionID,
|
TransactionID: resp.TransactionID,
|
||||||
SplitNumber: resp.SplitNumber,
|
SplitNumber: resp.SplitNumber,
|
||||||
SplitTotal: resp.SplitTotal,
|
SplitTotal: resp.SplitTotal,
|
||||||
|
SplitType: resp.SplitType,
|
||||||
SplitDescription: resp.SplitDescription,
|
SplitDescription: resp.SplitDescription,
|
||||||
RefundAmount: resp.RefundAmount,
|
RefundAmount: resp.RefundAmount,
|
||||||
RefundReason: resp.RefundReason,
|
RefundReason: resp.RefundReason,
|
||||||
@ -342,3 +372,46 @@ func RefundOrderContractToModel(req *contract.RefundOrderRequest) *models.Refund
|
|||||||
OrderItems: orderItems,
|
OrderItems: orderItems,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitBillContractToModel(req *contract.SplitBillRequest) *models.SplitBillRequest {
|
||||||
|
items := make([]models.SplitBillItemRequest, len(req.Items))
|
||||||
|
for i, item := range req.Items {
|
||||||
|
items[i] = models.SplitBillItemRequest{
|
||||||
|
OrderItemID: item.OrderItemID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &models.SplitBillRequest{
|
||||||
|
OrderID: req.OrderID,
|
||||||
|
PaymentMethodID: req.PaymentMethodID,
|
||||||
|
CustomerID: req.CustomerID,
|
||||||
|
Type: req.Type,
|
||||||
|
Items: items,
|
||||||
|
Amount: req.Amount,
|
||||||
|
OrganizationID: req.OrganizationID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SplitBillModelToContract(resp *models.SplitBillResponse) *contract.SplitBillResponse {
|
||||||
|
if resp == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
items := make([]contract.SplitBillItemResponse, len(resp.Items))
|
||||||
|
for i, item := range resp.Items {
|
||||||
|
items[i] = contract.SplitBillItemResponse{
|
||||||
|
OrderItemID: item.OrderItemID,
|
||||||
|
Amount: item.Amount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &contract.SplitBillResponse{
|
||||||
|
PaymentID: resp.PaymentID,
|
||||||
|
OrderID: resp.OrderID,
|
||||||
|
CustomerID: resp.CustomerID,
|
||||||
|
Type: resp.Type,
|
||||||
|
Amount: resp.Amount,
|
||||||
|
Items: items,
|
||||||
|
Message: resp.Message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ func OutletModelResponseToResponse(model *models.OutletResponse) contract.Outlet
|
|||||||
OrganizationID: model.OrganizationID,
|
OrganizationID: model.OrganizationID,
|
||||||
Name: model.Name,
|
Name: model.Name,
|
||||||
Address: *model.Address,
|
Address: *model.Address,
|
||||||
BusinessType: string(constants.BusinessTypeRestaurant), // Default business type
|
BusinessType: string(constants.BusinessTypeRestaurant),
|
||||||
Currency: model.Currency,
|
Currency: model.Currency,
|
||||||
TaxRate: model.TaxRate,
|
TaxRate: model.TaxRate,
|
||||||
IsActive: model.IsActive,
|
IsActive: model.IsActive,
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
-- Remove constraint
|
||||||
|
ALTER TABLE orders DROP CONSTRAINT IF EXISTS check_remaining_amount_non_negative;
|
||||||
|
|
||||||
|
-- Remove remaining_amount column from orders table
|
||||||
|
ALTER TABLE orders DROP COLUMN IF EXISTS remaining_amount;
|
||||||
8
migrations/000033_add_remaining_amount_to_orders.up.sql
Normal file
8
migrations/000033_add_remaining_amount_to_orders.up.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- Add remaining_amount column to orders table
|
||||||
|
ALTER TABLE orders ADD COLUMN remaining_amount DECIMAL(10,2) DEFAULT 0.00;
|
||||||
|
|
||||||
|
-- Update existing orders to set remaining_amount equal to total_amount
|
||||||
|
UPDATE orders SET remaining_amount = total_amount WHERE remaining_amount = 0.00;
|
||||||
|
|
||||||
|
-- Add constraint to ensure remaining_amount is not negative
|
||||||
|
ALTER TABLE orders ADD CONSTRAINT check_remaining_amount_non_negative CHECK (remaining_amount >= 0.00);
|
||||||
8
migrations/000034_add_split_type_to_payments.down.sql
Normal file
8
migrations/000034_add_split_type_to_payments.down.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- Remove constraint
|
||||||
|
ALTER TABLE payments DROP CONSTRAINT IF EXISTS check_split_type_valid;
|
||||||
|
|
||||||
|
-- Remove index
|
||||||
|
DROP INDEX IF EXISTS idx_payments_split_type;
|
||||||
|
|
||||||
|
-- Remove split_type column from payments table
|
||||||
|
ALTER TABLE payments DROP COLUMN IF EXISTS split_type;
|
||||||
8
migrations/000034_add_split_type_to_payments.up.sql
Normal file
8
migrations/000034_add_split_type_to_payments.up.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- Add split_type column to payments table
|
||||||
|
ALTER TABLE payments ADD COLUMN split_type VARCHAR(20);
|
||||||
|
|
||||||
|
-- Add index for better query performance
|
||||||
|
CREATE INDEX idx_payments_split_type ON payments(split_type);
|
||||||
|
|
||||||
|
-- Add constraint to ensure split_type is valid
|
||||||
|
ALTER TABLE payments ADD CONSTRAINT check_split_type_valid CHECK (split_type IS NULL OR split_type IN ('AMOUNT', 'ITEM'));
|
||||||
Loading…
x
Reference in New Issue
Block a user