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,
+9 -20
View File
@@ -15,24 +15,12 @@ type Repository interface {
UpdateOrder(ctx mycontext.Context, id int64, status string, description string) error
UpdateOrderItem(ctx mycontext.Context, orderItemID int64, quantity int) error
UpdateOrderTotals(ctx mycontext.Context, orderID int64, amount, tax, total float64) error
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
GetOrderPaymentMethodBreakdown(
ctx mycontext.Context,
partnerID int64,
req entity.SearchRequest,
) ([]entity.PaymentMethodBreakdown, error)
GetRevenueOverview(
ctx mycontext.Context,
req entity.RevenueOverviewRequest,
) ([]entity.RevenueOverviewItem, error)
GetSalesByCategory(
ctx mycontext.Context,
req entity.SalesByCategoryRequest,
) ([]entity.SalesByCategoryItem, error)
GetPopularProducts(
ctx mycontext.Context,
req entity.PopularProductsRequest,
) ([]entity.PopularProductItem, error)
CreateOrderItem(ctx mycontext.Context, orderID int64, item *entity.OrderItem) error
GetOrderHistoryByPartnerID(ctx mycontext.Context, partnerID *int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
GetOrderPaymentMethodBreakdown(ctx mycontext.Context, partnerID int64, req entity.SearchRequest) ([]entity.PaymentMethodBreakdown, error)
GetRevenueOverview(ctx mycontext.Context, req entity.RevenueOverviewRequest) ([]entity.RevenueOverviewItem, error)
GetSalesByCategory(ctx mycontext.Context, req entity.SalesByCategoryRequest) ([]entity.SalesByCategoryItem, error)
GetPopularProducts(ctx mycontext.Context, req entity.PopularProductsRequest) ([]entity.PopularProductItem, error)
GetOrderHistoryByUserID(ctx mycontext.Context, userID int64, req entity.SearchRequest) ([]*entity.Order, int64, error)
FindByIDAndPartnerID(ctx mycontext.Context, id int64, partnerID int64) (*entity.Order, error)
FindByIDAndCustomerID(ctx mycontext.Context, id int64, customerID int64) (*entity.Order, error)
@@ -71,8 +59,8 @@ type Service interface {
RefundRequest(ctx mycontext.Context, partnerID, orderID int64, reason string) error
PartialRefundRequest(ctx mycontext.Context, partnerID, orderID int64, reason string, items []entity.PartialRefundItem) error
VoidOrderRequest(ctx mycontext.Context, partnerID, orderID int64, reason string, voidType string, items []entity.VoidItem) error
SplitBillRequest(ctx mycontext.Context, partnerID, orderID int64, splitType string, paymentMethod string, paymentProvider string, items []entity.SplitBillItem, amount float64) (*entity.Order, error)
GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error)
SplitBillRequest(ctx mycontext.Context, partnerID, orderID int64, splitType string, items []entity.SplitBillItem, amount float64) (*entity.Order, error)
GetOrderHistory(ctx mycontext.Context, request entity.SearchRequest) ([]*entity.Order, int64, error)
CalculateOrderTotals(
ctx mycontext.Context,
items []entity.OrderItemRequest,
@@ -110,6 +98,7 @@ type Service interface {
GetCustomerOrderHistory(ctx mycontext.Context, userID int64, request entity.SearchRequest) ([]*entity.Order, int64, error)
GetOrderByOrderAndCustomerID(ctx mycontext.Context, customerID int64, orderID int64) (*entity.Order, error)
GetOrderByID(ctx mycontext.Context, orderID int64) (*entity.Order, error)
GetOrderByIDAndPartnerID(ctx mycontext.Context, orderID int64, partnerID int64) (*entity.Order, error)
}
type Config interface {
+16 -2
View File
@@ -4,12 +4,13 @@ import (
"enaklo-pos-be/internal/common/logger"
"enaklo-pos-be/internal/common/mycontext"
"enaklo-pos-be/internal/entity"
"github.com/pkg/errors"
"go.uber.org/zap"
)
func (s *orderSvc) GetOrderHistory(ctx mycontext.Context, partnerID int64, request entity.SearchRequest) ([]*entity.Order, int64, error) {
return s.repo.GetOrderHistoryByPartnerID(ctx, partnerID, request)
func (s *orderSvc) GetOrderHistory(ctx mycontext.Context, request entity.SearchRequest) ([]*entity.Order, int64, error) {
return s.repo.GetOrderHistoryByPartnerID(ctx, ctx.GetPartnerID(), request)
}
func (s *orderSvc) GetCustomerOrderHistory(ctx mycontext.Context, userID int64, request entity.SearchRequest) ([]*entity.Order, int64, error) {
@@ -39,3 +40,16 @@ func (s *orderSvc) GetOrderByID(ctx mycontext.Context, orderID int64) (*entity.O
return order, nil
}
func (s *orderSvc) GetOrderByIDAndPartnerID(ctx mycontext.Context, orderID int64, partnerID int64) (*entity.Order, error) {
order, err := s.repo.FindByIDAndPartnerID(ctx, orderID, partnerID)
if err != nil {
logger.ContextLogger(ctx).Error("failed to get order by ID and partner ID",
zap.Error(err),
zap.Int64("orderID", orderID),
zap.Int64("partnerID", partnerID))
return nil, errors.Wrap(err, "failed to get order")
}
return order, nil
}