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, outletID uuid.UUID) (*entities.Order, error) GetWithRelations(ctx context.Context, id uuid.UUID, outletID 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) ListBySessionID(ctx context.Context, sessionID string) ([]*entities.Order, 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, outletID uuid.UUID) error VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID, outletID uuid.UUID) error RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID, outletID uuid.UUID) error UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus, outletID uuid.UUID) error UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, outletID uuid.UUID) error UpdateStatusSuccess(ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus, outletID uuid.UUID) 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 applyOutletFilter(query *gorm.DB, outletID uuid.UUID) *gorm.DB { if outletID != uuid.Nil { return query.Where("outlet_id = ?", outletID) } return query } func (r *OrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error) { var order entities.Order query := r.db.WithContext(ctx) query = applyOutletFilter(query, outletID) err := query.First(&order, "id = ?", id).Error if err != nil { return nil, err } return &order, nil } func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID, outletID uuid.UUID) (*entities.Order, error) { var order entities.Order query := 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") query = applyOutletFilter(query, outletID) err := query.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) UpdateStatusSuccess( ctx context.Context, id uuid.UUID, orderStatus entities.OrderStatus, paymentStatus entities.PaymentStatus, outletID uuid.UUID, ) error { query := r.db.WithContext(ctx). Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.Where("id = ?", id.String()). Updates(map[string]interface{}{ "status": orderStatus, "payment_status": paymentStatus, }).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"). Preload("Payments.PaymentOrderItems") 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) ListBySessionID(ctx context.Context, sessionID string) ([]*entities.Order, error) { var orders []*entities.Order err := 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"). Preload("Payments.PaymentOrderItems"). Where("metadata->>'session_id' = ?", sessionID). Order("created_at ASC"). Find(&orders).Error return orders, 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, outletID uuid.UUID) error { now := time.Now() query := r.db.WithContext(ctx).Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.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, outletID uuid.UUID) error { now := time.Now() query := r.db.WithContext(ctx).Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.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, outletID uuid.UUID) error { now := time.Now() query := r.db.WithContext(ctx).Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.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, outletID uuid.UUID) error { query := r.db.WithContext(ctx).Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.Where("id = ?", id). Update("payment_status", status).Error } func (r *OrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, outletID uuid.UUID) error { query := r.db.WithContext(ctx).Model(&entities.Order{}) query = applyOutletFilter(query, outletID) return query.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 }