批量更新功能

This commit is contained in:
2026-09-14 16:15:44 +08:00
parent 6f106e43af
commit a26c98b92b
79 changed files with 4529 additions and 724 deletions
+3 -1
View File
@@ -12,7 +12,9 @@ type BackendErpContact struct {
ContactType int8 `orm:"column(contact_type);default(1)" json:"contact_type"`
ContactName string `orm:"column(contact_name);size(64)" json:"contact_name"`
Gender int8 `orm:"column(gender);default(0)" json:"gender"`
Phone *string `orm:"column(phone);size(20);null" json:"phone"`
Phone *string `orm:"column(phone);size(20);null" json:"phone"` // 手机号(保留第一个,兼容旧逻辑)
// 手机号可登记多个(有的人有多个号码):JSON 数组,最多 5 个,phone 始终与其中一个(第一个)保持一致
Mobiles *string `orm:"column(mobiles);type(text);null" json:"mobiles"`
WorkPhone *string `orm:"column(work_phone);size(20);null" json:"work_phone"`
Email *string `orm:"column(email);size(128);null" json:"email"`
Wechat *string `orm:"column(wechat);size(64);null" json:"wechat"`
+20 -5
View File
@@ -156,6 +156,7 @@ func Init(_ string) {
EnsureCrmPaybackRecordTable()
EnsureCrmProductDevColumns()
EnsureCrmUnitTable()
EnsureErpCompanyContactMobilesColumn()
}
// EnsureCrmPaybackTable 回款计划表建表(CREATE TABLE IF NOT EXISTS,可重复执行;
@@ -278,7 +279,7 @@ func EnsureCrmContractTable() {
total_amount decimal(14,2) NOT NULL DEFAULT '0.00' COMMENT '合同总金额',
total_cost decimal(14,2) NOT NULL DEFAULT '0.00' COMMENT '产品总成本',
total_profit decimal(14,2) NOT NULL DEFAULT '0.00' COMMENT '合同总利润',
status tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1草稿/2已完成/3已作废/4履约中/5执行异常',
status tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1草稿/2已完成/3已作废/4履约中/5异常',
step tinyint(4) NOT NULL DEFAULT '1' COMMENT '进度步骤:1合同信息/2产品清单',
remark text COMMENT '备注',
create_user_id varchar(64) NOT NULL DEFAULT '' COMMENT '创建人用户ID',
@@ -335,13 +336,30 @@ func EnsureCrmUnitTable() {
}
}
// EnsureCrmProductDevColumns 补齐产品表的软件开发字段(line_type / dev_unit_price / dev_tree);
// EnsureErpCompanyContactMobilesColumn 补齐公司联系人表(yz_backend_contact_company)的多手机号字段 mobiles。
// 该表为客户/供应商正式联系人的共用库(项目联系人选择、通讯录、客户-供应商转化等均读取),
// 早期多手机号升级遗漏了此表,缺列会导致查询报错(前端表现为「联系人不显示」)。
// 表不存在或列已存在时忽略错误,可用 docs/sql/alter_contact_mobiles.sql 手动修复。
func EnsureErpCompanyContactMobilesColumn() {
table := new(ErpCompanyContact).TableName()
sql := fmt.Sprintf("ALTER TABLE %s ADD COLUMN mobiles text NULL COMMENT '手机号 JSON 数组(最多5个),phone 保留座机语义'", table)
if _, err := Orm.Raw(sql).Exec(); err != nil {
// 列已存在或表不存在时忽略
return
}
// 历史数据回填:原 phone 中的号码作为第一个手机号(仅在 mobiles 为空时)
_, _ = Orm.Raw("UPDATE " + table +
" SET mobiles = JSON_ARRAY(phone) WHERE (mobiles IS NULL OR mobiles = '') AND phone IS NOT NULL AND phone <> ''").Exec()
}
// EnsureCrmProductDevColumns 补齐产品表的软件开发字段(line_type / dev_unit_price / dev_mode / dev_tree);
// 表不存在或列已存在时忽略错误,可用 docs/sql/alter_backend_crm_product_dev_columns.sql 手动修复。
func EnsureCrmProductDevColumns() {
table := new(TenantCrmProduct).TableName()
cols := []string{
"line_type varchar(20) NOT NULL DEFAULT 'product' COMMENT '行类型:product成品/dev软件开发'",
"dev_unit_price decimal(14,2) NOT NULL DEFAULT '0.00' COMMENT '软件开发统一人天单价'",
"dev_mode varchar(20) NOT NULL DEFAULT 'self' COMMENT '软件开发研发方式:self自行研发/outsource外协研发'",
"dev_tree text COMMENT '软件开发模块树JSON(叶子人天×统一人天单价)'",
}
for _, col := range cols {
@@ -353,9 +371,6 @@ func EnsureCrmProductDevColumns() {
}
}
// EnsureCrmCreateUserColumn 补齐客户/供应商的创建人字段(用于删除权限校验)。
// 同样兼容历史表名差异,表不存在或列已存在时忽略错误。
func EnsureCrmCreateUserColumn() {
+1 -1
View File
@@ -22,7 +22,7 @@ type TenantCrmClue struct {
ContactWechat string `orm:"column(contact_wechat);size(64)" json:"contact_wechat"` // 对接人微信
ContactQQ string `orm:"column(contact_qq);size(20)" json:"contact_qq"` // 对接人QQ
Address string `orm:"column(address);size(255)" json:"address"` // 公司地址
Status int8 `orm:"column(status);default(1)" json:"status"` // 1跟进中/2已转化/3已关闭
Status int8 `orm:"column(status);default(1)" json:"status"` // 1跟进中/2已转化/3已废弃
Locked int8 `orm:"column(locked);default(0)" json:"locked"` // 0否/1是(转化后锁定不可编辑)
BusinessID *uint64 `orm:"column(business_id);null" json:"business_id"` // 转化后的商机ID
ConvertTime *time.Time `orm:"column(convert_time);type(datetime);null" json:"convert_time"` // 转化时间
+3 -3
View File
@@ -13,8 +13,8 @@ import "time"
type TenantCrmContract struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
ContractNo string `orm:"column(contract_no);size(50)" json:"contract_no"` // 合同编号
ContractName string `orm:"column(contract_name);size(100)" json:"contract_name"` // 合同名称
ContractNo string `orm:"column(contract_no);size(50)" json:"contract_no"` // 合同编号
ContractName string `orm:"column(contract_name);size(100)" json:"contract_name"` // 合同名称
ContractCategory string `orm:"column(contract_category);size(20)" json:"contract_category"` // 分类:1开发/2服务/3销售/4租赁/5采购/6运维/7咨询/8其他
OurRole int8 `orm:"column(our_role);default(2)" json:"our_role"` // 我方角色:1甲方/2乙方/3丙方/4丁方
PartyCount int8 `orm:"column(party_count);default(2)" json:"party_count"` // 合同形式:2/3/4 方
@@ -33,7 +33,7 @@ type TenantCrmContract struct {
TotalAmount float64 `orm:"column(total_amount);digits(14);decimals(2);default(0)" json:"total_amount"`
TotalCost float64 `orm:"column(total_cost);digits(14);decimals(2);default(0)" json:"total_cost"`
TotalProfit float64 `orm:"column(total_profit);digits(14);decimals(2);default(0)" json:"total_profit"`
Status int8 `orm:"column(status);default(1)" json:"status"` // 1草稿/2已完成/3已作废/4履约中/5执行异常
Status int8 `orm:"column(status);default(1)" json:"status"` // 1草稿/2已完成/3已作废/4履约中/5异常
Step int8 `orm:"column(step);default(1)" json:"step"` // 进度式创建步骤:1合同信息/2产品清单
Remark string `orm:"column(remark);type(text);null" json:"remark"`
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
+3 -1
View File
@@ -11,7 +11,9 @@ type TenantCrmEntityContact struct {
RelatedID uint64 `orm:"column(related_id)" json:"related_id"`
ContactName string `orm:"column(contact_name);size(50)" json:"contact_name"`
Position string `orm:"column(position);size(50)" json:"position"`
Mobile string `orm:"column(mobile);size(20)" json:"mobile"`
Mobile string `orm:"column(mobile);size(20)" json:"mobile"` // 手机号(保留第一个,兼容旧逻辑)
// 手机号可登记多个(有的人有多个号码):JSON 数组,最多 5 个,mobile 始终与其中一个(第一个)保持一致
Mobiles string `orm:"column(mobiles);type(text);null" json:"mobiles"`
Wechat string `orm:"column(wechat);size(64)" json:"wechat"`
QQ string `orm:"column(qq);size(20)" json:"qq"`
Email string `orm:"column(email);size(100)" json:"email"`
+6 -1
View File
@@ -9,12 +9,17 @@ import "time"
// contract_id 冗余存储,便于按合同直接查询进度列表;
// - 每次「新建回款」追加一条记录,构成回款进度(一笔笔实际到账),
// 与计划内的 items(计划分期)区分开:前者是实际发生,后者是计划安排;
// - 计划表 received_amount 由本表记录金额汇总回写(refreshPlanReceived)。
// - 计划表 received_amount 由本表记录金额汇总回写(refreshPlanReceived);
// - item_seq / item_name 标记本笔回款对应的计划明细节点(进度 / 分期):
// item_seq 对应计划 items 中的 seq(1-based),0=未指定(按整单登记);
// item_name 为节点名称快照,便于明细节点被改动后仍可追溯。
type TenantCrmPaybackRecord struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
ContractID *uint64 `orm:"column(contract_id);null" json:"contract_id"` // 关联合同ID
PaybackID uint64 `orm:"column(payback_id)" json:"payback_id"` // 关联回款计划ID
ItemSeq int `orm:"column(item_seq);default(0)" json:"item_seq"` // 对应计划明细序号(1-based),0=未指定
ItemName string `orm:"column(item_name);size(50)" json:"item_name"` // 计划明细节点名称快照
Amount float64 `orm:"column(amount);digits(14);decimals(2);default(0)" json:"amount"` // 本次回款金额
ReceivedDate string `orm:"column(received_date);size(20)" json:"received_date"` // 回款日期 YYYY-MM-DD
PayMethod int8 `orm:"column(pay_method);default(1)" json:"pay_method"` // 回款方式:1对公/2网银/3现金/4支票/5支付宝/6微信/7其他
+1
View File
@@ -23,6 +23,7 @@ type TenantCrmProduct struct {
TaxRate float64 `orm:"column(tax_rate);digits(6);decimals(2);default(0)" json:"tax_rate"` // 税率(%)
LineType string `orm:"column(line_type);size(20);default(product)" json:"line_type"` // 行类型 product/dev
DevUnitPrice float64 `orm:"column(dev_unit_price);digits(14);decimals(2);default(0)" json:"dev_unit_price"` // 软件开发统一人天单价
DevMode string `orm:"column(dev_mode);size(20);default(self)" json:"dev_mode"` // 软件开发研发方式:self自行研发/outsource外协研发(别人帮我们研发)
DevTree string `orm:"column(dev_tree);type(text);null" json:"dev_tree"` // 软件开发模块树JSON文本
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用/0停用
ProjectID *uint64 `orm:"column(project_id);null" json:"project_id"` // 来源项目ID(由项目生成时填入)
+7 -5
View File
@@ -6,14 +6,16 @@ import "time"
//
// 说明:
// - 与 CMS 产品分类类似,是 CRM 产品台账的产品归类(如:服务器类 / 软件许可 / 实施服务 等);
// - 产品表单的「产品分类」下拉取自该表;status 1启用/0停用。
// - 产品表单的「产品分类」下拉取自该表;status 1启用/0停用;
// - is_default 1=系统默认分类(「未分类」,由系统确保存在,不可编辑、不可删除,排序置顶)/0=租户自定义分类(可增删改)。
type TenantCrmProductCategory struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
Name string `orm:"column(name);size(100)" json:"name"` // 分类名称
Code string `orm:"column(code);size(50)" json:"code"` // 分类编码(选填)
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用/0停用
Name string `orm:"column(name);size(100)" json:"name"` // 分类名称
Code string `orm:"column(code);size(50)" json:"code"` // 分类编码(选填)
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用/0停用
IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` // 1系统默认(不可编辑/删除)/0租户自定义
Remark string `orm:"column(remark);type(text);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`
+11 -3
View File
@@ -16,18 +16,20 @@ type TenantCrmProject struct {
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"`
// 下游主体:可从供应商库或客户库选择(supplier_ref_type + supplier_id 标明来源),也允许手工填写名称
// 对方主体(2=上下游项目的下游 / 3=上中游项目的中游):可从供应商库或客户库选择(supplier_ref_type + supplier_id 标明来源),也允许手工填写名称
SupplierID *uint64 `orm:"column(supplier_id);null" json:"supplier_id"`
SupplierRefType int8 `orm:"column(supplier_ref_type);default(0)" json:"supplier_ref_type"` // 0未关联 1客户 2供应商
SupplierName string `orm:"column(supplier_name);size(100)" json:"supplier_name"`
SupplierIndustry string `orm:"column(supplier_industry);size(50)" json:"supplier_industry"`
// 金额拆分为上下游:上游=对客户的收入,下游=对供应商的成本(上下游项目毛利 = 上游 - 下游)
// 金额拆分为上下游:上游=对客户的收入,下游=对供应商的成本(上下游项目毛利 = 上游 - 下游);
// 上中游项目:上游=上游 A 公司合同额,下游字段存中游 B 公司(与我方)合同额
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已暂停
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"`
@@ -39,6 +41,12 @@ type TenantCrmProject struct {
UpstreamPhone string `orm:"column(upstream_phone);size(20)" json:"upstream_phone"`
DownstreamContact string `orm:"column(downstream_contact);size(50)" json:"downstream_contact"`
DownstreamPhone string `orm:"column(downstream_phone);size(20)" json:"downstream_phone"`
// 项目联系人:从对应企业通讯录中挑选的联系人ID集合(JSON 数组字符串,如 [1,5,8],空为未选)。
// 上游=客户方(yz_backend_erp_contact.company_type=customer & company_id=customer_id);
// 下游/中游=对方主体方(company_type 按 supplier_ref_type:1=customer 2=supplier,company_id=supplier_id)。
// 单一项目只使用上游集合;联系人详情实时来自企业通讯录,此处仅存 ID 引用。
UpstreamContactIDs string `orm:"column(upstream_contact_ids);type(text);null" json:"upstream_contact_ids"`
DownstreamContactIDs string `orm:"column(downstream_contact_ids);type(text);null" json:"downstream_contact_ids"`
// 以下为旧版单一对接人信息,已被上下游对接人取代,仅保留列以兼容历史数据
ContactPerson string `orm:"column(contact_person);size(50)" json:"contact_person"`
ContactPosition string `orm:"column(contact_position);size(50)" json:"contact_position"` // 对接人职位(已废弃)
+7 -5
View File
@@ -7,15 +7,17 @@ import "time"
// 说明:
// - 维护产品、合同等业务使用的计量单位(套 / 台 / 个 / 人天 …);
// - 在「CRM -> 基本设置 -> 单位设置」中维护,产品表单的「单位」下拉取自该表;
// - is_default 1=系统默认单位(由系统写入,不可编辑、不可删除)/0=租户自定义单位(可增删改);
// - status 1启用/0停用;同租户下单位名称唯一(uk_tenant_name)。
type TenantCrmUnit struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
TenantID string `orm:"column(tenant_id);size(64)" json:"tenant_id"`
Name string `orm:"column(name);size(20)" json:"name"` // 单位名称
Code string `orm:"column(code);size(50)" json:"code"` // 单位编码(选填)
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用/0停用
Remark string `orm:"column(remark);size(255)" json:"remark"` // 备注
Name string `orm:"column(name);size(20)" json:"name"` // 单位名称
Code string `orm:"column(code);size(50)" json:"code"` // 单位编码(选填)
Sort int `orm:"column(sort);default(0)" json:"sort"` // 排序
Status int8 `orm:"column(status);default(1)" json:"status"` // 1启用/0停用
IsDefault int8 `orm:"column(is_default);default(0)" json:"is_default"` // 1系统默认(不可编辑/删除)/0租户自定义
Remark string `orm:"column(remark);size(255)" json:"remark"` // 备注
CreateUserID string `orm:"column(create_user_id);size(64)" json:"create_user_id"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime time.Time `orm:"column(update_time);auto_now;type(datetime)" json:"update_time"`