package handler import ( "net/http" "strconv" "time" "bj_power_wms/ent" "bj_power_wms/ent/zone" "bj_power_wms/internal/svc" ) // createZoneHandler 创建区域 func createZoneHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { ZoneCode string `json:"zoneCode"` ZoneName string `json:"zoneName"` Description string `json:"description"` ShelfNo string `json:"shelfNo"` LayerNo string `json:"layerNo"` PositionNo string `json:"positionNo"` } if err := parseJSON(r, &req); err != nil { fail(w, http.StatusBadRequest, "参数错误") return } if req.ZoneCode == "" || req.ZoneName == "" { fail(w, http.StatusBadRequest, "区域编码和区域名称必填") return } // 货位唯一性(U18):按四级组合(区域+货架+层+位置)判重,与 DB 组合唯一索引 ux_zones_location 一致。 // zone_code 不再单列唯一——同一区域下可登记多个货位(NULL/空串归一为""比较,与 COALESCE 索引一致)。 sameZone, err := ctx.EntClient.Zone.Query(). Where(zone.ZoneCodeEQ(req.ZoneCode)). All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } for _, sz := range sameZone { if sz.ShelfNo == req.ShelfNo && sz.LayerNo == req.LayerNo && sz.PositionNo == req.PositionNo { fail(w, http.StatusConflict, locDupMsg(req.ZoneCode, req.ShelfNo, req.LayerNo, req.PositionNo)) return } } z, err := ctx.EntClient.Zone.Create(). SetZoneCode(req.ZoneCode). SetZoneName(req.ZoneName). SetNillableDescription(strPtr(req.Description)). SetNillableShelfNo(strPtr(req.ShelfNo)). SetNillableLayerNo(strPtr(req.LayerNo)). SetNillablePositionNo(strPtr(req.PositionNo)). SetStatus("启用"). Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ctx.EventLog.Write(ctx0(), "zone.create", r.Header.Get("X-Username"), "zone", z.ZoneCode, "新建区域 "+req.ZoneName, nil) ok(w, z) } } // updateZoneHandler 编辑区域 func updateZoneHandler(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req struct { ID int `json:"id"` ZoneCode string `json:"zoneCode"` ZoneName string `json:"zoneName"` Description string `json:"description"` ShelfNo string `json:"shelfNo"` LayerNo string `json:"layerNo"` PositionNo string `json:"positionNo"` } if err := parseJSON(r, &req); err != nil || req.ID <= 0 { fail(w, http.StatusBadRequest, "参数错误") return } if req.ZoneName == "" { fail(w, http.StatusBadRequest, "区域名称必填") return } // 货位唯一性(排除自身,U18):改后四级组合不得与其他货位重复(DB 组合唯一索引兜底)。 if req.ZoneCode != "" { others, e := ctx.EntClient.Zone.Query(). Where(zone.ZoneCodeEQ(req.ZoneCode), zone.IDNEQ(req.ID)).All(ctx0()) if e != nil { fail(w, http.StatusInternalServerError, e.Error()) return } for _, oz := range others { if oz.ShelfNo == req.ShelfNo && oz.LayerNo == req.LayerNo && oz.PositionNo == req.PositionNo { fail(w, http.StatusConflict, locDupMsg(req.ZoneCode, req.ShelfNo, req.LayerNo, req.PositionNo)) return } } } upd := ctx.EntClient.Zone.UpdateOneID(req.ID).SetZoneName(req.ZoneName) if req.ZoneCode != "" { upd.SetZoneCode(req.ZoneCode) } upd.SetNillableDescription(strPtr(req.Description)) upd.SetNillableShelfNo(strPtr(req.ShelfNo)) upd.SetNillableLayerNo(strPtr(req.LayerNo)) upd.SetNillablePositionNo(strPtr(req.PositionNo)) z, err := upd.Save(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, "更新区域失败: "+err.Error()) return } ctx.EventLog.Write(ctx0(), "zone.update", r.Header.Get("X-Username"), "zone", req.ZoneCode, "编辑区域 #"+strconv.Itoa(req.ID)+" "+req.ZoneName, nil) ok(w, z) } } // listZonesHandler 区域列表(分页 + 关键字) func listZonesHandler(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) zoneCode := r.URL.Query().Get("zoneCode") zoneName := r.URL.Query().Get("zoneName") status := r.URL.Query().Get("status") startDate := r.URL.Query().Get("startDate") endDate := r.URL.Query().Get("endDate") q := ctx.EntClient.Zone.Query() if zoneCode != "" { // 区域编码搜索大小写不敏感 q = q.Where(zone.ZoneCodeContainsFold(zoneCode)) } if zoneName != "" { q = q.Where(zone.ZoneNameContainsFold(zoneName)) } if status != "" { q = q.Where(zone.StatusEQ(status)) } if startDate != "" { if t, err := time.ParseInLocation("2006-01-02", startDate, time.Local); err == nil { q = q.Where(zone.CreatedAtGTE(t.Unix())) } } if endDate != "" { if t, err := time.ParseInLocation("2006-01-02", endDate, time.Local); err == nil { q = q.Where(zone.CreatedAtLTE(t.Add(24 * time.Hour).Unix())) } } total, err := q.Count(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } list, err := q.Order(ent.Asc("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, }) } } // listZonesForPicker 区域下拉列表(不分页,仅启用) func listZonesForPicker(ctx *svc.ServiceContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { list, err := ctx.EntClient.Zone.Query(). Where(zone.StatusEQ("启用")). Order(ent.Asc("id")). All(ctx0()) if err != nil { fail(w, http.StatusInternalServerError, err.Error()) return } ok(w, list) } } // locDupMsg 货位重复登记的统一提示(复用 locLabel 拼四级货位串,U18)。 func locDupMsg(zoneCode, shelfNo, layerNo, positionNo string) string { return "该货位已存在:" + locLabel(zoneCode, shelfNo, layerNo, positionNo) }