apskel-pos-backend/internal/repository/order_repository.go

291 lines
9.3 KiB
Go

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
}