211 lines
8.5 KiB
Python
211 lines
8.5 KiB
Python
# -*- 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',
|
||
}
|