Files
gzth/yuthon_property/wizard/batch_invoice_export_wizard.py
T
2026-04-24 18:36:03 +08:00

211 lines
8.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from odoo.exceptions import UserError
import io
from copy import copy
from openpyxl import load_workbook
from openpyxl.styles import Alignment
from odoo.modules.module import get_module_resource
from datetime import datetime
import base64
class BatchInvoiceExportWizard(models.TransientModel):
_name = 'batch.invoice.export.wizard'
_description = '批量开票导出向导'
include_rent = fields.Boolean(string='租金', default=True)
include_deposit = fields.Boolean(string='押金', default=False)
include_penalty = fields.Boolean(string='违约金', default=False)
active_ids = fields.Char(string='选中记录IDs')
def action_export(self):
"""执行导出"""
active_ids = eval(self.active_ids) if self.active_ids else []
if not active_ids:
raise UserError('请至少选择一条应收账单记录')
receivable_records = self.env['property.accounts.receivable'].browse(active_ids)
# 按应收类型过滤
allowed = []
if self.include_rent:
allowed.append('租金')
if self.include_deposit:
allowed.append('押金')
if self.include_penalty:
allowed.append('违约金')
if allowed:
receivable_records = receivable_records.filtered(
lambda r: r.management_type_id and r.management_type_id.name in allowed
)
# allowed 为空时不过滤,导出全部
if not receivable_records:
raise UserError('过滤后没有符合条件的记录,请检查所选记录的应收类型')
# 加载模板文件
template_path = get_module_resource('yuthon_property', 'static', 'tmpl', 'piliangkaipiao (3).xlsx')
workbook = load_workbook(template_path)
sheet = workbook['原始数据']
# 统计最常见年月(用于B2)
year_month_list = []
for record in receivable_records:
if record.due_date:
year_month_list.append((record.due_date.year, record.due_date.month))
if year_month_list:
from collections import Counter
most_common_ym = Counter(year_month_list).most_common(1)[0][0]
title_year, title_month = most_common_ym
else:
now = datetime.now()
title_year, title_month = now.year, now.month
sheet['B2'] = f'{title_year}年{title_month}月'
# 统计最常见公司(用于C2,取合同出租方公司简称)
company_list = []
for record in receivable_records:
if record.contract_id and record.contract_id.lessor_id:
referred = record.contract_id.lessor_id.referred
if referred:
company_list.append(referred)
if company_list:
from collections import Counter
most_common_company = Counter(company_list).most_common(1)[0][0]
else:
most_common_company = ''
sheet['C2'] = most_common_company
# 统计最常见权属类型(用于文件名)
tenure_type_map = {'self': '自有', 'agency': '代管'}
tenure_list = []
for record in receivable_records:
if record.contract_id and record.contract_id.tenure_type:
tenure_list.append(record.contract_id.tenure_type)
if tenure_list:
from collections import Counter
most_common_tenure = Counter(tenure_list).most_common(1)[0][0]
tenure_type_label = tenure_type_map.get(most_common_tenure, most_common_tenure)
else:
tenure_type_label = ''
# 从第4行开始写入数据
row = 4
template_row_idx = 4
if sheet.max_row >= template_row_idx:
template_styles = {}
for col in range(1, 11):
cell = sheet.cell(row=template_row_idx, column=col)
template_styles[col] = {
'font': copy(cell.font),
'fill': copy(cell.fill),
'border': copy(cell.border),
'alignment': copy(cell.alignment),
}
else:
template_styles = {}
for index, record in enumerate(receivable_records, start=1):
merged_cells_to_unmerge = []
for merged_range in sheet.merged_cells.ranges:
if merged_range.min_row <= row <= merged_range.max_row:
merged_cells_to_unmerge.append(str(merged_range))
for merged_range_str in merged_cells_to_unmerge:
sheet.unmerge_cells(merged_range_str)
for col in range(1, 11):
cell = sheet.cell(row=row, column=col)
if col in template_styles:
cell.font = copy(template_styles[col]['font'])
cell.fill = copy(template_styles[col]['fill'])
cell.border = copy(template_styles[col]['border'])
cell.alignment = Alignment(horizontal='center', vertical='center')
# A列:序号
sheet.cell(row=row, column=1, value=index)
# B列:应收租期
date_range = ''
if record.due_date and record.due_date_end:
date_range = f"{record.due_date.strftime('%Y-%m-%d')} ~ {record.due_date_end.strftime('%Y-%m-%d')}"
elif record.due_date:
date_range = record.due_date.strftime('%Y-%m-%d')
sheet.cell(row=row, column=2, value=date_range)
# C列:金额(当期金额 - 减免金额)
sheet.cell(row=row, column=3, value=(record.original_amount or 0) - (record.discount_amount or 0))
# D列:物业名称
sheet.cell(row=row, column=4, value=record.property_name or '')
# E列:合同编号
contract_code = record.contract_id.contract_code if record.contract_id else ''
sheet.cell(row=row, column=5, value=contract_code)
# F列:承租方名称
sheet.cell(row=row, column=6, value=record.lessee_id.name if record.lessee_id else '')
# G列:统一社会信用代码/纳税人识别号
tax_number = ''
if record.lessee_id and record.lessee_id.invoicing_ids:
for inv_line in record.lessee_id.invoicing_ids:
if inv_line.tax_number:
tax_number = inv_line.tax_number
break
sheet.cell(row=row, column=7, value=tax_number)
# H列:产权证
title_info = record.contract_id.title_information if record.contract_id else ''
sheet.cell(row=row, column=8, value=title_info)
# I列:发票类型
invoicing_type = ''
if record.lessee_id and record.lessee_id.invoicing_ids:
for inv_line in record.lessee_id.invoicing_ids:
if inv_line.invoicing_type_id:
invoicing_type = inv_line.invoicing_type_id.name or ''
break
sheet.cell(row=row, column=9, value=invoicing_type)
# J列:税率
tax_rate_value = 0.0
if record.contract_id and record.contract_id.tax_rate:
tax_rate_name = record.contract_id.tax_rate.name or '0'
try:
if '%' in tax_rate_name:
tax_rate_value = float(tax_rate_name.replace('%', '')) / 100
else:
tax_rate_value = float(tax_rate_name)
except (ValueError, TypeError):
tax_rate_value = 0.0
sheet.cell(row=row, column=10, value=tax_rate_value)
if not sheet.row_dimensions[row].height or sheet.row_dimensions[row].height < 30:
sheet.row_dimensions[row].height = 30
row += 1
output = io.BytesIO()
workbook.save(output)
output.seek(0)
file_data = base64.b64encode(output.read()).decode('utf-8')
attachment = self.env['ir.attachment'].create({
'name': f'{most_common_company}_{tenure_type_label}_{title_year}年{title_month}月批量开票.xlsx',
'type': 'binary',
'datas': file_data,
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
})
return {
'type': 'ir.actions.act_url',
'url': f'/web/content/{attachment.id}?download=true',
'target': 'self',
}