upodate system
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user