Files
gzth/excel_report_designer/models/report_excel.py
T
2025-06-01 18:55:49 +08:00

784 lines
36 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 -*-
################################################################################
#
# Cybrosys Technologies Pvt. Ltd.
#
# Copyright (C) 2024-TODAY Cybrosys Technologies (<https://www.cybrosys.com>).
# Author: Cybrosys Techno Solutions (<https://www.cybrosys.com>)
#
# This program is free software: you can modify
# it under the terms of the GNU Affero General Public License (AGPL) as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
################################################################################
from odoo import api, fields, models, _
try:
from odoo.tools.misc import xlsxwriter
except ImportError:
import xlsxwriter
import io
import json
import datetime
from odoo.tools import date_utils
from collections import defaultdict
from odoo.exceptions import UserError
class ReportExcel(models.Model):
"""this is used to create the a binding action"""
_name = 'report.excel'
_description = "report excel"
_rec_name = 'name'
name = fields.Char(string='名称', help='Name')
model_id = fields.Many2one('ir.model', string='模型',
required=True,
ondelete="cascade", help="The binding model")
fields_ids = fields.Many2many('ir.model.fields',
string='字段',
required=True, ondelete="cascade",
help="the fields to be printed in the report")
date_field = fields.Many2one('ir.model.fields',
string='日期筛选器',
ondelete="cascade",
help="filter on the basis of date")
start_date = fields.Date(string='开始日期', help="start date")
end_date = fields.Date(string='结束日期', help="end date")
field_order = fields.Char(string="字段顺序", default='[]',
help="the field order")
action_button = fields.Boolean(default=False, string="动作",
help="visibility of action")
state = fields.Selection([
('code', '执行 Python 代码'),
('object_create', '创建新记录'),
('object_write', '更新记录'),
('multi', '执行多个作')], string='要执行的动作',
default='code', required=True, copy=True,
help="根据")
binding_model_id = fields.Many2one('ir.model',
ondelete="cascade",
string="绑定模型 ID",
help="绑定模型 ID")
binding_type = fields.Selection([('action', 'Action'),
('report', 'Report')],
required=True, default='action',
string="绑定类型",
help="绑定类型")
ir_act_server_ref = fields.Many2one('ir.actions.act_window',
readonly=True,
copy=False, string="作参考",
help="作参考")
fields_ids_domain = fields.Char(compute="_compute_fields_ids_domain",
readonly=True, store=False)
date_field_domain = fields.Char(compute="_compute_date_field_domain",
readonly=True, store=False)
@api.depends('model_id')
def _compute_fields_ids_domain(self):
"""Domain for fields_ids"""
for rec in self:
rec.fields_ids_domain = json.dumps(
[('model_id', '=', rec.model_id.id)]
)
@api.depends('model_id')
def _compute_date_field_domain(self):
"""Domain for date_field"""
for rec in self:
rec.date_field_domain = json.dumps(
[('model_id', '=', rec.model_id.id),
('ttype', 'in', ['date', 'datetime'])]
)
@api.onchange('model_id')
def onchange_model_id(self):
"""This is used to return the fields"""
if self.model_id:
self.name = self.model_id.name + ' Report'
self.fields_ids = False
self.date_field = False
def print_report(self):
"""Create a function for printing reports"""
for rec in self:
data = {
'report_name': rec.name,
'model_name': rec.model_id.model,
'fields_name': rec.fields_ids.mapped('name'),
'field_label': rec.fields_ids.mapped('field_description'),
'date_field': rec.date_field.name,
'date_name': rec.date_field.field_description,
'start_date': rec.start_date,
'end_date': rec.end_date,
'field_order': rec.field_order
}
return {
'type': 'ir.actions.report',
'data': {'model': 'report.excel',
'options': json.dumps(data, default=date_utils.json_default),
'output_format': 'xlsx',
'report_name': rec.name,
},
'report_type': 'xlsx',
}
def get_xlsx_report(self, data, response):
"""this is used to print the report of all records"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet()
# Formats
format1 = workbook.add_format(
{'font_size': 15, 'align': 'center', 'bold': True})
format1.set_font_color('#000080')
format2 = workbook.add_format(
{'font_size': 11, 'bold': True, 'border': 1, 'bg_color': '#928E8E'})
format4 = workbook.add_format(
{'font_size': 10, 'num_format': 'yyyy-m-d', 'align': 'center',
'bold': True})
format5 = workbook.add_format(
{'font_size': 10, 'border': 1, 'text_wrap': True})
format6 = workbook.add_format({'font_size': 10, 'bold': True})
format8 = workbook.add_format({'font_size': 10, 'border': 1})
format9 = workbook.add_format(
{'font_size': 10, 'num_format': 'yyyy-m-d'})
format10 = workbook.add_format(
{'font_size': 10, 'num_format': 'yyyy-m-d', 'border': 1})
format2.set_align('center')
format4.set_align('right')
format6.set_align('right')
format8.set_align('left')
sheet.merge_range(1, 1, 1, len(data['field_label']) + 1,
data['report_name'], format1)
sheet.write(2, 0, "Date :", format4)
sheet.write(2, 1, fields.Datetime.today(), format4)
if data['date_field']:
sheet.write(3, 0, data['date_name'], format4)
if data['start_date']:
sheet.write(3, 1, "From:", format4)
sheet.write(3, 2, data['start_date'], format9)
else:
sheet.write(3, 2, "", format9)
if data['end_date']:
sheet.write(3, 3, "To:", format4)
sheet.write(3, 4, data['end_date'], format9)
else:
sheet.write(3, 4, "", format9)
sl_no = 1
sheet.write(5, 1, "SL No", format2)
row_num = 5
col_num = 2
order = data['field_order'].strip('][').split(', ')
for field_id in order:
field_name = self.env['ir.model.fields'].browse(
int(field_id)).field_description
sheet.write(row_num, col_num, field_name, format2)
col_num += 1
row_num += 1
records = []
if data['date_field']:
if data['start_date'] and data['end_date']:
records = self.env[data['model_name']].search([
(data['date_field'], '>=', data['start_date']),
(data['date_field'], '<=', data['end_date'])])
elif data['start_date'] and not data['end_date']:
records = self.env[data['model_name']].search([
(data['date_field'], '>=', data['start_date'])])
elif not data['start_date'] and data['end_date']:
records = self.env[data['model_name']].search([
(data['date_field'], '<=', data['end_date'])])
else:
records = self.env[data['model_name']].search([])
new_table = []
for record in records:
order = data['field_order'].strip('][').split(', ')
record_dict = {}
for field_id in order:
field = self.env['ir.model.fields'].browse(int(field_id))
field_name = field.name
field_type = self.env['ir.model.fields'].browse(
int(field_id)).ttype
if field_type in ['many2many']:
one2many_values = ', '.join(
record[field_name].mapped('name'))
record_dict[field] = [one2many_values]
elif field_type in ['one2many']:
o2m_list = []
for rec in record[field_name]:
if rec:
o2m_list.append(rec)
else:
o2m_list.append('')
record_dict[field] = o2m_list
else:
record_dict[field] = [record[field_name]]
new_table.append(record_dict)
for record in new_table:
col_num = 1
sheet.write(row_num, col_num, sl_no, format5)
col_num += 1
occupied_rows = max(len(value) for value in record.values())
for field in record:
field_type = self.env['ir.model.fields'].browse(
int(field.id)).ttype
if not field_type in ['one2many', 'many2many']:
try:
if isinstance(record[field][0], datetime.date):
sheet.write(row_num, col_num, record[field][0],
format10)
elif isinstance(record[field][0], bool):
if not field:
sheet.write(row_num, col_num, " ", format5)
else:
sheet.write(row_num, col_num, "Yes", format5)
else:
sheet.write(row_num, col_num, record[field][0],
format5)
except Exception as e:
if 'display_name' in record[field][0]:
sheet.write(row_num, col_num,
record[field][0].display_name, format5)
else:
sheet.write(row_num, col_num, "", format5)
elif field_type == 'one2many':
for i in range(occupied_rows):
if len(record[field]) > i:
try:
if isinstance(record[field][i], datetime.date):
sheet.write(row_num + i, col_num,
record[field][i], format10)
elif isinstance(record[field][i], bool):
if not field:
sheet.write(row_num + i, col_num, "NO",
format5)
else:
sheet.write(row_num + i, col_num, "Yes",
format5)
else:
sheet.write(row_num + i, col_num,
record[field][i], format5)
except Exception as e:
if 'display_name' in record[field][i]:
sheet.write(row_num + i, col_num,
record[field][i].display_name, format5)
else:
sheet.write(row_num + i, col_num, "",
format5)
elif field_type == 'many2many':
if record[field]:
try:
if isinstance(record[field], datetime.date):
sheet.write(row_num, col_num, record[field][0],
format10)
elif isinstance(record[field], bool):
if not field:
sheet.write(row_num, col_num, " ", format5)
else:
sheet.write(row_num, col_num, "Yes",
format5)
else:
sheet.write(row_num, col_num, record[field][0],
format5)
except Exception as e:
if 'display_name' in record[field][0]:
sheet.write(row_num, col_num,
record[field].display_name, format5)
else:
sheet.write(row_num, col_num, "", format5)
col_num += 1
row_num += occupied_rows
sl_no += 1
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def create_model_action(self):
""" Create a contextual action for each server action."""
self.action_button = True
WindowAction = self.env['ir.actions.act_window']
data = self.env['ir.model.data']
for rec in self.browse(self._ids):
binding_model_id = rec.model_id.id
model_data_id = data._load_xmlid('excel_report_designer')
res_id = data.browse(model_data_id).res_id
button_name = _('打印报告 (%s)') % rec.name
act_id = WindowAction.create({
'name': button_name,
'type': 'ir.actions.act_window',
'res_model': 'excel.report.wizards',
'binding_model_id': binding_model_id,
'context': "{'excel' : %d}" % (rec.id),
'view_mode': 'form,tree',
'view_id': res_id,
'target': 'new',
})
rec.write({
'ir_act_server_ref': act_id.id,
})
return True
def unlink_model_action(self):
""" Remove the contextual actions created for the server actions. """
self.action_button = False
self.check_access_rights('write', raise_exception=True)
self.filtered('binding_model_id').write({'binding_model_id': False})
self.ir_act_server_ref.unlink()
return {
'type': 'ir.actions.client',
'tag': 'reload',
}
@api.onchange('fields_ids')
def onchange_fields(self):
"""this is used to find the fields of new models"""
self.fields_ids = []
if self.fields_ids:
self.field_order = str(self.fields_ids._origin.ids)
def get_xlsx_salary_report(self, data, response):
company_name = self.env.user.company_id.name
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True, 'default_sheet_index': -1})
# 定义基本格式
title_format = workbook.add_format({
'font_size': 16,
'align': 'center',
'valign': 'vcenter',
'bold': True
})
# 定义序号列的格式,使其居中
serial_number_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 定义备注格式
note_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'left',
'valign': 'top',
'text_wrap': True
})
# 定义审批字段格式
approval_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 定义靠右无边框显示的格式
right_align_format = workbook.add_format({
'font_size': 10,
'align': 'right',
'valign': 'vcenter'
})
# 定义靠右有边框显示的格式
right_format = workbook.add_format({
'font_size': 10,
'align': 'right',
'valign': 'vcenter',
'border': 1,
})
header_format = workbook.add_format({'font_size': 11, 'bold': True, 'border': 1})
data_format = workbook.add_format({'font_size': 10, 'border': 1})
domain = self._get_payslip_domain(data)
records = self.env[data['model_name']].search(domain)
# # 按 salary_group_id 分组
grouped_records = defaultdict(list)
for record in records:
group_id = record.salary_group_id.id if record.salary_group_id else None
grouped_records[group_id].append(record)
for group_id, group_records in grouped_records.items():
if group_id:
group_name = self.env['yuthon.salary.group'].browse(group_id).name
else:
group_name = '未分组'
# 创建新的工作表
sheet = workbook.add_worksheet(group_name[:31])
# 写入公司和年月信息
headers = ['序号'] + data['field_label']
columns_data = [[] for _ in range(len(headers) - 1)]
float_totals = [0] * (len(headers) - 1)
current_year = int(data['current_year'])
current_month = int(data['current_month'])
valid_columns = []
for index, field_name in enumerate(data['fields_name']):
field = self.env[data['model_name']]._fields.get(field_name)
if not field:
continue
# 检查字段是否存在于 yuthon.payroll.common 和 yuthon.payroll.temporary 模型中
common_model = self.env['yuthon.payroll.common']
temp_model = self.env['yuthon.payroll.temporary']
common_records = common_model.search([('name', '=', field.string)])
temp_records = temp_model.search([('name', '=', field.string)])
if not common_records and not temp_records:
# 若两个模型中都没找到该字段,跳过日期判断,直接认为有效
valid_columns.append(index)
continue
valid = False
if field.type == 'float':
# 分别处理 common_records
for rec in common_records:
start_date = rec.start_date
end_date = rec.end_date
if not end_date:
end_date = datetime.date(current_year + 99, current_month, 1)
if start_date and end_date:
start_year_month = (start_date.year, start_date.month)
end_year_month = (end_date.year, end_date.month)
current_year_month = (current_year, current_month)
if start_year_month <= current_year_month <= end_year_month:
valid = True
break
# 如果 common_records 未找到有效记录,再处理 temp_records
if not valid:
for rec in temp_records:
start_date = rec.start_date
end_date = rec.end_date
if not end_date:
end_date = datetime.date(current_year + 99, current_month, 1)
if start_date and end_date:
start_year_month = (start_date.year, start_date.month)
end_year_month = (end_date.year, end_date.month)
current_year_month = (current_year, current_month)
if start_year_month <= current_year_month <= end_year_month:
valid = True
break
else:
valid = True
if valid:
valid_columns.append(index)
for record in group_records:
col_num = 1
for index, field_name in enumerate(data['fields_name']):
if index in valid_columns and index < len(columns_data):
value = record[field_name]
columns_data[index].append(value)
field = self.env[data['model_name']]._fields.get(field_name)
if field and field.type == 'float':
float_totals[index] += float(value) if value else 0
col_num += 1
# 记录实际显示的列索引
visible_cols = [0]
first_float_col_index = None
for index in range(len(headers) - 1):
if index in valid_columns and not all([x == 0 for x in columns_data[index]]):
visible_cols.append(index + 1)
if first_float_col_index is None and self.env[data['model_name']]._fields[
data['fields_name'][index]].type == 'float':
first_float_col_index = len(visible_cols) - 1
actual_header_length = len(visible_cols)
# 写入大表头,根据实际显示列数确定合并范围
company_year_month_text = f'员工{data["current_year"]}年{data["current_month"]}月工资表({company_name})'
sheet.merge_range(0, 0, 0, actual_header_length - 1, company_year_month_text, title_format)
# 添加单位和日期
current_date_str = datetime.datetime.now().strftime('%Y年%m月%d日')
sheet.write(1, actual_header_length - 1, f"单位:元 日期:{current_date_str}", right_align_format)
# 写入表头
col_num = 0
for index in visible_cols:
sheet.write(2, col_num, headers[index], header_format)
col_num += 1
# 写入数据
row_num = 3
serial_number = 1
for record in group_records:
col_num = 0
sheet.write(row_num, col_num, serial_number, serial_number_format)
col_num += 1
for index in visible_cols[1:]:
field_name = data['fields_name'][index - 1]
field = self.env[data['model_name']]._fields.get(field_name)
value = record[field_name]
if isinstance(value, datetime.date):
value = value.strftime('%Y-%m-%d')
elif field:
if field.type == 'many2one':
if value:
value = value.display_name
else:
value = ""
elif field.type == 'many2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'one2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'selection':
# 获取选择字段的选项列表
selection_options = dict(field.selection)
# 将值转换为对应的显示标签
value = selection_options.get(value, value)
sheet.write(row_num, col_num, value, data_format)
col_num += 1
row_num += 1
serial_number += 1 # 序号递增
if first_float_col_index is not None:
# 合并从首列到首个float类型字段所在列的前一列的单元格
sheet.merge_range(row_num, 0, row_num, first_float_col_index - 1, "总计", serial_number_format)
# 调整起始列索引
start_col = first_float_col_index
for col_index, visible_col in enumerate(visible_cols[first_float_col_index:]):
field_name = data['fields_name'][visible_col - 1]
field = self.env[data['model_name']]._fields.get(field_name)
if field and field.type == 'float':
# 计算正确的列索引
target_col = start_col + col_index
sheet.write(row_num, target_col, float_totals[visible_col - 1], right_format)
else:
# 计算正确的列索引
target_col = start_col + col_index
sheet.write(row_num, target_col, "", right_format)
# 写入备注,根据实际显示列数确定合并范围
start_row = row_num + 1
end_row = start_row + 4
note_text = "备注"
sheet.merge_range(start_row, 0, end_row, actual_header_length - 1, note_text, note_format)
col_num = 0
# 记录表头实际占据的列数
header_column_count = len(visible_cols)
for index in visible_cols:
sheet.write(2, col_num, headers[index], header_format)
col_num += 1
# 添加审批字段行
approval_row = end_row + 1
approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"]
approval_text = " ".join(approval_fields)
# 合并单元格,使其宽度和表头一致
sheet.merge_range(approval_row, 0, approval_row, header_column_count - 1, approval_text, approval_format)
# 创建劳务信息工作表
labor_sheet = workbook.add_worksheet('劳务信息')
labor_fields = ['employee_id', 'labor_amount']
labor_labels = ['员工姓名', '劳务金额']
labor_headers = ['序号'] + labor_labels
# 写入劳务费大标题
labor_title = f'员工{data["current_year"]}年{data["current_month"]}月劳务费({company_name})'
labor_sheet.merge_range(0, 0, 0, len(labor_headers) - 1, labor_title, title_format)
title_width = len(labor_title) + 2 # 适当增加一些宽度余量
labor_sheet.set_column(0, len(labor_headers) - 1, title_width)
# 添加单位和日期信息
current_time = datetime.datetime.now().strftime('%Y-%m-%d')
info_text = f"单位:元 日期:{current_time}"
labor_sheet.merge_range(1, 0, 1, len(labor_headers) - 1, info_text, right_align_format)
# 写入表头
col_num = 0
for label in labor_headers:
labor_sheet.write(2, col_num, label, header_format)
col_num += 1
labor_records = self.env['yuthon.payroll.labor'].search([
('current_year', '=', data['current_year']),
('current_month', '=', data['current_month'])
])
row_num = 3
serial_number = 1
column_totals = [0] * len(labor_headers)
first_float_col_index = None
# 找出首个 float 类型字段所在列的索引
for col_index, field_name in enumerate(['serial_number'] + labor_fields):
field = self.env['yuthon.payroll.labor']._fields.get(field_name) if field_name in labor_fields else None
if field and field.type == 'float':
first_float_col_index = col_index
break
for labor_record in labor_records:
col_num = 0
labor_sheet.write(row_num, col_num, serial_number, serial_number_format)
if first_float_col_index is not None and col_num >= first_float_col_index:
try:
column_totals[col_num] += float(serial_number)
except (ValueError, TypeError):
pass
col_num += 1
for field_name in labor_fields:
field = self.env['yuthon.payroll.labor']._fields.get(field_name)
value = labor_record[field_name]
if isinstance(value, datetime.date):
value = value.strftime('%Y-%m-%d')
elif field:
if field.type == 'many2one':
if value:
value = value.display_name
else:
value = ""
elif field.type == 'many2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'one2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'selection':
selection_options = dict(field.selection)
value = selection_options.get(value, value)
labor_sheet.write(row_num, col_num, value, data_format)
if first_float_col_index is not None and col_num >= first_float_col_index:
try:
column_totals[col_num] += float(value)
except (ValueError, TypeError):
pass
col_num += 1
row_num += 1
serial_number += 1
# 写入总计行
total_row = row_num
if first_float_col_index is not None:
labor_sheet.merge_range(total_row, 0, total_row, first_float_col_index - 1, '合计', serial_number_format)
for col_num in range(first_float_col_index, len(labor_headers)):
labor_sheet.write(total_row, col_num, column_totals[col_num], data_format)
end_row = total_row # 假设 end_row 是总计行
approval_row = end_row + 1
approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"]
approval_text = " ".join(approval_fields)
# 合并单元格,使其宽度和表头一致
labor_sheet.merge_range(approval_row, 0, approval_row, len(labor_headers) - 1, approval_text, approval_format)
# 创建退休人员费用工作表
retired_sheet = workbook.add_worksheet('退休人员费用')
retired_fields = ['name', 'retired_amount']
retired_labels = ['姓名', '费用']
retired_headers = ['序号'] + retired_labels
# 根据标题长度动态计算并设置列宽
retired_title = f'员工{data["current_year"]}年{data["current_month"]}月退休费用({company_name})'
retired_sheet.merge_range(0, 0, 0, len(labor_headers) - 1, retired_title, title_format)
title_width = len(retired_title) + 2 # 适当增加一些宽度余量
retired_sheet.set_column(0, len(retired_headers) - 1, title_width)
# 添加单位和日期信息
current_time = datetime.datetime.now().strftime('%Y-%m-%d')
info_text = f"单位:元 日期:{current_time}"
retired_sheet.merge_range(1, 0, 1, len(retired_headers) - 1, info_text, right_align_format)
# 写入表头
col_num = 0
for label in retired_headers:
retired_sheet.write(2, col_num, label, header_format)
col_num += 1
retired_records = self.env['yuthon.payroll.retired'].search([
('current_year', '=', data['current_year']),
('current_month', '=', data['current_month'])
])
row_num = 3
serial_number = 1
column_totals = [0] * len(retired_headers)
first_float_col_index = None
for col_index, field_name in enumerate(['serial_number'] + retired_fields):
field = self.env['yuthon.payroll.retired']._fields.get(field_name) if field_name in retired_fields else None
if field and field.type == 'float':
first_float_col_index = col_index
break
for retired_record in retired_records:
col_num = 0
retired_sheet.write(row_num, col_num, serial_number, serial_number_format)
if first_float_col_index is not None and col_num >= first_float_col_index:
try:
column_totals[col_num] += float(serial_number)
except (ValueError, TypeError):
pass
col_num += 1
for field_name in retired_fields:
field = self.env['yuthon.payroll.retired']._fields.get(field_name)
value = retired_record[field_name]
if isinstance(value, datetime.date):
value = value.strftime('%Y-%m-%d')
elif field:
if field.type == 'many2one':
if value:
value = value.display_name
else:
value = ""
elif field.type == 'many2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'one2many':
value = ', '.join([rec.display_name for rec in value]) if value else ""
elif field.type == 'selection':
selection_options = dict(field.selection)
value = selection_options.get(value, value)
retired_sheet.write(row_num, col_num, value, data_format)
if first_float_col_index is not None and col_num >= first_float_col_index:
try:
column_totals[col_num] += float(value)
except (ValueError, TypeError):
pass
col_num += 1
row_num += 1
serial_number += 1
# 写入总计行
total_row = row_num
if first_float_col_index is not None:
retired_sheet.merge_range(total_row, 0, total_row, first_float_col_index - 1, '合计', serial_number_format)
for col_num in range(first_float_col_index, len(retired_headers)):
retired_sheet.write(total_row, col_num, column_totals[col_num], data_format)
end_row = total_row # 假设 end_row 是总计行
approval_row = end_row + 1
approval_fields = ["领导审批:", "财务审核:", "部门审核:", "制表:"]
approval_text = " ".join(approval_fields)
# 合并单元格,使其宽度和表头一致
retired_sheet.merge_range(approval_row, 0, approval_row, len(labor_headers) - 1, approval_text, approval_format)
# 关闭工作簿
workbook.close()
# 将文件指针移动到文件开头
output.seek(0)
# 将文件内容写入响应流
response.stream.write(output.read())
# 关闭文件对象
output.close()
def _get_payslip_domain(self, data):
current_month = data['current_month']
current_year = data['current_year']
# 把数字月份转换为中文月份
month_mapping = {
'1': '一月',
'2': '二月',
'3': '三月',
'4': '四月',
'5': '五月',
'6': '六月',
'7': '七月',
'8': '八月',
'9': '九月',
'10': '十月',
'11': '十一月',
'12': '十二月'
}
chinese_month = month_mapping.get(current_month)
target_name = f"{current_year}年-{chinese_month}"
return [
('is_salary_push', '=', True),
('is_salary_release', '=', False),
('payslip_run_id.name', '=', target_name)
]