Files
apskel-pos-backend/internal/repository/customer_order_repository.go
T

158 lines
5.5 KiB
Go
Raw Normal View History

package repository
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"apskel-pos-be/internal/entities"
)
// ErrCustomerOrderNotFound means the order does not exist or belongs to another
// customer; the two are not told apart.
var ErrCustomerOrderNotFound = errors.New("order not found")
// CustomerOrderRow is one order as the customer app lists it.
type CustomerOrderRow struct {
ID uuid.UUID
OrderNumber string
OutletID uuid.UUID
OutletName string
OrderType string
TableNumber *string
Status string
PaymentStatus string
Subtotal float64
DiscountAmount float64
TaxAmount float64
TotalAmount float64
RefundAmount float64
IsVoid bool
IsRefund bool
ItemCount int64
CreatedAt time.Time
}
// CustomerOrderItemRow is one line of an order, without costs.
type CustomerOrderItemRow struct {
ID uuid.UUID
ProductID uuid.UUID
ProductName string
VariantName *string
Quantity int
Weight *float64
UnitName *string
UnitPrice float64
TotalPrice float64
RefundQuantity int
Modifiers entities.Modifiers `gorm:"type:jsonb"`
Notes *string
Status string
}
// CustomerOrderPaymentRow is one payment of an order.
type CustomerOrderPaymentRow struct {
ID uuid.UUID
MethodName string
MethodType string
Amount float64
Status string
RefundAmount float64
PointsUsed *int64
PointValue *float64
CreatedAt time.Time
}
// CustomerOrderRepository reads a customer's own orders for the customer app. Every
// read is scoped to the customer, so one customer can never see another's order.
type CustomerOrderRepository interface {
// ListOrders returns a page of the customer's orders, newest first, and the total.
ListOrders(ctx context.Context, customerID uuid.UUID, offset, limit int) ([]CustomerOrderRow, int64, error)
// GetOrder returns ErrCustomerOrderNotFound unless the order is the customer's.
GetOrder(ctx context.Context, customerID, orderID uuid.UUID) (*CustomerOrderRow, error)
ListItems(ctx context.Context, orderID uuid.UUID) ([]CustomerOrderItemRow, error)
ListPayments(ctx context.Context, orderID uuid.UUID) ([]CustomerOrderPaymentRow, error)
}
type customerOrderRepository struct {
db *gorm.DB
}
func NewCustomerOrderRepository(db *gorm.DB) CustomerOrderRepository {
return &customerOrderRepository{db: db}
}
const customerOrderColumns = `o.id, o.order_number, o.outlet_id, COALESCE(ol.name, '') AS outlet_name,
o.order_type, o.table_number, o.status, o.payment_status, o.subtotal, o.discount_amount,
o.tax_amount, o.total_amount, o.refund_amount, o.is_void, o.is_refund,
(SELECT COUNT(*) FROM order_items oi WHERE oi.order_id = o.id) AS item_count, o.created_at`
func (r *customerOrderRepository) ListOrders(ctx context.Context, customerID uuid.UUID, offset, limit int) ([]CustomerOrderRow, int64, error) {
db := DBFromContext(ctx, r.db).WithContext(ctx)
var total int64
if err := db.Table("orders").Where("customer_id = ?", customerID).Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("failed to count customer orders: %w", err)
}
var rows []CustomerOrderRow
err := db.Raw(`SELECT `+customerOrderColumns+`
FROM orders o LEFT JOIN outlets ol ON ol.id = o.outlet_id
WHERE o.customer_id = ?
ORDER BY o.created_at DESC, o.id
LIMIT ? OFFSET ?`, customerID, limit, offset).Scan(&rows).Error
if err != nil {
return nil, 0, fmt.Errorf("failed to list customer orders: %w", err)
}
return rows, total, nil
}
func (r *customerOrderRepository) GetOrder(ctx context.Context, customerID, orderID uuid.UUID) (*CustomerOrderRow, error) {
var rows []CustomerOrderRow
err := DBFromContext(ctx, r.db).WithContext(ctx).Raw(`SELECT `+customerOrderColumns+`
FROM orders o LEFT JOIN outlets ol ON ol.id = o.outlet_id
WHERE o.id = ? AND o.customer_id = ?`, orderID, customerID).Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("failed to get customer order: %w", err)
}
if len(rows) == 0 {
return nil, ErrCustomerOrderNotFound
}
return &rows[0], nil
}
func (r *customerOrderRepository) ListItems(ctx context.Context, orderID uuid.UUID) ([]CustomerOrderItemRow, error) {
var rows []CustomerOrderItemRow
err := DBFromContext(ctx, r.db).WithContext(ctx).Raw(`
SELECT oi.id, oi.product_id, COALESCE(p.name, '') AS product_name, pv.name AS variant_name,
oi.quantity, oi.weight, COALESCE(u.abbreviation, u.name) AS unit_name,
oi.unit_price, oi.total_price, oi.refund_quantity, oi.modifiers, oi.notes, oi.status
FROM order_items oi
LEFT JOIN products p ON p.id = oi.product_id
LEFT JOIN product_variants pv ON pv.id = oi.product_variant_id
LEFT JOIN units u ON u.id = oi.unit_id
WHERE oi.order_id = ?
ORDER BY oi.created_at, oi.id`, orderID).Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("failed to list order items: %w", err)
}
return rows, nil
}
func (r *customerOrderRepository) ListPayments(ctx context.Context, orderID uuid.UUID) ([]CustomerOrderPaymentRow, error) {
var rows []CustomerOrderPaymentRow
err := DBFromContext(ctx, r.db).WithContext(ctx).Raw(`
SELECT pay.id, COALESCE(pm.name, '') AS method_name, COALESCE(pm.type, '') AS method_type,
pay.amount, pay.status, pay.refund_amount, pay.points_used, pay.point_value, pay.created_at
FROM payments pay
LEFT JOIN payment_methods pm ON pm.id = pay.payment_method_id
WHERE pay.order_id = ?
ORDER BY pay.created_at, pay.id`, orderID).Scan(&rows).Error
if err != nil {
return nil, fmt.Errorf("failed to list order payments: %w", err)
}
return rows, nil
}