添加文件

This commit is contained in:
李鹏宇
2026-02-09 00:05:53 +08:00
parent 969db3549a
commit 2c4af33fa8
10 changed files with 284 additions and 1 deletions
@@ -1351,3 +1351,82 @@ class ReportExcel(models.Model):
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']:
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()
+2
View File
@@ -113,6 +113,8 @@
'yuthon_hr_attendance/static/src/xml/button_overtime_export.xml',
'yuthon_hr_attendance/static/src/js/button_overtime_active_export.js',
'yuthon_hr_attendance/static/src/xml/button_overtime_active_export.xml',
'yuthon_hr_attendance/static/src/js/button_meal_active_export.js',
'yuthon_hr_attendance/static/src/xml/button_meal_active_export.xml',
'yuthon_hr_attendance/static/src/js/button_vacation_put.js',
'yuthon_hr_attendance/static/src/xml/button_vacation_put.xml',
'yuthon_hr_attendance/static/src/planning_gantt/planning_gantt_controller.xml',
@@ -0,0 +1,35 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomMealActiveExportController extends ListController {
setup() {
super.setup();
}
async exportMealActiveExcel() {
const selectedIds = await this.getSelectedResIds();
return this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'export.date.wizard',
name: '打印薪资',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {
default_export_type: 'meal_active',
default_model_name: 'vacation.meal.active',
default_active_ids: JSON.stringify(selectedIds),
},
});
}
}
registry.category("views").add("custom_meal_active_export_button", {
...listView,
Controller: CustomMealActiveExportController,
buttonTemplate: "meal_active_export_module.ListView.Buttons",
});
@@ -0,0 +1,37 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomOvertimeActiveExportController extends ListController {
setup() {
super.setup();
}
async exportOvertimeActiveExcel() {
// 获取选中的记录 ID
const selectedIds = await this.getSelectedResIds();
// 直接打开向导,不经过 Python 方法
return this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'export.date.wizard',
name: '打印薪资',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {
default_export_type: 'overtime_active',
default_model_name: 'overtime.active.line',
default_active_ids: JSON.stringify(selectedIds),
},
});
}
}
registry.category("views").add("custom_overtime_active_export_button", {
...listView,
Controller: CustomOvertimeActiveExportController,
buttonTemplate: "overtime_active_export_module.ListView.Buttons",
});
@@ -0,0 +1,56 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomOvertimeExportController extends ListController {
setup() {
super.setup();
}
async exportOvertimeExcel() {
// 获取当前选中的记录 ID(如果有选中)
const selectedIds = await this.getSelectedResIds();
return this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'export.date.wizard',
name: '打印薪资',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {
default_export_type: 'overtime',
default_model_name: 'vacation.over.line',
default_active_ids: JSON.stringify(selectedIds),
},
});
}
async exportOvertimeDetailExcel() {
// 导出加班详情(包含审批类型、开始结束时间、事由、餐费等)
const selectedIds = await this.getSelectedResIds();
return this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'export.date.wizard',
name: '打印薪资',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'new',
context: {
default_export_type: 'overtime_detail',
default_model_name: 'vacation.over.line',
default_active_ids: JSON.stringify(selectedIds),
},
});
}
}
registry.category("views").add("custom_overtime_export_button", {
...listView,
Controller: CustomOvertimeExportController,
buttonTemplate: "overtime_export_module.ListView.Buttons",
});
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="meal_active_export_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-success" style="margin-left: 10px;" t-on-click="exportMealActiveExcel">
导出误餐归档明细
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="overtime_active_export_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-success" style="margin-left: 10px;" t-on-click="exportOvertimeActiveExcel">
导出加班归档明细
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="overtime_export_module.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-success" style="margin-left: 10px;" t-on-click="exportOvertimeExcel">
导出加班明细
</button>
<button type="button" class="btn btn-info" style="margin-left: 10px;" t-on-click="exportOvertimeDetailExcel">
导出加班详情
</button>
</xpath>
</t>
</templates>
@@ -4,7 +4,7 @@
<field name="name">vacation.meal.active.tree</field>
<field name="model">vacation.meal.active</field>
<field name="arch" type="xml">
<tree string="误餐记录" editable="bottom">
<tree string="误餐记录" editable="bottom" js_class="custom_meal_active_export_button">
<header>
<button name="revoke" type="object" string="批量撤销" dclass="oe_highlight"/>
</header>
@@ -43,6 +43,8 @@ class ExportDateWizard(models.TransientModel):
return self._export_overtime_active(start_date, end_date)
elif self.export_type == 'meal':
return self._export_meal(start_date, end_date)
elif self.export_type == 'meal_active':
return self._export_meal_active(start_date, end_date)
else:
raise UserError('未知的导出类型')
@@ -282,3 +284,42 @@ class ExportDateWizard(models.TransientModel):
},
'report_type': 'xlsx',
}
def _export_meal_active(self, start_date, end_date):
"""导出误餐归档明细"""
active_ids = json.loads(self.active_ids) if self.active_ids else []
domain = [('start_time', '>=', start_date), ('start_time', '<', end_date)]
if active_ids:
domain = [('id', 'in', active_ids)] + domain
filtered_records = self.env['vacation.meal.active'].search(domain)
if not filtered_records:
raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的误餐归档记录')
filtered_ids = filtered_records.ids
fields_name = ['employee_id', 'department_id', 'workflow_id', 'start_time',
'end_time', 'duration', 'reasons', 'meal']
field_label = ['发起人', '部门', '审批类型', '加班开始时间', '加班结束时间',
'加班时长', '事由', '加班餐费']
data = {
'report_name': '误餐归档明细报表',
'model_name': 'vacation.meal.active',
'fields_name': fields_name,
'field_label': field_label,
'active_ids': filtered_ids,
}
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',
}