diff --git a/yuthon_report/__manifest__.py b/yuthon_report/__manifest__.py index 0dd3c1ccd..a7e1bd7ec 100644 --- a/yuthon_report/__manifest__.py +++ b/yuthon_report/__manifest__.py @@ -16,6 +16,8 @@ 'views/penalty_month_received_report.xml', 'views/penalty_year_cum_report.xml', 'views/deposit_reports.xml', + 'views/cum_income_report.xml', + 'views/cum_arrears_report.xml', 'views/menu.xml', 'views/ledger_report_views.xml', 'views/receivable_report_views.xml', diff --git a/yuthon_report/models/__init__.py b/yuthon_report/models/__init__.py index d2816f2c3..31cf08fe5 100644 --- a/yuthon_report/models/__init__.py +++ b/yuthon_report/models/__init__.py @@ -18,6 +18,8 @@ from . import pre_arrears_report from . import pre_advance_report from . import month_receivable_report from . import total_received_report +from . import cum_income_report +from . import cum_arrears_report from . import penalty_arrears_report from . import penalty_month_received_report from . import penalty_year_cum_report diff --git a/yuthon_report/models/cum_arrears_report.py b/yuthon_report/models/cum_arrears_report.py new file mode 100644 index 000000000..dc7fee57f --- /dev/null +++ b/yuthon_report/models/cum_arrears_report.py @@ -0,0 +1,112 @@ +import datetime +import calendar + +from odoo import api, models + + +class CumArrearsReport(models.Model): + _inherit = 'receivable.modify.line' + + @api.model + def get_cum_arrears_report(self, params): + """ + 累计欠收报表(数据源:receivable.modify.line): + - 状态为挂账(bank_state = 'registered') + - 应收开始日期(receivable_id.due_date)小于等于查询月月末 + - 排除押金和违约金类型 + - 金额取 not_collected(未收) + """ + today = datetime.date.today() + + date_start = params.get("date_start") + if date_start: + query_date = datetime.datetime.fromisoformat(date_start).date() + else: + query_date = today + + # 查询月月末 + last_day = calendar.monthrange(query_date.year, query_date.month)[1] + month_end = query_date.replace(day=last_day) + + domain = [ + ('bank_state', '=', 'registered'), + ('receivable_id.due_date', '<=', month_end), + ('receivable_id.is_invalid', '=', False), + ] + + keyword = params.get("keyword") + if keyword: + domain += [ + '|', '|', + ('receivable_id.contract_id.contract_code', 'ilike', keyword), + ('receivable_id.property_name', 'ilike', keyword), + ('receivable_id.lessee_id.name', 'ilike', keyword), + ] + + condition = params.get('condition') + partner_id = params.get('partner_id') + company_id = params.get('company_id') + management_type_id = params.get('management_type_id') + + if condition: + domain.append(('receivable_id.tenure_type', '=', condition)) + if partner_id: + domain.append(('receivable_id.precinct_id', '=', partner_id)) + if company_id: + domain.append(('receivable_id.company_id', '=', company_id)) + if management_type_id: + domain.append(('receivable_id.management_type_id', '=', management_type_id)) + else: + domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金'])) + + lines = self.search(domain, order="receivable_id asc") + + rows = [] + total_amount = 0.0 + for idx, line in enumerate(lines, start=1): + rec = line.receivable_id + contract = rec.contract_id if rec else None + amount = round(line.not_collected or 0.0, 2) + total_amount += amount + + # 物业穿透 + property_val = '' + if contract and contract.lease_ids and contract.lease_ids[0].address_id: + prop = contract.lease_ids[0].address_id + property_val = { + 'model': 'yuthon.property', + 'id': prop.id, + 'display_name': rec.property_name or prop.name, + } + elif rec: + property_val = rec.property_name or '' + + rows.append({ + "序号": idx, + "合同编号": { + 'model': 'property.lease.contract', + 'id': contract.id, + 'display_name': contract.contract_code, + } if contract else "", + "物业名称": property_val, + "承租方名称": { + 'model': 'property.tenant.information', + 'id': rec.lessee_id.id, + 'display_name': rec.lessee_id.name, + } if rec and rec.lessee_id else "", + "收款类型": rec.management_type_id.name if rec and rec.management_type_id else "", + "未收金额": amount, + "所属公司": contract.lessor_id.name if contract and contract.lessor_id else "", + "管理员": { + 'model': 'res.users', + 'id': rec.admin_id.id, + 'display_name': rec.admin_id.name, + } if rec and rec.admin_id else "", + }) + + stats = [ + {'name': '记录总数', 'amount': len(rows)}, + {'name': '累计欠收', 'amount': round(total_amount, 2)}, + ] + + return {"rows": rows, "stats": stats} diff --git a/yuthon_report/models/cum_income_report.py b/yuthon_report/models/cum_income_report.py new file mode 100644 index 000000000..5deb3078e --- /dev/null +++ b/yuthon_report/models/cum_income_report.py @@ -0,0 +1,105 @@ +import datetime + +from odoo import api, models + + +class CumIncomeReport(models.Model): + _inherit = 'receivable.modify.line' + + @api.model + def get_cum_income_report(self, params): + """ + 累计收入报表(数据源:receivable.modify.line): + - 按年查询 + - 取交易金额(original_amount) + - 排除押金和违约金类型 + """ + today = datetime.date.today() + current_year = today.year + + date_start = params.get("date_start") + # 按年查询:如果有传值则使用该年份,否则使用当前年份 + if date_start: + year = datetime.datetime.fromisoformat(date_start).year + else: + year = current_year + + # 构建年份的开始和结束日期 + year_start = datetime.date(year, 1, 1) + year_end = datetime.date(year, 12, 31) + + domain = [ + ('receivable_id.due_date', '>=', year_start), + ('receivable_id.due_date', '<=', year_end), + ('receivable_id.is_invalid', '=', False), + ] + + keyword = params.get("keyword") + if keyword: + domain += [ + '|', '|', + ('receivable_id.contract_id.contract_code', 'ilike', keyword), + ('receivable_id.property_name', 'ilike', keyword), + ('receivable_id.lessee_id.name', 'ilike', keyword), + ] + + condition = params.get('condition') + partner_id = params.get('partner_id') + company_id = params.get('company_id') + management_type_id = params.get('management_type_id') + + if condition: + domain.append(('receivable_id.tenure_type', '=', condition)) + if partner_id: + domain.append(('receivable_id.precinct_id', '=', partner_id)) + if company_id: + domain.append(('receivable_id.company_id', '=', company_id)) + if management_type_id: + domain.append(('receivable_id.management_type_id', '=', management_type_id)) + else: + domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金'])) + + lines = self.search(domain, order="receivable_id asc") + + rows = [] + total_amount = 0.0 + for idx, line in enumerate(lines, start=1): + rec = line.receivable_id + contract = rec.contract_id if rec else None + amount = round(line.original_amount or 0.0, 2) + total_amount += amount + + # 物业穿透 + property_val = '' + if contract and contract.lease_ids and contract.lease_ids[0].address_id: + prop = contract.lease_ids[0].address_id + property_val = { + 'model': 'yuthon.property', + 'id': prop.id, + 'display_name': rec.property_name or prop.name, + } + elif rec: + property_val = rec.property_name or '' + + rows.append({ + "序号": idx, + "合同编号": { + 'model': 'property.lease.contract', + 'id': contract.id, + 'display_name': contract.contract_code, + } if contract else "", + "承租方名称": { + 'model': 'property.tenant.information', + 'id': rec.lessee_id.id, + 'display_name': rec.lessee_id.name, + } if rec and rec.lessee_id else "", + "物业名称": property_val, + "金额": amount, + }) + + stats = [ + {'name': '记录总数', 'amount': len(rows)}, + {'name': '累计收入', 'amount': round(total_amount, 2)}, + ] + + return {"rows": rows, "stats": stats} diff --git a/yuthon_report/models/monthly_precollection_report.py b/yuthon_report/models/monthly_precollection_report.py index 1f78e0334..7af723cd2 100644 --- a/yuthon_report/models/monthly_precollection_report.py +++ b/yuthon_report/models/monthly_precollection_report.py @@ -1,90 +1,121 @@ import datetime +import calendar from odoo import api, models class MonthlyPrecollectionReport(models.Model): - _inherit = 'property.accounts.receivable' + _inherit = 'receivable.modify.line' @api.model def get_monthly_precollection_report(self, params): - """汇总当前筛选条件下的应收数据(本月预收视图)。""" - domain = [] + """ + 本月预收报表(数据源:receivable.modify.line): + - 应收开始日期(receivable_id.due_date)在查询月当月(月初 ~ 月末) + - delivered1 > original_amount(已交超过当期金额,即有预收) + - 应收账单非作废:receivable_id.is_invalid = False + - 排除押金和违约金类型 + - 金额取 delivered1 - original_amount(预收金额) + """ + today = datetime.date.today() + + date_start = params.get("date_start") + if date_start: + query_date = datetime.datetime.fromisoformat(date_start).date() + else: + query_date = today + + # 查询月月初和月末 + month_start = query_date.replace(day=1) + last_day = calendar.monthrange(query_date.year, query_date.month)[1] + month_end = query_date.replace(day=last_day) + + domain = [ + ('receivable_id.due_date', '>=', month_start), + ('receivable_id.due_date', '<=', month_end), + ('receivable_id.delivered1', '>', 0), + ('receivable_id.is_invalid', '=', False), + ] keyword = params.get("keyword") if keyword: domain += [ '|', '|', - ('lessee_id.name', 'ilike', keyword), - ('property_name', 'ilike', keyword), - ('title_deed', 'ilike', keyword), + ('receivable_id.contract_id.contract_code', 'ilike', keyword), + ('receivable_id.property_name', 'ilike', keyword), + ('receivable_id.lessee_id.name', 'ilike', keyword), ] - date_start = params.get("date_start") - date_end = params.get("date_end") - if date_start: - start_dt = datetime.datetime.fromisoformat(date_start).date() - domain.append(("due_date", ">=", start_dt)) - if date_end: - end_dt = datetime.datetime.fromisoformat(date_end).date() - domain.append(("due_date", "<=", end_dt)) - condition = params.get('condition') partner_id = params.get('partner_id') company_id = params.get('company_id') management_type_id = params.get('management_type_id') if condition: - domain.append(('tenure_type', '=', condition)) + domain.append(('receivable_id.tenure_type', '=', condition)) if partner_id: - domain.append(('precinct_id', '=', partner_id)) + domain.append(('receivable_id.precinct_id', '=', partner_id)) if company_id: - domain.append(('company_id', '=', company_id)) + domain.append(('receivable_id.company_id', '=', company_id)) if management_type_id: - domain.append(('management_type_id', '=', management_type_id)) + domain.append(('receivable_id.management_type_id', '=', management_type_id)) + else: + domain.append(('receivable_id.management_type_id.name', 'not in', ['押金', '违约金'])) - receivables = self.search(domain, limit=1000, order="due_date asc") + lines = self.search(domain, order="receivable_id asc") - result = [] + rows = [] total_amount = 0.0 - for index, rec in enumerate(receivables, start=1): - amount = rec.original_amount or 0.0 + idx = 1 + for line in lines: + rec = line.receivable_id + contract = rec.contract_id if rec else None + # 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收 + amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2) + if amount <= 0: + continue total_amount += amount - result.append({ - "序号": index, + + # 物业穿透 + property_val = '' + if contract and contract.lease_ids and contract.lease_ids[0].address_id: + prop = contract.lease_ids[0].address_id + property_val = { + 'model': 'yuthon.property', + 'id': prop.id, + 'display_name': rec.property_name or prop.name, + } + elif rec: + property_val = rec.property_name or '' + + rows.append({ + "序号": idx, "合同编号": { 'model': 'property.lease.contract', - 'id': rec.contract_id.id, - 'display_name': rec.contract_id.contract_code, - } if rec.contract_id else "", - "物业名称": { - 'model': 'yuthon.property', - 'id': rec.contract_id.lease_ids[0].address_id.id, - 'display_name': rec.property_name, - } if rec.contract_id and rec.contract_id.lease_ids and rec.contract_id.lease_ids[0].address_id else rec.property_name or "", + 'id': contract.id, + 'display_name': contract.contract_code, + } if contract else "", + "物业名称": property_val, "承租方名称": { 'model': 'property.tenant.information', 'id': rec.lessee_id.id, 'display_name': rec.lessee_id.name, - } if rec.lessee_id else "", - "金额": round(amount, 2), - "所属公司": ( - rec.contract_id.lessor_id.referred - or rec.contract_id.lessor_id.name - if rec.contract_id and rec.contract_id.lessor_id - else "" - ), + } if rec and rec.lessee_id else "", + "收款类型": rec.management_type_id.name if rec and rec.management_type_id else "", + "金额": amount, + "所属公司": contract.lessor_id.name if contract and contract.lessor_id else "", "管理员": { 'model': 'res.users', - 'id': rec.contract_id.admin_id.id, - 'display_name': rec.contract_id.admin_id.name, - } if rec.contract_id and rec.contract_id.admin_id else "", + 'id': rec.admin_id.id, + 'display_name': rec.admin_id.name, + } if rec and rec.admin_id else "", }) + idx += 1 - return { - "rows": result, - "stats": [ - {"name": "应收总额", "amount": round(total_amount, 2)}, - ], - } + stats = [ + {'name': '合同总数', 'amount': len(rows)}, + {'name': '预收总额', 'amount': round(total_amount, 2)}, + ] + + return {"rows": rows, "stats": stats} diff --git a/yuthon_report/models/pre_advance_report.py b/yuthon_report/models/pre_advance_report.py index 2e694625a..51c740a31 100644 --- a/yuthon_report/models/pre_advance_report.py +++ b/yuthon_report/models/pre_advance_report.py @@ -11,11 +11,10 @@ class PreAdvanceReport(models.Model): """ 前期预收报表(数据源:receivable.modify.line): - 应收开始日期(receivable_id.due_date)在查询月最后一天之后(未来应收) - - 应收账单未收金额(receivable_id.submitted)= 0(已全额收款) - - original_amount > 0(有交易金额) + - delivered1 > original_amount(已交超过当期金额,即有预收) - 应收账单非作废:receivable_id.is_invalid = False - 排除押金和违约金类型 - - 金额取 original_amount(交易金额) + - 金额取 delivered1 - original_amount(预收金额) """ today = datetime.date.today() if today.month == 12: @@ -30,8 +29,7 @@ class PreAdvanceReport(models.Model): domain = [ ('receivable_id.due_date', '>', cutoff_date), - ('receivable_id.submitted', '=', 0), - ('receivable_id.original_amount', '>', 0), + ('receivable_id.delivered1', '>', 0), ('receivable_id.is_invalid', '=', False), ] @@ -64,10 +62,14 @@ class PreAdvanceReport(models.Model): rows = [] total_amount = 0.0 - for idx, line in enumerate(lines, start=1): + idx = 1 + for line in lines: rec = line.receivable_id contract = rec.contract_id if rec else None - amount = round(rec.original_amount or 0.0, 2) + # 预收金额 = 已交 - 当期金额,必须 > 0 才是真正预收 + amount = round((rec.delivered1 or 0.0) - (rec.original_amount or 0.0), 2) + if amount <= 0: + continue total_amount += amount # 物业穿透 @@ -103,6 +105,7 @@ class PreAdvanceReport(models.Model): 'display_name': rec.admin_id.name, } if rec and rec.admin_id else "", }) + idx += 1 stats = [ {'name': '合同总数', 'amount': len(rows)}, diff --git a/yuthon_report/views/cum_arrears_report.xml b/yuthon_report/views/cum_arrears_report.xml new file mode 100644 index 000000000..21bce27e6 --- /dev/null +++ b/yuthon_report/views/cum_arrears_report.xml @@ -0,0 +1,29 @@ + + + + + 累计欠收 + receivable_report_client + + { + "sideBar": true, + "report_config": { + "model": "receivable.modify.line", + "method": "get_cum_arrears_report", + "dateMode": "single", + "columns": [ + {"headerName": "序号", "field": "序号", "width": 45, "flex": 0, "pinned": "left"}, + {"headerName": "合同编号", "field": "合同编号", "flex": 1.2}, + {"headerName": "物业名称", "field": "物业名称", "flex": 2.5}, + {"headerName": "承租方名称", "field": "承租方名称", "flex": 1.4}, + {"headerName": "收款类型", "field": "收款类型", "flex": 1}, + {"headerName": "未收金额", "field": "未收金额", "flex": 1, "type": "number"}, + {"headerName": "所属公司", "field": "所属公司", "flex": 1}, + {"headerName": "管理员", "field": "管理员", "flex": 1} + ] + } + } + + + + diff --git a/yuthon_report/views/cum_income_report.xml b/yuthon_report/views/cum_income_report.xml new file mode 100644 index 000000000..a2916dbf3 --- /dev/null +++ b/yuthon_report/views/cum_income_report.xml @@ -0,0 +1,8 @@ + + + + 累计收入 + receivable_report_client + {"report_config": {"model": "receivable.modify.line", "method": "get_cum_income_report", "columns": [{"headerName": "序号", "field": "序号", "width": 80, "flex": 0}, {"headerName": "合同编号", "field": "合同编号", "flex": 1}, {"headerName": "承租方名称", "field": "承租方名称", "flex": 1}, {"headerName": "物业名称", "field": "物业名称", "flex": 1}, {"headerName": "金额", "field": "金额", "flex": 1}]}} + + diff --git a/yuthon_report/views/menu.xml b/yuthon_report/views/menu.xml index b3f2a28c6..3e08126a8 100644 --- a/yuthon_report/views/menu.xml +++ b/yuthon_report/views/menu.xml @@ -62,12 +62,12 @@ sequence="8" name="累计欠收" parent="yuthon_property_income_menu" - action="action_report_placeholder"/> + action="action_cum_arrears_report"/> + action="action_cum_income_report"/>