Merge pull request !9157 from 李鹏宇/dev_wang
This commit is contained in:
李鹏宇
2026-03-03 04:07:45 +00:00
committed by Gitee
3 changed files with 117 additions and 65 deletions
@@ -188,42 +188,47 @@ class PropertyAccountsReceivable(models.Model):
}
def export_batch_invoice(self):
"""导出批量开票数据到Excel模板"""
# 获取勾选的应收账单记录
"""导出批量开票数据到新Excel模板"""
receivable_ids = self._context.get('active_ids', [])
if not receivable_ids:
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': '请先勾选需要导出的应收账单记录',
'title': '警告',
'message': '请至少选择一条应收账单记录',
'type': 'warning',
'sticky': False,
}
}
receivable_records = self.env['property.accounts.receivable'].browse(receivable_ids)
# 加载模板文件
template_path = get_module_resource('yuthon_property', 'static', 'tmpl', 'piliangkaipiao.xlsx')
workbook = load_workbook(template_path)
sheet = workbook.active
# 找到第一个数据行(跳过表头)
# 从第1行开始检查,找到第一个完全空的行作为数据起始行
start_row = 1
for row_idx in range(1, 100): # 最多检查100行
# 检查该行的前10列是否都为空
is_empty_row = all(sheet.cell(row=row_idx, column=col).value is None for col in range(1, 11))
if is_empty_row:
start_row = row_idx
break
# 如果没找到空行,默认从第2行开始(假设第1行是表头)
if start_row == 1:
start_row = 2
receivable_records = self.env['property.accounts.receivable'].browse(receivable_ids)
# 加载新模板文件
template_path = get_module_resource('yuthon_property', 'static', 'tmpl', 'piliangkaipiao (2).xlsx')
workbook = load_workbook(template_path)
# 指定写入"原始数据"sheet
sheet = workbook['原始数据']
row = start_row
# 从第3行开始写入数据(第1-2行是表头)
row = 3
# 获取模板样式行(使用第3行作为参考)
template_row_idx = 3
if sheet.max_row >= template_row_idx:
template_styles = {}
for col in range(1, 11): # A到J列(10列)
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 = []
@@ -233,69 +238,97 @@ class PropertyAccountsReceivable(models.Model):
for merged_range_str in merged_cells_to_unmerge:
sheet.unmerge_cells(merged_range_str)
# A列:序号
# 先为所有列应用模板样式并统一居中对齐
for col in range(1, 11): # A到J列
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列:序号(从1开始)
sheet.cell(row=row, column=1, value=index)
# B列:类型
acc_type_display = dict(record._fields['acc_type'].selection).get(record.acc_type, '') if record.acc_type else ''
sheet.cell(row=row, column=2, value=acc_type_display)
# B列:应收租期(due_date ~ due_date_end)
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列:应收时间段(due_date ~ due_date_end)
due_date_str = record.due_date.strftime('%Y-%m-%d') if record.due_date else ''
due_date_end_str = record.due_date_end.strftime('%Y-%m-%d') if record.due_date_end else ''
date_range = f"{due_date_str} ~ {due_date_end_str}" if due_date_str and due_date_end_str else due_date_str
sheet.cell(row=row, column=3, value=date_range)
# C列:承租方名称(lessee_id)
sheet.cell(row=row, column=3, value=record.lessee_id.name if record.lessee_id else '')
# D列:承租方
sheet.cell(row=row, column=4, value=record.lessee_id.name if record.lessee_id else '')
# D列:统一社会信用代码/纳税人识别号(从invoicing.information.line获取)
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=4, value=tax_number)
# E列:统一社会信用代码(从台账获取)
credit_code = ''
if record.contract_id and record.contract_id.lessee_id:
ledger = self.env['yuthon.property.ledger'].search([
('lessee_id', '=', record.contract_id.lessee_id.id)
], limit=1)
if ledger:
credit_code = ledger.credit_code or ''
sheet.cell(row=row, column=5, value=credit_code)
# E列:金额(original_amount)
sheet.cell(row=row, column=5, value=record.original_amount or 0)
# F列:当期金额
sheet.cell(row=row, column=6, value=record.original_amount or 0)
# F列:物业名称(property_name)
sheet.cell(row=row, column=6, value=record.property_name or '')
# G列:物业地址
sheet.cell(row=row, column=7, value=record.property_name or '')
# G列:产权证(title_information,从合同获取)
title_info = ''
if record.contract_id:
title_info = record.contract_id.title_information or ''
sheet.cell(row=row, column=7, value=title_info)
# H列:产权证信息
title_info = record.contract_id.title_information if record.contract_id else ''
sheet.cell(row=row, column=8, value=title_info or '')
# H列:发票类型(从invoicing.information.line的invoicing_type_id获取)
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=8, value=invoicing_type)
# I列:开票类型
invoicing_type_display = ''
if record.lessee_id and record.lessee_id.invoicing_type:
invoicing_type_display = dict(record.lessee_id._fields['invoicing_type'].selection).get(
record.lessee_id.invoicing_type, '')
sheet.cell(row=row, column=9, value=invoicing_type_display)
# I列:税率(从合同的tax_rate获取)
tax_rate_name = ''
if record.contract_id and record.contract_id.tax_rate:
tax_rate_name = record.contract_id.tax_rate.name or ''
sheet.cell(row=row, column=9, value=tax_rate_name)
# J列:税率
sheet.cell(row=row, column=10, value=record.tax or 0)
# J列:合同编号(contract_code,从合同获取)
contract_code = ''
if record.contract_id:
contract_code = record.contract_id.contract_code or ''
sheet.cell(row=row, column=10, value=contract_code)
# 设置最小行高
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)
excel_data = base64.b64encode(output.read())
output.close()
# 创建附件并返回下载
# 创建附件
filename = f'批量开票_{datetime.now().strftime("%Y%m%d")}.xlsx'
attachment = self.env['ir.attachment'].create({
'name': '批量开票数据.xlsx',
'name': filename,
'type': 'binary',
'datas': excel_data,
'mimetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'type': 'binary',
})
# 返回下载链接
return {
'type': 'ir.actions.act_url',
'url': f'/web/content/{attachment.id}?download=true',
@@ -1,5 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_meal_expenses_search" model="ir.ui.view">
<field name="name">yuthon.meal.expenses.search</field>
<field name="model">yuthon.meal.expenses</field>
<field name="arch" type="xml">
<search>
<field name="employee_id"/>
<field name="card"/>
<field name="select_year"/>
<field name="select_time"/>
<field name="company_id"/>
<field name="meal_state"/>
<group expand="0" string="分组">
<filter string="年份" name="group_by_year" context="{'group_by':'select_year'}"/>
<filter string="月份" name="group_by_month" context="{'group_by':'select_time'}"/>
<filter string="状态" name="group_by_state" context="{'group_by':'meal_state'}"/>
</group>
</search>
</field>
</record>
<record id="view_yuthon_meal_expenses_tree" model="ir.ui.view">
<field name="name">yuthon.meal.expenses.tree</field>
<field name="model">yuthon.meal.expenses</field>