784 lines
36 KiB
Python
784 lines
36 KiB
Python
# -*- 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)
|
||
]
|