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

View File

@ -67,12 +67,27 @@ type OrderResponse struct {
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"`
IsRefund bool `json:"is_refund"`
Payments []PaymentResponse `json:"payments,omitempty"`
TotalPaid float64 `json:"total_paid"`
PaymentCount int `json:"payment_count"`
SplitType *string `json:"split_type,omitempty"`
}
type OrderItemResponse struct {
@ -124,6 +139,7 @@ type ListOrdersRequest struct {
type ListOrdersResponse struct {
Orders []OrderResponse `json:"orders"`
Payments []PaymentResponse `json:"payments"`
TotalCount int `json:"total_count"`
Page int `json:"page"`
Limit int `json:"limit"`
@ -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"`
}

View File

@ -28,6 +28,7 @@ const (
const (
PaymentStatusPending PaymentStatus = "pending"
PaymentStatusPartial PaymentStatus = "partial"
PaymentStatusCompleted PaymentStatus = "completed"
PaymentStatusFailed PaymentStatus = "failed"
PaymentStatusRefunded PaymentStatus = "refunded"
@ -49,6 +50,7 @@ type Order struct {
DiscountAmount float64 `gorm:"type:decimal(10,2);default:0.00" json:"discount_amount" validate:"min=0"`
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"`

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import (
"github.com/google/uuid"
)
// Entity to Response mappers
func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
if order == nil {
return nil
@ -29,6 +28,7 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
DiscountAmount: order.DiscountAmount,
TotalAmount: order.TotalAmount,
TotalCost: order.TotalCost,
RemainingAmount: order.RemainingAmount,
PaymentStatus: constants.PaymentStatus(order.PaymentStatus),
RefundAmount: order.RefundAmount,
IsVoid: order.IsVoid,
@ -44,6 +44,29 @@ func OrderEntityToResponse(order *entities.Order) *models.OrderResponse {
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),
}

View File

@ -22,6 +22,7 @@ type Order struct {
DiscountAmount float64
TotalAmount float64
TotalCost float64
RemainingAmount float64
PaymentStatus constants.PaymentStatus
RefundAmount float64
IsVoid bool
@ -156,6 +157,7 @@ type OrderResponse struct {
DiscountAmount float64
TotalAmount float64
TotalCost float64
RemainingAmount float64
PaymentStatus constants.PaymentStatus
RefundAmount float64
IsVoid bool
@ -172,6 +174,9 @@ type OrderResponse struct {
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

View File

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

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"
"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,
@ -216,6 +230,7 @@ func (p *OrderProcessorImpl) CreateOrder(ctx context.Context, req *models.Create
DiscountAmount: 0,
TotalAmount: totalAmount,
TotalCost: totalCost,
RemainingAmount: totalAmount, // Initialize remaining amount equal to total amount
PaymentStatus: entities.PaymentStatusPending,
IsVoid: false,
IsRefund: false,
@ -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
}

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

View File

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

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

View File

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

View File

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

View File

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

View File

@ -111,6 +111,12 @@ 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,
@ -123,12 +129,27 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
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,
IsRefund: resp.IsRefund,
Payments: payments,
TotalPaid: resp.TotalPaid,
PaymentCount: resp.PaymentCount,
SplitType: resp.SplitType,
}
}
@ -257,8 +278,14 @@ func ListOrdersModelToContract(resp *models.ListOrdersResponse) *contract.ListOr
orders[i] = *OrderModelToContract(&order)
}
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,
}
}

View File

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

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