Add void and printer type

This commit is contained in:
Aditya Siregar
2025-08-06 00:02:49 +07:00
parent 11d814ab2f
commit 44dc3fa61c
8 changed files with 67 additions and 37 deletions
+2
View File
@@ -72,6 +72,7 @@ type OrderResponse struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
IsRefund bool `json:"is_refund"`
}
type OrderItemResponse struct {
@@ -90,6 +91,7 @@ type OrderItemResponse struct {
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrinterType string `json:"printer_type"`
}
type ListOrdersQuery struct {
+1 -2
View File
@@ -91,14 +91,13 @@ func OrderItemEntityToResponse(item *entities.OrderItem) *models.OrderItemRespon
Status: constants.OrderItemStatus(item.Status),
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
PrinterType: item.Product.PrinterType,
}
// Set product name if product is preloaded
if item.Product.ID != uuid.Nil {
response.ProductName = item.Product.Name
}
// Set product variant name if product variant is preloaded
if item.ProductVariant != nil {
response.ProductVariantName = &item.ProductVariant.Name
}
+1
View File
@@ -199,6 +199,7 @@ type OrderItemResponse struct {
Status constants.OrderItemStatus
CreatedAt time.Time
UpdatedAt time.Time
PrinterType string
}
type PaymentOrderItemResponse struct {
+4 -14
View File
@@ -532,7 +532,6 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
}
if req.Type == "ALL" {
// Update order status to cancelled and mark as voided in a single transaction
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
return fmt.Errorf("failed to void order: %w", err)
}
@@ -552,62 +551,53 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
return fmt.Errorf("order item not found: %w", err)
}
// Verify the order item belongs to this order
if orderItem.OrderID != req.OrderID {
return fmt.Errorf("order item does not belong to this order")
}
// Validate void quantity
if itemVoid.Quantity > orderItem.Quantity {
return fmt.Errorf("void quantity cannot exceed original quantity for item %d", itemVoid.OrderItemID)
}
// Calculate voided amounts
voidedAmount := float64(itemVoid.Quantity) * orderItem.UnitPrice
voidedCost := float64(itemVoid.Quantity) * orderItem.UnitCost
totalVoidedAmount += voidedAmount
totalVoidedCost += voidedCost
// Void the order item
if err := p.orderItemRepo.VoidOrderItem(ctx, orderItemID, itemVoid.Quantity, req.Reason, voidedBy); err != nil {
return fmt.Errorf("failed to void order item %d: %w", itemVoid.OrderItemID, err)
}
}
// Get outlet information for tax rate
outlet, err := p.outletRepo.GetByID(ctx, order.OutletID)
if err != nil {
return fmt.Errorf("outlet not found: %w", err)
}
// Update order totals
order.Subtotal -= totalVoidedAmount
order.TotalCost -= totalVoidedCost
order.TaxAmount = order.Subtotal * outlet.TaxRate // Recalculate tax using outlet's tax rate
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
// Update the order
if err := p.orderRepo.Update(ctx, order); err != nil {
return fmt.Errorf("failed to update order totals: %w", err)
}
// Check if all items are voided, then void the entire order
remainingItems, err := p.orderItemRepo.GetByOrderID(ctx, req.OrderID)
if err != nil {
return fmt.Errorf("failed to get remaining order items: %w", err)
}
allItemsVoided := true
hasActiveItems := false
for _, item := range remainingItems {
if item.Quantity > 0 {
allItemsVoided = false
if item.Status != entities.OrderItemStatusCancelled {
hasActiveItems = true
break
}
}
if allItemsVoided {
// Update order status to cancelled and mark as voided when all items are voided
if !hasActiveItems {
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
return fmt.Errorf("failed to void order after all items voided: %w", err)
}
+51 -16
View File
@@ -2,6 +2,7 @@ package repository
import (
"context"
"fmt"
"time"
"apskel-pos-be/internal/entities"
@@ -103,31 +104,65 @@ func (r *OrderItemRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID
func (r *OrderItemRepositoryImpl) VoidOrderItem(ctx context.Context, id uuid.UUID, voidQuantity int, reason string, voidedBy uuid.UUID) error {
now := time.Now()
// Get current order item
var orderItem entities.OrderItem
if err := r.db.WithContext(ctx).First(&orderItem, "id = ?", id).Error; err != nil {
return err
}
// Calculate new voided quantity
newVoidedQuantity := orderItem.RefundQuantity + voidQuantity // Using refund_quantity field for voided quantity
if voidQuantity >= orderItem.Quantity {
voidedAmount := float64(voidQuantity) * orderItem.UnitPrice
// Determine if fully or partially voided
isFullyVoided := newVoidedQuantity >= orderItem.Quantity
isPartiallyVoided := newVoidedQuantity > 0 && newVoidedQuantity < orderItem.Quantity
updates := map[string]interface{}{
"refund_quantity": voidQuantity,
"refund_amount": voidedAmount,
"is_partially_refunded": false,
"is_fully_refunded": true,
"refund_reason": reason,
"refunded_at": now,
"refunded_by": voidedBy,
"status": entities.OrderItemStatusCancelled,
}
// Calculate voided amount
voidedAmount := float64(voidQuantity) * orderItem.UnitPrice
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
Where("id = ?", id).
Updates(updates).Error
}
voidedOrderItem := entities.OrderItem{
OrderID: orderItem.OrderID,
ProductID: orderItem.ProductID,
ProductVariantID: orderItem.ProductVariantID,
Quantity: voidQuantity,
UnitPrice: orderItem.UnitPrice,
TotalPrice: float64(voidQuantity) * orderItem.UnitPrice,
UnitCost: orderItem.UnitCost,
TotalCost: float64(voidQuantity) * orderItem.UnitCost,
RefundAmount: float64(voidQuantity) * orderItem.UnitPrice,
RefundQuantity: voidQuantity,
IsPartiallyRefunded: false,
IsFullyRefunded: true,
RefundReason: &reason,
RefundedAt: &now,
RefundedBy: &voidedBy,
Modifiers: orderItem.Modifiers,
Notes: orderItem.Notes,
Metadata: orderItem.Metadata,
Status: entities.OrderItemStatusCancelled,
}
if err := r.db.WithContext(ctx).Create(&voidedOrderItem).Error; err != nil {
return fmt.Errorf("failed to create voided order item: %w", err)
}
remainingQuantity := orderItem.Quantity - voidQuantity
remainingTotalPrice := float64(remainingQuantity) * orderItem.UnitPrice
remainingTotalCost := float64(remainingQuantity) * orderItem.UnitCost
updates := map[string]interface{}{
"refund_quantity": newVoidedQuantity, // Reusing refund_quantity field for voided quantity
"refund_amount": orderItem.RefundAmount + voidedAmount,
"is_partially_refunded": isPartiallyVoided, // Reusing refunded flags for voided status
"is_fully_refunded": isFullyVoided,
"refund_reason": reason,
"refunded_at": now,
"refunded_by": voidedBy,
"status": entities.OrderItemStatusCancelled, // Mark as cancelled when voided
"quantity": remainingQuantity,
"total_price": remainingTotalPrice,
"total_cost": remainingTotalCost,
"updated_at": now,
}
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
@@ -108,6 +108,7 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
Status: string(item.Status),
CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt,
PrinterType: item.PrinterType,
}
}
return &contract.OrderResponse{
@@ -127,6 +128,7 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
CreatedAt: resp.CreatedAt,
UpdatedAt: resp.UpdatedAt,
OrderItems: items,
IsRefund: resp.IsRefund,
}
}