package repository import ( "context" "fmt" "time" "apskel-pos-be/internal/entities" "github.com/google/uuid" "gorm.io/gorm" ) type OrderRepository interface { Create(ctx context.Context, order *entities.Order) error GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error) Update(ctx context.Context, order *entities.Order) error Delete(ctx context.Context, id uuid.UUID) error List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error) GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error) ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error) VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error) } type OrderRepositoryImpl struct { db *gorm.DB } func NewOrderRepositoryImpl(db *gorm.DB) *OrderRepositoryImpl { return &OrderRepositoryImpl{ db: db, } } func (r *OrderRepositoryImpl) Create(ctx context.Context, order *entities.Order) error { return r.db.WithContext(ctx).Create(order).Error } func (r *OrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error) { var order entities.Order err := r.db.WithContext(ctx).First(&order, "id = ?", id).Error if err != nil { return nil, err } return &order, nil } func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error) { var order entities.Order err := r.db.WithContext(ctx). Preload("Organization"). Preload("Outlet"). Preload("User"). Preload("OrderItems"). Preload("OrderItems.Product"). Preload("OrderItems.ProductVariant"). Preload("Payments"). Preload("Payments.PaymentMethod"). Preload("Payments.PaymentOrderItems"). First(&order, "id = ?", id).Error if err != nil { return nil, err } return &order, nil } func (r *OrderRepositoryImpl) Update(ctx context.Context, order *entities.Order) error { return r.db.WithContext(ctx).Save(order).Error } func (r *OrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error { return r.db.WithContext(ctx).Delete(&entities.Order{}, "id = ?", id).Error } func (r *OrderRepositoryImpl) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error) { var orders []*entities.Order var total int64 query := r.db.WithContext(ctx).Model(&entities.Order{}). Preload("Organization"). Preload("Outlet"). Preload("User"). Preload("OrderItems"). Preload("OrderItems.Product"). Preload("OrderItems.ProductVariant"). Preload("Payments"). Preload("Payments.PaymentMethod") for key, value := range filters { switch key { case "search": searchValue := "%" + value.(string) + "%" query = query.Where("order_number ILIKE ?", searchValue) case "date_from": query = query.Where("created_at >= ?", value) case "date_to": query = query.Where("created_at <= ?", value) default: query = query.Where(key+" = ?", value) } } if err := query.Count(&total).Error; err != nil { return nil, 0, err } err := query.Limit(limit).Offset(offset).Order("created_at DESC").Find(&orders).Error return orders, total, err } func (r *OrderRepositoryImpl) GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error) { var order entities.Order err := r.db.WithContext(ctx).First(&order, "order_number = ?", orderNumber).Error if err != nil { return nil, err } return &order, nil } func (r *OrderRepositoryImpl) ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error) { var count int64 err := r.db.WithContext(ctx).Model(&entities.Order{}).Where("order_number = ?", orderNumber).Count(&count).Error return count > 0, err } func (r *OrderRepositoryImpl) VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error { now := time.Now() return r.db.WithContext(ctx).Model(&entities.Order{}). Where("id = ?", id). Updates(map[string]interface{}{ "is_void": true, "void_reason": reason, "voided_at": now, "voided_by": voidedBy, }).Error } func (r *OrderRepositoryImpl) VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error { now := time.Now() return r.db.WithContext(ctx).Model(&entities.Order{}). Where("id = ?", id). Updates(map[string]interface{}{ "status": status, "is_void": true, "void_reason": reason, "voided_at": now, "voided_by": voidedBy, }).Error } func (r *OrderRepositoryImpl) RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error { now := time.Now() return r.db.WithContext(ctx).Model(&entities.Order{}). Where("id = ?", id). Updates(map[string]interface{}{ "is_refund": true, "refund_reason": reason, "refunded_at": now, "refunded_by": refundedBy, }).Error } func (r *OrderRepositoryImpl) UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error { return r.db.WithContext(ctx).Model(&entities.Order{}). Where("id = ?", id). Update("payment_status", status).Error } func (r *OrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error { return r.db.WithContext(ctx).Model(&entities.Order{}). Where("id = ?", id). Update("status", status).Error } func (r *OrderRepositoryImpl) GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error) { now := time.Now() year := now.Year() month := int(now.Month()) // Use a transaction to ensure atomic sequence increment tx := r.db.WithContext(ctx).Begin() if tx.Error != nil { return "", tx.Error } defer func() { if r := recover(); r != nil { tx.Rollback() } }() // Get or create sequence record var sequence entities.OrderSequence err := tx.Where("organization_id = ? AND outlet_id = ? AND year = ? AND month = ?", organizationID, outletID, year, month). First(&sequence).Error if err != nil { if err == gorm.ErrRecordNotFound { // Create new sequence record sequence = entities.OrderSequence{ OrganizationID: organizationID, OutletID: outletID, Year: year, Month: month, SequenceNumber: 0, } if err := tx.Create(&sequence).Error; err != nil { tx.Rollback() return "", err } } else { tx.Rollback() return "", err } } // Increment sequence number sequence.SequenceNumber++ if err := tx.Save(&sequence).Error; err != nil { tx.Rollback() return "", err } // Commit transaction if err := tx.Commit().Error; err != nil { return "", err } orderNumber := fmt.Sprintf("ORD/%04d%02d/%06d", year, month, sequence.SequenceNumber) return orderNumber, nil }