修复bug

This commit is contained in:
hero920103 committed 2026-11-03 17:48:07 +08:00
1 parent fc8c3ee3ad
commit 68617335f0
9 files changed
+645 -161

No files matched your search

+58 -24
View File
@@ -51,6 +51,7 @@ type projectPayload struct {
// 3=上中游项目(上游 A 公司 + 中游 B 公司,下游固定为本公司)
ProjectType int8 `json:"project_type"`
CustomerID uint64 `json:"customer_id"`
CustomerRefType int8 `json:"customer_ref_type"` // 上游主体来源:1客户 2供应商(缺省按客户库)
CustomerName string `json:"customer_name"`
SupplierID uint64 `json:"supplier_id"`
SupplierRefType int8 `json:"supplier_ref_type"` // 对方主体来源:1客户 2供应商(下游 / 中游)
@@ -68,7 +69,7 @@ type projectPayload struct {
// Reason 异常原因:状态改为「异常(5)」时必填,会写入操作日志便于追溯
Reason string `json:"reason"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
EndDate string `json:"end_date"`
// 上下游对接人:仅姓名 + 电话;Contact* 为旧字段,未传新字段时回退使用
UpstreamContact string `json:"upstream_contact"`
UpstreamPhone string `json:"upstream_phone"`
@@ -107,6 +108,40 @@ func normalizeProjectFields(p *projectPayload) (pt int8, upstream, downstream fl
return
}
// resolveProjectCustomer 解析项目上游主体:
// refType=2 关联供应商库(同一家单位可能只以供应商身份建档),其余按客户库(默认);
// 命中时以库中的 ID 与标准名称为准,未命中(或未选择库记录)时保留手工填写的名称、不保留关联。
func resolveProjectCustomer(tenantID string, refType int8, refID uint64, name string) (*uint64, int8, string) {
name = strings.TrimSpace(name)
if refID == 0 {
return nil, 0, name
}
if refType == 2 {
var sup models.TenantCrmSupplier
if err := models.Orm.QueryTable(new(models.TenantCrmSupplier)).
Filter("id", refID).Filter("tenant_id", tenantID).
Filter("delete_time__isnull", true).One(&sup); err != nil {
return nil, 0, name
}
id := sup.ID
if strings.TrimSpace(sup.SupplierName) != "" {
name = sup.SupplierName
}
return &id, 2, name
}
var cus models.TenantCrmCustomer
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
Filter("id", refID).Filter("tenant_id", tenantID).
Filter("delete_time__isnull", true).One(&cus); err != nil {
return nil, 0, name
}
id := cus.ID
if strings.TrimSpace(cus.CustomerName) != "" {
name = cus.CustomerName
}
return &id, 1, name
}
// resolveProjectSupplier 解析项目下游主体:
// refType=2 关联供应商库,refType=1 关联客户库(同一家单位可能以客户身份建档);
// 命中时以库中的 ID 与标准名称为准,未命中(或未选择库记录)时保留手工填写的名称、不保留关联。
@@ -190,7 +225,8 @@ func attachProjectContractPeriod(tenantID string, list []models.TenantCrmProject
// crmProjectNameDuplicated 项目重名校验:同租户 + 同名 + 同一客户(服务主体)才算重复。
// 不同客户的同名项目可正常创建(如两家公司都有「官网建设」项目);
// 未关联客户的项目按「客户为空」分组校验(与同样未关联客户的同名项目视为重复)。
func crmProjectNameDuplicated(tenantID, name string, customerID *uint64) bool {
// 上游来源库不同视为不同主体:客户库与供应商库各有一套 ID,需按 customer_ref_type 一并区分。
func crmProjectNameDuplicated(tenantID, name string, customerID *uint64, customerRefType int8) bool {
name = strings.TrimSpace(name)
if name == "" || strings.TrimSpace(tenantID) == "" || models.Orm == nil {
return false
@@ -201,6 +237,11 @@ func crmProjectNameDuplicated(tenantID, name string, customerID *uint64) bool {
And("project_name", name)
if customerID != nil && *customerID > 0 {
cond = cond.And("customer_id", *customerID)
if customerRefType == 2 {
cond = cond.And("customer_ref_type", 2)
} else {
cond = cond.And("customer_ref_type__in", []int{0, 1})
}
} else {
cond = cond.AndCond(orm.NewCondition().Or("customer_id__isnull", true).Or("customer_id", 0))
}
@@ -390,18 +431,8 @@ func (c *BackendCrmProjectController) Create() {
}
tenantID := pipelineTenantID(claims)
var custIDVal *uint64
customerName := strings.TrimSpace(p.CustomerName)
if p.CustomerID > 0 {
var cust models.TenantCrmCustomer
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
Filter("id", p.CustomerID).Filter("tenant_id", tenantID).
Filter("delete_time__isnull", true).One(&cust); err == nil {
cid := cust.ID
custIDVal = &cid
customerName = cust.CustomerName
}
}
// 上游主体:默认客户库,也可从供应商库选择(customer_ref_type=2)
custIDVal, custRefType, customerName := resolveProjectCustomer(tenantID, p.CustomerRefType, p.CustomerID, p.CustomerName)
status := p.Status
if status == 0 {
status = 1
@@ -419,6 +450,7 @@ func (c *BackendCrmProjectController) Create() {
ProjectNo: strings.TrimSpace(p.ProjectNo),
ProjectType: pt,
CustomerID: custIDVal,
CustomerRefType: custRefType,
CustomerName: customerName,
SupplierID: supplierID,
SupplierRefType: supplierRefType,
@@ -448,7 +480,7 @@ func (c *BackendCrmProjectController) Create() {
}
// 唯一性校验:同一客户(服务主体)下的同名项目不允许重复创建(重复数据请联系管理员转移跟进);
// 客户不同则允许同名创建(不同主体可各自立项,如两家客户都做「官网建设」)
if crmProjectNameDuplicated(tenantID, proj.ProjectName, proj.CustomerID) {
if crmProjectNameDuplicated(tenantID, proj.ProjectName, proj.CustomerID, proj.CustomerRefType) {
pipelineErr(&c.Controller, 409, 409, "该客户下已存在同名项目(可能由其他同事创建),如需继续跟进请联系管理员转移")
return
}
@@ -515,14 +547,11 @@ func (c *BackendCrmProjectController) Update() {
proj.ProjectName = strings.TrimSpace(p.ProjectName)
proj.ProjectNo = strings.TrimSpace(p.ProjectNo)
if p.CustomerID > 0 {
var cust models.TenantCrmCustomer
if err := models.Orm.QueryTable(new(models.TenantCrmCustomer)).
Filter("id", p.CustomerID).Filter("tenant_id", tenantID).
Filter("delete_time__isnull", true).One(&cust); err == nil {
cid := cust.ID
proj.CustomerID = &cid
proj.CustomerName = cust.CustomerName
}
// 上游主体:按来源库解析(默认客户库,也支持从供应商库选择)
cid, cref, cname := resolveProjectCustomer(tenantID, p.CustomerRefType, p.CustomerID, p.CustomerName)
proj.CustomerID = cid
proj.CustomerRefType = cref
proj.CustomerName = cname
} else if strings.TrimSpace(p.CustomerName) != "" {
proj.CustomerName = strings.TrimSpace(p.CustomerName)
}
@@ -728,7 +757,12 @@ func (c *BackendCrmProjectController) SaveContacts() {
if pt != 2 && pt != 3 {
pt = 1
}
upIDs := filterCompanyContactIDs(tenantID, "customer", proj.CustomerID, p.UpstreamContactIDs)
// 上游主体联系人:按上游来源库取(customer_ref_type=2 供应商库,其余客户库)
upCompanyType := "customer"
if proj.CustomerRefType == 2 {
upCompanyType = "supplier"
}
upIDs := filterCompanyContactIDs(tenantID, upCompanyType, proj.CustomerID, p.UpstreamContactIDs)
var downIDs []uint64
// 上下游 / 上中游项目:对方主体(下游 / 中游)联系人按 supplier_ref_type 选择对应库
if pt != 1 && proj.SupplierID != nil && *proj.SupplierID > 0 {
@@ -0,0 +1,33 @@
-- =============================================================
-- 项目上游主体支持从「供应商库」选择(CRM 项目管理)
--
-- 背景:上游主体(customer_id / customer_name)原本固定取客户库;
-- 同一家单位在系统里可能只以供应商身份建档,因此上游也允许从供应商库选择。
-- 新增列 yz_backend_crm_project.customer_ref_type:
-- 1 = 客户库(默认,历史数据同此)
-- 2 = 供应商库
--
-- 幂等:MySQL 的 ADD COLUMN 不支持 IF NOT EXISTS,这里先用 information_schema
-- 判断列是否存在,缺失才执行 ALTER,可重复执行。
-- 执行:mysql -h <host> -P <port> -u <user> -p <db> < alter_crm_project_customer_ref_type.sql
-- =============================================================
SET @has_col := (SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'yz_backend_crm_project'
AND COLUMN_NAME = 'customer_ref_type');
SET @sql := IF(@has_col = 0,
'ALTER TABLE `yz_backend_crm_project` ADD COLUMN `customer_ref_type` TINYINT NOT NULL DEFAULT 1 COMMENT ''上游主体来源:1客户 2供应商'' AFTER `customer_name`',
'SELECT ''yz_backend_crm_project.customer_ref_type already exists'' AS msg');
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 结果校验:应返回一行 customer_ref_type
SELECT COLUMN_NAME, COLUMN_TYPE, COLUMN_DEFAULT, COLUMN_COMMENT
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'yz_backend_crm_project'
AND COLUMN_NAME = 'customer_ref_type';
+15 -13
View File
@@ -4,17 +4,19 @@ import "time"
// TenantCrmProject 项目表: yz_backend_crm_project
type TenantCrmProject struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
ProjectName string `orm:"column(project_name);size(100)" json:"project_name"` // 项目名称
ProjectNo string `orm:"column(project_no);size(50)" json:"project_no"` // 项目编号
BusinessID *uint64 `orm:"column(business_id);null" json:"business_id"` // 来源商机ID
BusinessName string `orm:"-" json:"business_name"` // 来源商机名称(非表字段,查询时填充)
CustomerID *uint64 `orm:"column(customer_id);null" json:"customer_id"`
CustomerName string `orm:"column(customer_name);size(100)" json:"customer_name"`
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
Industry string `orm:"column(industry);size(50)" json:"industry"`
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
ProjectName string `orm:"column(project_name);size(100)" json:"project_name"` // 项目名称
ProjectNo string `orm:"column(project_no);size(50)" json:"project_no"` // 项目编号
BusinessID *uint64 `orm:"column(business_id);null" json:"business_id"` // 来源商机ID
BusinessName string `orm:"-" json:"business_name"` // 来源商机名称(非表字段,查询时填充)
CustomerID *uint64 `orm:"column(customer_id);null" json:"customer_id"`
CustomerName string `orm:"column(customer_name);size(100)" json:"customer_name"`
// 上游主体来源库:1=客户库(默认,历史数据同此) 2=供应商库(同一家单位可能只以供应商身份建档)
CustomerRefType int8 `orm:"column(customer_ref_type);default(1)" json:"customer_ref_type"`
OwnerUserID string `orm:"column(owner_user_id);size(64)" json:"owner_user_id"`
OwnerUserName string `orm:"column(owner_user_name);size(128)" json:"owner_user_name"`
Industry string `orm:"column(industry);size(50)" json:"industry"`
// 项目类型:1=单一项目(只有上游客户) 2=上下游项目(上游客户 + 下游供应商)
// 3=上中游项目(上游 A 公司 + 中游 B 公司,下游固定为本公司)
ProjectType int8 `orm:"column(project_type);default(1)" json:"project_type"`
@@ -28,8 +30,8 @@ type TenantCrmProject struct {
UpstreamAmount float64 `orm:"column(upstream_amount);digits(14);decimals(2);default(0)" json:"upstream_amount"`
DownstreamAmount float64 `orm:"column(downstream_amount);digits(14);decimals(2);default(0)" json:"downstream_amount"`
// Amount 为旧「项目金额」列,已由 UpstreamAmount 取代;写入时始终同步为上游金额,仅作历史兼容
Amount float64 `orm:"column(amount);digits(14);decimals(2);default(0)" json:"amount"`
Status int8 `orm:"column(status);default(1)" json:"status"` // 1未开始/2进行中/3已完成/4已暂停/5异常
Amount float64 `orm:"column(amount);digits(14);decimals(2);default(0)" json:"amount"`
Status int8 `orm:"column(status);default(1)" json:"status"` // 1未开始/2进行中/3已完成/4已暂停/5异常
// StartDate / EndDate 为旧的手工维护列,已改由关联合同自动汇总,保留仅用于兼容历史数据
StartDate *time.Time `orm:"column(start_date);type(date);null" json:"start_date"`
EndDate *time.Time `orm:"column(end_date);type(date);null" json:"end_date"`