Files
gzth/excel_report_designer/models/report_excel.py
T
2026-04-18 21:23:37 +08:00

1645 lines
71 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)
]
def get_xlsx_leave_report(self, data, response):
"""请假情况报表的自定义 Excel 样式"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('请假情况')
# 表头格式:加粗、居中、带边框、背景色
header_format = workbook.add_format({
'font_size': 11,
'bold': True,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'bg_color': '#D3D3D3'
})
# 数据格式:有边框、居中
data_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 日期时间格式
datetime_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd hh:mm'
})
# 日期格式
date_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd'
})
# 写表头(第一行)
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
# 设置列宽
if col == 0: # 发起人
sheet.set_column(col, col, 12)
elif col == 1: # 部门
sheet.set_column(col, col, 15)
elif col == 3: # 标题
sheet.set_column(col, col, 30)
elif col == 7: # 申请原因
sheet.set_column(col, col, 40)
else:
sheet.set_column(col, col, 15)
col += 1
# 取记录:优先用 active_ids
records = []
active_ids = data.get('active_ids')
if active_ids:
records = self.env[data['model_name']].browse(active_ids)
else:
records = self.env[data['model_name']].search([])
# 写数据行
row = 1
for rec in records:
col = 0
for field_name in data['fields_name']:
field = rec._fields.get(field_name)
value = rec[field_name]
# 根据字段类型处理值
if isinstance(value, datetime.datetime):
# datetime 类型:需要从 UTC 转换到用户时区
if value:
from pytz import UTC, timezone
tz = self.env.user.tz or 'Asia/Shanghai'
user_tz = timezone(tz)
value_utc = UTC.localize(value) if value.tzinfo is None else value
value_local = value_utc.astimezone(user_tz)
# 移除时区信息后写入 Excel
sheet.write_datetime(row, col, value_local.replace(tzinfo=None), datetime_format)
else:
sheet.write(row, col, '', data_format)
elif isinstance(value, datetime.date):
# date 类型
sheet.write_datetime(row, col, value, date_format)
elif field:
if field.type == 'many2one':
value = value.display_name if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'many2many':
value = ', '.join(value.mapped('display_name')) if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'selection':
# 获取选择字段的选项
selection_dict = dict(field.selection)
value = selection_dict.get(value, value or '')
sheet.write(row, col, value, data_format)
elif field.type == 'float':
# 浮点数保留2位小数
sheet.write(row, col, value or 0, data_format)
else:
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
# 给整表加筛选(类似 Excel 的筛选下拉箭头)
if row > 1: # 至少有一行数据
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
# 冻结首行(表头)
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_overtime_detail_report(self, data, response):
"""加班详情报表的自定义 Excel 样式"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('加班详情')
# 获取当前用户的时区(用于 datetime 转换)
tz = self.env.user.tz or 'Asia/Shanghai'
from pytz import timezone
user_tz = timezone(tz)
# 表头格式:加粗、居中、带边框、背景色
header_format = workbook.add_format({
'font_size': 11,
'bold': True,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'bg_color': '#D3D3D3'
})
# 数据格式:有边框、居中
data_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 日期时间格式
datetime_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd hh:mm'
})
# 日期格式
date_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd'
})
# 写表头(第一行)
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
# 设置列宽
if col == 0: # 员工
sheet.set_column(col, col, 15)
elif col == 1: # 部门
sheet.set_column(col, col, 18)
elif col == 2: # 审批类型
sheet.set_column(col, col, 18)
elif col in [3, 4]: # 开始/结束时间
sheet.set_column(col, col, 20)
elif col == 6: # 事由
sheet.set_column(col, col, 40)
else:
sheet.set_column(col, col, 15)
col += 1
# 取记录:优先用 active_ids
records = []
active_ids = data.get('active_ids')
if active_ids:
records = self.env[data['model_name']].browse(active_ids)
else:
records = self.env[data['model_name']].search([])
# 写数据行
row = 1
for rec in records:
col = 0
for field_name in data['fields_name']:
field = rec._fields.get(field_name)
value = rec[field_name]
# 根据字段类型处理值
if isinstance(value, datetime.datetime):
# datetime 类型:需要从 UTC 转换到用户时区
if value:
from pytz import UTC
value_utc = UTC.localize(value) if value.tzinfo is None else value
value_local = value_utc.astimezone(user_tz)
sheet.write_datetime(row, col, value_local.replace(tzinfo=None), datetime_format)
else:
sheet.write(row, col, '', data_format)
elif isinstance(value, datetime.date):
# date 类型
sheet.write_datetime(row, col, value, date_format)
elif field:
if field.type == 'many2one':
value = value.display_name if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'many2many':
value = ', '.join(value.mapped('display_name')) if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'selection':
# 获取选择字段的选项
selection_dict = dict(field.selection)
value = selection_dict.get(value, value or '')
sheet.write(row, col, value, data_format)
elif field.type == 'float':
# 浮点数保留2位小数
sheet.write(row, col, value or 0, data_format)
elif field.type == 'text':
# Text 字段(如事由)
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
# 给整表加筛选
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
# 冻结首行
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_overtime_report(self, data, response):
"""加班明细报表的自定义 Excel 样式"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('加班明细')
# 获取当前用户的时区(用于 datetime 转换)
tz = self.env.user.tz or 'Asia/Shanghai'
from pytz import timezone
user_tz = timezone(tz)
# 表头格式:加粗、居中、带边框、背景色
header_format = workbook.add_format({
'font_size': 11,
'bold': True,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'bg_color': '#D3D3D3'
})
# 数据格式:有边框、居中
data_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 日期时间格式
datetime_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd hh:mm'
})
# 日期格式
date_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd'
})
# 写表头(第一行)
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
# 设置列宽
if col == 0: # 员工
sheet.set_column(col, col, 15)
elif col == 1: # 部门
sheet.set_column(col, col, 18)
elif col == 2: # 加班时间
sheet.set_column(col, col, 20)
else:
sheet.set_column(col, col, 15)
col += 1
# 取记录:优先用 active_ids
records = []
active_ids = data.get('active_ids')
if active_ids:
records = self.env[data['model_name']].browse(active_ids)
else:
records = self.env[data['model_name']].search([])
# 写数据行
row = 1
for rec in records:
col = 0
for field_name in data['fields_name']:
field = rec._fields.get(field_name)
value = rec[field_name]
# 根据字段类型处理值
if isinstance(value, datetime.datetime):
# datetime 类型:需要从 UTC 转换到用户时区
if value:
from pytz import UTC
value_utc = UTC.localize(value) if value.tzinfo is None else value
value_local = value_utc.astimezone(user_tz)
# 移除时区信息后写入 Excel
sheet.write_datetime(row, col, value_local.replace(tzinfo=None), datetime_format)
else:
sheet.write(row, col, '', data_format)
elif isinstance(value, datetime.date):
# date 类型
sheet.write_datetime(row, col, value, date_format)
elif field:
if field.type == 'many2one':
value = value.display_name if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'many2many':
value = ', '.join(value.mapped('display_name')) if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'selection':
# 获取选择字段的选项
selection_dict = dict(field.selection)
value = selection_dict.get(value, value or '')
sheet.write(row, col, value, data_format)
elif field.type == 'float':
# 浮点数保留2位小数
sheet.write(row, col, value or 0, data_format)
else:
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
# 给整表加筛选(类似 Excel 的筛选下拉箭头)
if row > 1: # 至少有一行数据
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
# 冻结首行(表头)
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def _export_overtime_active(self, start_date, end_date):
"""导出加班归档明细"""
active_ids = json.loads(self.active_ids) if self.active_ids else []
domain = [
('carry_forward_date', '>=', start_date),
('carry_forward_date', '<', end_date),
('is_void', '=', False),
('carryover_type', '=', 'fee')
]
if active_ids:
domain = [('id', 'in', active_ids)] + domain
filtered_records = self.env['overtime.carryover'].search(domain)
if not filtered_records:
raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的加班归档记录')
export_data = []
for record in filtered_records:
over_line = record.over_line_id
# 提前处理时区转换,避免在Excel函数中重复处理
tz = self.env.user.tz or 'Asia/Shanghai'
user_tz = timezone(tz)
# 处理开始时间时区转换
start_time = ''
if over_line and over_line.start_time:
start_time_utc = UTC.localize(
over_line.start_time) if over_line.start_time.tzinfo is None else over_line.start_time
start_time = start_time_utc.astimezone(user_tz).replace(tzinfo=None)
# 处理结束时间时区转换
end_time = ''
if over_line and over_line.end_time:
end_time_utc = UTC.localize(
over_line.end_time) if over_line.end_time.tzinfo is None else over_line.end_time
end_time = end_time_utc.astimezone(user_tz).replace(tzinfo=None)
row_data = {
'employee_id': over_line.employee_id.name if over_line and over_line.employee_id else '',
'department_id': over_line.department_id.name if over_line and over_line.department_id else '',
'start_time': start_time,
'end_time': end_time,
'carry_forward_date': record.carry_forward_date,
'carry_forward_quantity': record.carry_forward_quantity or 0,
'carry_forward_state': dict(record._fields.get('carry_forward_state').selection).get(
record.carry_forward_state, '') if hasattr(record, 'carry_forward_state') else ''
}
export_data.append(row_data)
fields_name = ['employee_id', 'department_id', 'start_time', 'end_time',
'carry_forward_date', 'carry_forward_quantity', 'carry_forward_state']
field_label = ['姓名', '部门', '加班开始时间', '加班结束时间', '结转日期',
'结转时长', '结转结果']
data = {
'report_name': '加班归档明细报表',
'model_name': 'overtime.carryover',
'fields_name': fields_name,
'field_label': field_label,
'export_data': export_data, # 传递预处理好的数据
}
return {
'type': 'ir.actions.report',
'data': {
'model': 'report.excel',
'options': json.dumps(data, default=date_utils.json_default),
'output_format': 'xlsx',
'report_name': '加班归档明细报表',
},
'report_type': 'xlsx',
}
def get_xlsx_overtime_active_report(self, data, response):
"""加班归档明细报表的自定义 Excel 样式"""
import io
import xlsxwriter
from pytz import timezone, UTC
from datetime import datetime, date
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('加班归档明细')
# 获取当前用户的时区
tz = self.env.user.tz or 'Asia/Shanghai'
user_tz = timezone(tz)
# 表头格式
header_format = workbook.add_format({
'font_size': 11,
'bold': True,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'bg_color': '#D3D3D3'
})
# 数据格式
data_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 日期时间格式
datetime_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd hh:mm'
})
# 日期格式
date_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd'
})
# 写表头(保持原有样式)
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
# 设置列宽
if col == 0: # 姓名
sheet.set_column(col, col, 15)
elif col == 1: # 部门
sheet.set_column(col, col, 18)
elif col in [2, 3]: # 开始/结束时间
sheet.set_column(col, col, 20)
else:
sheet.set_column(col, col, 15)
col += 1
# ========== 核心修复:使用预处理的 export_data ==========
records_data = data.get('export_data', [])
# 写数据行
row = 1
for rec_data in records_data:
col = 0
for field_name in data['fields_name']:
value = rec_data.get(field_name, '')
# 根据值类型应用对应格式
if isinstance(value, datetime):
sheet.write_datetime(row, col, value, datetime_format)
elif isinstance(value, date):
sheet.write_datetime(row, col, value, date_format)
elif isinstance(value, float):
sheet.write(row, col, value, data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
# 给整表加筛选
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
# 冻结首行
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_overtime_detail_report(self, data, response):
"""加班详情报表的自定义 Excel 样式 - 对应 vacation.over.line.overtime.tree 视图"""
import io
import xlsxwriter
from datetime import datetime, date
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('加班详情')
header_format = workbook.add_format({
'font_size': 11, 'bold': True, 'border': 1,
'align': 'center', 'valign': 'vcenter', 'bg_color': '#D3D3D3'
})
data_format = workbook.add_format({
'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter'
})
datetime_format = workbook.add_format({
'font_size': 10, 'border': 1, 'align': 'center',
'valign': 'vcenter', 'num_format': 'yyyy-mm-dd hh:mm'
})
date_format = workbook.add_format({
'font_size': 10, 'border': 1, 'align': 'center',
'valign': 'vcenter', 'num_format': 'yyyy-mm-dd'
})
col_widths = [40, 12, 18, 20, 20, 8, 10, 12, 12, 10, 15]
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
width = col_widths[col] if col < len(col_widths) else 15
sheet.set_column(col, col, width)
col += 1
records_data = data.get('export_data', [])
row = 1
for rec_data in records_data:
col = 0
for field_name in data['fields_name']:
value = rec_data.get(field_name, '')
if isinstance(value, datetime):
sheet.write_datetime(row, col, value, datetime_format)
elif isinstance(value, date):
sheet.write_datetime(row, col, value, date_format)
elif isinstance(value, float):
sheet.write(row, col, value, data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_overtime_carryover_report(self, data, response):
"""加班结转明细报表的自定义 Excel 样式"""
import io
import xlsxwriter
from datetime import datetime, date
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('加班结转明细')
# 表头格式
header_format = workbook.add_format({
'font_size': 11,
'bold': True,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'bg_color': '#D3D3D3'
})
# 数据格式
data_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter'
})
# 日期时间格式
datetime_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd hh:mm'
})
# 日期格式
date_format = workbook.add_format({
'font_size': 10,
'border': 1,
'align': 'center',
'valign': 'vcenter',
'num_format': 'yyyy-mm-dd'
})
# 写表头
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
if col == 0: # 姓名
sheet.set_column(col, col, 15)
elif col == 1: # 部门
sheet.set_column(col, col, 18)
elif col in [2, 3]: # 开始/结束时间
sheet.set_column(col, col, 20)
else:
sheet.set_column(col, col, 15)
col += 1
# 写数据行(使用预处理的 export_data)
records_data = data.get('export_data', [])
row = 1
for rec_data in records_data:
col = 0
for field_name in data['fields_name']:
value = rec_data.get(field_name, '')
if isinstance(value, datetime):
sheet.write_datetime(row, col, value, datetime_format)
elif isinstance(value, date):
sheet.write_datetime(row, col, value, date_format)
elif isinstance(value, float):
sheet.write(row, col, value, data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
# 加筛选
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
# 冻结首行
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_meal_report(self, data, response):
"""误餐明细报表的自定义 Excel 样式"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('误餐明细')
tz = self.env.user.tz or 'Asia/Shanghai'
from pytz import timezone
user_tz = timezone(tz)
header_format = workbook.add_format({'font_size': 11, 'bold': True, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'bg_color': '#D3D3D3'})
data_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter'})
datetime_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'num_format': 'yyyy-mm-dd hh:mm'})
date_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'num_format': 'yyyy-mm-dd'})
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
if col == 0:
sheet.set_column(col, col, 15)
elif col == 1:
sheet.set_column(col, col, 18)
elif col == 2:
sheet.set_column(col, col, 18)
elif col in [3, 4]:
sheet.set_column(col, col, 20)
elif col == 6:
sheet.set_column(col, col, 40)
else:
sheet.set_column(col, col, 15)
col += 1
records = []
active_ids = data.get('active_ids')
if active_ids:
records = self.env[data['model_name']].browse(active_ids)
else:
records = self.env[data['model_name']].search([])
row = 1
for rec in records:
col = 0
for field_name in data['fields_name']:
field = rec._fields.get(field_name)
value = rec[field_name]
if isinstance(value, datetime.datetime):
if value:
from pytz import UTC
value_utc = UTC.localize(value) if value.tzinfo is None else value
value_local = value_utc.astimezone(user_tz)
sheet.write_datetime(row, col, value_local.replace(tzinfo=None), datetime_format)
else:
sheet.write(row, col, '', data_format)
elif isinstance(value, datetime.date):
sheet.write_datetime(row, col, value, date_format)
elif field:
if field.type == 'many2one':
value = value.display_name if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'many2many':
value = ', '.join(value.mapped('display_name')) if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'selection':
selection_dict = dict(field.selection)
value = selection_dict.get(value, value or '')
sheet.write(row, col, value, data_format)
elif field.type == 'float':
sheet.write(row, col, value or 0, data_format)
elif field.type == 'text':
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()
def get_xlsx_meal_active_report(self, data, response):
"""误餐归档明细报表的自定义 Excel 样式"""
output = io.BytesIO()
workbook = xlsxwriter.Workbook(output, {'in_memory': True})
sheet = workbook.add_worksheet('误餐归档明细')
tz = self.env.user.tz or 'Asia/Shanghai'
from pytz import timezone
user_tz = timezone(tz)
header_format = workbook.add_format({'font_size': 11, 'bold': True, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'bg_color': '#D3D3D3'})
data_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter'})
datetime_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'num_format': 'yyyy-mm-dd hh:mm'})
date_format = workbook.add_format({'font_size': 10, 'border': 1, 'align': 'center', 'valign': 'vcenter', 'num_format': 'yyyy-mm-dd'})
col = 0
for label in data['field_label']:
sheet.write(0, col, label, header_format)
if col == 0:
sheet.set_column(col, col, 15)
elif col == 1:
sheet.set_column(col, col, 18)
elif col == 2:
sheet.set_column(col, col, 18)
elif col in [3, 4]:
sheet.set_column(col, col, 20)
elif col == 6:
sheet.set_column(col, col, 40)
else:
sheet.set_column(col, col, 15)
col += 1
records = []
active_ids = data.get('active_ids')
if active_ids:
records = self.env[data['model_name']].browse(active_ids)
else:
records = self.env[data['model_name']].search([])
row = 1
for rec in records:
col = 0
for field_name in data['fields_name']:
# 特殊处理:vacation.meal.active 没有 department_id,通过 employee_id 获取
if field_name == 'department_id' and 'department_id' not in rec._fields:
value = rec.employee_id.department_id.display_name if rec.employee_id and rec.employee_id.department_id else ''
sheet.write(row, col, value, data_format)
col += 1
continue
field = rec._fields.get(field_name)
if not field:
sheet.write(row, col, '', data_format)
col += 1
continue
value = rec[field_name]
if isinstance(value, datetime.datetime):
if value:
from pytz import UTC
value_utc = UTC.localize(value) if value.tzinfo is None else value
value_local = value_utc.astimezone(user_tz)
sheet.write_datetime(row, col, value_local.replace(tzinfo=None), datetime_format)
else:
sheet.write(row, col, '', data_format)
elif isinstance(value, datetime.date):
sheet.write_datetime(row, col, value, date_format)
elif field:
if field.type == 'many2one':
value = value.display_name if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'many2many':
value = ', '.join(value.mapped('display_name')) if value else ''
sheet.write(row, col, value, data_format)
elif field.type == 'selection':
selection_dict = dict(field.selection)
value = selection_dict.get(value, value or '')
sheet.write(row, col, value, data_format)
elif field.type == 'float':
sheet.write(row, col, value or 0, data_format)
elif field.type == 'text':
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
else:
sheet.write(row, col, value or '', data_format)
col += 1
row += 1
if row > 1:
sheet.autofilter(0, 0, row - 1, len(data['field_label']) - 1)
sheet.freeze_panes(1, 0)
workbook.close()
output.seek(0)
response.stream.write(output.read())
output.close()