Files
2026-10-19 01:13:17 +08:00

235 lines
6.5 KiB
Go

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