package controllers import ( "fmt" "strings" "time" "server/models" "github.com/beego/beego/v2/client/orm" ) // BackendErpWarehouseController 仓库档案 type BackendErpWarehouseController struct { BackendErpBaseController } type erpWarehousePayload struct { WarehouseCode string `json:"warehouse_code"` WarehouseName string `json:"warehouse_name"` Address string `json:"address"` Manager string `json:"manager"` Phone string `json:"phone"` IsDefault int8 `json:"is_default"` Status *int8 `json:"status"` Remark string `json:"remark"` } // List GET /backend/erp/warehouse/list func (c *BackendErpWarehouseController) List() { cl, err := c.claims() if c.denied(err) { return } rows, code, msg := erpWarehouseListCore( erpNewActor(cl), c.GetString("keyword"), c.GetString("status"), strings.TrimSpace(c.GetString("only_enabled")) == "1", ) if msg != "" { c.fail(code, code, msg) return } c.ok(map[string]interface{}{"list": rows}) } // Detail GET /backend/erp/warehouse/:id func (c *BackendErpWarehouseController) Detail() { cl, err := c.claims() if c.denied(err) { return } id := c.pathID() var row models.ErpWarehouse if err := models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("id", id).Filter("tenant_id", c.tid(cl)). Filter("delete_time__isnull", true).One(&row); err != nil { c.fail(404, 404, "仓库未找到") return } c.ok(row) } // Create POST /backend/erp/warehouse func (c *BackendErpWarehouseController) Create() { cl, err := c.claims() if c.denied(err) { return } var p erpWarehousePayload if !c.bindJSON(&p) { return } if strings.TrimSpace(p.WarehouseName) == "" { c.fail(400, 400, "仓库名称不能为空") return } tid := c.tid(cl) name := strings.TrimSpace(p.WarehouseName) code := strings.TrimSpace(p.WarehouseCode) if erpWarehouseFieldTaken(tid, "warehouse_name", name, 0) { c.fail(400, 400, "仓库名称「"+name+"」已存在") return } if code != "" && erpWarehouseFieldTaken(tid, "warehouse_code", code, 0) { c.fail(400, 400, "仓库编号「"+code+"」已存在") return } now := time.Now() row := models.ErpWarehouse{ TenantID: tid, WarehouseCode: strings.TrimSpace(p.WarehouseCode), WarehouseName: strings.TrimSpace(p.WarehouseName), Address: p.Address, Manager: p.Manager, Phone: p.Phone, IsDefault: p.IsDefault, Status: erpStatusOr(p.Status, 1), Remark: p.Remark, CreateTime: now, UpdateTime: now, } if row.WarehouseCode == "" { row.WarehouseCode = fmt.Sprintf("CK%s", now.Format("01021504")) } id, err := models.Orm.Insert(&row) if err != nil { c.fail(500, 500, "创建失败: "+err.Error()) return } if row.IsDefault == 1 { erpClearDefaultWarehouse(tid, uint64(id)) } c.ok(map[string]interface{}{"id": id}) } // Update PUT /backend/erp/warehouse/:id func (c *BackendErpWarehouseController) Update() { cl, err := c.claims() if c.denied(err) { return } id := c.pathID() if id == 0 { c.fail(400, 400, "无效的ID") return } var p erpWarehousePayload if !c.bindJSON(&p) { return } if strings.TrimSpace(p.WarehouseName) == "" { c.fail(400, 400, "仓库名称不能为空") return } tid := c.tid(cl) var row models.ErpWarehouse if err := models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("id", id).Filter("tenant_id", tid). Filter("delete_time__isnull", true).One(&row); err != nil { c.fail(404, 404, "仓库未找到") return } name := strings.TrimSpace(p.WarehouseName) code := strings.TrimSpace(p.WarehouseCode) if erpWarehouseFieldTaken(tid, "warehouse_name", name, id) { c.fail(400, 400, "仓库名称「"+name+"」已存在") return } if code != "" && erpWarehouseFieldTaken(tid, "warehouse_code", code, id) { c.fail(400, 400, "仓库编号「"+code+"」已存在") return } if _, err := models.Orm.QueryTable(new(models.ErpWarehouse)).Filter("id", id). Update(orm.Params{ "warehouse_code": strings.TrimSpace(p.WarehouseCode), "warehouse_name": strings.TrimSpace(p.WarehouseName), "address": p.Address, "manager": p.Manager, "phone": p.Phone, "is_default": p.IsDefault, "status": erpStatusOr(p.Status, row.Status), "remark": p.Remark, "update_time": time.Now(), }); err != nil { c.fail(500, 500, "更新失败: "+err.Error()) return } if p.IsDefault == 1 { erpClearDefaultWarehouse(tid, id) } c.ok(nil) } // Delete DELETE /backend/erp/warehouse/:id func (c *BackendErpWarehouseController) Delete() { cl, err := c.claims() if c.denied(err) { return } id := c.pathID() if id == 0 { c.fail(400, 400, "无效的ID") return } tid := c.tid(cl) var row models.ErpWarehouse if err := models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("id", id).Filter("tenant_id", tid). Filter("delete_time__isnull", true).One(&row); err != nil { c.fail(404, 404, "仓库未找到") return } var qty float64 var stocks []models.ErpStock _, _ = models.Orm.QueryTable(new(models.ErpStock)). Filter("tenant_id", tid).Filter("warehouse_id", id).All(&stocks, "quantity") for _, s := range stocks { qty += s.Quantity } if qty > 0.000001 { c.fail(400, 400, "该仓库仍有库存,不能删除,请先调拨出库或停用") return } now := time.Now() if _, err := models.Orm.QueryTable(new(models.ErpWarehouse)).Filter("id", id). Update(orm.Params{"delete_time": now, "update_time": now}); err != nil { c.fail(500, 500, "删除失败: "+err.Error()) return } c.ok(nil) } // erpStatusOr status 用指针区分「未传」与「显式传 0(停用)」,未传时回落到调用方给的默认值。 func erpStatusOr(v *int8, def int8) int8 { if v == nil { return def } return *v } // erpWarehouseFieldTaken 仓库名称/编号在租户内是否已被占用(excludeID>0 时排除自身)。 // 名称唯一是硬约束:ERP 基础设置里的默认仓库按名称存储,重名会让设置指向错误的仓。 func erpWarehouseFieldTaken(tid, field, value string, excludeID uint64) bool { if strings.TrimSpace(value) == "" { return false } qs := models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("tenant_id", tid).Filter("delete_time__isnull", true). Filter(field, value) if excludeID > 0 { qs = qs.Exclude("id", excludeID) } n, _ := qs.Count() return n > 0 } // erpClearDefaultWarehouse 默认仓库全租户唯一 func erpClearDefaultWarehouse(tid string, keepID uint64) { _, _ = models.Orm.QueryTable(new(models.ErpWarehouse)). Filter("tenant_id", tid).Filter("is_default", 1).Exclude("id", keepID). Update(orm.Params{"is_default": 0, "update_time": time.Now()}) }