diff --git a/yuthon_property/models/property_accounts_receivable.py b/yuthon_property/models/property_accounts_receivable.py index 4b7ebadf3..fa978fb60 100644 --- a/yuthon_property/models/property_accounts_receivable.py +++ b/yuthon_property/models/property_accounts_receivable.py @@ -1,5 +1,8 @@ from odoo import models, fields, api import xlwt +import base64 +import io +from openpyxl import load_workbook from odoo.modules import get_module_resource from datetime import datetime, timedelta, date @@ -168,3 +171,118 @@ class PropertyAccountsReceivable(models.Model): 'default_name' : self.receivable_log } } + + def export_batch_invoice(self): + """导出批量开票数据到Excel模板""" + # 获取勾选的应收账单记录 + receivable_ids = self._context.get('active_ids', []) + if not receivable_ids: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + '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 + + row = start_row + 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) + + # A列:序号 + 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) + + # 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) + + # D列:承租方 + sheet.cell(row=row, column=4, value=record.lessee_id.name if record.lessee_id else '') + + # 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) + + # F列:当期金额 + sheet.cell(row=row, column=6, value=record.original_amount or 0) + + # G列:物业地址 + sheet.cell(row=row, column=7, value=record.property_name or '') + + # H列:产权证信息 + title_info = record.contract_id.title_information if record.contract_id else '' + sheet.cell(row=row, column=8, value=title_info or '') + + # 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) + + # J列:税率 + sheet.cell(row=row, column=10, value=record.tax or 0) + + row += 1 + + # 保存到内存 + output = io.BytesIO() + workbook.save(output) + output.seek(0) + excel_data = base64.b64encode(output.read()) + output.close() + + # 创建附件并返回下载 + attachment = self.env['ir.attachment'].create({ + 'name': '批量开票数据.xlsx', + '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', + 'target': 'self', + } diff --git a/yuthon_property/static/tmpl/piliangkaipiao.xlsx b/yuthon_property/static/tmpl/piliangkaipiao.xlsx new file mode 100644 index 000000000..c41b6e4ca Binary files /dev/null and b/yuthon_property/static/tmpl/piliangkaipiao.xlsx differ diff --git a/yuthon_property/views/property_accounts_receivable_views.xml b/yuthon_property/views/property_accounts_receivable_views.xml index 8a4f91bcc..c0b48def5 100644 --- a/yuthon_property/views/property_accounts_receivable_views.xml +++ b/yuthon_property/views/property_accounts_receivable_views.xml @@ -154,4 +154,12 @@ ]]> + + 批量开票导出 + + + list + code + action = model.export_batch_invoice() + \ No newline at end of file