fix: prevent race condition on order subtotal calculation

This commit is contained in:
Efril 2026-06-03 23:59:15 +07:00
parent afa1aa5b75
commit ea9dceb333
3 changed files with 44 additions and 5 deletions

View File

@ -338,7 +338,7 @@ func (p *OrderProcessorImpl) AddToOrder(ctx context.Context, orderID uuid.UUID,
ProductID: itemReq.ProductID,
ProductVariantID: itemReq.ProductVariantID,
Quantity: itemReq.Quantity,
UnitPrice: unitPrice, // Use price from database
UnitPrice: unitPrice,
TotalPrice: itemTotalPrice,
UnitCost: unitCost,
TotalCost: itemTotalCost,
@ -594,6 +594,10 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
return fmt.Errorf("order item does not belong to this order")
}
if orderItem.Status == entities.OrderItemStatusCancelled {
return fmt.Errorf("order item %s is already cancelled", orderItemID)
}
if itemVoid.Quantity > orderItem.Quantity {
return fmt.Errorf("void quantity cannot exceed original quantity for item %d", itemVoid.OrderItemID)
}
@ -614,9 +618,15 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
return fmt.Errorf("outlet not found: %w", err)
}
// Reload order to get latest state
order, err = p.orderRepo.GetByID(ctx, req.OrderID)
if err != nil {
return fmt.Errorf("failed to reload order: %w", err)
}
order.Subtotal -= totalVoidedAmount
order.TotalCost -= totalVoidedCost
order.TaxAmount = order.Subtotal * outlet.TaxRate // Recalculate tax using outlet's tax rate
order.TaxAmount = order.Subtotal * outlet.TaxRate
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
if err := p.orderRepo.Update(ctx, order); err != nil {

View File

@ -2,6 +2,7 @@ package repository
import (
"context"
"database/sql"
"gorm.io/gorm"
)
@ -37,3 +38,15 @@ func (m *TxManager) WithTransaction(ctx context.Context, fn func(ctx context.Con
return fn(ctxTx)
})
}
// WithTransactionOptions runs fn inside a DB transaction with custom TxOptions (e.g. isolation level).
func (m *TxManager) WithTransactionOptions(ctx context.Context, opts *sql.TxOptions, fn func(ctx context.Context) error) error {
if m == nil || m.db == nil {
return fn(ctx)
}
return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
ctxTx := context.WithValue(ctx, txKey, tx)
return fn(ctxTx)
}, opts)
}

View File

@ -3,6 +3,7 @@ package service
import (
"apskel-pos-be/internal/appcontext"
"context"
"database/sql"
"fmt"
"time"
@ -228,7 +229,9 @@ func (s *OrderServiceImpl) AddToOrder(ctx context.Context, orderID uuid.UUID, re
var response *models.AddToOrderResponse
var ingredientTransactions []*contract.CreateOrderIngredientTransactionRequest
err := s.txManager.WithTransaction(ctx, func(txCtx context.Context) error {
err := s.txManager.WithTransactionOptions(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
}, func(txCtx context.Context) error {
addResp, err := s.orderProcessor.AddToOrder(txCtx, orderID, req)
if err != nil {
return fmt.Errorf("failed to add items to order: %w", err)
@ -305,8 +308,16 @@ func (s *OrderServiceImpl) VoidOrder(ctx context.Context, req *models.VoidOrderR
return fmt.Errorf("invalid user ID")
}
if err := s.orderProcessor.VoidOrder(ctx, req, voidedBy); err != nil {
return fmt.Errorf("failed to void order: %w", err)
err := s.txManager.WithTransactionOptions(ctx, &sql.TxOptions{
Isolation: sql.LevelSerializable,
}, func(txCtx context.Context) error {
if err := s.orderProcessor.VoidOrder(txCtx, req, voidedBy); err != nil {
return fmt.Errorf("failed to void order: %w", err)
}
return nil
})
if err != nil {
return err
}
if err := s.handleTableReleaseOnVoid(ctx, req.OrderID); err != nil {
@ -561,9 +572,14 @@ func (s *OrderServiceImpl) validateCreatePaymentRequest(req *models.CreatePaymen
return fmt.Errorf("payment item amount must be greater than zero for item %d", i+1)
}
fmt.Printf("[DEBUG] CreatePayment order_id=%s item[%d] order_item_id=%s amount=%.10f\n",
req.OrderID, i, item.OrderItemID, item.Amount)
totalItemAmount += item.Amount
}
fmt.Printf("[DEBUG] CreatePayment order_id=%s total_amount=%.10f sum_items=%.10f diff=%.10f\n",
req.OrderID, req.Amount, totalItemAmount, req.Amount-totalItemAmount)
if totalItemAmount != req.Amount {
return fmt.Errorf("sum of payment item amounts must equal total payment amount")
}