upodate system

This commit is contained in:
aditya.siregar
2025-06-27 13:01:39 +07:00
parent 1201b2e45b
commit f31f83e485
36 changed files with 2400 additions and 689 deletions
@@ -5,6 +5,7 @@ import (
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"fmt"
"github.com/pkg/errors"
"go.uber.org/zap"
)
@@ -108,25 +109,61 @@ func (s *orderSvc) VoidOrderRequest(ctx mycontext.Context, partnerID, orderID in
return err
}
// Only allow voiding for NEW, PENDING orders
if order.Status != "NEW" && order.Status != "PENDING" {
return errors.New("only new or pending orders can be voided")
}
if voidType == "ALL" {
// Void entire order
// Void all items - create new VOIDED items for all existing items
for _, orderItem := range order.OrderItems {
if orderItem.Status == "ACTIVE" && orderItem.Quantity > 0 {
// Create new VOIDED order item with the voided quantity
voidedItem := &entity.OrderItem{
OrderID: orderID,
ItemID: orderItem.ItemID,
ItemType: orderItem.ItemType,
Price: orderItem.Price,
Quantity: orderItem.Quantity, // Void the full quantity
Status: "VOIDED",
CreatedBy: orderItem.CreatedBy,
ItemName: orderItem.ItemName,
Notes: reason, // Use the reason as notes for tracking
}
err = s.repo.CreateOrderItem(ctx, orderID, voidedItem)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create voided order item", zap.Error(err))
return err
}
// Update original item quantity to 0
err = s.repo.UpdateOrderItem(ctx, orderItem.ID, 0)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update original order item", zap.Error(err))
return err
}
}
}
// Update order status to VOIDED
err = s.repo.UpdateOrder(ctx, orderID, "VOIDED", reason)
if err != nil {
logger.ContextLogger(ctx).Error("failed to void order", zap.Error(err))
return err
}
// Recalculate order totals (should be 0 for voided order)
err = s.repo.UpdateOrderTotals(ctx, orderID, 0, 0, 0)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order totals", zap.Error(err))
return err
}
} else if voidType == "ITEM" {
// Void specific items
voidedAmount := 0.0
orderItemMap := make(map[int64]*entity.OrderItem)
for _, item := range order.OrderItems {
orderItemMap[item.ID] = &item
for i := range order.OrderItems {
orderItemMap[order.OrderItems[i].ID] = &order.OrderItems[i]
}
for _, voidItem := range items {
@@ -135,55 +172,114 @@ func (s *orderSvc) VoidOrderRequest(ctx mycontext.Context, partnerID, orderID in
return errors.New(fmt.Sprintf("order item %d not found", voidItem.OrderItemID))
}
if orderItem.Status != "ACTIVE" {
return errors.New(fmt.Sprintf("order item %d is not active", voidItem.OrderItemID))
}
if voidItem.Quantity > orderItem.Quantity {
return errors.New(fmt.Sprintf("void quantity %d exceeds available quantity %d for item %d",
voidItem.Quantity, orderItem.Quantity, voidItem.OrderItemID))
}
voidedAmount += orderItem.Price * float64(voidItem.Quantity)
}
// Update order items with reduced quantities
for _, voidItem := range items {
orderItem := orderItemMap[voidItem.OrderItemID]
newQuantity := orderItem.Quantity - voidItem.Quantity
if newQuantity == 0 {
// Remove item completely
err = s.repo.UpdateOrderItem(ctx, voidItem.OrderItemID, 0)
} else {
// Update quantity
err = s.repo.UpdateOrderItem(ctx, voidItem.OrderItemID, newQuantity)
// Create new VOIDED order item with the voided quantity
voidedItem := &entity.OrderItem{
OrderID: orderID,
ItemID: orderItem.ItemID,
ItemType: orderItem.ItemType,
Price: orderItem.Price,
Quantity: voidItem.Quantity, // Void the requested quantity
Status: "VOIDED",
CreatedBy: orderItem.CreatedBy,
ItemName: orderItem.ItemName,
Notes: reason, // Use the reason as notes for tracking
}
err = s.repo.CreateOrderItem(ctx, orderID, voidedItem)
if err != nil {
logger.ContextLogger(ctx).Error("failed to create voided order item", zap.Error(err))
return err
}
// Update original item quantity
newQuantity := orderItem.Quantity - voidItem.Quantity
err = s.repo.UpdateOrderItem(ctx, voidItem.OrderItemID, newQuantity)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order item", zap.Error(err))
return err
}
}
// Recalculate order totals
remainingAmount := order.Amount - voidedAmount
remainingTax := (remainingAmount / order.Amount) * order.Tax
remainingTotal := remainingAmount + remainingTax
// Update order totals
err = s.repo.UpdateOrderTotals(ctx, orderID, remainingAmount, remainingTax, remainingTotal)
updatedOrder, err := s.repo.FindByIDAndPartnerID(ctx, orderID, partnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order totals", zap.Error(err))
logger.ContextLogger(ctx).Error("failed to fetch updated order for recalculation", zap.Error(err))
return err
}
// Update order status to PARTIAL if some items remain, otherwise to VOIDED
newStatus := "PARTIAL"
if remainingAmount <= 0 {
newStatus = "VOIDED"
var activeItems []entity.OrderItemRequest
for _, item := range updatedOrder.OrderItems {
if item.Status == "ACTIVE" && item.Quantity > 0 {
activeItems = append(activeItems, entity.OrderItemRequest{
ProductID: item.ItemID,
Quantity: item.Quantity,
Notes: item.Notes,
})
}
}
err = s.repo.UpdateOrder(ctx, orderID, newStatus, reason)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order status", zap.Error(err))
return err
if len(activeItems) > 0 {
productIDs, _, err := s.ValidateOrderItems(ctx, activeItems)
if err != nil {
logger.ContextLogger(ctx).Error("failed to validate order items for recalculation", zap.Error(err))
return err
}
productDetails, err := s.product.GetProductDetails(ctx, productIDs, partnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get product details for recalculation", zap.Error(err))
return err
}
orderCalculation, err := s.CalculateOrderTotals(ctx, activeItems, productDetails, order.Source, partnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to calculate order totals", zap.Error(err))
return err
}
// Update order totals
err = s.repo.UpdateOrderTotals(ctx, orderID, orderCalculation.Subtotal, orderCalculation.Tax, orderCalculation.Total)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order totals", zap.Error(err))
return err
}
// Update order status based on remaining amount
newStatus := "PENDING"
if orderCalculation.Subtotal <= 0 {
newStatus = "CANCELED"
}
err = s.repo.UpdateOrder(ctx, orderID, newStatus, reason)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order status", zap.Error(err))
return err
}
} else {
// No active items left, cancel the order
err = s.repo.UpdateOrderTotals(ctx, orderID, 0, 0, 0)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order totals", zap.Error(err))
return err
}
err = s.repo.UpdateOrder(ctx, orderID, "CANCELED", reason)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update order status", zap.Error(err))
return err
}
}
}
@@ -195,8 +291,7 @@ func (s *orderSvc) VoidOrderRequest(ctx mycontext.Context, partnerID, orderID in
return nil
}
// SplitBillRequest handles splitting bills by items or amounts
func (s *orderSvc) SplitBillRequest(ctx mycontext.Context, partnerID, orderID int64, splitType string, paymentMethod string, paymentProvider string, items []entity.SplitBillItem, amount float64) (*entity.Order, error) {
func (s *orderSvc) SplitBillRequest(ctx mycontext.Context, partnerID, orderID int64, splitType string, items []entity.SplitBillItem, amount float64) (*entity.Order, error) {
order, err := s.repo.FindByIDAndPartnerID(ctx, orderID, partnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to find order for split bill", zap.Error(err))
@@ -210,9 +305,9 @@ func (s *orderSvc) SplitBillRequest(ctx mycontext.Context, partnerID, orderID in
var splitOrder *entity.Order
if splitType == "ITEM" {
splitOrder, err = s.splitByItems(ctx, order, paymentMethod, paymentProvider, items)
splitOrder, err = s.splitByItems(ctx, order, items)
} else if splitType == "AMOUNT" {
splitOrder, err = s.splitByAmount(ctx, order, paymentMethod, paymentProvider, amount)
splitOrder, err = s.splitByAmount(ctx, order, amount)
}
if err != nil {
@@ -228,12 +323,12 @@ func (s *orderSvc) SplitBillRequest(ctx mycontext.Context, partnerID, orderID in
return splitOrder, nil
}
func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Order, paymentMethod string, paymentProvider string, items []entity.SplitBillItem) (*entity.Order, error) {
func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Order, items []entity.SplitBillItem) (*entity.Order, error) {
var splitOrderItems []entity.OrderItem
orderItemMap := make(map[int64]*entity.OrderItem)
for _, item := range originalOrder.OrderItems {
orderItemMap[item.ID] = &item
for i := range originalOrder.OrderItems {
orderItemMap[originalOrder.OrderItems[i].ID] = &originalOrder.OrderItems[i]
}
assignedItems := make(map[int64]bool)
@@ -275,7 +370,6 @@ func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Ord
splitTax := (splitAmount / originalOrder.Amount) * originalOrder.Tax
splitTotal := splitAmount + splitTax
// Create new PAID order for the split
splitOrder := &entity.Order{
PartnerID: originalOrder.PartnerID,
CustomerID: originalOrder.CustomerID,
@@ -284,8 +378,6 @@ func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Ord
Amount: splitAmount,
Tax: splitTax,
Total: splitTotal,
PaymentType: paymentMethod,
PaymentProvider: paymentProvider,
Source: originalOrder.Source,
CreatedBy: originalOrder.CreatedBy,
OrderItems: splitOrderItems,
@@ -300,16 +392,13 @@ func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Ord
return nil, err
}
// Adjust original order items (reduce quantities)
for _, item := range items {
orderItem := orderItemMap[item.OrderItemID]
newQuantity := orderItem.Quantity - item.Quantity
if newQuantity == 0 {
// Remove item completely
err = s.repo.UpdateOrderItem(ctx, item.OrderItemID, 0)
} else {
// Update quantity
err = s.repo.UpdateOrderItem(ctx, item.OrderItemID, newQuantity)
}
@@ -319,12 +408,10 @@ func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Ord
}
}
// Recalculate original order totals
remainingAmount := originalOrder.Amount - splitAmount
remainingTax := (remainingAmount / originalOrder.Amount) * originalOrder.Tax
remainingTotal := remainingAmount + remainingTax
// Update original order totals
err = s.repo.UpdateOrderTotals(ctx, originalOrder.ID, remainingAmount, remainingTax, remainingTotal)
if err != nil {
logger.ContextLogger(ctx).Error("failed to update original order totals", zap.Error(err))
@@ -335,7 +422,7 @@ func (s *orderSvc) splitByItems(ctx mycontext.Context, originalOrder *entity.Ord
}
// splitByAmount splits the order by assigning specific amounts to each split
func (s *orderSvc) splitByAmount(ctx mycontext.Context, originalOrder *entity.Order, paymentMethod string, paymentProvider string, amount float64) (*entity.Order, error) {
func (s *orderSvc) splitByAmount(ctx mycontext.Context, originalOrder *entity.Order, amount float64) (*entity.Order, error) {
// Validate that split amount is less than original order total
if amount >= originalOrder.Total {
return nil, errors.New(fmt.Sprintf("split amount %.2f must be less than order total %.2f",
@@ -362,7 +449,6 @@ func (s *orderSvc) splitByAmount(ctx mycontext.Context, originalOrder *entity.Or
splitTax := (splitAmount / originalOrder.Amount) * originalOrder.Tax
splitTotal := splitAmount + splitTax
// Create new PAID order for the split
splitOrder := &entity.Order{
PartnerID: originalOrder.PartnerID,
CustomerID: originalOrder.CustomerID,
@@ -371,8 +457,6 @@ func (s *orderSvc) splitByAmount(ctx mycontext.Context, originalOrder *entity.Or
Amount: splitAmount,
Tax: splitTax,
Total: splitTotal,
PaymentType: paymentMethod,
PaymentProvider: paymentProvider,
Source: originalOrder.Source,
CreatedBy: originalOrder.CreatedBy,
OrderItems: splitOrderItems,