package orders import ( "context" "fmt" "furtuna-be/internal/common/logger" "furtuna-be/internal/entity" "go.uber.org/zap" "gorm.io/gorm" ) type OrderRepository struct { db *gorm.DB } func NewOrderRepository(db *gorm.DB) *OrderRepository { return &OrderRepository{ db: db, } } func (o *OrderRepository) CreateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) { tx := o.db.Begin() if err := tx.Select("branch_id", "status", "customer_name", "customer_phone", "pax", "amount", "created_by").Create(order).Error; err != nil { tx.Rollback() logError(ctx, "creating order", err) return nil, err } for i, orditem := range order.OrderItem { orderItem := orditem.ToOrderItemDB() orderItem.OrderID = order.ID err := tx.Select("order_id", "item_id", "item_type", "price", "qty", "created_by").Create(orderItem).Error if err != nil { logger.ContextLogger(ctx).Error("error when create order item", zap.Error(err)) return nil, err } order.OrderItem[i] = *orderItem.ToOrderItem() } //insert transaction transaction := order.Transaction.ToTransactionDB() transaction.OrderID = order.ID if err := tx.Select("branch_id", "status", "amount", "order_id", "payment_method", "customer_name", "customer_phone", "created_by").Create(transaction).Error; err != nil { tx.Rollback() logError(ctx, "creating transaction", err) return nil, err } if err := tx.Commit().Error; err != nil { tx.Rollback() logError(ctx, "committing transaction", err) return nil, err } return order, nil } func (b *OrderRepository) UpdateOrder(ctx context.Context, order *entity.OrderDB) (*entity.OrderDB, error) { if err := b.db.Select("status", "updated_at", "updated_by").Save(order).Error; err != nil { logError(ctx, "update order", err) return nil, err } return order, nil } func (b *OrderRepository) GetAllOrders(ctx context.Context, req entity.OrderSearch) (entity.OrderList, int, error) { var orders []*entity.OrderDB var total int64 query := b.db.Table("orders"). Select("orders.id, orders.branch_id, b.name as branch_name, orders.status, orders.amount, orders.created_at, orders.updated_at, oi.order_item_id, oi.order_id, oi.item_id, oi.item_type, COALESCE(p.name, s.name, '') as item_name, oi.price, oi.qty, oi.created_at, oi.updated_at, COALESCE(t.payment_method, ''), COALESCE(orders.customer_name, ''), COALESCE(orders.customer_phone, ''), COALESCE(orders.pax, 0)"). Joins("LEFT JOIN order_items oi ON orders.id = oi.order_id"). Joins("LEFT JOIN transactions t ON orders.id = t.order_id"). Joins("LEFT JOIN products p ON oi.item_id = p.id AND oi.item_type ='PRODUCT' "). Joins("LEFT JOIN studios s ON oi.item_id = s.id AND oi.item_type ='STUDIO' "). Joins("LEFT JOIN branches b ON orders.branch_id = b.id") if req.Search != "" { query = query.Where("b.name ILIKE ? or orders.status ILIKE ? or oi.item_type ILIKE ? or p.name ILIKE ? or orders.customer_name ILIKE ? ", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%", "%"+req.Search+"%") } if req.Status != "" { query = query.Where("orders.status = ?", req.Status) } if req.BranchID > 0 { query = query.Where("orders.branch_id = ?", req.BranchID) } if req.StatusActive.IsActive() { query = query.Joins("INNER JOIN (SELECT o.id, oi.qty, o.created_at FROM orders o INNER JOIN order_items oi ON o.id = oi.order_id AND oi.item_type = 'STUDIO' where o.status != 'CANCEL' and CURRENT_TIMESTAMP > o.created_at AND CURRENT_TIMESTAMP < (o.created_at + (oi.qty || ' hours')::interval)) order_active on order_active.id=orders.id") } if req.Limit > 0 { query = query.Limit(req.Limit) } if req.Offset > 0 { query = query.Offset(req.Offset) } query.Order("orders.created_at DESC") rows, err := query.Rows() if err != nil { return nil, 0, err } defer rows.Close() ordersMap := make(map[int64]*entity.OrderDB) // Map to store orders by ID for rows.Next() { var ordr entity.OrderDB var oi entity.OrderItem err := rows.Scan(&ordr.ID, &ordr.BranchID, &ordr.BranchName, &ordr.Status, &ordr.Amount, &ordr.CreatedAt, &ordr.UpdatedAt, &oi.OrderItemID, &oi.OrderID, &oi.ItemID, &oi.ItemType, &oi.ItemName, &oi.Price, &oi.Qty, &oi.CreatedAt, &oi.UpdatedAt, &ordr.Transaction.PaymentMethod, &ordr.CustomerName, &ordr.CustomerPhone, &ordr.Pax) if err != nil { logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) return nil, 0, err } if order, ok := ordersMap[ordr.ID]; ok { // Order already exists in map, append OrderItem to existing order order.OrderItem = append(order.OrderItem, oi) } else { // Order doesn't exist in map, create a new OrderDB newOrder := ordr newOrder.OrderItem = []entity.OrderItem{oi} ordersMap[ordr.ID] = &newOrder orders = append(orders, &ordr) } } // assign value order item for _, v := range orders { v.OrderItem = ordersMap[v.ID].OrderItem } //reset limit for count total data query = query.Offset(-1).Limit(-1) if err := query.Count(&total).Error; err != nil { logger.ContextLogger(ctx).Error("error when count orders", zap.Error(err)) return nil, 0, err } return orders, int(total), nil } func (b *OrderRepository) GetOrderByID(ctx context.Context, id int64) (*entity.OrderDB, error) { var orders *entity.OrderDB query := b.db.Table("orders"). Select("orders.id, orders.branch_id, b.name as branch_name, orders.status, orders.amount, orders.created_at, orders.updated_at, oi.order_item_id, oi.order_id, oi.item_id, oi.item_type, COALESCE(p.name, s.name, '') as item_name, oi.price, oi.qty, oi.created_at, oi.updated_at, t.payment_method, orders.customer_name, orders.customer_phone, orders.pax"). Joins("LEFT JOIN order_items oi ON orders.id = oi.order_id"). Joins("LEFT JOIN transactions t ON orders.id = t.order_id"). Joins("LEFT JOIN products p ON oi.item_id = p.id AND oi.item_type ='PRODUCT' "). Joins("LEFT JOIN studios s ON oi.item_id = s.id AND oi.item_type ='STUDIO' "). Joins("LEFT JOIN branches b ON orders.branch_id = b.id"). Where("orders.id = ?", id) rows, err := query.Rows() if err != nil { return nil, err } defer rows.Close() var ordr entity.OrderDB // Map to store orders by ID for rows.Next() { var oi entity.OrderItem err := rows.Scan(&ordr.ID, &ordr.BranchID, &ordr.BranchName, &ordr.Status, &ordr.Amount, &ordr.CreatedAt, &ordr.UpdatedAt, &oi.OrderItemID, &oi.OrderID, &oi.ItemID, &oi.ItemType, &oi.ItemName, &oi.Price, &oi.Qty, &oi.CreatedAt, &oi.UpdatedAt, &ordr.Transaction.PaymentMethod, &ordr.CustomerName, &ordr.CustomerPhone, &ordr.Pax) if err != nil { logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) return nil, err } ordr.OrderItem = append(ordr.OrderItem, oi) } orders = &ordr if orders == nil { return nil, fmt.Errorf("order not found") } return orders, nil } func (b *OrderRepository) GetTotalRevenue(ctx context.Context, req entity.OrderTotalRevenueSearch) (float64, int64, error) { var ( totalmonthlyRevenue float64 totalmonthlyTrans int64 ) query := b.db.Table("orders"). Select("COALESCE(sum(amount),0) as total_amount, COALESCE(count(id),0) as total_transaction"). Where("status in ('NEW','PAID') ") if req.BranchID > 0 { query = query.Where("branch_id = ?", req.BranchID) } if req.Month > 0 { query = query.Where("EXTRACT(MONTH FROM created_at) = ? ", req.Month) } if req.Year > 0 { query = query.Where("EXTRACT(YEAR FROM created_at) = ? ", req.Year) } if req.DateStart != nil { query = query.Where("created_at >= ? ", req.DateStart) } if req.DateEnd != nil { query = query.Where("created_at <= ? ", req.DateEnd) } rows, err := query.Rows() if err != nil { return totalmonthlyRevenue, totalmonthlyTrans, err } defer rows.Close() for rows.Next() { err := rows.Scan(&totalmonthlyRevenue, &totalmonthlyTrans) if err != nil { logger.ContextLogger(ctx).Error("error scanning rows", zap.Error(err)) return totalmonthlyRevenue, totalmonthlyTrans, err } } return totalmonthlyRevenue, totalmonthlyTrans, nil } func (b *OrderRepository) GetYearlyRevenue(ctx context.Context, year int) (entity.OrderYearlyRevenueList, error) { var result entity.OrderYearlyRevenueList err := b.db.Raw(` SELECT oi.item_type, EXTRACT(MONTH FROM o.created_at) AS month_number, SUM(oi.price ) AS total_amount FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE EXTRACT(YEAR FROM o.created_at) = ? AND o.status IN ('NEW', 'PAID') GROUP BY EXTRACT(MONTH FROM o.created_at), oi.item_type ORDER BY month_number, oi.item_type`, year).Scan(&result).Error return result, err } func (b *OrderRepository) GetBranchRevenue(ctx context.Context, req entity.OrderBranchRevenueSearch) (entity.OrderBranchRevenueList, error) { var result entity.OrderBranchRevenueList query := b.db.Table("orders o"). Joins("JOIN branches ON branches.id = o.branch_id"). Select("o.branch_id, branches.name, branches.location, SUM(o.amount) as total_amount, COUNT(o.id) as total_trans"). Where("o.status IN ('NEW', 'PAID')"). Group("o.branch_id, branches.name, branches.location"). Order("total_amount DESC, total_trans DESC") if req.DateStart != nil { query = query.Where("o.created_at >= ? ", req.DateStart) } if req.DateEnd != nil { query = query.Where("o.created_at <= ? ", req.DateEnd) } if err := query.Find(&result).Error; err != nil { logger.ContextLogger(ctx).Error("error when GetBranchRevenue", zap.Error(err)) return nil, err } return result, nil } func logError(ctx context.Context, s string, err error) { panic("unimplemented") }