package processor import ( "context" "fmt" "github.com/google/uuid" "apskel-pos-be/internal/models" "apskel-pos-be/internal/repository" ) const ( customerOrdersPageLimit = 20 customerOrdersMaxLimit = 100 ) type orderEarnedReader interface { EarnedByOrders(ctx context.Context, orderIDs []uuid.UUID) (map[uuid.UUID]OrderEarned, error) } // CustomerOrderProcessor serves the customer app's order history: the customer's own // orders only, without costs or staff details. type CustomerOrderProcessor struct { repo repository.CustomerOrderRepository earned orderEarnedReader } func NewCustomerOrderProcessor(repo repository.CustomerOrderRepository, earned orderEarnedReader) *CustomerOrderProcessor { return &CustomerOrderProcessor{repo: repo, earned: earned} } // List is GET /customer/orders: the customer's orders across all outlets, newest first. func (p *CustomerOrderProcessor) List(ctx context.Context, customerID uuid.UUID, query models.ListCustomerOrdersQuery) (*models.PaginatedResponse[models.CustomerOrderSummary], error) { page, limit := query.Page, query.Limit if page == 0 { page = 1 } if limit == 0 { limit = customerOrdersPageLimit } if page < 1 || limit < 1 || limit > customerOrdersMaxLimit { return nil, fmt.Errorf("%w: page must be at least 1 and limit between 1 and %d", ErrInvalidWalletQuery, customerOrdersMaxLimit) } rows, total, err := p.repo.ListOrders(ctx, customerID, (page-1)*limit, limit) if err != nil { return nil, err } ids := make([]uuid.UUID, 0, len(rows)) for _, r := range rows { ids = append(ids, r.ID) } earned, err := p.earned.EarnedByOrders(ctx, ids) if err != nil { return nil, err } data := make([]models.CustomerOrderSummary, 0, len(rows)) for _, r := range rows { data = append(data, customerOrderSummary(r, earned[r.ID])) } return &models.PaginatedResponse[models.CustomerOrderSummary]{ Data: data, Pagination: models.Pagination{ Page: page, Limit: limit, Total: total, TotalPages: int((total + int64(limit) - 1) / int64(limit)), }, }, nil } // Detail is GET /customer/orders/:id. Another customer's order is // repository.ErrCustomerOrderNotFound, like one that does not exist. func (p *CustomerOrderProcessor) Detail(ctx context.Context, customerID, orderID uuid.UUID) (*models.CustomerOrderDetail, error) { row, err := p.repo.GetOrder(ctx, customerID, orderID) if err != nil { return nil, err } items, err := p.repo.ListItems(ctx, orderID) if err != nil { return nil, err } payments, err := p.repo.ListPayments(ctx, orderID) if err != nil { return nil, err } earned, err := p.earned.EarnedByOrders(ctx, []uuid.UUID{orderID}) if err != nil { return nil, err } detail := &models.CustomerOrderDetail{ CustomerOrderSummary: customerOrderSummary(*row, earned[orderID]), TableNumber: row.TableNumber, Subtotal: row.Subtotal, DiscountAmount: row.DiscountAmount, TaxAmount: row.TaxAmount, RefundAmount: row.RefundAmount, Items: make([]models.CustomerOrderItem, 0, len(items)), Payments: make([]models.CustomerOrderPayment, 0, len(payments)), } for _, it := range items { modifiers := []map[string]interface{}(it.Modifiers) if modifiers == nil { modifiers = []map[string]interface{}{} } detail.Items = append(detail.Items, models.CustomerOrderItem{ ID: it.ID, ProductID: it.ProductID, ProductName: it.ProductName, VariantName: it.VariantName, Quantity: it.Quantity, Weight: it.Weight, UnitName: it.UnitName, UnitPrice: it.UnitPrice, TotalPrice: it.TotalPrice, RefundQuantity: it.RefundQuantity, Modifiers: modifiers, Notes: it.Notes, Status: it.Status, }) } for _, pay := range payments { detail.Payments = append(detail.Payments, models.CustomerOrderPayment{ ID: pay.ID, MethodName: pay.MethodName, MethodType: pay.MethodType, Amount: pay.Amount, Status: pay.Status, RefundAmount: pay.RefundAmount, CreatedAt: pay.CreatedAt, }) } return detail, nil } func customerOrderSummary(r repository.CustomerOrderRow, earned OrderEarned) models.CustomerOrderSummary { return models.CustomerOrderSummary{ ID: r.ID, OrderNumber: r.OrderNumber, OutletID: r.OutletID, OutletName: r.OutletName, OrderType: r.OrderType, Status: r.Status, PaymentStatus: r.PaymentStatus, TotalAmount: r.TotalAmount, ItemCount: r.ItemCount, IsVoid: r.IsVoid, IsRefund: r.IsRefund, PointsEarned: earned.Points, CoinsEarned: earned.Coins, CreatedAt: r.CreatedAt, } }