修复bug
This commit is contained in:
1 parent
fc8c3ee3ad
commit
68617335f0
9 files changed
+645
-161
No files matched your search
@@ -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';
|
||||
@@ -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"`
|
||||
|
||||
Reference in new issue
Block a user