From ac5f5610c2112b65a99caa1892bac5b5d775fdab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= Date: Sat, 13 Jun 2026 20:17:55 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9D=E7=BA=A6=E9=87=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/penalty_month_received_report.py | 81 +++++++++++++------ .../models/penalty_year_cum_report.py | 77 +++++++++++++----- 2 files changed, 115 insertions(+), 43 deletions(-) diff --git a/yuthon_report/models/penalty_month_received_report.py b/yuthon_report/models/penalty_month_received_report.py index 3d94fa291..1739bbefe 100644 --- a/yuthon_report/models/penalty_month_received_report.py +++ b/yuthon_report/models/penalty_month_received_report.py @@ -239,9 +239,10 @@ class PenaltyMonthReceivedReport(models.Model): }) # ========== 三、来源B:receivable.modify.line 中 is_penalty=True 的记录 ========== - # 通过 charging_range 反查关联租金 - # 步骤:取 is_penalty=True 的 modify.line → 读 receivable_id 的 charging_range → - # 解析出最后付款日 → 在 modify.line 中找匹配的租金记录(同承租方+同物业+同合同+同收款日期) + # 核心关系:违约金的 due_date(应收开始)= 关联租金的 payout_date(收款日期) + # 租金的 due_date +1个月 = 违约金收费区间的开始月份 + # 反查逻辑:通过违约金的 due_date 直接在 property.accounts.receivable 中查找 + # payout_date 等于违约金 due_date 的租金记录 source_b_domain = [ ('is_penalty', '=', True), # 标记为违约金付款 @@ -262,37 +263,71 @@ class PenaltyMonthReceivedReport(models.Model): if not penalty_rec: continue - # --- 读取 charging_range 并解析日期 --- - charging_range = penalty_rec.charging_range or "" - range_start, range_end = _parse_charging_range(charging_range) - if not range_end: - # 无法从收费区间解析出日期,跳过这条 + # --- 通过违约金的 due_date 反查关联租金 --- + # 关键关系:违约金的 due_date = 租金的 payout_date + penalty_due_date = penalty_rec.due_date + if not penalty_due_date: + # 违约金没有应收开始日期,无法关联租金 continue - # --- 通过 charging_range 的结束日期反查租金 modify.line --- - # 匹配条件:同一承租方 + 同一合同 + 收款日期=range_end + 非违约金记录 - rent_ml_domain = [ - ('id', '!=', pml.id), # 排除自身 - ('is_penalty', '=', False), # 非违约金(即租金) - ('reconciliation_date', '=', range_end), # 收款日期 = 收费区间结束日期 + # 统一转为 date 对象用于搜索 + penalty_due = penalty_due_date.date() if hasattr(penalty_due_date, 'date') else penalty_due_date + + # 在 property.accounts.receivable 中查找关联租金 + # 条件:payout_date = 违约金的 due_date + 同承租方 + 同合同 + rent_domain = [ + ('payout_date', '=', penalty_due), # 租金收款日期 = 违约金应收开始日期 ] if pml.lessee_id: - rent_ml_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方 + rent_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方 if pml.contract_id: - rent_ml_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同 + rent_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同 - rent_mls = self.env['receivable.modify.line'].sudo().search(rent_ml_domain, limit=1) - if not rent_mls: + rent_recs = self.sudo().search(rent_domain) + + if not rent_recs: continue - rent_ml = rent_mls[0] - # 获取租金对应的应收单 - rent_receivable = rent_ml.receivable_id + # --- 多候选时用收费区间开始月辅助定位 --- + # 租金 due_date +1个月 ≈ 违约金收费区间开始月 + # 选 due_date 与 charging_range 开始日期最接近的租金 + rent_receivable = None + if len(rent_recs) == 1: + # 只有1个候选,直接使用 + rent_receivable = rent_recs[0] + else: + # 多个候选:解析 charging_range 开始日期,找 due_date 最接近的租金 + charging_range = penalty_rec.charging_range or "" + range_start, _range_end = _parse_charging_range(charging_range) + best_rec = None + best_diff = None + for cand in rent_recs: + if not cand.due_date: + continue + cand_due = cand.due_date.date() if hasattr(cand.due_date, 'date') else cand.due_date + if range_start: + # 用 charging_range 开始日期与租金 due_date 比较 + # 充电区间开始月 ≈ 租金 due_date +1个月,所以区间开始日 ≈ due_date +30天 + diff = abs((cand_due - range_start).days) + else: + # 无 charging_range,无法精确定位,取第一个 + diff = 0 + if best_diff is None or diff < best_diff: + best_diff = diff + best_rec = cand + if best_rec: + rent_receivable = best_rec + + if not rent_receivable: + continue + + # 收费区间(从违约金应收单获取) + charging_range = penalty_rec.charging_range or "" # 物业名称(优先从违约金应收单取) prop_name = penalty_rec.property_name or (rent_receivable.property_name if rent_receivable else "") - # 内存过滤(用违约金应收单的信息过滤) + # 内存过滤(用违约金 modify.line 的信息过滤) if not _match_filters_ml(pml, prop_name): continue @@ -302,7 +337,7 @@ class PenaltyMonthReceivedReport(models.Model): continue processed_keys.add(key) - # --- 构建租金信息(从租金 modify.line 和对应应收单获取)--- + # --- 构建租金信息 --- rent_amount_val = 0.0 rent_collected_amt = 0.0 rent_uncollected_amt = 0.0 diff --git a/yuthon_report/models/penalty_year_cum_report.py b/yuthon_report/models/penalty_year_cum_report.py index e172985fb..1c3b26808 100644 --- a/yuthon_report/models/penalty_year_cum_report.py +++ b/yuthon_report/models/penalty_year_cum_report.py @@ -241,7 +241,11 @@ class PenaltyYearCumReport(models.Model): }) # ========== 三、来源B:receivable.modify.line 中 is_penalty=True 的记录 ========== - # 通过 charging_range 反查关联租金 + # 核心关系:违约金的 due_date(应收开始)= 关联租金的 payout_date(收款日期) + # 租金的 due_date +1个月 = 违约金收费区间的开始月份 + # 反查逻辑:通过违约金的 due_date 直接在 property.accounts.receivable 中查找 + # payout_date 等于违约金 due_date 的租金记录 + source_b_domain = [ ('is_penalty', '=', True), # 标记为违约金付款 ('reconciliation_date', '!=', False), # 有对账日期 @@ -261,32 +265,65 @@ class PenaltyYearCumReport(models.Model): if not penalty_rec: continue - # --- 读取 charging_range 并解析日期 --- - charging_range = penalty_rec.charging_range or "" - range_start, range_end = _parse_charging_range(charging_range) - if not range_end: - # 无法从收费区间解析出日期,跳过这条 + # --- 通过违约金的 due_date 反查关联租金 --- + # 关键关系:违约金的 due_date = 租金的 payout_date + penalty_due_date = penalty_rec.due_date + if not penalty_due_date: + # 违约金没有应收开始日期,无法关联租金 continue - # --- 通过 charging_range 的结束日期反查租金 modify.line --- - # 匹配条件:同一承租方 + 同一合同 + 收款日期=range_end + 非违约金记录 - rent_ml_domain = [ - ('id', '!=', pml.id), # 排除自身 - ('is_penalty', '=', False), # 非违约金(即租金) - ('reconciliation_date', '=', range_end), # 收款日期 = 收费区间结束日期 + # 统一转为 date 对象用于搜索 + penalty_due = penalty_due_date.date() if hasattr(penalty_due_date, 'date') else penalty_due_date + + # 在 property.accounts.receivable 中查找关联租金 + # 条件:payout_date = 违约金的 due_date + 同承租方 + 同合同 + rent_domain = [ + ('payout_date', '=', penalty_due), # 租金收款日期 = 违约金应收开始日期 ] if pml.lessee_id: - rent_ml_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方 + rent_domain.append(('lessee_id', '=', pml.lessee_id.id)) # 同一承租方 if pml.contract_id: - rent_ml_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同 + rent_domain.append(('contract_id', '=', pml.contract_id.id)) # 同一合同 - rent_mls = self.env['receivable.modify.line'].sudo().search(rent_ml_domain, limit=1) - if not rent_mls: + rent_recs = self.sudo().search(rent_domain) + + if not rent_recs: continue - rent_ml = rent_mls[0] - # 获取租金对应的应收单 - rent_receivable = rent_ml.receivable_id + # --- 多候选时用收费区间开始月辅助定位 --- + # 租金 due_date +1个月 ≈ 违约金收费区间开始月 + # 选 due_date 与 charging_range 开始日期最接近的租金 + rent_receivable = None + if len(rent_recs) == 1: + # 只有1个候选,直接使用 + rent_receivable = rent_recs[0] + else: + # 多个候选:解析 charging_range 开始日期,找 due_date 最接近的租金 + charging_range = penalty_rec.charging_range or "" + range_start, _range_end = _parse_charging_range(charging_range) + best_rec = None + best_diff = None + for cand in rent_recs: + if not cand.due_date: + continue + cand_due = cand.due_date.date() if hasattr(cand.due_date, 'date') else cand.due_date + if range_start: + # 用 charging_range 开始日期与租金 due_date 比较 + diff = abs((cand_due - range_start).days) + else: + # 无 charging_range,无法精确定位,取第一个 + diff = 0 + if best_diff is None or diff < best_diff: + best_diff = diff + best_rec = cand + if best_rec: + rent_receivable = best_rec + + if not rent_receivable: + continue + + # 收费区间(从违约金应收单获取) + charging_range = penalty_rec.charging_range or "" # 物业名称(优先从违约金应收单取) prop_name = penalty_rec.property_name or (rent_receivable.property_name if rent_receivable else "") @@ -301,7 +338,7 @@ class PenaltyYearCumReport(models.Model): continue processed_keys.add(key) - # --- 构建租金信息(从租金 modify.line 和对应应收单获取)--- + # --- 构建租金信息 --- rent_amount_val = 0.0 rent_collected_amt = 0.0 rent_uncollected_amt = 0.0