批量更新功能

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
+26 -4
View File
@@ -30,11 +30,20 @@ type crmProductPayload struct {
TaxRate float64 `json:"tax_rate"`
LineType string `json:"line_type"`
DevUnitPrice float64 `json:"dev_unit_price"`
DevMode string `json:"dev_mode"`
DevTree json.RawMessage `json:"dev_tree"`
Status int8 `json:"status"`
Remark string `json:"remark"`
}
// normalizeDevMode 规范软件开发「研发方式」:self=自行研发(默认)/ outsource=外协研发(别人帮我们研发)。
func normalizeDevMode(v string) string {
if strings.TrimSpace(v) == "outsource" {
return "outsource"
}
return "self"
}
// crmProductItem 项目产品清单中的单行(也用于项目生成时写入产品管理)。
type crmProductItem struct {
ProductNo string `json:"product_no"`
@@ -148,9 +157,11 @@ func (c *BackendCrmProductController) Create() {
}
// 非开发行不保留开发字段,避免残留(如 dev_tree="[]")导致清单调用时被误判为软件开发行
devUnitPrice := p.DevUnitPrice
devMode := normalizeDevMode(p.DevMode)
devTree := string(p.DevTree)
if lineType != "dev" {
devUnitPrice = 0
devMode = ""
devTree = ""
}
now := time.Now()
@@ -166,6 +177,7 @@ func (c *BackendCrmProductController) Create() {
TaxRate: p.TaxRate,
LineType: lineType,
DevUnitPrice: devUnitPrice,
DevMode: devMode,
DevTree: devTree,
Status: status,
Remark: p.Remark,
@@ -221,9 +233,11 @@ func (c *BackendCrmProductController) Update() {
}
// 非开发行清空开发字段,避免残留导致清单调用时被误判为软件开发行
devUnitPrice := p.DevUnitPrice
devMode := normalizeDevMode(p.DevMode)
devTree := string(p.DevTree)
if lineType != "dev" {
devUnitPrice = 0
devMode = ""
devTree = ""
}
now := time.Now()
@@ -240,6 +254,7 @@ func (c *BackendCrmProductController) Update() {
"tax_rate": p.TaxRate,
"line_type": lineType,
"dev_unit_price": devUnitPrice,
"dev_mode": devMode,
"dev_tree": devTree,
"status": status,
"remark": p.Remark,
@@ -454,9 +469,10 @@ func SyncContractProducts(tenantID, uid string, projectID uint64, projectName st
CreateTime: now,
UpdateTime: now,
}
// 软件开发行:写入统一人天单价与模块树,作为可复用的「开发包」
// 软件开发行:写入统一人天单价、研发方式与模块树,作为可复用的「开发包」
if lineType == "dev" {
row.DevUnitPrice = toFloat64(it["dev_unit_price"])
row.DevMode = normalizeDevMode(productFieldStr(it["dev_mode"]))
if tree := it["dev_tree"]; tree != nil {
if b, me := json.Marshal(tree); me == nil {
row.DevTree = string(b)
@@ -504,7 +520,7 @@ func productFieldStr(v interface{}) string {
return strings.TrimSpace(fmt.Sprintf("%v", v))
}
// syncDevTreeToProduct 软件开发行引用到产品档案时,回写最新模块树与人天单价
// syncDevTreeToProduct 软件开发行引用到产品档案时,回写最新模块树、人天单价与研发方式
// (仅在有变化时更新;销售/成本单价不回写,仍由产品管理主导),
// 保证后续合同再选该产品能拿到最新的开发包。
func syncDevTreeToProduct(p *models.TenantCrmProduct, it map[string]interface{}) {
@@ -521,12 +537,18 @@ func syncDevTreeToProduct(p *models.TenantCrmProduct, it map[string]interface{})
}
newTree := string(b)
newUnitPrice := toFloat64(it["dev_unit_price"])
if newTree == p.DevTree && newUnitPrice == p.DevUnitPrice {
// 研发方式:清单行传了值就跟随回写(自行研发 / 外协研发)
newMode := p.DevMode
if m := productFieldStr(it["dev_mode"]); m != "" {
newMode = normalizeDevMode(m)
}
if newTree == p.DevTree && newUnitPrice == p.DevUnitPrice && newMode == p.DevMode {
return
}
p.DevTree = newTree
p.DevUnitPrice = newUnitPrice
_, _ = models.Orm.Update(p, "DevTree", "DevUnitPrice")
p.DevMode = newMode
_, _ = models.Orm.Update(p, "DevTree", "DevUnitPrice", "DevMode")
}
// genProductNo 生成产品编号 P-yyyymmdd-4位序号,并保证本租户内不重复。