package repository import ( "context" "fmt" "time" "apskel-pos-be/internal/entities" "github.com/google/uuid" "gorm.io/gorm" ) type OrderItemRepository interface { Create(ctx context.Context, orderItem *entities.OrderItem) error GetByID(ctx context.Context, id uuid.UUID) (*entities.OrderItem, error) GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.OrderItem, error) Update(ctx context.Context, orderItem *entities.OrderItem) error Delete(ctx context.Context, id uuid.UUID) error RefundOrderItem(ctx context.Context, id uuid.UUID, refundQuantity int, refundAmount float64, reason string, refundedBy uuid.UUID) error VoidOrderItem(ctx context.Context, id uuid.UUID, voidQuantity int, reason string, voidedBy uuid.UUID) error UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderItemStatus) error } type OrderItemRepositoryImpl struct { db *gorm.DB } func NewOrderItemRepositoryImpl(db *gorm.DB) *OrderItemRepositoryImpl { return &OrderItemRepositoryImpl{ db: db, } } func (r *OrderItemRepositoryImpl) Create(ctx context.Context, orderItem *entities.OrderItem) error { return r.db.WithContext(ctx).Create(orderItem).Error } func (r *OrderItemRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.OrderItem, error) { var orderItem entities.OrderItem err := r.db.WithContext(ctx).First(&orderItem, "id = ?", id).Error if err != nil { return nil, err } return &orderItem, nil } func (r *OrderItemRepositoryImpl) GetByOrderID(ctx context.Context, orderID uuid.UUID) ([]*entities.OrderItem, error) { var orderItems []*entities.OrderItem err := r.db.WithContext(ctx). Preload("Product"). Preload("ProductVariant"). Preload("Unit"). Where("order_id = ?", orderID). Find(&orderItems).Error return orderItems, err } func (r *OrderItemRepositoryImpl) Update(ctx context.Context, orderItem *entities.OrderItem) error { return r.db.WithContext(ctx).Save(orderItem).Error } func (r *OrderItemRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error { return r.db.WithContext(ctx).Delete(&entities.OrderItem{}, "id = ?", id).Error } func (r *OrderItemRepositoryImpl) RefundOrderItem(ctx context.Context, id uuid.UUID, refundQuantity int, refundAmount float64, reason string, refundedBy 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 refund quantities and amounts newRefundQuantity := orderItem.RefundQuantity + refundQuantity newRefundAmount := orderItem.RefundAmount + refundAmount // Determine if fully or partially refunded isFullyRefunded := newRefundQuantity >= orderItem.Quantity isPartiallyRefunded := newRefundQuantity > 0 && newRefundQuantity < orderItem.Quantity updates := map[string]interface{}{ "refund_quantity": newRefundQuantity, "refund_amount": newRefundAmount, "is_partially_refunded": isPartiallyRefunded, "is_fully_refunded": isFullyRefunded, "refund_reason": reason, "refunded_at": now, "refunded_by": refundedBy, } return r.db.WithContext(ctx).Model(&entities.OrderItem{}). Where("id = ?", id). Updates(updates).Error } func (r *OrderItemRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderItemStatus) error { return r.db.WithContext(ctx).Model(&entities.OrderItem{}). Where("id = ?", id). Update("status", status).Error } func (r *OrderItemRepositoryImpl) VoidOrderItem(ctx context.Context, id uuid.UUID, voidQuantity int, reason string, voidedBy uuid.UUID) error { now := time.Now() var orderItem entities.OrderItem if err := r.db.WithContext(ctx).First(&orderItem, "id = ?", id).Error; err != nil { return err } // A weighed line is one weighing, so it is voided whole — never split into a // remainder row. chk_order_items_weight_single_line pins its quantity to 1, which // already sends it down this branch; the explicit check keeps that guarantee // visible and independent of the comparison below. if orderItem.IsWeighed() || voidQuantity >= orderItem.Quantity { voidedAmount := entities.RoundMoney(orderItem.BillableQuantityFor(voidQuantity) * orderItem.UnitPrice) 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, } return r.db.WithContext(ctx).Model(&entities.OrderItem{}). Where("id = ?", id). Updates(updates).Error } // Only unit-priced lines reach here: weighed lines were voided whole above. voidedOrderItem := entities.OrderItem{ OrderID: orderItem.OrderID, ProductID: orderItem.ProductID, ProductVariantID: orderItem.ProductVariantID, Quantity: voidQuantity, UnitID: orderItem.UnitID, UnitPrice: orderItem.UnitPrice, TotalPrice: entities.RoundMoney(float64(voidQuantity) * orderItem.UnitPrice), UnitCost: orderItem.UnitCost, TotalCost: entities.RoundMoney(float64(voidQuantity) * orderItem.UnitCost), RefundAmount: entities.RoundMoney(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 := entities.RoundMoney(float64(remainingQuantity) * orderItem.UnitPrice) remainingTotalCost := entities.RoundMoney(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{}). Where("id = ?", id). Updates(updates).Error }