Chever John 9c39c8cbd7
init: iloom WMS monorepo with K8s deployment manifests
Go workspace (go.work) with 5 microservices + shared library:
- gateway (8080), auth-service (8081), purchaser-service (8082)
- textile-service (8083), washing-service (8084)
- shared: proto definitions, common packages

Infrastructure: docker-compose for local dev, K8s manifests for
K3s cluster deployment (mysql/redis/etcd + traefik ingress).

Frontend (iloom-flatten) added as git submodule.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-06-14 11:33:31 +08:00

275 lines
7.2 KiB
Go

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/types"
"github.com/muyuqingfeng/iloom/shared/pkg/websocket"
)
type PlanHandler struct {
svc *service.PlanService
wsHub *websocket.Hub
}
func NewPlanHandler(svc *service.PlanService, wsHub *websocket.Hub) *PlanHandler {
return &PlanHandler{svc: svc, wsHub: wsHub}
}
type CreatePlanReq 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"`
TargetQuantity float64 `json:"target_quantity" binding:"required,gt=0"`
ProductionPrice *float64 `json:"production_price"`
YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"`
Remark string `json:"remark"`
YarnRatios []CreateYarnRatioReq `json:"yarn_ratios"`
}
type CreateYarnRatioReq struct {
YarnName string `json:"yarn_name" binding:"required"`
YarnType string `json:"yarn_type"`
Ratio float64 `json:"ratio" binding:"required"`
AmountPerMeter float64 `json:"amount_per_meter"`
TotalAmount float64 `json:"total_amount"`
}
type UpdatePlanReq struct {
ProductName string `json:"product_name"`
FabricCode string `json:"fabric_code"`
Color string `json:"color"`
ColorCode string `json:"color_code"`
TargetQuantity float64 `json:"target_quantity"`
ProductionPrice *float64 `json:"production_price"`
YarnUsagePerMeter *float64 `json:"yarn_usage_per_meter"`
Status string `json:"status"`
Remark string `json:"remark"`
}
type AssignFactoryReq struct {
FactoryID string `json:"factory_id" binding:"required"`
FactoryType string `json:"factory_type" binding:"required"`
}
func (h *PlanHandler) List(c *gin.Context) {
companyID := c.GetHeader("X-Company-ID")
if companyID == "" {
response.BadRequest(c, "missing company id")
return
}
var page types.Pagination
if err := c.ShouldBindQuery(&page); err != nil {
response.BadRequest(c, "invalid pagination")
return
}
page.Normalize()
status := c.Query("status")
plans, total, err := h.svc.List(c.Request.Context(), companyID, page, status)
if err != nil {
log.Printf("list plans error: %v", err)
response.InternalError(c, "failed to list plans")
return
}
response.OKWithMeta(c, plans, &response.Meta{
Page: page.Page,
PageSize: page.PageSize,
Total: total,
HasMore: page.Page*page.PageSize < total,
})
}
func (h *PlanHandler) Create(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
}
var req CreatePlanReq
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
svcReq := service.CreatePlanRequest{
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
TargetQuantity: req.TargetQuantity,
ProductionPrice: req.ProductionPrice,
YarnUsagePerMeter: req.YarnUsagePerMeter,
Remark: req.Remark,
}
for _, yr := range req.YarnRatios {
svcReq.YarnRatios = append(svcReq.YarnRatios, service.CreateYarnRatio{
YarnName: yr.YarnName,
YarnType: yr.YarnType,
Ratio: yr.Ratio,
AmountPerMeter: yr.AmountPerMeter,
TotalAmount: yr.TotalAmount,
})
}
plan, err := h.svc.Create(c.Request.Context(), companyID, userID, svcReq)
if err != nil {
log.Printf("create plan error: %v", err)
response.InternalError(c, "failed to create plan")
return
}
h.wsHub.BroadcastToCompany(companyID, &websocket.Event{
Table: "production_plans",
Action: "INSERT",
})
c.JSON(http.StatusCreated, response.Response{
Code: 0,
Message: "success",
Data: plan,
})
}
func (h *PlanHandler) Get(c *gin.Context) {
companyID := c.GetHeader("X-Company-ID")
if companyID == "" {
response.BadRequest(c, "missing company id")
return
}
id := c.Param("id")
plan, err := h.svc.GetByID(c.Request.Context(), id, companyID)
if err != nil {
log.Printf("get plan error: %v", err)
response.NotFound(c, "plan not found")
return
}
response.OK(c, plan)
}
func (h *PlanHandler) 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 UpdatePlanReq
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
svcReq := service.UpdatePlanRequest{
ProductName: req.ProductName,
FabricCode: req.FabricCode,
Color: req.Color,
ColorCode: req.ColorCode,
TargetQuantity: req.TargetQuantity,
ProductionPrice: req.ProductionPrice,
YarnUsagePerMeter: req.YarnUsagePerMeter,
Status: req.Status,
Remark: req.Remark,
}
if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil {
log.Printf("update plan error: %v", err)
response.InternalError(c, "failed to update plan")
return
}
h.wsHub.BroadcastToCompany(companyID, &websocket.Event{
Table: "production_plans",
Action: "UPDATE",
})
response.OK(c, nil)
}
func (h *PlanHandler) 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 plan error: %v", err)
response.InternalError(c, "failed to delete plan")
return
}
h.wsHub.BroadcastToCompany(companyID, &websocket.Event{
Table: "production_plans",
Action: "DELETE",
})
response.OK(c, nil)
}
func (h *PlanHandler) AssignFactory(c *gin.Context) {
companyID := c.GetHeader("X-Company-ID")
if companyID == "" {
response.BadRequest(c, "missing company id")
return
}
planID := c.Param("id")
var req AssignFactoryReq
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, err.Error())
return
}
if err := h.svc.AssignFactory(c.Request.Context(), planID, req.FactoryID, req.FactoryType); err != nil {
log.Printf("assign factory error: %v", err)
response.InternalError(c, "failed to assign factory")
return
}
h.wsHub.BroadcastToCompany(companyID, &websocket.Event{
Table: "plan_factories",
Action: "INSERT",
})
response.OK(c, nil)
}
func (h *PlanHandler) GetSteps(c *gin.Context) {
planID := c.Param("id")
steps, err := h.svc.GetSteps(c.Request.Context(), planID)
if err != nil {
log.Printf("get steps error: %v", err)
response.InternalError(c, "failed to get steps")
return
}
response.OK(c, steps)
}
func (h *PlanHandler) GetInventory(c *gin.Context) {
planID := c.Param("id")
records, err := h.svc.GetInventory(c.Request.Context(), planID)
if err != nil {
log.Printf("get inventory error: %v", err)
response.InternalError(c, "failed to get inventory")
return
}
response.OK(c, records)
}