This commit is contained in:
CFOU
2026-09-14 20:50:19 +02:00
commit 3091fb4020
135 changed files with 10262 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
package orders
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"backend/internal/modules/pricing"
"backend/internal/modules/products"
)
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"`
Status string `json:"status"`
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,
Status: order.Status,
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 public: a visitor (connected or not, per spec section 17) can
// place an order without an account.
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,
})
}
order, orderItems, err := h.service.Create(c.Request.Context(), CreateInput{
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))
}
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) ListAdmin(c *gin.Context) {
list, err := h.service.List(c.Request.Context(), c.Query("status"))
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) 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))
}
type updateStatusRequest struct {
Status string `json:"status" binding:"required"`
}
func (h *Handler) UpdateStatus(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var req updateStatusRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "validation error", "details": err.Error()})
return
}
order, err := h.service.UpdateStatus(c.Request.Context(), id, req.Status)
if err != nil {
switch {
case errors.Is(err, ErrNotFound):
c.JSON(http.StatusNotFound, gin.H{"error": "order not found"})
case errors.Is(err, ErrInvalidStatus):
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid status"})
default:
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update order status"})
}
return
}
c.JSON(http.StatusOK, toResponse(order, nil))
}