Files
zsp-project/backend/internal/handler/delivery_handler.go
2026-06-03 20:59:39 +08:00

531 lines
18 KiB
Go
Executable File

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"})
}