183 lines
5.5 KiB
Go
183 lines
5.5 KiB
Go
package orders
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
"backend/internal/modules/pricing"
|
|
"backend/internal/modules/products"
|
|
"backend/internal/platform/middleware"
|
|
)
|
|
|
|
type Handler struct {
|
|
service *Service
|
|
}
|
|
|
|
func NewHandler(service *Service) *Handler {
|
|
return &Handler{service: service}
|
|
}
|
|
|
|
type itemRequest struct {
|
|
ProductID uuid.UUID `json:"product_id" binding:"required"`
|
|
PriceTierID uuid.UUID `json:"price_tier_id" binding:"required"`
|
|
Multiplier int64 `json:"multiplier" binding:"required,gte=1"`
|
|
}
|
|
|
|
type createRequest struct {
|
|
CustomerName string `json:"customer_name" binding:"required"`
|
|
CustomerEmail string `json:"customer_email" binding:"required,email"`
|
|
CustomerPhone string `json:"customer_phone"`
|
|
Notes string `json:"notes"`
|
|
Items []itemRequest `json:"items" binding:"required,min=1,dive"`
|
|
}
|
|
|
|
type orderItemResponse struct {
|
|
ProductID *uuid.UUID `json:"product_id,omitempty"`
|
|
ProductName string `json:"product_name"`
|
|
UnitSymbol string `json:"unit_symbol"`
|
|
TierQuantity float64 `json:"tier_quantity"`
|
|
Multiplier int64 `json:"multiplier"`
|
|
UnitPriceCents int64 `json:"unit_price_cents"`
|
|
TotalCents int64 `json:"total_cents"`
|
|
}
|
|
|
|
type orderResponse struct {
|
|
ID uuid.UUID `json:"id"`
|
|
CustomerName string `json:"customer_name"`
|
|
CustomerEmail string `json:"customer_email"`
|
|
CustomerPhone string `json:"customer_phone"`
|
|
TotalCents int64 `json:"total_cents"`
|
|
Notes string `json:"notes"`
|
|
Items []orderItemResponse `json:"items,omitempty"`
|
|
}
|
|
|
|
func toResponse(order *Order, items []*OrderItem) orderResponse {
|
|
resp := orderResponse{
|
|
ID: order.ID,
|
|
CustomerName: order.CustomerName,
|
|
CustomerEmail: order.CustomerEmail,
|
|
CustomerPhone: order.CustomerPhone,
|
|
TotalCents: order.TotalCents,
|
|
Notes: order.Notes,
|
|
}
|
|
for _, item := range items {
|
|
resp.Items = append(resp.Items, orderItemResponse{
|
|
ProductID: item.ProductID,
|
|
ProductName: item.ProductName,
|
|
UnitSymbol: item.UnitSymbol,
|
|
TierQuantity: item.TierQuantity,
|
|
Multiplier: item.Multiplier,
|
|
UnitPriceCents: item.UnitPriceCents,
|
|
TotalCents: item.TotalCents,
|
|
})
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// Create is gated by RequireAccountsEnabled/RequireCustomer/
|
|
// RequireApprovedVerificationIfNeeded (see routes.go): rejected outright
|
|
// until the admin enables customer accounts, then requires a logged-in
|
|
// customer (and an approved verification too, if that's also required).
|
|
func (h *Handler) Create(c *gin.Context) {
|
|
var req createRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "validation error", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
items := make([]ItemInput, 0, len(req.Items))
|
|
for _, item := range req.Items {
|
|
items = append(items, ItemInput{
|
|
ProductID: item.ProductID,
|
|
PriceTierID: item.PriceTierID,
|
|
Multiplier: item.Multiplier,
|
|
})
|
|
}
|
|
|
|
var customerID *uuid.UUID
|
|
if uid, ok := middleware.GetUserID(c); ok {
|
|
customerID = &uid
|
|
}
|
|
|
|
order, orderItems, err := h.service.Create(c.Request.Context(), CreateInput{
|
|
CustomerID: customerID,
|
|
CustomerName: req.CustomerName,
|
|
CustomerEmail: req.CustomerEmail,
|
|
CustomerPhone: req.CustomerPhone,
|
|
Notes: req.Notes,
|
|
Items: items,
|
|
})
|
|
if err != nil {
|
|
h.respondCreateError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, toResponse(order, orderItems))
|
|
}
|
|
|
|
// ListMine returns the authenticated customer's own order history.
|
|
func (h *Handler) ListMine(c *gin.Context) {
|
|
userID, ok := middleware.GetUserID(c)
|
|
if !ok {
|
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
|
return
|
|
}
|
|
list, err := h.service.ListByCustomer(c.Request.Context(), userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list orders"})
|
|
return
|
|
}
|
|
resp := make([]orderResponse, 0, len(list))
|
|
for _, order := range list {
|
|
resp = append(resp, toResponse(order, nil))
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"orders": resp})
|
|
}
|
|
|
|
func (h *Handler) respondCreateError(c *gin.Context, err error) {
|
|
switch {
|
|
case errors.Is(err, ErrEmptyOrder), errors.Is(err, ErrProductMismatch):
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
case errors.Is(err, pricing.ErrNotFound):
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown price tier"})
|
|
case errors.Is(err, products.ErrNotFound):
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown product"})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create order"})
|
|
}
|
|
}
|
|
|
|
func (h *Handler) GetAdmin(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
order, items, err := h.service.Get(c.Request.Context(), id)
|
|
if err != nil {
|
|
if errors.Is(err, ErrNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "order not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load order"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, toResponse(order, items))
|
|
}
|
|
|
|
// ListAdmin returns every order, most recent first.
|
|
func (h *Handler) ListAdmin(c *gin.Context) {
|
|
list, err := h.service.List(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list orders"})
|
|
return
|
|
}
|
|
resp := make([]orderResponse, 0, len(list))
|
|
for _, order := range list {
|
|
resp = append(resp, toResponse(order, nil))
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"orders": resp})
|
|
}
|