优化项目管理
This commit is contained in:
@@ -183,6 +183,27 @@ func attachProjectContractPeriod(tenantID string, list []models.TenantCrmProject
|
||||
}
|
||||
}
|
||||
|
||||
// crmProjectNameDuplicated 项目重名校验:同租户 + 同名 + 同一客户(服务主体)才算重复。
|
||||
// 不同客户的同名项目可正常创建(如两家公司都有「官网建设」项目);
|
||||
// 未关联客户的项目按「客户为空」分组校验(与同样未关联客户的同名项目视为重复)。
|
||||
func crmProjectNameDuplicated(tenantID, name string, customerID *uint64) bool {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" || strings.TrimSpace(tenantID) == "" || models.Orm == nil {
|
||||
return false
|
||||
}
|
||||
cond := orm.NewCondition().
|
||||
And("tenant_id", tenantID).
|
||||
And("delete_time__isnull", true).
|
||||
And("project_name", name)
|
||||
if customerID != nil && *customerID > 0 {
|
||||
cond = cond.And("customer_id", *customerID)
|
||||
} else {
|
||||
cond = cond.AndCond(orm.NewCondition().Or("customer_id__isnull", true).Or("customer_id", 0))
|
||||
}
|
||||
cnt, err := models.Orm.QueryTable(new(models.TenantCrmProject)).SetCond(cond).Count()
|
||||
return err == nil && cnt > 0
|
||||
}
|
||||
|
||||
// List GET /backend/crm/project/list
|
||||
func (c *BackendCrmProjectController) List() {
|
||||
claims, err := pipelineClaims(&c.Controller)
|
||||
@@ -229,13 +250,6 @@ func (c *BackendCrmProjectController) List() {
|
||||
projectQuery := func(cond *orm.Condition) orm.QuerySeter {
|
||||
return models.Orm.QueryTable(new(models.TenantCrmProject)).SetCond(cond)
|
||||
}
|
||||
// 未完成组:除「已完成」外的全部状态(含历史数据中的空状态),沉底排列时取列表前段
|
||||
unfinishedCond := func() *orm.Condition {
|
||||
return buildCond().AndCond(orm.NewCondition().
|
||||
Or("status__ne", crmProjectStatusDone).
|
||||
Or("status__isnull", true))
|
||||
}
|
||||
|
||||
total, _ := projectQuery(buildCond()).Count()
|
||||
var list []models.TenantCrmProject
|
||||
if total > 0 {
|
||||
@@ -247,32 +261,47 @@ func (c *BackendCrmProjectController) List() {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 列表排列:未完成项目在前、「已完成」项目沉底,两组内部仍按新→旧。
|
||||
// 先按两组数量把当前页窗口换算成各组区间,再分别取数按序拼接,保证翻页后整体顺序一致。
|
||||
unfinished, _ := projectQuery(unfinishedCond()).Count()
|
||||
unfinishedTotal := int(unfinished)
|
||||
finishedTotal := int(total) - unfinishedTotal
|
||||
// 列表排列:按状态分组展示 —— 未开始(1) → 异常(5) → 进行中(2) → 已暂停(4) → 已完成(3),
|
||||
// 其余状态(NULL / 历史异常值)沉底;组内仍按新→旧。
|
||||
// 先统计各组数量,把当前页窗口换算成各组区间,再分别取数按序拼接,保证翻页后整体顺序一致。
|
||||
groupConds := []*orm.Condition{
|
||||
orm.NewCondition().And("status", 1), // 未开始
|
||||
orm.NewCondition().And("status", 5), // 异常
|
||||
orm.NewCondition().And("status", 2), // 进行中
|
||||
orm.NewCondition().And("status", 4), // 已暂停
|
||||
orm.NewCondition().And("status", 3), // 已完成
|
||||
orm.NewCondition().AndNot("status__in", []int8{1, 2, 3, 4, 5}).Or("status__isnull", true),
|
||||
}
|
||||
end := offset + pageSize
|
||||
if end > int(total) {
|
||||
end = int(total)
|
||||
}
|
||||
aFrom, aTo := min(offset, unfinishedTotal), min(end, unfinishedTotal)
|
||||
bFrom := min(max(offset-unfinishedTotal, 0), finishedTotal)
|
||||
bTo := min(max(end-unfinishedTotal, 0), finishedTotal)
|
||||
if aTo > aFrom {
|
||||
if _, err := projectQuery(unfinishedCond()).OrderBy("-id").Offset(aFrom).Limit(aTo - aFrom).All(&list); err != nil {
|
||||
cursor := 0
|
||||
for _, gc := range groupConds {
|
||||
if cursor >= end {
|
||||
break
|
||||
}
|
||||
// 注意:beego 的 AndCond 会修改接收者,这里用新条件对象包装,避免循环内条件累积
|
||||
groupCond := orm.NewCondition().AndCond(buildCond()).AndCond(gc)
|
||||
cnt, _ := projectQuery(groupCond).Count()
|
||||
groupTotal := int(cnt)
|
||||
if groupTotal == 0 {
|
||||
continue
|
||||
}
|
||||
groupStart := cursor
|
||||
groupEnd := cursor + groupTotal
|
||||
cursor = groupEnd
|
||||
// 当前页与本组区间的交集
|
||||
from, to := max(offset, groupStart), min(end, groupEnd)
|
||||
if to <= from {
|
||||
continue
|
||||
}
|
||||
var part []models.TenantCrmProject
|
||||
if _, err := projectQuery(groupCond).OrderBy("-id").Offset(from - groupStart).Limit(to - from).All(&part); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
if bTo > bFrom {
|
||||
var finished []models.TenantCrmProject
|
||||
if _, err := projectQuery(buildCond().And("status", crmProjectStatusDone)).
|
||||
OrderBy("-id").Offset(bFrom).Limit(bTo - bFrom).All(&finished); err != nil {
|
||||
pipelineErr(&c.Controller, 500, 500, "查询失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
list = append(list, finished...)
|
||||
list = append(list, part...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -394,9 +423,10 @@ func (c *BackendCrmProjectController) Create() {
|
||||
CreateTime: now,
|
||||
UpdateTime: now,
|
||||
}
|
||||
// 唯一性校验:同租户内不允许重复创建同名项目(重复数据请联系管理员转移跟进)
|
||||
if crmNameDuplicated(new(models.TenantCrmProject), tenantID, "project_name", proj.ProjectName) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该项目名称已存在(可能由其他同事创建),如需继续跟进请联系管理员转移")
|
||||
// 唯一性校验:同一客户(服务主体)下的同名项目不允许重复创建(重复数据请联系管理员转移跟进);
|
||||
// 客户不同则允许同名创建(不同主体可各自立项,如两家客户都做「官网建设」)
|
||||
if crmProjectNameDuplicated(tenantID, proj.ProjectName, proj.CustomerID) {
|
||||
pipelineErr(&c.Controller, 409, 409, "该客户下已存在同名项目(可能由其他同事创建),如需继续跟进请联系管理员转移")
|
||||
return
|
||||
}
|
||||
id, err := models.Orm.Insert(&proj)
|
||||
|
||||
Reference in New Issue
Block a user