Fix Split Bill

This commit is contained in:
Aditya Siregar 2025-08-07 22:45:02 +07:00
parent 3696451dc6
commit 93a3b29ae9
24 changed files with 894 additions and 197 deletions

View File

@ -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),

View File

@ -56,23 +56,38 @@ type UpdateOrderItemRequest struct {
} }
type OrderResponse struct { type OrderResponse struct {
ID uuid.UUID `json:"id"` ID uuid.UUID `json:"id"`
OrderNumber string `json:"order_number"` OrderNumber string `json:"order_number"`
OutletID uuid.UUID `json:"outlet_id"` OutletID uuid.UUID `json:"outlet_id"`
UserID uuid.UUID `json:"user_id"` UserID uuid.UUID `json:"user_id"`
TableNumber *string `json:"table_number"` TableNumber *string `json:"table_number"`
OrderType string `json:"order_type"` OrderType string `json:"order_type"`
Status string `json:"status"` Status string `json:"status"`
Subtotal float64 `json:"subtotal"` Subtotal float64 `json:"subtotal"`
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"`
Notes *string `json:"notes"` TotalCost float64 `json:"total_cost"`
Metadata map[string]interface{} `json:"metadata,omitempty"` RemainingAmount float64 `json:"remaining_amount"`
CreatedAt time.Time `json:"created_at"` PaymentStatus string `json:"payment_status"`
UpdatedAt time.Time `json:"updated_at"` RefundAmount float64 `json:"refund_amount"`
OrderItems []OrderItemResponse `json:"order_items,omitempty"` IsVoid bool `json:"is_void"`
IsRefund bool `json:"is_refund"` 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 { type OrderItemResponse struct {
@ -123,11 +138,12 @@ type ListOrdersRequest struct {
} }
type ListOrdersResponse struct { type ListOrdersResponse struct {
Orders []OrderResponse `json:"orders"` Orders []OrderResponse `json:"orders"`
TotalCount int `json:"total_count"` Payments []PaymentResponse `json:"payments"`
Page int `json:"page"` TotalCount int `json:"total_count"`
Limit int `json:"limit"` Page int `json:"page"`
TotalPages int `json:"total_pages"` Limit int `json:"limit"`
TotalPages int `json:"total_pages"`
} }
type VoidOrderRequest struct { type VoidOrderRequest struct {
@ -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"`
}

View File

@ -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"
@ -35,33 +36,34 @@ const (
) )
type Order struct { type Order 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"`
OrganizationID uuid.UUID `gorm:"type:uuid;not null;index" json:"organization_id" validate:"required"` 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"` 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"` UserID uuid.UUID `gorm:"type:uuid;not null;index" json:"user_id" validate:"required"`
CustomerID *uuid.UUID `gorm:"type:uuid;index" json:"customer_id"` CustomerID *uuid.UUID `gorm:"type:uuid;index" json:"customer_id"`
OrderNumber string `gorm:"uniqueIndex;not null;size:50" json:"order_number" validate:"required"` OrderNumber string `gorm:"uniqueIndex;not null;size:50" json:"order_number" validate:"required"`
TableNumber *string `gorm:"size:20" json:"table_number"` 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"` 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"` Status OrderStatus `gorm:"default:'pending';size:50" json:"status"`
Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal" validate:"required,min=0"` 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"` 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"` 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"`
PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"` RemainingAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"remaining_amount"`
RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"` PaymentStatus PaymentStatus `gorm:"default:'pending';size:50" json:"payment_status"`
IsVoid bool `gorm:"default:false" json:"is_void"` RefundAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"refund_amount"`
IsRefund bool `gorm:"default:false" json:"is_refund"` IsVoid bool `gorm:"default:false" json:"is_void"`
VoidReason *string `gorm:"size:255" json:"void_reason,omitempty"` IsRefund bool `gorm:"default:false" json:"is_refund"`
VoidedAt *time.Time `gorm:"" json:"voided_at,omitempty"` VoidReason *string `gorm:"size:255" json:"void_reason,omitempty"`
VoidedBy *uuid.UUID `gorm:"type:uuid" json:"voided_by,omitempty"` VoidedAt *time.Time `gorm:"" json:"voided_at,omitempty"`
RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"` VoidedBy *uuid.UUID `gorm:"type:uuid" json:"voided_by,omitempty"`
RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"` RefundReason *string `gorm:"size:255" json:"refund_reason,omitempty"`
RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"` RefundedAt *time.Time `gorm:"" json:"refunded_at,omitempty"`
Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"` RefundedBy *uuid.UUID `gorm:"type:uuid" json:"refunded_by,omitempty"`
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` Metadata Metadata `gorm:"type:jsonb;default:'{}'" json:"metadata"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"`
Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"` Organization Organization `gorm:"foreignKey:OrganizationID" json:"organization,omitempty"`
Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"` Outlet Outlet `gorm:"foreignKey:OutletID" json:"outlet,omitempty"`

View File

@ -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"`

View File

@ -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")
}

View File

@ -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

View File

@ -8,42 +8,65 @@ 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
} }
response := &models.OrderResponse{ response := &models.OrderResponse{
ID: order.ID, ID: order.ID,
OrganizationID: order.OrganizationID, OrganizationID: order.OrganizationID,
OutletID: order.OutletID, OutletID: order.OutletID,
UserID: order.UserID, UserID: order.UserID,
CustomerID: order.CustomerID, CustomerID: order.CustomerID,
OrderNumber: order.OrderNumber, OrderNumber: order.OrderNumber,
TableNumber: order.TableNumber, TableNumber: order.TableNumber,
OrderType: constants.OrderType(order.OrderType), OrderType: constants.OrderType(order.OrderType),
Status: constants.OrderStatus(order.Status), Status: constants.OrderStatus(order.Status),
Subtotal: order.Subtotal, Subtotal: order.Subtotal,
TaxAmount: order.TaxAmount, TaxAmount: order.TaxAmount,
DiscountAmount: order.DiscountAmount, DiscountAmount: order.DiscountAmount,
TotalAmount: order.TotalAmount, TotalAmount: order.TotalAmount,
TotalCost: order.TotalCost, TotalCost: order.TotalCost,
PaymentStatus: constants.PaymentStatus(order.PaymentStatus), RemainingAmount: order.RemainingAmount,
RefundAmount: order.RefundAmount, PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
IsVoid: order.IsVoid, RefundAmount: order.RefundAmount,
IsRefund: order.IsRefund, IsVoid: order.IsVoid,
VoidReason: order.VoidReason, IsRefund: order.IsRefund,
VoidedAt: order.VoidedAt, VoidReason: order.VoidReason,
VoidedBy: order.VoidedBy, VoidedAt: order.VoidedAt,
RefundReason: order.RefundReason, VoidedBy: order.VoidedBy,
RefundedAt: order.RefundedAt, RefundReason: order.RefundReason,
RefundedBy: order.RefundedBy, RefundedAt: order.RefundedAt,
Metadata: map[string]interface{}(order.Metadata), RefundedBy: order.RefundedBy,
CreatedAt: order.CreatedAt, Metadata: map[string]interface{}(order.Metadata),
UpdatedAt: order.UpdatedAt, 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 // 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),
} }

View File

@ -8,33 +8,34 @@ import (
) )
type Order struct { type Order struct {
ID uuid.UUID ID uuid.UUID
OrganizationID uuid.UUID OrganizationID uuid.UUID
OutletID uuid.UUID OutletID uuid.UUID
UserID uuid.UUID UserID uuid.UUID
CustomerID *uuid.UUID CustomerID *uuid.UUID
OrderNumber string OrderNumber string
TableNumber *string TableNumber *string
OrderType constants.OrderType OrderType constants.OrderType
Status constants.OrderStatus Status constants.OrderStatus
Subtotal float64 Subtotal float64
TaxAmount float64 TaxAmount float64
DiscountAmount float64 DiscountAmount float64
TotalAmount float64 TotalAmount float64
TotalCost float64 TotalCost float64
PaymentStatus constants.PaymentStatus RemainingAmount float64
RefundAmount float64 PaymentStatus constants.PaymentStatus
IsVoid bool RefundAmount float64
IsRefund bool IsVoid bool
VoidReason *string IsRefund bool
VoidedAt *time.Time VoidReason *string
VoidedBy *uuid.UUID VoidedAt *time.Time
RefundReason *string VoidedBy *uuid.UUID
RefundedAt *time.Time RefundReason *string
RefundedBy *uuid.UUID RefundedAt *time.Time
Metadata map[string]interface{} RefundedBy *uuid.UUID
CreatedAt time.Time Metadata map[string]interface{}
UpdatedAt time.Time CreatedAt time.Time
UpdatedAt time.Time
} }
type OrderItem struct { type OrderItem struct {
@ -142,36 +143,40 @@ type RefundOrderItemRequest struct {
// Response DTOs // Response DTOs
type OrderResponse struct { type OrderResponse struct {
ID uuid.UUID ID uuid.UUID
OrganizationID uuid.UUID OrganizationID uuid.UUID
OutletID uuid.UUID OutletID uuid.UUID
UserID uuid.UUID UserID uuid.UUID
CustomerID *uuid.UUID CustomerID *uuid.UUID
OrderNumber string OrderNumber string
TableNumber *string TableNumber *string
OrderType constants.OrderType OrderType constants.OrderType
Status constants.OrderStatus Status constants.OrderStatus
Subtotal float64 Subtotal float64
TaxAmount float64 TaxAmount float64
DiscountAmount float64 DiscountAmount float64
TotalAmount float64 TotalAmount float64
TotalCost float64 TotalCost float64
PaymentStatus constants.PaymentStatus RemainingAmount float64
RefundAmount float64 PaymentStatus constants.PaymentStatus
IsVoid bool RefundAmount float64
IsRefund bool IsVoid bool
VoidReason *string IsRefund bool
VoidedAt *time.Time VoidReason *string
VoidedBy *uuid.UUID VoidedAt *time.Time
RefundReason *string VoidedBy *uuid.UUID
RefundedAt *time.Time RefundReason *string
RefundedBy *uuid.UUID RefundedAt *time.Time
Notes *string RefundedBy *uuid.UUID
Metadata map[string]interface{} Notes *string
CreatedAt time.Time Metadata map[string]interface{}
UpdatedAt time.Time CreatedAt time.Time
OrderItems []OrderItemResponse UpdatedAt time.Time
Payments []PaymentResponse OrderItems []OrderItemResponse
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

View File

@ -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

View 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"`
}

View File

@ -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,
@ -203,23 +217,24 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
metadata["customer_name"] = *req.CustomerName metadata["customer_name"] = *req.CustomerName
} }
order := &entities.Order{ order := &entities.Order{
OrganizationID: organizationID, OrganizationID: organizationID,
OutletID: req.OutletID, OutletID: req.OutletID,
UserID: req.UserID, UserID: req.UserID,
CustomerID: req.CustomerID, CustomerID: req.CustomerID,
OrderNumber: orderNumber, OrderNumber: orderNumber,
TableNumber: req.TableNumber, TableNumber: req.TableNumber,
OrderType: entities.OrderType(req.OrderType), OrderType: entities.OrderType(req.OrderType),
Status: entities.OrderStatusPending, Status: entities.OrderStatusPending,
Subtotal: subtotal, Subtotal: subtotal,
TaxAmount: taxAmount, TaxAmount: taxAmount,
DiscountAmount: 0, DiscountAmount: 0,
TotalAmount: totalAmount, TotalAmount: totalAmount,
TotalCost: totalCost, TotalCost: totalCost,
PaymentStatus: entities.PaymentStatusPending, RemainingAmount: totalAmount, // Initialize remaining amount equal to total amount
IsVoid: false, PaymentStatus: entities.PaymentStatusPending,
IsRefund: false, IsVoid: false,
Metadata: metadata, IsRefund: false,
Metadata: metadata,
} }
if err := p.orderRepo.Create(ctx, order); err != nil { 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.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
}

View File

@ -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)

View File

@ -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 {

View 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
}

View File

@ -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 {

View File

@ -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")

View File

@ -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
}

View File

@ -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)
} }

View File

@ -111,24 +111,45 @@ 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,
OutletID: resp.OutletID, OutletID: resp.OutletID,
UserID: resp.UserID, UserID: resp.UserID,
TableNumber: resp.TableNumber, TableNumber: resp.TableNumber,
OrderType: string(resp.OrderType), OrderType: string(resp.OrderType),
Status: string(resp.Status), Status: string(resp.Status),
Subtotal: resp.Subtotal, Subtotal: resp.Subtotal,
TaxAmount: resp.TaxAmount, TaxAmount: resp.TaxAmount,
DiscountAmount: resp.DiscountAmount, DiscountAmount: resp.DiscountAmount,
TotalAmount: resp.TotalAmount, TotalAmount: resp.TotalAmount,
Notes: resp.Notes, TotalCost: resp.TotalCost,
Metadata: resp.Metadata, RemainingAmount: resp.RemainingAmount,
CreatedAt: resp.CreatedAt, PaymentStatus: string(resp.PaymentStatus),
UpdatedAt: resp.UpdatedAt, RefundAmount: resp.RefundAmount,
OrderItems: items, IsVoid: resp.IsVoid,
IsRefund: resp.IsRefund, 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) 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,
}
}

View File

@ -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,

View File

@ -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;

View 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);

View 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;

View 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'));