474 lines
14 KiB
Go
474 lines
14 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// BackendCrmUnitController CRM 基本设置-计量单位(产品等单位下拉取自该表)
|
|
type BackendCrmUnitController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
type crmUnitPayload struct {
|
|
Name string `json:"name"`
|
|
Code string `json:"code"`
|
|
Sort int `json:"sort"`
|
|
Status int8 `json:"status"`
|
|
Remark string `json:"remark"`
|
|
}
|
|
|
|
// crmUnitItem 列表项:单位信息 + 引用数(引用该单位的产品数量)
|
|
type crmUnitItem struct {
|
|
models.TenantCrmUnit
|
|
UsedCount int64 `json:"used_count"`
|
|
}
|
|
|
|
// crmUnitUsageRow 单位引用统计(按产品表 unit 分组计数)
|
|
type crmUnitUsageRow struct {
|
|
Unit string `orm:"column(unit)"`
|
|
Cnt int64 `orm:"column(cnt)"`
|
|
}
|
|
|
|
// crmDefaultUnits 默认单位:租户首次使用单位设置时自动写入(顺序即排序)
|
|
var crmDefaultUnits = []struct {
|
|
Name string
|
|
Code string
|
|
}{
|
|
{"套", "SET"},
|
|
{"台", "TAI"},
|
|
{"个", "PCS"},
|
|
{"件", "PIECE"},
|
|
{"批", "BATCH"},
|
|
{"次", "TIME"},
|
|
{"项", "ITEM"},
|
|
{"人月", "PM"},
|
|
{"人天", "PD"},
|
|
{"年", "YEAR"},
|
|
{"月", "MONTH"},
|
|
{"米", "M"},
|
|
{"平方米", "SQM"},
|
|
{"吨", "TON"},
|
|
{"千克", "KG"},
|
|
{"箱", "BOX"},
|
|
}
|
|
|
|
// crmEnsureDefaultUnits 租户首次使用(从未有过任何单位记录,含已软删除的)时写入默认单位。
|
|
// 【租户级初始化】新租户首次使用时补建默认业务数据(非表结构/全局预置数据;结构变更须走 docs/sql/ SQL 文件)。
|
|
// 用户手动清空后不再自动补,避免越删越有;需要恢复时用「恢复默认单位」接口。
|
|
// 已有记录时顺带修复存量默认单位的 is_default 标记(历史数据升级)。
|
|
func crmEnsureDefaultUnits(tenantID string) {
|
|
if models.Orm == nil || strings.TrimSpace(tenantID) == "" {
|
|
return
|
|
}
|
|
cnt, err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).Count()
|
|
if err != nil {
|
|
return
|
|
}
|
|
if cnt == 0 {
|
|
crmInsertDefaultUnits(tenantID, nil)
|
|
return
|
|
}
|
|
crmMarkDefaultUnits(tenantID)
|
|
}
|
|
|
|
// crmMarkDefaultUnits 将默认清单中已存在的存量单位标记为 is_default=1(历史数据升级自修复,
|
|
// 避免早期写入的默认单位因未打标而被误编辑/删除)。
|
|
func crmMarkDefaultUnits(tenantID string) {
|
|
names := make([]string, 0, len(crmDefaultUnits))
|
|
for _, u := range crmDefaultUnits {
|
|
names = append(names, u.Name)
|
|
}
|
|
if len(names) == 0 {
|
|
return
|
|
}
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).
|
|
Filter("name__in", names).
|
|
Update(orm.Params{"is_default": 1})
|
|
}
|
|
|
|
// crmInsertDefaultUnits 写入默认单位,跳过已存在的名称(existing 为已存在单位名集合)。
|
|
// 返回新增数量。
|
|
func crmInsertDefaultUnits(tenantID string, existing map[string]bool) int {
|
|
now := time.Now()
|
|
added := 0
|
|
for i, u := range crmDefaultUnits {
|
|
if existing != nil && existing[u.Name] {
|
|
continue
|
|
}
|
|
row := models.TenantCrmUnit{
|
|
TenantID: tenantID,
|
|
Name: u.Name,
|
|
Code: u.Code,
|
|
Sort: i + 1,
|
|
Status: 1,
|
|
IsDefault: 1, // 系统默认单位:不可编辑、不可删除
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
if _, err := models.Orm.Insert(&row); err == nil {
|
|
added++
|
|
}
|
|
}
|
|
return added
|
|
}
|
|
|
|
// crmInitDefaultUnits 补充缺失的默认单位:
|
|
// - 已存在(启用/停用)的单位不动;
|
|
// - 已软删除的同名单位直接恢复(清 delete_time)并置为启用。
|
|
//
|
|
// 返回本次新增/恢复的数量。
|
|
func crmInitDefaultUnits(tenantID string) int {
|
|
var rows []models.TenantCrmUnit
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).All(&rows)
|
|
|
|
existing := make(map[string]bool, len(rows))
|
|
deleted := make(map[string]uint64, len(rows))
|
|
for _, r := range rows {
|
|
if r.DeleteTime != nil {
|
|
deleted[r.Name] = r.ID
|
|
continue
|
|
}
|
|
existing[r.Name] = true
|
|
}
|
|
|
|
now := time.Now()
|
|
table := new(models.TenantCrmUnit).TableName()
|
|
added := 0
|
|
for i, u := range crmDefaultUnits {
|
|
if existing[u.Name] {
|
|
continue
|
|
}
|
|
if id, ok := deleted[u.Name]; ok {
|
|
if _, err := models.Orm.Raw("UPDATE "+table+
|
|
" SET delete_time = NULL, status = 1, is_default = 1, sort = ?, update_time = ? WHERE id = ?",
|
|
i+1, now, id).Exec(); err == nil {
|
|
existing[u.Name] = true
|
|
added++
|
|
}
|
|
continue
|
|
}
|
|
row := models.TenantCrmUnit{
|
|
TenantID: tenantID,
|
|
Name: u.Name,
|
|
Code: u.Code,
|
|
Sort: i + 1,
|
|
Status: 1,
|
|
IsDefault: 1, // 系统默认单位:不可编辑、不可删除
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
if _, err := models.Orm.Insert(&row); err == nil {
|
|
existing[u.Name] = true
|
|
added++
|
|
}
|
|
}
|
|
return added
|
|
}
|
|
|
|
// crmUnitUsage 统计各单位的引用数(引用该单位且未删除的产品数量)。
|
|
func crmUnitUsage(tenantID string) map[string]int64 {
|
|
usage := make(map[string]int64, len(crmDefaultUnits))
|
|
if models.Orm == nil {
|
|
return usage
|
|
}
|
|
var rows []crmUnitUsageRow
|
|
_, err := models.Orm.Raw("SELECT unit, COUNT(*) AS cnt FROM "+
|
|
new(models.TenantCrmProduct).TableName()+
|
|
" WHERE tenant_id = ? AND delete_time IS NULL AND unit <> '' GROUP BY unit",
|
|
tenantID).QueryRows(&rows)
|
|
if err != nil {
|
|
return usage
|
|
}
|
|
for _, r := range rows {
|
|
usage[strings.TrimSpace(r.Unit)] = r.Cnt
|
|
}
|
|
return usage
|
|
}
|
|
|
|
// List GET /backend/crm/unit/list
|
|
func (c *BackendCrmUnitController) List() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 200 {
|
|
pageSize = 20
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
status := strings.TrimSpace(c.GetString("status"))
|
|
|
|
tenantID := pipelineTenantID(claims)
|
|
crmEnsureDefaultUnits(tenantID)
|
|
|
|
cond := orm.NewCondition().And("tenant_id", tenantID).And("delete_time__isnull", true)
|
|
if keyword != "" {
|
|
kw := orm.NewCondition().
|
|
Or("name__contains", keyword).
|
|
Or("code__contains", keyword)
|
|
cond = cond.AndCond(kw)
|
|
}
|
|
if status != "" {
|
|
cond = cond.And("status", status)
|
|
}
|
|
qs := models.Orm.QueryTable(new(models.TenantCrmUnit)).SetCond(cond)
|
|
|
|
total, _ := qs.Count()
|
|
items := make([]crmUnitItem, 0, 8)
|
|
if total > 0 {
|
|
usage := crmUnitUsage(tenantID)
|
|
var list []models.TenantCrmUnit
|
|
_, _ = qs.OrderBy("sort", "id").Offset((page - 1) * pageSize).Limit(pageSize).All(&list)
|
|
for _, row := range list {
|
|
items = append(items, crmUnitItem{TenantCrmUnit: row, UsedCount: usage[row.Name]})
|
|
}
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{
|
|
"list": items, "total": total, "page": page, "pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// All GET /backend/crm/unit/all —— 下拉数据源:仅返回启用中的单位
|
|
func (c *BackendCrmUnitController) All() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
crmEnsureDefaultUnits(tenantID)
|
|
|
|
var list []models.TenantCrmUnit
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).
|
|
Filter("status", 1).
|
|
OrderBy("sort", "id").Limit(200).All(&list)
|
|
pipelineOk(&c.Controller, map[string]interface{}{"list": list})
|
|
}
|
|
|
|
// Detail GET /backend/crm/unit/:id
|
|
func (c *BackendCrmUnitController) Detail() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
var row models.TenantCrmUnit
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("id", id).Filter("tenant_id", pipelineTenantID(claims)).
|
|
Filter("delete_time__isnull", true).One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "单位未找到")
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, row)
|
|
}
|
|
|
|
// Create POST /backend/crm/unit
|
|
func (c *BackendCrmUnitController) Create() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p crmUnitPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
name := strings.TrimSpace(p.Name)
|
|
if name == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称不能为空")
|
|
return
|
|
}
|
|
status := p.Status
|
|
if status != 0 && status != 1 {
|
|
status = 1
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
now := time.Now()
|
|
|
|
// 同租户下单位名称唯一:命中已存在的记录时,若已软删除则「复活」复用,避免唯一索引冲突
|
|
var old models.TenantCrmUnit
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).Filter("name", name).One(&old); err == nil {
|
|
if old.DeleteTime == nil {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称已存在")
|
|
return
|
|
}
|
|
if _, err := models.Orm.Raw("UPDATE "+new(models.TenantCrmUnit).TableName()+
|
|
" SET code = ?, sort = ?, status = ?, remark = ?, update_time = ?, delete_time = NULL WHERE id = ?",
|
|
strings.TrimSpace(p.Code), p.Sort, status, strings.TrimSpace(p.Remark), now, old.ID).Exec(); err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": old.ID})
|
|
return
|
|
}
|
|
|
|
row := models.TenantCrmUnit{
|
|
TenantID: tenantID,
|
|
Name: name,
|
|
Code: strings.TrimSpace(p.Code),
|
|
Sort: p.Sort,
|
|
Status: status,
|
|
Remark: strings.TrimSpace(p.Remark),
|
|
CreateUserID: pipelineUID(claims),
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "uk_tenant_name") {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称已存在")
|
|
return
|
|
}
|
|
pipelineErr(&c.Controller, 500, 500, "创建失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Update PUT /backend/crm/unit/:id
|
|
func (c *BackendCrmUnitController) Update() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
|
var p crmUnitPayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
pipelineErr(&c.Controller, 400, 400, "参数错误")
|
|
return
|
|
}
|
|
name := strings.TrimSpace(p.Name)
|
|
if name == "" {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称不能为空")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var row models.TenantCrmUnit
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "单位未找到")
|
|
return
|
|
}
|
|
// 系统默认单位只读:不可编辑
|
|
if row.IsDefault == 1 {
|
|
pipelineErr(&c.Controller, 400, 400, "系统默认单位不可编辑")
|
|
return
|
|
}
|
|
status := p.Status
|
|
if status != 0 && status != 1 {
|
|
status = row.Status
|
|
}
|
|
// 名称重复校验:与他人在用的单位冲突直接拒绝;命中的是历史软删除记录则彻底清理后继续
|
|
var dup models.TenantCrmUnit
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("tenant_id", tenantID).Filter("name", name).Exclude("id", id).One(&dup); err == nil {
|
|
if dup.DeleteTime == nil {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称已存在")
|
|
return
|
|
}
|
|
_, _ = models.Orm.QueryTable(new(models.TenantCrmUnit)).Filter("id", dup.ID).Delete()
|
|
}
|
|
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Update(orm.Params{
|
|
"name": name,
|
|
"code": strings.TrimSpace(p.Code),
|
|
"sort": p.Sort,
|
|
"status": status,
|
|
"remark": strings.TrimSpace(p.Remark),
|
|
"update_time": now,
|
|
})
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "uk_tenant_name") {
|
|
pipelineErr(&c.Controller, 400, 400, "单位名称已存在")
|
|
return
|
|
}
|
|
pipelineErr(&c.Controller, 500, 500, "更新失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, map[string]interface{}{"id": id})
|
|
}
|
|
|
|
// Delete DELETE /backend/crm/unit/:id
|
|
func (c *BackendCrmUnitController) Delete() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
id, _ := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
|
|
if id == 0 {
|
|
pipelineErr(&c.Controller, 400, 400, "无效的ID")
|
|
return
|
|
}
|
|
tenantID := pipelineTenantID(claims)
|
|
var row models.TenantCrmUnit
|
|
if err := models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Filter("delete_time__isnull", true).One(&row); err != nil {
|
|
pipelineErr(&c.Controller, 404, 404, "单位未找到")
|
|
return
|
|
}
|
|
// 系统默认单位只读:不可删除
|
|
if row.IsDefault == 1 {
|
|
pipelineErr(&c.Controller, 400, 400, "系统默认单位不可删除")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
_, err = models.Orm.QueryTable(new(models.TenantCrmUnit)).
|
|
Filter("id", id).Filter("tenant_id", tenantID).
|
|
Update(orm.Params{"delete_time": now, "update_time": now})
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 500, 500, "删除失败: "+err.Error())
|
|
return
|
|
}
|
|
pipelineOk(&c.Controller, nil)
|
|
}
|
|
|
|
// InitDefault POST /backend/crm/unit/init-default —— 补齐缺失的系统默认单位
|
|
func (c *BackendCrmUnitController) InitDefault() {
|
|
claims, err := pipelineClaims(&c.Controller)
|
|
if err != nil {
|
|
pipelineErr(&c.Controller, 401, 401, err.Error())
|
|
return
|
|
}
|
|
added := crmInitDefaultUnits(pipelineTenantID(claims))
|
|
pipelineOk(&c.Controller, map[string]interface{}{"added": added})
|
|
}
|