Fix Split Bill
This commit is contained in:
parent
3696451dc6
commit
93a3b29ae9
@ -135,6 +135,7 @@ type repositories struct {
|
||||
orderRepo *repository.OrderRepositoryImpl
|
||||
orderItemRepo *repository.OrderItemRepositoryImpl
|
||||
paymentRepo *repository.PaymentRepositoryImpl
|
||||
paymentOrderItemRepo *repository.PaymentOrderItemRepositoryImpl
|
||||
paymentMethodRepo *repository.PaymentMethodRepositoryImpl
|
||||
fileRepo *repository.FileRepositoryImpl
|
||||
customerRepo *repository.CustomerRepository
|
||||
@ -158,6 +159,7 @@ func (a *App) initRepositories() *repositories {
|
||||
orderRepo: repository.NewOrderRepositoryImpl(a.db),
|
||||
orderItemRepo: repository.NewOrderItemRepositoryImpl(a.db),
|
||||
paymentRepo: repository.NewPaymentRepositoryImpl(a.db),
|
||||
paymentOrderItemRepo: repository.NewPaymentOrderItemRepositoryImpl(a.db),
|
||||
paymentMethodRepo: repository.NewPaymentMethodRepositoryImpl(a.db),
|
||||
fileRepo: repository.NewFileRepositoryImpl(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),
|
||||
productVariantProcessor: processor.NewProductVariantProcessorImpl(repos.productVariantRepo, repos.productRepo),
|
||||
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),
|
||||
fileProcessor: processor.NewFileProcessorImpl(repos.fileRepo, fileClient),
|
||||
customerProcessor: processor.NewCustomerProcessor(repos.customerRepo),
|
||||
|
||||
@ -56,23 +56,38 @@ type UpdateOrderItemRequest struct {
|
||||
}
|
||||
|
||||
type OrderResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
TableNumber *string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
Status string `json:"status"`
|
||||
Subtotal float64 `json:"subtotal"`
|
||||
TaxAmount float64 `json:"tax_amount"`
|
||||
DiscountAmount float64 `json:"discount_amount"`
|
||||
TotalAmount float64 `json:"total_amount"`
|
||||
Notes *string `json:"notes"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
||||
IsRefund bool `json:"is_refund"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderNumber string `json:"order_number"`
|
||||
OutletID uuid.UUID `json:"outlet_id"`
|
||||
UserID uuid.UUID `json:"user_id"`
|
||||
TableNumber *string `json:"table_number"`
|
||||
OrderType string `json:"order_type"`
|
||||
Status string `json:"status"`
|
||||
Subtotal float64 `json:"subtotal"`
|
||||
TaxAmount float64 `json:"tax_amount"`
|
||||
DiscountAmount float64 `json:"discount_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"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
||||
Payments []PaymentResponse `json:"payments,omitempty"`
|
||||
TotalPaid float64 `json:"total_paid"`
|
||||
PaymentCount int `json:"payment_count"`
|
||||
SplitType *string `json:"split_type,omitempty"`
|
||||
}
|
||||
|
||||
type OrderItemResponse struct {
|
||||
@ -123,11 +138,12 @@ type ListOrdersRequest struct {
|
||||
}
|
||||
|
||||
type ListOrdersResponse struct {
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
Payments []PaymentResponse `json:"payments"`
|
||||
TotalCount int `json:"total_count"`
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
}
|
||||
|
||||
type VoidOrderRequest struct {
|
||||
@ -152,7 +168,6 @@ type SetOrderCustomerResponse struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// Payment-related contracts
|
||||
type CreatePaymentRequest struct {
|
||||
OrderID uuid.UUID `json:"order_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"`
|
||||
SplitNumber int `json:"split_number,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"`
|
||||
PaymentOrderItems []CreatePaymentOrderItemRequest `json:"payment_order_items,omitempty" validate:"omitempty,dive"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
@ -174,11 +190,14 @@ type PaymentResponse struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
OrderID uuid.UUID `json:"order_id"`
|
||||
PaymentMethodID uuid.UUID `json:"payment_method_id"`
|
||||
PaymentMethodName string `json:"payment_method_name"`
|
||||
PaymentMethodType string `json:"payment_method_type"`
|
||||
Amount float64 `json:"amount"`
|
||||
Status string `json:"status"`
|
||||
TransactionID *string `json:"transaction_id,omitempty"`
|
||||
SplitNumber int `json:"split_number"`
|
||||
SplitTotal int `json:"split_total"`
|
||||
SplitType *string `json:"split_type,omitempty"`
|
||||
SplitDescription *string `json:"split_description,omitempty"`
|
||||
RefundAmount float64 `json:"refund_amount"`
|
||||
RefundReason *string `json:"refund_reason,omitempty"`
|
||||
@ -216,3 +235,33 @@ type RefundPaymentRequest struct {
|
||||
RefundAmount float64 `json:"refund_amount" validate:"required,min=0"`
|
||||
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 (
|
||||
PaymentStatusPending PaymentStatus = "pending"
|
||||
PaymentStatusPartial PaymentStatus = "partial"
|
||||
PaymentStatusCompleted PaymentStatus = "completed"
|
||||
PaymentStatusFailed PaymentStatus = "failed"
|
||||
PaymentStatusRefunded PaymentStatus = "refunded"
|
||||
@ -35,33 +36,34 @@ const (
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id" validate:"required"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id" validate:"required"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id" validate:"required"`
|
||||
CustomerID *uuid.UUID `gorm:"type:uuid;index" json:"customer_id"`
|
||||
OrderNumber string `gorm:"uniqueIndex;not null;size:50" json:"order_number" validate:"required"`
|
||||
TableNumber *string `gorm:"size:20" json:"table_number"`
|
||||
OrderType OrderType `gorm:"not null;size:50" json:"order_type" validate:"required,oneof=dine_in takeout delivery"`
|
||||
Status OrderStatus `gorm:"default:'pending';size:50" json:"status"`
|
||||
Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal" validate:"required,min=0"`
|
||||
TaxAmount float64 `gorm:"type:decimal(10,2);not null" json:"tax_amount" validate:"required,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"`
|
||||
TotalCost float64 `gorm:"type:decimal(10,2);default:0.00" json:"total_cost"`
|
||||
PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
IsVoid bool `gorm:"default:false" json:"is_void"`
|
||||
IsRefund bool `gorm:"default:false" json:"is_refund"`
|
||||
VoidReason *string `gorm:"size:255" json:"void_reason,omitempty"`
|
||||
VoidedAt *time.Time `gorm:"" json:"voided_at,omitempty"`
|
||||
VoidedBy *uuid.UUID `gorm:"type:uuid" json:"voided_by,omitempty"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id" validate:"required"`
|
||||
OutletID uuid.UUID `gorm:"type:uuid;not null;index" json:"outlet_id" validate:"required"`
|
||||
UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id" validate:"required"`
|
||||
CustomerID *uuid.UUID `gorm:"type:uuid;index" json:"customer_id"`
|
||||
OrderNumber string `gorm:"uniqueIndex;not null;size:50" json:"order_number" validate:"required"`
|
||||
TableNumber *string `gorm:"size:20" json:"table_number"`
|
||||
OrderType OrderType `gorm:"not null;size:50" json:"order_type" validate:"required,oneof=dine_in takeout delivery"`
|
||||
Status OrderStatus `gorm:"default:'pending';size:50" json:"status"`
|
||||
Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal" validate:"required,min=0"`
|
||||
TaxAmount float64 `gorm:"type:decimal(10,2);not null" json:"tax_amount" validate:"required,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"`
|
||||
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"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
IsVoid bool `gorm:"default:false" json:"is_void"`
|
||||
IsRefund bool `gorm:"default:false" json:"is_refund"`
|
||||
VoidReason *string `gorm:"size:255" json:"void_reason,omitempty"`
|
||||
VoidedAt *time.Time `gorm:"" json:"voided_at,omitempty"`
|
||||
VoidedBy *uuid.UUID `gorm:"type:uuid" json:"voided_by,omitempty"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
|
||||
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
|
||||
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
|
||||
|
||||
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
|
||||
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`
|
||||
|
||||
@ -50,6 +50,13 @@ const (
|
||||
PaymentTransactionStatusRefunded PaymentTransactionStatus = "refunded"
|
||||
)
|
||||
|
||||
type SplitType string
|
||||
|
||||
const (
|
||||
SplitTypeAmount SplitType = "AMOUNT"
|
||||
SplitTypeItem SplitType = "ITEM"
|
||||
)
|
||||
|
||||
type Payment struct {
|
||||
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"`
|
||||
@ -59,6 +66,7 @@ type Payment struct {
|
||||
TransactionID *string `gorm:"size:255" json:"transaction_id"`
|
||||
SplitNumber int `gorm:"default:1" json:"split_number"`
|
||||
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"`
|
||||
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
|
||||
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
|
||||
|
||||
@ -265,7 +265,6 @@ func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
contextInfo := appcontext.FromGinContext(ctx)
|
||||
|
||||
// Parse order ID from URL parameter
|
||||
orderIDStr := c.Param("id")
|
||||
orderID, err := uuid.Parse(orderIDStr)
|
||||
if err != nil {
|
||||
@ -273,24 +272,47 @@ func (h *OrderHandler) SetOrderCustomer(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Parse request body
|
||||
var req contract.SetOrderCustomerRequest
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform contract to model
|
||||
modelReq := transformer.SetOrderCustomerContractToModel(&req)
|
||||
|
||||
// Call service
|
||||
response, err := h.orderService.SetOrderCustomer(ctx, orderID, modelReq, contextInfo.OrganizationID)
|
||||
if err != nil {
|
||||
util.HandleResponse(c.Writer, c.Request, contract.BuildErrorResponse([]*contract.ResponseError{contract.NewResponseError("internal_error", "OrderHandler::SetOrderCustomer", err.Error())}), "OrderHandler::SetOrderCustomer")
|
||||
return
|
||||
}
|
||||
|
||||
// Transform model to contract
|
||||
contractResp := transformer.SetOrderCustomerModelToContract(response)
|
||||
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,
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if page, err := strconv.Atoi(pageStr); err == nil {
|
||||
req.Page = page
|
||||
|
||||
@ -8,42 +8,65 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Entity to Response mappers
|
||||
func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
|
||||
if order == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
response := &models.OrderResponse{
|
||||
ID: order.ID,
|
||||
OrganizationID: order.OrganizationID,
|
||||
OutletID: order.OutletID,
|
||||
UserID: order.UserID,
|
||||
CustomerID: order.CustomerID,
|
||||
OrderNumber: order.OrderNumber,
|
||||
TableNumber: order.TableNumber,
|
||||
OrderType: constants.OrderType(order.OrderType),
|
||||
Status: constants.OrderStatus(order.Status),
|
||||
Subtotal: order.Subtotal,
|
||||
TaxAmount: order.TaxAmount,
|
||||
DiscountAmount: order.DiscountAmount,
|
||||
TotalAmount: order.TotalAmount,
|
||||
TotalCost: order.TotalCost,
|
||||
PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
|
||||
RefundAmount: order.RefundAmount,
|
||||
IsVoid: order.IsVoid,
|
||||
IsRefund: order.IsRefund,
|
||||
VoidReason: order.VoidReason,
|
||||
VoidedAt: order.VoidedAt,
|
||||
VoidedBy: order.VoidedBy,
|
||||
RefundReason: order.RefundReason,
|
||||
RefundedAt: order.RefundedAt,
|
||||
RefundedBy: order.RefundedBy,
|
||||
Metadata: map[string]interface{}(order.Metadata),
|
||||
CreatedAt: order.CreatedAt,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
ID: order.ID,
|
||||
OrganizationID: order.OrganizationID,
|
||||
OutletID: order.OutletID,
|
||||
UserID: order.UserID,
|
||||
CustomerID: order.CustomerID,
|
||||
OrderNumber: order.OrderNumber,
|
||||
TableNumber: order.TableNumber,
|
||||
OrderType: constants.OrderType(order.OrderType),
|
||||
Status: constants.OrderStatus(order.Status),
|
||||
Subtotal: order.Subtotal,
|
||||
TaxAmount: order.TaxAmount,
|
||||
DiscountAmount: order.DiscountAmount,
|
||||
TotalAmount: order.TotalAmount,
|
||||
TotalCost: order.TotalCost,
|
||||
RemainingAmount: order.RemainingAmount,
|
||||
PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
|
||||
RefundAmount: order.RefundAmount,
|
||||
IsVoid: order.IsVoid,
|
||||
IsRefund: order.IsRefund,
|
||||
VoidReason: order.VoidReason,
|
||||
VoidedAt: order.VoidedAt,
|
||||
VoidedBy: order.VoidedBy,
|
||||
RefundReason: order.RefundReason,
|
||||
RefundedAt: order.RefundedAt,
|
||||
RefundedBy: order.RefundedBy,
|
||||
Metadata: map[string]interface{}(order.Metadata),
|
||||
CreatedAt: order.CreatedAt,
|
||||
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
|
||||
if order.OrderItems != nil {
|
||||
response.OrderItems = make([]models.OrderItemResponse, len(order.OrderItems))
|
||||
@ -119,6 +142,7 @@ func PaymentEntityToResponse(payment *entities.Payment) *models.PaymentResponse
|
||||
TransactionID: payment.TransactionID,
|
||||
SplitNumber: payment.SplitNumber,
|
||||
SplitTotal: payment.SplitTotal,
|
||||
SplitType: (*string)(payment.SplitType),
|
||||
SplitDescription: payment.SplitDescription,
|
||||
RefundAmount: payment.RefundAmount,
|
||||
RefundReason: payment.RefundReason,
|
||||
@ -129,6 +153,12 @@ func PaymentEntityToResponse(payment *entities.Payment) *models.PaymentResponse
|
||||
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
|
||||
if payment.PaymentOrderItems != nil {
|
||||
response.PaymentOrderItems = make([]models.PaymentOrderItemResponse, len(payment.PaymentOrderItems))
|
||||
@ -201,6 +231,12 @@ func CreatePaymentRequestToEntity(req *models.CreatePaymentRequest) *entities.Pa
|
||||
return nil
|
||||
}
|
||||
|
||||
var splitType *entities.SplitType
|
||||
if req.SplitType != nil {
|
||||
st := entities.SplitType(*req.SplitType)
|
||||
splitType = &st
|
||||
}
|
||||
|
||||
payment := &entities.Payment{
|
||||
OrderID: req.OrderID,
|
||||
PaymentMethodID: req.PaymentMethodID,
|
||||
@ -209,6 +245,7 @@ func CreatePaymentRequestToEntity(req *models.CreatePaymentRequest) *entities.Pa
|
||||
TransactionID: req.TransactionID,
|
||||
SplitNumber: req.SplitNumber,
|
||||
SplitTotal: req.SplitTotal,
|
||||
SplitType: splitType,
|
||||
SplitDescription: req.SplitDescription,
|
||||
Metadata: entities.Metadata(req.Metadata),
|
||||
}
|
||||
|
||||
@ -8,33 +8,34 @@ import (
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
OutletID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CustomerID *uuid.UUID
|
||||
OrderNumber string
|
||||
TableNumber *string
|
||||
OrderType constants.OrderType
|
||||
Status constants.OrderStatus
|
||||
Subtotal float64
|
||||
TaxAmount float64
|
||||
DiscountAmount float64
|
||||
TotalAmount float64
|
||||
TotalCost float64
|
||||
PaymentStatus constants.PaymentStatus
|
||||
RefundAmount float64
|
||||
IsVoid bool
|
||||
IsRefund bool
|
||||
VoidReason *string
|
||||
VoidedAt *time.Time
|
||||
VoidedBy *uuid.UUID
|
||||
RefundReason *string
|
||||
RefundedAt *time.Time
|
||||
RefundedBy *uuid.UUID
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
OutletID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CustomerID *uuid.UUID
|
||||
OrderNumber string
|
||||
TableNumber *string
|
||||
OrderType constants.OrderType
|
||||
Status constants.OrderStatus
|
||||
Subtotal float64
|
||||
TaxAmount float64
|
||||
DiscountAmount float64
|
||||
TotalAmount float64
|
||||
TotalCost float64
|
||||
RemainingAmount float64
|
||||
PaymentStatus constants.PaymentStatus
|
||||
RefundAmount float64
|
||||
IsVoid bool
|
||||
IsRefund bool
|
||||
VoidReason *string
|
||||
VoidedAt *time.Time
|
||||
VoidedBy *uuid.UUID
|
||||
RefundReason *string
|
||||
RefundedAt *time.Time
|
||||
RefundedBy *uuid.UUID
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type OrderItem struct {
|
||||
@ -142,36 +143,40 @@ type RefundOrderItemRequest struct {
|
||||
|
||||
// Response DTOs
|
||||
type OrderResponse struct {
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
OutletID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CustomerID *uuid.UUID
|
||||
OrderNumber string
|
||||
TableNumber *string
|
||||
OrderType constants.OrderType
|
||||
Status constants.OrderStatus
|
||||
Subtotal float64
|
||||
TaxAmount float64
|
||||
DiscountAmount float64
|
||||
TotalAmount float64
|
||||
TotalCost float64
|
||||
PaymentStatus constants.PaymentStatus
|
||||
RefundAmount float64
|
||||
IsVoid bool
|
||||
IsRefund bool
|
||||
VoidReason *string
|
||||
VoidedAt *time.Time
|
||||
VoidedBy *uuid.UUID
|
||||
RefundReason *string
|
||||
RefundedAt *time.Time
|
||||
RefundedBy *uuid.UUID
|
||||
Notes *string
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
OrderItems []OrderItemResponse
|
||||
Payments []PaymentResponse
|
||||
ID uuid.UUID
|
||||
OrganizationID uuid.UUID
|
||||
OutletID uuid.UUID
|
||||
UserID uuid.UUID
|
||||
CustomerID *uuid.UUID
|
||||
OrderNumber string
|
||||
TableNumber *string
|
||||
OrderType constants.OrderType
|
||||
Status constants.OrderStatus
|
||||
Subtotal float64
|
||||
TaxAmount float64
|
||||
DiscountAmount float64
|
||||
TotalAmount float64
|
||||
TotalCost float64
|
||||
RemainingAmount float64
|
||||
PaymentStatus constants.PaymentStatus
|
||||
RefundAmount float64
|
||||
IsVoid bool
|
||||
IsRefund bool
|
||||
VoidReason *string
|
||||
VoidedAt *time.Time
|
||||
VoidedBy *uuid.UUID
|
||||
RefundReason *string
|
||||
RefundedAt *time.Time
|
||||
RefundedBy *uuid.UUID
|
||||
Notes *string
|
||||
Metadata map[string]interface{}
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
OrderItems []OrderItemResponse
|
||||
Payments []PaymentResponse
|
||||
TotalPaid float64
|
||||
PaymentCount int
|
||||
SplitType *string
|
||||
}
|
||||
|
||||
type OrderItemResponse struct {
|
||||
@ -230,6 +235,7 @@ type ListOrdersRequest struct {
|
||||
|
||||
type ListOrdersResponse struct {
|
||||
Orders []OrderResponse
|
||||
Payments []PaymentResponse
|
||||
TotalCount int
|
||||
Page int
|
||||
Limit int
|
||||
|
||||
@ -16,6 +16,7 @@ type Payment struct {
|
||||
TransactionID *string
|
||||
SplitNumber int
|
||||
SplitTotal int
|
||||
SplitType *string
|
||||
SplitDescription *string
|
||||
RefundAmount float64
|
||||
RefundReason *string
|
||||
@ -33,6 +34,7 @@ type CreatePaymentRequest struct {
|
||||
TransactionID *string `validate:"omitempty"`
|
||||
SplitNumber int `validate:"omitempty,min=1"`
|
||||
SplitTotal int `validate:"omitempty,min=1"`
|
||||
SplitType *string `validate:"omitempty,oneof=AMOUNT ITEM"`
|
||||
SplitDescription *string `validate:"omitempty,max=255"`
|
||||
PaymentOrderItems []CreatePaymentOrderItemRequest `validate:"omitempty,dive"`
|
||||
Metadata map[string]interface{}
|
||||
@ -48,11 +50,14 @@ type PaymentResponse struct {
|
||||
ID uuid.UUID
|
||||
OrderID uuid.UUID
|
||||
PaymentMethodID uuid.UUID
|
||||
PaymentMethodName string
|
||||
PaymentMethodType constants.PaymentMethodType
|
||||
Amount float64
|
||||
Status constants.PaymentTransactionStatus
|
||||
TransactionID *string
|
||||
SplitNumber int
|
||||
SplitTotal int
|
||||
SplitType *string
|
||||
SplitDescription *string
|
||||
RefundAmount float64
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/mappers"
|
||||
@ -24,6 +26,7 @@ type OrderProcessor interface {
|
||||
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
|
||||
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 {
|
||||
@ -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
|
||||
}
|
||||
|
||||
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 {
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*entities.PaymentMethod, error)
|
||||
}
|
||||
@ -95,6 +106,7 @@ type OrderProcessorImpl struct {
|
||||
orderRepo OrderRepository
|
||||
orderItemRepo OrderItemRepository
|
||||
paymentRepo PaymentRepository
|
||||
paymentOrderItemRepo PaymentOrderItemRepository
|
||||
productRepo ProductRepository
|
||||
paymentMethodRepo PaymentMethodRepository
|
||||
inventoryRepo repository.InventoryRepository
|
||||
@ -108,6 +120,7 @@ func NewOrderProcessorImpl(
|
||||
orderRepo OrderRepository,
|
||||
orderItemRepo OrderItemRepository,
|
||||
paymentRepo PaymentRepository,
|
||||
paymentOrderItemRepo PaymentOrderItemRepository,
|
||||
productRepo ProductRepository,
|
||||
paymentMethodRepo PaymentMethodRepository,
|
||||
inventoryRepo repository.InventoryRepository,
|
||||
@ -120,6 +133,7 @@ func NewOrderProcessorImpl(
|
||||
orderRepo: orderRepo,
|
||||
orderItemRepo: orderItemRepo,
|
||||
paymentRepo: paymentRepo,
|
||||
paymentOrderItemRepo: paymentOrderItemRepo,
|
||||
productRepo: productRepo,
|
||||
paymentMethodRepo: paymentMethodRepo,
|
||||
inventoryRepo: inventoryRepo,
|
||||
@ -203,23 +217,24 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
|
||||
metadata["customer_name"] = *req.CustomerName
|
||||
}
|
||||
order := &entities.Order{
|
||||
OrganizationID: organizationID,
|
||||
OutletID: req.OutletID,
|
||||
UserID: req.UserID,
|
||||
CustomerID: req.CustomerID,
|
||||
OrderNumber: orderNumber,
|
||||
TableNumber: req.TableNumber,
|
||||
OrderType: entities.OrderType(req.OrderType),
|
||||
Status: entities.OrderStatusPending,
|
||||
Subtotal: subtotal,
|
||||
TaxAmount: taxAmount,
|
||||
DiscountAmount: 0,
|
||||
TotalAmount: totalAmount,
|
||||
TotalCost: totalCost,
|
||||
PaymentStatus: entities.PaymentStatusPending,
|
||||
IsVoid: false,
|
||||
IsRefund: false,
|
||||
Metadata: metadata,
|
||||
OrganizationID: organizationID,
|
||||
OutletID: req.OutletID,
|
||||
UserID: req.UserID,
|
||||
CustomerID: req.CustomerID,
|
||||
OrderNumber: orderNumber,
|
||||
TableNumber: req.TableNumber,
|
||||
OrderType: entities.OrderType(req.OrderType),
|
||||
Status: entities.OrderStatusPending,
|
||||
Subtotal: subtotal,
|
||||
TaxAmount: taxAmount,
|
||||
DiscountAmount: 0,
|
||||
TotalAmount: totalAmount,
|
||||
TotalCost: totalCost,
|
||||
RemainingAmount: totalAmount, // Initialize remaining amount equal to total amount
|
||||
PaymentStatus: entities.PaymentStatusPending,
|
||||
IsVoid: false,
|
||||
IsRefund: false,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if err := p.orderRepo.Create(ctx, order); err != nil {
|
||||
@ -325,6 +340,17 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
|
||||
order.TaxAmount = order.Subtotal * outlet.TaxRate
|
||||
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 order.Metadata == nil {
|
||||
order.Metadata = make(entities.Metadata)
|
||||
@ -409,6 +435,17 @@ func (p *OrderProcessorImpl) UpdateOrder(ctx context.Context, id uuid.UUID, req
|
||||
order.DiscountAmount = *req.DiscountAmount
|
||||
// Recalculate total amount
|
||||
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 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)
|
||||
}
|
||||
|
||||
// Convert to responses
|
||||
orderResponses := make([]models.OrderResponse, len(orders))
|
||||
allPayments := make([]models.PaymentResponse, 0)
|
||||
|
||||
for i, order := range orders {
|
||||
response := mappers.OrderEntityToResponse(order)
|
||||
if response != nil {
|
||||
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
|
||||
if int(total)%req.Limit > 0 {
|
||||
totalPages++
|
||||
@ -506,6 +547,7 @@ func (p *OrderProcessorImpl) ListOrders(ctx context.Context, req *models.ListOrd
|
||||
|
||||
return &models.ListOrdersResponse{
|
||||
Orders: orderResponses,
|
||||
Payments: allPayments,
|
||||
TotalCount: int(total),
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
@ -738,6 +780,21 @@ func (p *OrderProcessorImpl) CreatePayment(ctx context.Context, req *models.Crea
|
||||
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)
|
||||
if err != nil {
|
||||
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) {
|
||||
// Get the order
|
||||
order, err := p.orderRepo.GetByID(ctx, orderID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("order not found: %w", err)
|
||||
}
|
||||
|
||||
// Verify order belongs to the organization
|
||||
if order.OrganizationID != organizationID {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
offset := (page - 1) * limit
|
||||
|
||||
// Get outlets with pagination
|
||||
outlets, total, err := p.outletRepo.GetByOrganizationIDWithPagination(ctx, organizationID, limit, offset)
|
||||
if err != nil {
|
||||
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.ProductVariant").
|
||||
Preload("Payments").
|
||||
Preload("Payments.PaymentMethod")
|
||||
Preload("Payments.PaymentMethod").
|
||||
Preload("Payments.PaymentOrderItems")
|
||||
|
||||
for key, value := range filters {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
|
||||
@ -211,6 +211,7 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
||||
orders.PUT("/:id/customer", r.orderHandler.SetOrderCustomer)
|
||||
orders.POST("/void", r.orderHandler.VoidOrder)
|
||||
orders.POST("/:id/refund", r.orderHandler.RefundOrder)
|
||||
orders.POST("/split-bill", r.orderHandler.SplitBill)
|
||||
}
|
||||
|
||||
payments := protected.Group("/payments")
|
||||
|
||||
@ -21,6 +21,7 @@ type OrderService interface {
|
||||
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
|
||||
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 {
|
||||
@ -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) {
|
||||
// Validate request
|
||||
if err := s.validateListOrdersRequest(req); err != nil {
|
||||
return nil, fmt.Errorf("validation error: %w", err)
|
||||
}
|
||||
|
||||
// Process order listing
|
||||
response, err := s.orderProcessor.ListOrders(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list orders: %w", err)
|
||||
@ -384,3 +383,65 @@ func (s *OrderServiceImpl) validateSetOrderCustomerRequest(req *models.SetOrderC
|
||||
|
||||
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 {
|
||||
// Validate request
|
||||
if 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)
|
||||
}
|
||||
|
||||
// Create paginated response
|
||||
response := transformer.CreateListOutletsResponse(contractOutlets, int(total), req.Page, req.Limit)
|
||||
return contract.BuildSuccessResponse(response)
|
||||
}
|
||||
|
||||
@ -111,24 +111,45 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
|
||||
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{
|
||||
ID: resp.ID,
|
||||
OrderNumber: resp.OrderNumber,
|
||||
OutletID: resp.OutletID,
|
||||
UserID: resp.UserID,
|
||||
TableNumber: resp.TableNumber,
|
||||
OrderType: string(resp.OrderType),
|
||||
Status: string(resp.Status),
|
||||
Subtotal: resp.Subtotal,
|
||||
TaxAmount: resp.TaxAmount,
|
||||
DiscountAmount: resp.DiscountAmount,
|
||||
TotalAmount: resp.TotalAmount,
|
||||
Notes: resp.Notes,
|
||||
Metadata: resp.Metadata,
|
||||
CreatedAt: resp.CreatedAt,
|
||||
UpdatedAt: resp.UpdatedAt,
|
||||
OrderItems: items,
|
||||
IsRefund: resp.IsRefund,
|
||||
ID: resp.ID,
|
||||
OrderNumber: resp.OrderNumber,
|
||||
OutletID: resp.OutletID,
|
||||
UserID: resp.UserID,
|
||||
TableNumber: resp.TableNumber,
|
||||
OrderType: string(resp.OrderType),
|
||||
Status: string(resp.Status),
|
||||
Subtotal: resp.Subtotal,
|
||||
TaxAmount: resp.TaxAmount,
|
||||
DiscountAmount: resp.DiscountAmount,
|
||||
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,
|
||||
Metadata: resp.Metadata,
|
||||
CreatedAt: resp.CreatedAt,
|
||||
UpdatedAt: resp.UpdatedAt,
|
||||
OrderItems: items,
|
||||
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)
|
||||
}
|
||||
|
||||
payments := make([]contract.PaymentResponse, len(resp.Payments))
|
||||
for i, payment := range resp.Payments {
|
||||
payments[i] = *PaymentModelToContract(&payment)
|
||||
}
|
||||
|
||||
return &contract.ListOrdersResponse{
|
||||
Orders: orders,
|
||||
Payments: payments,
|
||||
TotalCount: resp.TotalCount,
|
||||
Page: resp.Page,
|
||||
Limit: resp.Limit,
|
||||
@ -309,11 +336,14 @@ func PaymentModelToContract(resp *models.PaymentResponse) *contract.PaymentRespo
|
||||
ID: resp.ID,
|
||||
OrderID: resp.OrderID,
|
||||
PaymentMethodID: resp.PaymentMethodID,
|
||||
PaymentMethodName: resp.PaymentMethodName,
|
||||
PaymentMethodType: string(resp.PaymentMethodType),
|
||||
Amount: resp.Amount,
|
||||
Status: string(resp.Status),
|
||||
TransactionID: resp.TransactionID,
|
||||
SplitNumber: resp.SplitNumber,
|
||||
SplitTotal: resp.SplitTotal,
|
||||
SplitType: resp.SplitType,
|
||||
SplitDescription: resp.SplitDescription,
|
||||
RefundAmount: resp.RefundAmount,
|
||||
RefundReason: resp.RefundReason,
|
||||
@ -342,3 +372,46 @@ func RefundOrderContractToModel(req *contract.RefundOrderRequest) *models.Refund
|
||||
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,
|
||||
Name: model.Name,
|
||||
Address: *model.Address,
|
||||
BusinessType: string(constants.BusinessTypeRestaurant), // Default business type
|
||||
BusinessType: string(constants.BusinessTypeRestaurant),
|
||||
Currency: model.Currency,
|
||||
TaxRate: model.TaxRate,
|
||||
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