package handler import ( "encoding/json" "fmt" "net/http" "os" "path/filepath" "strconv" "time" "github.com/gin-gonic/gin" "github.com/rovina/zsp-backend/internal/model" "github.com/rovina/zsp-backend/internal/service" "github.com/rovina/zsp-backend/pkg/utils" ) type DeliveryHandler struct { svc service.DeliveryService } func NewDeliveryHandler(svc service.DeliveryService) *DeliveryHandler { return &DeliveryHandler{svc: svc} } // ========== 我要提货 /api/apply-delivery ========== func (h *DeliveryHandler) ListApply(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) orderNumber := c.Query("orderNumber") blNumber := c.Query("blNumber") status := c.Query("status") list, total, err := h.svc.ListApply( page, pageSize, orderNumber, blNumber, status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"items": list, "total": total, "page": page, "pageSize": pageSize}}) } func (h *DeliveryHandler) GetApply(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetApplyByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) CreateApply(c *gin.Context) { var body model.DeliveryApply if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } body.OrderNumber = fmt.Sprintf("DA%d", time.Now().UnixNano()/1e6) body.Status = "pending" body.CreateTime = time.Now() body.UpdateTime = time.Now() if err := h.svc.CreateApply( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"id": body.ID, "orderNumber": body.OrderNumber}}) } func (h *DeliveryHandler) UpdateApply(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetApplyByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } if err := c.ShouldBindJSON(e); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } e.UpdateTime = time.Now() if err := h.svc.UpdateApply( e); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) CancelApply(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetApplyByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } e.Status = "cancelled" e.UpdateTime = time.Now() if err := h.svc.UpdateApply( e); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) DeleteApply(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } if err := h.svc.DeleteApply( uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "deleted"}) } // ========== 提货明细 /api/delivery-details ========== func (h *DeliveryHandler) ListOutbound(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) outboundNumber := c.Query("outboundNumber") blNumber := c.Query("blNumber") status := c.Query("outboundStatus") list, total, err := h.svc.ListOutbound( page, pageSize, outboundNumber, blNumber, status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"items": list, "total": total, "page": page, "pageSize": pageSize}}) } func (h *DeliveryHandler) GetOutbound(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetOutboundByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) CreateOutbound(c *gin.Context) { var body model.DeliveryOutbound if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } body.OutboundNumber = fmt.Sprintf("OB%d", time.Now().UnixNano()/1e6) body.OutboundStatus = "pending" body.ReceiptStatus = "pending" body.CreateTime = time.Now() body.UpdateTime = time.Now() if err := h.svc.CreateOutbound( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) } func (h *DeliveryHandler) UpdateOutbound(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetOutboundByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } if err := c.ShouldBindJSON(e); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } e.UpdateTime = time.Now() if err := h.svc.UpdateOutbound( e); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) DeleteOutbound(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } if err := h.svc.DeleteOutbound( uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "deleted"}) } // ========== 库存 /api/inventory ========== func (h *DeliveryHandler) ListWarehouses(c *gin.Context) { list, err := h.svc.ListWarehouses() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": list}) } func (h *DeliveryHandler) CreateWarehouse(c *gin.Context) { var body model.Warehouse if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } body.Status = "active" body.CreateTime = time.Now() body.UpdateTime = time.Now() if err := h.svc.CreateWarehouse( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) } func (h *DeliveryHandler) UpdateWarehouse(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetWarehouseByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } if err := c.ShouldBindJSON(e); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } e.UpdateTime = time.Now() if err := h.svc.UpdateWarehouse( e); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) DeleteWarehouse(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } if err := h.svc.DeleteWarehouse( uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "deleted"}) } func (h *DeliveryHandler) ListInbound(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) warehouse := c.Query("warehouse") commodity := c.Query("commodity") list, total, err := h.svc.ListInbound( page, pageSize, warehouse, commodity) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"items": list, "total": total, "page": page, "pageSize": pageSize}}) } func (h *DeliveryHandler) CreateInbound(c *gin.Context) { var body model.InventoryInbound if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } body.InboundNumber = fmt.Sprintf("IN%d", time.Now().UnixNano()/1e6) body.CreateTime = time.Now() body.UpdateTime = time.Now() if err := h.svc.CreateInbound( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) } func (h *DeliveryHandler) UpdateInbound(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetInboundByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } if err := c.ShouldBindJSON(e); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } e.UpdateTime = time.Now() if err := h.svc.UpdateInbound( e); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) DeleteInbound(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } if err := h.svc.DeleteInbound( uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "deleted"}) } // InventorySummary 库存汇总:入库 - 出库 - 货权转移 = 库存 func (h *DeliveryHandler) InventorySummary(c *gin.Context) { warehouse := c.Query("warehouse") commodity := c.Query("commodity") type row struct { Warehouse string `json:"warehouse"` Commodity string `json:"commodity"` TotalInbound float64 `json:"totalInbound"` TotalOutbound float64 `json:"totalOutbound"` TotalTransfer float64 `json:"totalTransfer"` CurrentStock float64 `json:"currentStock"` Unit string `json:"unit"` } var inSum []struct { Warehouse string Commodity string Sum float64 } qIn := h.svc.DB().Model(&model.InventoryInbound{}).Select("warehouse, commodity, SUM(inbound_weight) as sum").Group("warehouse, commodity") if warehouse != "" { qIn = qIn.Where("warehouse = ?", warehouse) } if commodity != "" { qIn = qIn.Where("commodity LIKE ?", "%"+commodity+"%") } qIn.Scan(&inSum) var outSum []struct { Warehouse string Commodity string Sum float64 } qOut := h.svc.DB().Model(&model.DeliveryOutbound{}).Select("warehouse, commodity, SUM(quantity) as sum").Group("warehouse, commodity") if warehouse != "" { qOut = qOut.Where("warehouse = ?", warehouse) } if commodity != "" { qOut = qOut.Where("commodity LIKE ?", "%"+commodity+"%") } qOut.Scan(&outSum) var trSum []struct { Warehouse string Commodity string Sum float64 } qTr := h.svc.DB().Model(&model.OwnershipTransfer{}). Select("ownership_transfers.warehouse, ownership_transfers.commodity, COALESCE((SELECT SUM(quantity) FROM ownership_transfer_bls WHERE transfer_id = ownership_transfers.id), 0) as sum"). Group("ownership_transfers.warehouse, ownership_transfers.commodity") if warehouse != "" { qTr = qTr.Where("warehouse = ?", warehouse) } if commodity != "" { qTr = qTr.Where("commodity LIKE ?", "%"+commodity+"%") } qTr.Scan(&trSum) m := make(map[string]*row) for _, r := range inSum { key := r.Warehouse + "|" + r.Commodity m[key] = &row{Warehouse: r.Warehouse, Commodity: r.Commodity, TotalInbound: r.Sum, Unit: "吨"} } for _, r := range outSum { key := r.Warehouse + "|" + r.Commodity if m[key] == nil { m[key] = &row{Warehouse: r.Warehouse, Commodity: r.Commodity, Unit: "吨"} } m[key].TotalOutbound = r.Sum } for _, r := range trSum { key := r.Warehouse + "|" + r.Commodity if m[key] == nil { m[key] = &row{Warehouse: r.Warehouse, Commodity: r.Commodity, Unit: "吨"} } m[key].TotalTransfer = r.Sum } var result []row for _, v := range m { v.CurrentStock = v.TotalInbound - v.TotalOutbound - v.TotalTransfer result = append(result, *v) } c.JSON(http.StatusOK, gin.H{"success": true, "data": result}) } // ========== 货权转移 /api/ownership-transfer ========== func (h *DeliveryHandler) ListOwnership(c *gin.Context) { page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10")) transferNumber := c.Query("transferNumber") blNumber := c.Query("blNumber") status := c.Query("status") list, total, err := h.svc.ListOwnership( page, pageSize, transferNumber, blNumber, status) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": gin.H{"items": list, "total": total}}) } func (h *DeliveryHandler) GetOwnership(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } e, err := h.svc.GetOwnershipByID( uint(id)) if err != nil { c.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": e}) } func (h *DeliveryHandler) CreateOwnership(c *gin.Context) { var body model.OwnershipTransfer if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": err.Error()}) return } body.TransferNumber = fmt.Sprintf("OT%d", time.Now().UnixNano()/1e6) body.Status = "draft" body.CreateTime = time.Now() body.UpdateTime = time.Now() if err := h.svc.CreateOwnership( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) } func (h *DeliveryHandler) CreateOwnershipWithFiles(c *gin.Context) { transferDate := c.PostForm("transferDate") commodity := c.PostForm("commodity") warehouse := c.PostForm("warehouse") transferor := c.PostForm("transferor") transferee := c.PostForm("transferee") remark := c.PostForm("remark") // Parse dynamic BL items from JSON string field "blItems" var blItems []model.OwnershipTransferBL if blItemsJSON := c.PostForm("blItems"); blItemsJSON != "" { json.Unmarshal([]byte(blItemsJSON), &blItems) } body := model.OwnershipTransfer{ TransferNumber: fmt.Sprintf("OT%d", time.Now().UnixNano()/1e6), TransferDate: transferDate, Commodity: commodity, Warehouse: warehouse, Transferor: transferor, Transferee: transferee, Remark: remark, BLItems: blItems, Status: "draft", CreateTime: time.Now(), UpdateTime: time.Now(), } if err := h.svc.CreateOwnership( &body); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } form, _ := c.MultipartForm() files := form.File["files"] basePath := filepath.Join(".", "data", "ownership") if len(files) > 0 { _ = os.MkdirAll(basePath, os.ModePerm) for _, fh := range files { filename := fmt.Sprintf("%d_%s", time.Now().UnixNano(), fh.Filename) savePath := filepath.Join(basePath, filename) if err := utils.SaveUploadedFile(fh, savePath); err != nil { continue } fileRec := model.OwnershipTransferFile{TransferID: body.ID, Name: fh.Filename, URL: "ownership/" + filename} _ = h.svc.CreateOwnershipFiles( []model.OwnershipTransferFile{fileRec}) } } c.JSON(http.StatusOK, gin.H{"success": true, "data": body}) } func (h *DeliveryHandler) DeleteOwnership(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 32) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"success": false, "message": "invalid id"}) return } if err := h.svc.DeleteOwnership( uint(id)); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"success": false, "message": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true, "message": "deleted"}) }