package handler import ( "log" "net/http" "github.com/gin-gonic/gin" "github.com/muyuqingfeng/iloom/purchaser-service/internal/service" "github.com/muyuqingfeng/iloom/shared/pkg/response" "github.com/muyuqingfeng/iloom/shared/pkg/websocket" ) type ProductHandler struct { svc *service.ProductService wsHub *websocket.Hub } func NewProductHandler(svc *service.ProductService, wsHub *websocket.Hub) *ProductHandler { return &ProductHandler{svc: svc, wsHub: wsHub} } type CreateProductReq struct { ProductName string `json:"product_name" binding:"required"` FabricCode string `json:"fabric_code" binding:"required"` Color string `json:"color" binding:"required"` ColorCode string `json:"color_code"` Weight float64 `json:"weight"` YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` ProductionPrice *float64 `json:"production_price"` YarnTypes []string `json:"yarn_types"` YarnRatios []float64 `json:"yarn_ratios"` } type UpdateProductReq struct { ProductName string `json:"product_name"` FabricCode string `json:"fabric_code"` Color string `json:"color"` ColorCode string `json:"color_code"` Weight float64 `json:"weight"` YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"` ProductionPrice *float64 `json:"production_price"` YarnTypes []string `json:"yarn_types"` YarnRatios []float64 `json:"yarn_ratios"` } type InboundReq struct { BatchNo string `json:"batch_no" binding:"required"` Rolls int `json:"rolls" binding:"required,gt=0"` Meters float64 `json:"meters" binding:"required,gt=0"` Notes string `json:"notes"` WarehouseID string `json:"warehouse_id"` } type OutboundReq struct { BatchNo string `json:"batch_no"` Rolls int `json:"rolls" binding:"required,gt=0"` Meters float64 `json:"meters" binding:"required,gt=0"` OutboundType string `json:"outbound_type"` Notes string `json:"notes"` RecipientCompanyID string `json:"recipient_company_id"` RecipientCompanyName string `json:"recipient_company_name"` SourceBatchID string `json:"source_batch_id"` WashingPlanID string `json:"washing_plan_id"` } type UpdatePriceReq struct { NewPrice float64 `json:"new_price" binding:"required,gt=0"` Reason string `json:"reason"` } func (h *ProductHandler) List(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") if companyID == "" { response.BadRequest(c, "missing company id") return } products, err := h.svc.List(c.Request.Context(), companyID) if err != nil { log.Printf("list products error: %v", err) response.InternalError(c, "failed to list products") return } response.OK(c, products) } func (h *ProductHandler) Create(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") if companyID == "" { response.BadRequest(c, "missing company id") return } var req CreateProductReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } svcReq := service.CreateProductRequest{ ProductName: req.ProductName, FabricCode: req.FabricCode, Color: req.Color, ColorCode: req.ColorCode, Weight: req.Weight, YarnUsagePerMeter: req.YarnUsagePerMeter, ProductionPrice: req.ProductionPrice, YarnTypes: req.YarnTypes, YarnRatios: req.YarnRatios, } product, err := h.svc.Create(c.Request.Context(), companyID, svcReq) if err != nil { log.Printf("create product error: %v", err) response.InternalError(c, "failed to create product") return } h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ Table: "products", Action: "INSERT", }) c.JSON(http.StatusCreated, response.Response{ Code: 0, Message: "success", Data: product, }) } func (h *ProductHandler) Update(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") if companyID == "" { response.BadRequest(c, "missing company id") return } id := c.Param("id") var req UpdateProductReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } svcReq := service.UpdateProductRequest{ ProductName: req.ProductName, FabricCode: req.FabricCode, Color: req.Color, ColorCode: req.ColorCode, Weight: req.Weight, YarnUsagePerMeter: req.YarnUsagePerMeter, ProductionPrice: req.ProductionPrice, YarnTypes: req.YarnTypes, YarnRatios: req.YarnRatios, } if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil { log.Printf("update product error: %v", err) response.InternalError(c, "failed to update product") return } h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ Table: "products", Action: "UPDATE", }) response.OK(c, nil) } func (h *ProductHandler) Delete(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") if companyID == "" { response.BadRequest(c, "missing company id") return } id := c.Param("id") if err := h.svc.Delete(c.Request.Context(), id, companyID); err != nil { log.Printf("delete product error: %v", err) response.InternalError(c, "failed to delete product") return } h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ Table: "products", Action: "DELETE", }) response.OK(c, nil) } func (h *ProductHandler) Inbound(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") userID := c.GetHeader("X-User-ID") if companyID == "" || userID == "" { response.BadRequest(c, "missing user or company id") return } productID := c.Param("id") var req InboundReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } svcReq := service.InboundRequest{ BatchNo: req.BatchNo, Rolls: req.Rolls, Meters: req.Meters, Notes: req.Notes, WarehouseID: req.WarehouseID, } if err := h.svc.Inbound(c.Request.Context(), productID, companyID, userID, svcReq); err != nil { log.Printf("inbound error: %v", err) response.InternalError(c, "failed to inbound") return } h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ Table: "product_inventory_records", Action: "INSERT", }) response.OK(c, nil) } func (h *ProductHandler) Outbound(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") userID := c.GetHeader("X-User-ID") if companyID == "" || userID == "" { response.BadRequest(c, "missing user or company id") return } productID := c.Param("id") var req OutboundReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } svcReq := service.OutboundRequest{ BatchNo: req.BatchNo, Rolls: req.Rolls, Meters: req.Meters, OutboundType: req.OutboundType, Notes: req.Notes, RecipientCompanyID: req.RecipientCompanyID, RecipientCompanyName: req.RecipientCompanyName, SourceBatchID: req.SourceBatchID, WashingPlanID: req.WashingPlanID, } if err := h.svc.Outbound(c.Request.Context(), productID, companyID, userID, svcReq); err != nil { log.Printf("outbound error: %v", err) response.InternalError(c, "failed to outbound") return } h.wsHub.BroadcastToCompany(companyID, &websocket.Event{ Table: "product_outbound_records", Action: "INSERT", }) response.OK(c, nil) } func (h *ProductHandler) Records(c *gin.Context) { productID := c.Param("id") records, err := h.svc.Records(c.Request.Context(), productID) if err != nil { log.Printf("get records error: %v", err) response.InternalError(c, "failed to get records") return } response.OK(c, records) } func (h *ProductHandler) UpdatePrice(c *gin.Context) { companyID := c.GetHeader("X-Company-ID") userID := c.GetHeader("X-User-ID") if companyID == "" || userID == "" { response.BadRequest(c, "missing user or company id") return } productID := c.Param("id") var req UpdatePriceReq if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err.Error()) return } if err := h.svc.UpdatePrice(c.Request.Context(), productID, companyID, userID, req.NewPrice, req.Reason); err != nil { log.Printf("update price error: %v", err) response.InternalError(c, "failed to update price") return } response.OK(c, nil) } func (h *ProductHandler) PriceHistory(c *gin.Context) { productID := c.Param("id") history, err := h.svc.PriceHistory(c.Request.Context(), productID) if err != nil { log.Printf("get price history error: %v", err) response.InternalError(c, "failed to get price history") return } response.OK(c, history) }