Add void and printer type
This commit is contained in:
parent
11d814ab2f
commit
44dc3fa61c
4
go.mod
4
go.mod
@ -54,7 +54,7 @@ require (
|
|||||||
golang.org/x/arch v0.7.0 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.26.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.20.0 // indirect
|
||||||
google.golang.org/protobuf v1.32.0 // indirect
|
google.golang.org/protobuf v1.32.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
@ -68,5 +68,5 @@ require (
|
|||||||
go.uber.org/zap v1.21.0
|
go.uber.org/zap v1.21.0
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.28.0
|
||||||
gorm.io/driver/postgres v1.5.0
|
gorm.io/driver/postgres v1.5.0
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11
|
gorm.io/gorm v1.30.0
|
||||||
)
|
)
|
||||||
|
|||||||
7
go.sum
7
go.sum
@ -442,8 +442,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
@ -607,8 +607,9 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
|
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
|
||||||
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
|
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g=
|
|
||||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||||
|
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||||
|
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@ -72,6 +72,7 @@ type OrderResponse struct {
|
|||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
OrderItems []OrderItemResponse `json:"order_items,omitempty"`
|
||||||
|
IsRefund bool `json:"is_refund"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OrderItemResponse struct {
|
type OrderItemResponse struct {
|
||||||
@ -90,6 +91,7 @@ type OrderItemResponse struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
PrinterType string `json:"printer_type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListOrdersQuery struct {
|
type ListOrdersQuery struct {
|
||||||
|
|||||||
@ -91,14 +91,13 @@ func OrderItemEntityToResponse(item *entities.OrderItem) *models.OrderItemRespon
|
|||||||
Status: constants.OrderItemStatus(item.Status),
|
Status: constants.OrderItemStatus(item.Status),
|
||||||
CreatedAt: item.CreatedAt,
|
CreatedAt: item.CreatedAt,
|
||||||
UpdatedAt: item.UpdatedAt,
|
UpdatedAt: item.UpdatedAt,
|
||||||
|
PrinterType: item.Product.PrinterType,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set product name if product is preloaded
|
|
||||||
if item.Product.ID != uuid.Nil {
|
if item.Product.ID != uuid.Nil {
|
||||||
response.ProductName = item.Product.Name
|
response.ProductName = item.Product.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set product variant name if product variant is preloaded
|
|
||||||
if item.ProductVariant != nil {
|
if item.ProductVariant != nil {
|
||||||
response.ProductVariantName = &item.ProductVariant.Name
|
response.ProductVariantName = &item.ProductVariant.Name
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,6 +199,7 @@ type OrderItemResponse struct {
|
|||||||
Status constants.OrderItemStatus
|
Status constants.OrderItemStatus
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
PrinterType string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PaymentOrderItemResponse struct {
|
type PaymentOrderItemResponse struct {
|
||||||
|
|||||||
@ -532,7 +532,6 @@ func (p *OrderProcessorImpl) VoidOrder(ctx context.Context, req *models.VoidOrde
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.Type == "ALL" {
|
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 {
|
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
|
||||||
return fmt.Errorf("failed to void order: %w", err)
|
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)
|
return fmt.Errorf("order item not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the order item belongs to this order
|
|
||||||
if orderItem.OrderID != req.OrderID {
|
if orderItem.OrderID != req.OrderID {
|
||||||
return fmt.Errorf("order item does not belong to this order")
|
return fmt.Errorf("order item does not belong to this order")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate void quantity
|
|
||||||
if itemVoid.Quantity > orderItem.Quantity {
|
if itemVoid.Quantity > orderItem.Quantity {
|
||||||
return fmt.Errorf("void quantity cannot exceed original quantity for item %d", itemVoid.OrderItemID)
|
return fmt.Errorf("void quantity cannot exceed original quantity for item %d", itemVoid.OrderItemID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate voided amounts
|
|
||||||
voidedAmount := float64(itemVoid.Quantity) * orderItem.UnitPrice
|
voidedAmount := float64(itemVoid.Quantity) * orderItem.UnitPrice
|
||||||
voidedCost := float64(itemVoid.Quantity) * orderItem.UnitCost
|
voidedCost := float64(itemVoid.Quantity) * orderItem.UnitCost
|
||||||
|
|
||||||
totalVoidedAmount += voidedAmount
|
totalVoidedAmount += voidedAmount
|
||||||
totalVoidedCost += voidedCost
|
totalVoidedCost += voidedCost
|
||||||
|
|
||||||
// Void the order item
|
|
||||||
if err := p.orderItemRepo.VoidOrderItem(ctx, orderItemID, itemVoid.Quantity, req.Reason, voidedBy); err != nil {
|
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)
|
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)
|
outlet, err := p.outletRepo.GetByID(ctx, order.OutletID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("outlet not found: %w", err)
|
return fmt.Errorf("outlet not found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update order totals
|
|
||||||
order.Subtotal -= totalVoidedAmount
|
order.Subtotal -= totalVoidedAmount
|
||||||
order.TotalCost -= totalVoidedCost
|
order.TotalCost -= totalVoidedCost
|
||||||
order.TaxAmount = order.Subtotal * outlet.TaxRate // Recalculate tax using outlet's tax rate
|
order.TaxAmount = order.Subtotal * outlet.TaxRate // Recalculate tax using outlet's tax rate
|
||||||
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
order.TotalAmount = order.Subtotal + order.TaxAmount - order.DiscountAmount
|
||||||
|
|
||||||
// Update the order
|
|
||||||
if err := p.orderRepo.Update(ctx, order); err != nil {
|
if err := p.orderRepo.Update(ctx, order); err != nil {
|
||||||
return fmt.Errorf("failed to update order totals: %w", err)
|
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)
|
remainingItems, err := p.orderItemRepo.GetByOrderID(ctx, req.OrderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get remaining order items: %w", err)
|
return fmt.Errorf("failed to get remaining order items: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
allItemsVoided := true
|
hasActiveItems := false
|
||||||
for _, item := range remainingItems {
|
for _, item := range remainingItems {
|
||||||
if item.Quantity > 0 {
|
if item.Status != entities.OrderItemStatusCancelled {
|
||||||
allItemsVoided = false
|
hasActiveItems = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if allItemsVoided {
|
if !hasActiveItems {
|
||||||
// Update order status to cancelled and mark as voided when all items are voided
|
|
||||||
if err := p.orderRepo.VoidOrderWithStatus(ctx, req.OrderID, entities.OrderStatusCancelled, req.Reason, voidedBy); err != nil {
|
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)
|
return fmt.Errorf("failed to void order after all items voided: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"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 {
|
func (r *OrderItemRepositoryImpl) VoidOrderItem(ctx context.Context, id uuid.UUID, voidQuantity int, reason string, voidedBy uuid.UUID) error {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
// Get current order item
|
|
||||||
var orderItem entities.OrderItem
|
var orderItem entities.OrderItem
|
||||||
if err := r.db.WithContext(ctx).First(&orderItem, "id = ?", id).Error; err != nil {
|
if err := r.db.WithContext(ctx).First(&orderItem, "id = ?", id).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate new voided quantity
|
if voidQuantity >= orderItem.Quantity {
|
||||||
newVoidedQuantity := orderItem.RefundQuantity + voidQuantity // Using refund_quantity field for voided quantity
|
|
||||||
|
|
||||||
// Determine if fully or partially voided
|
|
||||||
isFullyVoided := newVoidedQuantity >= orderItem.Quantity
|
|
||||||
isPartiallyVoided := newVoidedQuantity > 0 && newVoidedQuantity < orderItem.Quantity
|
|
||||||
|
|
||||||
// Calculate voided amount
|
|
||||||
voidedAmount := float64(voidQuantity) * orderItem.UnitPrice
|
voidedAmount := float64(voidQuantity) * orderItem.UnitPrice
|
||||||
|
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"refund_quantity": newVoidedQuantity, // Reusing refund_quantity field for voided quantity
|
"refund_quantity": voidQuantity,
|
||||||
"refund_amount": orderItem.RefundAmount + voidedAmount,
|
"refund_amount": voidedAmount,
|
||||||
"is_partially_refunded": isPartiallyVoided, // Reusing refunded flags for voided status
|
"is_partially_refunded": false,
|
||||||
"is_fully_refunded": isFullyVoided,
|
"is_fully_refunded": true,
|
||||||
"refund_reason": reason,
|
"refund_reason": reason,
|
||||||
"refunded_at": now,
|
"refunded_at": now,
|
||||||
"refunded_by": voidedBy,
|
"refunded_by": voidedBy,
|
||||||
"status": entities.OrderItemStatusCancelled, // Mark as cancelled when voided
|
"status": entities.OrderItemStatusCancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
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{}{
|
||||||
|
"quantity": remainingQuantity,
|
||||||
|
"total_price": remainingTotalPrice,
|
||||||
|
"total_cost": remainingTotalCost,
|
||||||
|
"updated_at": now,
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
|
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
|
||||||
|
|||||||
@ -108,6 +108,7 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
|
|||||||
Status: string(item.Status),
|
Status: string(item.Status),
|
||||||
CreatedAt: item.CreatedAt,
|
CreatedAt: item.CreatedAt,
|
||||||
UpdatedAt: item.UpdatedAt,
|
UpdatedAt: item.UpdatedAt,
|
||||||
|
PrinterType: item.PrinterType,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &contract.OrderResponse{
|
return &contract.OrderResponse{
|
||||||
@ -127,6 +128,7 @@ func OrderModelToContract(resp *models.OrderResponse) *contract.OrderResponse {
|
|||||||
CreatedAt: resp.CreatedAt,
|
CreatedAt: resp.CreatedAt,
|
||||||
UpdatedAt: resp.UpdatedAt,
|
UpdatedAt: resp.UpdatedAt,
|
||||||
OrderItems: items,
|
OrderItems: items,
|
||||||
|
IsRefund: resp.IsRefund,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user