优化项目管理
This commit is contained in:
@@ -229,6 +229,11 @@ func (c *BackendCrmPaybackController) Create() {
|
||||
return
|
||||
}
|
||||
tenantID := pipelineTenantID(claims)
|
||||
// 回款已全部收齐(进度 100%)的合同不允许再新建回款计划(与回款进度汇总口径一致)
|
||||
if paybackContractPaidOff(tenantID, p.ContractID) {
|
||||
pipelineErr(&c.Controller, 400, 400, "回款已全部完成(进度 100%),不可再新建回款计划")
|
||||
return
|
||||
}
|
||||
|
||||
itemsJSON, totalAmount, err := normalizePaybackItems(p.Items)
|
||||
if err != nil {
|
||||
@@ -308,6 +313,11 @@ func (c *BackendCrmPaybackController) Update() {
|
||||
pipelineErr(&c.Controller, 403, 403, "无权操作该回款计划数据")
|
||||
return
|
||||
}
|
||||
// 已完成(回款已收齐)的回款计划不允许编辑,保证已收齐数据稳定
|
||||
if row.Status == 2 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该回款计划已完成(回款已收齐),不允许编辑")
|
||||
return
|
||||
}
|
||||
|
||||
if p.ContractID > 0 {
|
||||
row.ContractID = &p.ContractID
|
||||
@@ -393,7 +403,7 @@ func (c *BackendCrmPaybackController) Delete() {
|
||||
pipelineErr(&c.Controller, 403, 403, "只有创建人、租户管理员或平台管理员可以删除该回款计划")
|
||||
return
|
||||
}
|
||||
// 已完成(回款已收齐)的回款计划不允许删除
|
||||
// 已完成(回款已收齐)的回款计划不允许删除,避免破坏已收齐数据
|
||||
if row.Status == 2 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该回款计划已完成(回款已收齐),不允许删除")
|
||||
return
|
||||
@@ -801,6 +811,11 @@ func (c *BackendCrmPaybackController) CreateRecord() {
|
||||
pipelineErr(&c.Controller, 404, 404, "回款计划不存在")
|
||||
return
|
||||
}
|
||||
// 计划已完成(回款已收齐)时不允许再新建回款记录
|
||||
if plan.Status == 2 {
|
||||
pipelineErr(&c.Controller, 400, 400, "该回款计划已完成(回款已收齐),不可再新建回款记录")
|
||||
return
|
||||
}
|
||||
payMethod := p.PayMethod
|
||||
if payMethod <= 0 {
|
||||
payMethod = plan.PayMethod
|
||||
@@ -950,6 +965,115 @@ func (c *BackendCrmPaybackController) UpdateRecord() {
|
||||
})
|
||||
}
|
||||
|
||||
// paybackOverdueDateByContracts 批量计算各合同的「回款逾期日期」(yyyy-MM-dd,取最早):
|
||||
// 合同下全部未收齐回款计划中,存在「未收齐且已过计划回款日期」的明细时取最早日期;
|
||||
// 与回款逾期联动(合同置「异常」)同一判定口径,供合同列表 / 详情的「逾期」标签一并参考。
|
||||
func paybackOverdueDateByContracts(tenantID string, contractIDs []uint64) map[uint64]string {
|
||||
out := make(map[uint64]string)
|
||||
if models.Orm == nil || tenantID == "" || len(contractIDs) == 0 {
|
||||
return out
|
||||
}
|
||||
today := time.Now().Format("2006-01-02")
|
||||
var plans []models.TenantCrmPayback
|
||||
if _, err := models.Orm.QueryTable(new(models.TenantCrmPayback)).
|
||||
Filter("tenant_id", tenantID).
|
||||
Filter("contract_id__in", contractIDs).
|
||||
Filter("delete_time__isnull", true).
|
||||
Filter("status__ne", 2). // 已收齐(已完成)的计划跳过
|
||||
All(&plans, "id", "contract_id", "items"); err != nil {
|
||||
return out
|
||||
}
|
||||
if len(plans) == 0 {
|
||||
return out
|
||||
}
|
||||
// 回款记录按「计划 + 明细序号」汇总(item_seq=0 视为整单登记,不计入任何明细)
|
||||
planIDs := make([]uint64, 0, len(plans))
|
||||
for i := range plans {
|
||||
planIDs = append(planIDs, plans[i].ID)
|
||||
}
|
||||
var recs []models.TenantCrmPaybackRecord
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmPaybackRecord)).
|
||||
Filter("payback_id__in", planIDs).
|
||||
Filter("delete_time__isnull", true).
|
||||
All(&recs, "payback_id", "item_seq", "amount")
|
||||
receivedByPlan := make(map[uint64]map[int]float64, len(plans))
|
||||
for _, r := range recs {
|
||||
if r.ItemSeq <= 0 {
|
||||
continue
|
||||
}
|
||||
m := receivedByPlan[r.PaybackID]
|
||||
if m == nil {
|
||||
m = make(map[int]float64)
|
||||
receivedByPlan[r.PaybackID] = m
|
||||
}
|
||||
m[r.ItemSeq] += r.Amount
|
||||
}
|
||||
for i := range plans {
|
||||
plan := plans[i]
|
||||
if plan.ContractID == nil || *plan.ContractID == 0 {
|
||||
continue
|
||||
}
|
||||
date := paybackPlanEarliestOverdueDate(plan, receivedByPlan[plan.ID], today)
|
||||
if date == "" {
|
||||
continue
|
||||
}
|
||||
cid := *plan.ContractID
|
||||
if cur, ok := out[cid]; !ok || date < cur {
|
||||
out[cid] = date
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// paybackPlanEarliestOverdueDate 计划明细中最早的「未收齐且已过计划回款日期」的日期;无逾期返回空串。
|
||||
func paybackPlanEarliestOverdueDate(plan models.TenantCrmPayback, receivedBySeq map[int]float64, today string) string {
|
||||
items := parsePaybackItems(plan.Items)
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
earliest := ""
|
||||
for _, it := range items {
|
||||
received := receivedBySeq[it.Seq]
|
||||
if it.ReceivedAmount > received {
|
||||
received = it.ReceivedAmount
|
||||
}
|
||||
if it.Amount > 0 && received >= it.Amount {
|
||||
continue // 已收齐
|
||||
}
|
||||
planDate := strings.TrimSpace(it.PlanDate)
|
||||
if len(planDate) > 10 {
|
||||
planDate = planDate[:10]
|
||||
}
|
||||
if planDate == "" || planDate >= today {
|
||||
continue
|
||||
}
|
||||
if earliest == "" || planDate < earliest {
|
||||
earliest = planDate
|
||||
}
|
||||
}
|
||||
return earliest
|
||||
}
|
||||
|
||||
// paybackContractPaidOff 判断某合同的回款是否已全部收齐(进度 100%):
|
||||
// 汇总该合同全部有效回款计划的计划总额与已回款金额,口径与回款进度列表的汇总一致。
|
||||
// 已收齐时不允许再新建回款计划(避免收齐后继续追加,保证对账数据稳定)。
|
||||
func paybackContractPaidOff(tenantID string, contractID uint64) bool {
|
||||
if contractID == 0 {
|
||||
return false
|
||||
}
|
||||
var plans []models.TenantCrmPayback
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantCrmPayback)).
|
||||
Filter("tenant_id", tenantID).
|
||||
Filter("contract_id", contractID).
|
||||
Filter("delete_time__isnull", true).All(&plans)
|
||||
var planned, received float64
|
||||
for _, p := range plans {
|
||||
planned = round2(planned + p.TotalAmount)
|
||||
received = round2(received + p.ReceivedAmount)
|
||||
}
|
||||
return planned > 0 && received+0.005 >= planned
|
||||
}
|
||||
|
||||
// refreshPlanReceived 按回款记录重算计划的已回款金额,并同步计划状态(1进行中 / 2已完成)。
|
||||
func refreshPlanReceived(tenantID string, paybackID uint64) {
|
||||
if paybackID == 0 {
|
||||
|
||||
Reference in New Issue
Block a user