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
|
|
|
|
|
}
|