diff --git a/yuthon_property/models/property_accounts_receivable.py b/yuthon_property/models/property_accounts_receivable.py index 729767ffc..1a2c273af 100644 --- a/yuthon_property/models/property_accounts_receivable.py +++ b/yuthon_property/models/property_accounts_receivable.py @@ -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', diff --git a/yuthon_property/static/tmpl/piliangkaipiao.xlsx b/yuthon_property/static/tmpl/piliangkaipiao (2).xlsx similarity index 56% rename from yuthon_property/static/tmpl/piliangkaipiao.xlsx rename to yuthon_property/static/tmpl/piliangkaipiao (2).xlsx index c41b6e4ca..efbdd3753 100644 Binary files a/yuthon_property/static/tmpl/piliangkaipiao.xlsx and b/yuthon_property/static/tmpl/piliangkaipiao (2).xlsx differ diff --git a/yuthon_salary/views/yuthon_meal_expenses_views.xml b/yuthon_salary/views/yuthon_meal_expenses_views.xml index e4812f211..15fc0a8ab 100644 --- a/yuthon_salary/views/yuthon_meal_expenses_views.xml +++ b/yuthon_salary/views/yuthon_meal_expenses_views.xml @@ -1,5 +1,24 @@ + + yuthon.meal.expenses.search + yuthon.meal.expenses + + + + + + + + + + + + + + + + yuthon.meal.expenses.tree yuthon.meal.expenses