Files
bj_power/bj_power_wms/internal/handler/misc.go
T
SunYF 47573e44b0 feat&fix: 完善半成品/序列号闭环流转逻辑
1. 调整物料编码字段为可空,优化注释说明
2. 修复Vite构建自动清空产物目录冲突问题
3. 优化前端提示文案与表单占位符
4. 为各前端项目添加内联SVG图标
5. 重构MES半成品流转逻辑,不再静默吞掉WMS错误并记录日志
6. 生成并添加空的WMS客户端静态文件
7. 完善Ent ORM模型的空值支持与查询谓词
8. 优化半成品入库接口,支持doneProcessCodes数组格式,新增SN反查物料编码逻辑
9. 更新帮助文档与操作手册
2026-09-01 14:30:26 +08:00

361 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"encoding/json"
"net/http"
"strconv"
"strings"
"bj_power_wms/ent"
"bj_power_wms/ent/ordermaterialledger"
"bj_power_wms/ent/packagebox"
"bj_power_wms/ent/semifinished"
"bj_power_wms/ent/serialnumber"
"bj_power_wms/internal/svc"
)
// 半成品 / 包装 / 台账 相关(盘点见 stocktake.go
// semiInboundHandler 半成品入库(携带已完成工序)
// 与 MES 闭环:MES 工位报工暂存退库时调用本接口,把半成品 SN 入 WMS 库。
// 为保持两系统解耦,materialCode 不强制要求——优先用请求传入,
// 其次按 SN 反查 WMS 序列号台账,都没有则留空(SN 本身即追溯锚点)。
func semiInboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
MaterialCode string `json:"materialCode"`
DoneProcessCodes []int `json:"doneProcessCodes"`
CompletedProcess string `json:"completedProcess"`
ZoneCode string `json:"zoneCode"`
OrderNo string `json:"orderNo"`
Operator string `json:"operator"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Sn == "" {
fail(w, http.StatusBadRequest, "sn 必填")
return
}
exists, err := ctx.EntClient.SemiFinished.Query().
Where(semifinished.SnEQ(req.Sn)).
Exist(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
if exists {
fail(w, http.StatusConflict, "半成品已存在: "+req.Sn)
return
}
// 已完成工序:MES 传 doneProcessCodes([]int) 优先,否则用 completedProcess 字符串
completed := req.CompletedProcess
if len(req.DoneProcessCodes) > 0 {
parts := make([]string, 0, len(req.DoneProcessCodes))
for _, c := range req.DoneProcessCodes {
parts = append(parts, strconv.Itoa(c))
}
completed = strings.Join(parts, ",")
}
// 物料编码:优先请求传入,其次按 SN 反查序列号台账
materialCode := req.MaterialCode
if materialCode == "" {
if snRec, e := ctx.EntClient.SerialNumber.Query().
Where(serialnumber.SnCodeEQ(req.Sn)).Only(ctx0()); e == nil {
materialCode = snRec.MaterialCode
}
}
rec, err := ctx.EntClient.SemiFinished.Create().
SetSn(req.Sn).
SetNillableMaterialCode(strPtr(materialCode)).
SetNillableCompletedProcess(strPtr(completed)).
SetNillableZoneCode(strPtr(req.ZoneCode)).
SetStatus("在库").
SetNillableOrderNo(strPtr(req.OrderNo)).
Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
// 同时记入 SN 台账(半成品也是精密件,便于按 SN 追溯)
ctx.EntClient.SerialNumber.Create().
SetSnCode(req.Sn).
SetNillableMaterialCode(strPtr(materialCode)).
SetStatus("在库").
SetNillableCurrentZone(strPtr(req.ZoneCode)).
SaveX(ctx0())
ok(w, rec)
}
}
// semiOutboundHandler 半成品出库(重上线)
func semiOutboundHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Sn string `json:"sn"`
ZoneCode string `json:"zoneCode"`
Operator string `json:"operator"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Sn == "" {
fail(w, http.StatusBadRequest, "sn 必填")
return
}
rec, err := ctx.EntClient.SemiFinished.Query().
Where(semifinished.SnEQ(req.Sn)).
Only(ctx0())
if err != nil {
fail(w, http.StatusNotFound, "半成品不存在: "+req.Sn)
return
}
if rec.Status != "在库" {
fail(w, http.StatusConflict, "半成品不在库: "+rec.Status)
return
}
ctx.EntClient.SemiFinished.UpdateOneID(rec.ID).
SetStatus("出库").
ExecX(ctx0())
ctx.EntClient.SerialNumber.Update().
Where(serialnumber.SnCodeEQ(req.Sn)).
SetStatus("出库").
ExecX(ctx0())
ok(w, map[string]any{"sn": req.Sn, "status": "出库"})
}
}
// querySemiHandler 半成品查询
func querySemiHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
page := atoi(r.URL.Query().Get("page"), 1)
pageSize := atoi(r.URL.Query().Get("pageSize"), 20)
materialCode := r.URL.Query().Get("materialCode")
status := r.URL.Query().Get("status")
q := ctx.EntClient.SemiFinished.Query()
if materialCode != "" {
q = q.Where(semifinished.MaterialCodeEQ(materialCode))
}
if status != "" {
q = q.Where(semifinished.StatusEQ(status))
}
total, err := q.Count(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
list, err := q.Order(ent.Desc("id")).
Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, map[string]any{
"total": total,
"list": list,
"page": page,
"pageSize": pageSize,
})
}
}
// packageBindHandler 装箱绑定(箱号 ↔ SN),支持合同号
func packageBindHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
BoxNo string `json:"boxNo"`
SnList []string `json:"snList"`
MaterialCode string `json:"materialCode"`
ContractNo string `json:"contractNo"`
Operator string `json:"operator"`
Remark string `json:"remark"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.BoxNo == "" || len(req.SnList) == 0 {
fail(w, http.StatusBadRequest, "箱号和 SN 清单必填")
return
}
exists, err := ctx.EntClient.PackageBox.Query().
Where(packagebox.BoxNoEQ(req.BoxNo)).
Exist(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
if exists {
fail(w, http.StatusConflict, "箱号已存在: "+req.BoxNo)
return
}
// SN 列表序列化
snJSON := toJSON(req.SnList)
pkg, err := ctx.EntClient.PackageBox.Create().
SetBoxNo(req.BoxNo).
SetSnList(snJSON).
SetNillableMaterialCode(strPtr(req.MaterialCode)).
SetQuantity(len(req.SnList)).
SetNillableContractNo(strPtr(req.ContractNo)).
SetNillableOperator(strPtr(req.Operator)).
SetNillableRemark(strPtr(req.Remark)).
Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, pkg)
}
}
// packageUpdateHandler 编辑装箱(重新装箱 SN / 修改合同号等)
func packageUpdateHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
ID int `json:"id"`
SnList []string `json:"snList"`
MaterialCode string `json:"materialCode"`
ContractNo string `json:"contractNo"`
Operator string `json:"operator"`
Remark string `json:"remark"`
}
if err := parseJSON(r, &req); err != nil || req.ID <= 0 {
fail(w, http.StatusBadRequest, "参数错误")
return
}
upd := ctx.EntClient.PackageBox.UpdateOneID(req.ID)
if len(req.SnList) > 0 {
upd.SetSnList(toJSON(req.SnList)).SetQuantity(len(req.SnList))
}
upd.SetNillableMaterialCode(strPtr(req.MaterialCode)).
SetNillableContractNo(strPtr(req.ContractNo)).
SetNillableOperator(strPtr(req.Operator)).
SetNillableRemark(strPtr(req.Remark))
pkg, err := upd.Save(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, "更新装箱失败: "+err.Error())
return
}
ok(w, pkg)
}
}
// toJSON 序列化任意值为 JSON 字符串
func toJSON(v any) string {
b, err := json.Marshal(v)
if err != nil {
return "[]"
}
return string(b)
}
// queryPackageHandler 装箱查询(多条件:箱号/合同号/操作人/物料/时间范围)
func queryPackageHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
boxNo := r.URL.Query().Get("boxNo")
contractNo := r.URL.Query().Get("contractNo")
operator := r.URL.Query().Get("operator")
materialCode := r.URL.Query().Get("materialCode")
startAt := r.URL.Query().Get("startAt")
endAt := r.URL.Query().Get("endAt")
page := atoi(r.URL.Query().Get("page"), 1)
pageSize := atoi(r.URL.Query().Get("pageSize"), 20)
q := ctx.EntClient.PackageBox.Query()
if boxNo != "" {
q = q.Where(packagebox.BoxNoContains(boxNo))
}
if contractNo != "" {
q = q.Where(packagebox.ContractNoContains(contractNo))
}
if operator != "" {
q = q.Where(packagebox.OperatorContains(operator))
}
if materialCode != "" {
q = q.Where(packagebox.MaterialCodeEQ(materialCode))
}
if startAt != "" {
start := atoi64(startAt, 0)
if start > 0 {
q = q.Where(packagebox.CreatedAtGTE(start))
}
}
if endAt != "" {
end := atoi64(endAt, 0)
if end > 0 {
q = q.Where(packagebox.CreatedAtLTE(end))
}
}
total, err := q.Count(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
list, err := q.Order(ent.Desc("id")).
Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, map[string]any{
"total": total,
"list": list,
"page": page,
"pageSize": pageSize,
})
}
}
// ledger 相关:queryLedgerHandler 工单物料台账查询
func queryLedgerHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
orderNo := r.URL.Query().Get("orderNo")
materialCode := r.URL.Query().Get("materialCode")
page := atoi(r.URL.Query().Get("page"), 1)
pageSize := atoi(r.URL.Query().Get("pageSize"), 20)
q := ctx.EntClient.OrderMaterialLedger.Query()
if orderNo != "" {
q = q.Where(ordermaterialledger.OrderNoEQ(orderNo))
}
if materialCode != "" {
q = q.Where(ordermaterialledger.MaterialCodeEQ(materialCode))
}
total, err := q.Count(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
list, err := q.Order(ent.Desc("id")).
Offset((page - 1) * pageSize).Limit(pageSize).All(ctx0())
if err != nil {
fail(w, http.StatusInternalServerError, err.Error())
return
}
ok(w, map[string]any{
"total": total,
"list": list,
"page": page,
"pageSize": pageSize,
})
}
}