Files
apskel-pos-backend/internal/repository/order_item_repository.go
T

179 lines
6.4 KiB
Go
Raw Normal View History

2025-07-18 20:10:29 +07:00
package repository
import (
"context"
2025-08-06 00:02:49 +07:00
"fmt"
2025-07-18 20:10:29 +07:00
"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").
2026-09-06 17:14:42 +07:00
Preload("Unit").
2025-07-18 20:10:29 +07:00
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
}
2026-09-06 17:14:42 +07:00
// 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)
2025-07-18 20:10:29 +07:00
2025-08-06 00:02:49 +07:00
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,
}
2025-07-18 20:10:29 +07:00
2025-08-06 00:02:49 +07:00
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
Where("id = ?", id).
Updates(updates).Error
}
2026-09-06 17:14:42 +07:00
// Only unit-priced lines reach here: weighed lines were voided whole above.
2025-08-06 00:02:49 +07:00
voidedOrderItem := entities.OrderItem{
OrderID: orderItem.OrderID,
ProductID: orderItem.ProductID,
ProductVariantID: orderItem.ProductVariantID,
Quantity: voidQuantity,
2026-09-06 17:14:42 +07:00
UnitID: orderItem.UnitID,
2025-08-06 00:02:49 +07:00
UnitPrice: orderItem.UnitPrice,
2026-09-06 17:14:42 +07:00
TotalPrice: entities.RoundMoney(float64(voidQuantity) * orderItem.UnitPrice),
2025-08-06 00:02:49 +07:00
UnitCost: orderItem.UnitCost,
2026-09-06 17:14:42 +07:00
TotalCost: entities.RoundMoney(float64(voidQuantity) * orderItem.UnitCost),
RefundAmount: entities.RoundMoney(float64(voidQuantity) * orderItem.UnitPrice),
2025-08-06 00:02:49 +07:00
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
2026-09-06 17:14:42 +07:00
remainingTotalPrice := entities.RoundMoney(float64(remainingQuantity) * orderItem.UnitPrice)
remainingTotalCost := entities.RoundMoney(float64(remainingQuantity) * orderItem.UnitCost)
2025-07-18 20:10:29 +07:00
updates := map[string]interface{}{
2025-08-06 00:02:49 +07:00
"quantity": remainingQuantity,
"total_price": remainingTotalPrice,
"total_cost": remainingTotalCost,
"updated_at": now,
2025-07-18 20:10:29 +07:00
}
return r.db.WithContext(ctx).Model(&entities.OrderItem{}).
Where("id = ?", id).
Updates(updates).Error
}