# -*- coding: utf-8 -*- from odoo import fields, models import json from odoo.tools import date_utils from datetime import datetime, timedelta from odoo.exceptions import UserError class ExportDateWizard(models.TransientModel): _name = 'export.date.wizard' _description = '导出日期选择向导' year = fields.Integer(string='结转年份', required=True, default=lambda self: datetime.now().year) month = fields.Selection([ ('1', '1月'), ('2', '2月'), ('3', '3月'), ('4', '4月'), ('5', '5月'), ('6', '6月'), ('7', '7月'), ('8', '8月'), ('9', '9月'), ('10', '10月'), ('11', '11月'), ('12', '12月'), ], string='结转月份', required=True, default=lambda self: str(datetime.now().month)) export_type = fields.Char(string='导出类型') model_name = fields.Char(string='模型名称') active_ids = fields.Char(string='活动记录ID') def action_confirm_export(self): """确认导出""" self.ensure_one() start_date = datetime(int(self.year), int(self.month), 1) if int(self.month) == 12: end_date = datetime(int(self.year) + 1, 1, 1) else: end_date = datetime(int(self.year), int(self.month) + 1, 1) if self.export_type == 'leave': return self._export_leave(start_date, end_date) elif self.export_type == 'leave_filing': return self._export_leave_filing(start_date, end_date) elif self.export_type == 'overtime': return self._export_overtime(start_date, end_date) elif self.export_type == 'overtime_detail': return self._export_overtime_detail(start_date, end_date) elif self.export_type == 'overtime_active': 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) elif self.export_type == 'overtime_carryover': return self._export_overtime_carryover(start_date, end_date) else: raise UserError('未知的导出类型') def _export_leave(self, start_date, end_date): """导出请假情况""" active_ids = json.loads(self.active_ids) if self.active_ids else [] domain = [('date_from', '>=', start_date), ('date_from', '<', end_date), ('state', '!=', 'refuse')] if active_ids: domain = [('id', 'in', active_ids)] + domain filtered_records = self.env['hr.leave'].search(domain) if not filtered_records: raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的请假记录') filtered_ids = filtered_records.ids fields_name = ['employee_id', 'department_id', 'holiday_status_id', 'date_from', 'date_to', 'length_leave', 'application_reason', 'mobile_phone'] field_label = ['发起人', '部门', '休假类型', '开始时间', '结束时间', '请假时长', '申请原因', '手机号'] data = { 'report_name': '请假情况报表', 'model_name': 'hr.leave', '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', } def _export_leave_filing(self, start_date, end_date): """导出归档请假情况(is_filing=True)""" active_ids = json.loads(self.active_ids) if self.active_ids else [] domain = [ ('date_from', '>=', start_date), ('date_from', '<', end_date), ('is_filing', '=', True) ] if active_ids: domain = [('id', 'in', active_ids)] + domain filtered_records = self.env['hr.leave'].search(domain) if not filtered_records: raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的归档请假记录') filtered_ids = filtered_records.ids fields_name = ['employee_id', 'department_id', 'holiday_status_id', 'date_from', 'date_to', 'length_leave', 'application_reason', 'mobile_phone'] field_label = ['发起人', '部门', '休假类型', '开始时间', '结束时间', '请假时长', '申请原因', '手机号'] data = { 'report_name': '归档请假情况报表', 'model_name': 'hr.leave', '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', } def _export_overtime(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), '|', ('workflow_record_id', '!=', False), ('is_initial_number', '=', True), ('update_type', '=', 'add'), ('is_filing', '=', False), ('is_visible', '=', False)] if active_ids: domain = [('id', 'in', active_ids)] + domain filtered_records = self.env['vacation.over.line'].search(domain) if not filtered_records: raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的加班记录') filtered_ids = filtered_records.ids fields_name = ['employee_id', 'department_id', 'start_time', 'carry_forward_date', 'duration', 'carry_result'] field_label = ['员工', '部门', '加班时间', '结转日期', '结转时长', '结转结果'] data = { 'report_name': '加班明细报表', 'model_name': 'vacation.over.line', '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', } def _export_overtime_detail(self, start_date, end_date): """导出加班详情 - 对应 vacation.over.line.overtime.tree 视图字段""" active_ids = json.loads(self.active_ids) if self.active_ids else [] carry_forward_state_map = { 'pending': '待结转', 'completed': '已结转', 'partial': '部分结转', } if active_ids: records = self.env['vacation.over.line'].browse(active_ids) else: domain = [('start_time', '>=', start_date), ('start_time', '<', end_date)] records = self.env['vacation.over.line'].search(domain) if not records: raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的加班记录') export_data = [] for rec in records: export_data.append({ 'overtime_name': rec.overtime_name or '', 'employee_id': rec.employee_id.name if rec.employee_id else '', 'department_id': rec.department_id.name if rec.department_id else '', 'start_time': (rec.start_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if rec.start_time else '', 'end_time': (rec.end_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if rec.end_time else '', 'duration': rec.duration, 'carry_forward_quantity': rec.carry_forward_quantity, 'carry_forward_date': rec.carry_forward_date, 'carry_forward_state': carry_forward_state_map.get(rec.carry_forward_state, rec.carry_forward_state or ''), 'remaining_quantity': rec.remaining_quantity, 'validity': rec.validity or '', }) fields_name = ['overtime_name', 'employee_id', 'department_id', 'start_time', 'end_time', 'duration', 'carry_forward_quantity', 'carry_forward_date', 'carry_forward_state', 'remaining_quantity', 'validity'] field_label = ['标题', '员工', '部门', '开始时间', '结束时间', '加班时长(小时)', '结转时长(小时)', '结转日期', '结转状态', '剩余数', '有效期'] data = { 'report_name': '加班详情报表', 'model_name': 'vacation.over.line', 'fields_name': fields_name, 'field_label': field_label, 'export_data': export_data, 'date_field': None, } 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 _export_overtime_active(self, start_date, end_date): """导出加班归档明细 规则:弹窗填N年M月 → 查 select_year/select_month = N年(M-1)月 的 vacation.overtime.active 记录(每人一行), 对每条归档记录复用 open_record 的 domain + 累减逻辑,汇总为每人一行后导出。 """ import datetime as dt year = int(self.year) month = int(self.month) # 往前推一个月 if month == 1: prev_year, prev_month = year - 1, 12 else: prev_year, prev_month = year, month - 1 carry_forward_state_map = { 'pending': '待结转', 'completed': '已结转', 'partial': '部分结转', } active_records = self.env['vacation.overtime.active'].search([ ('select_year', '=', str(prev_year)), ('select_month', '=', str(prev_month)), ('select_revoke', '=', False) ]) if not active_records: raise UserError(f'在 {prev_year} 年 {prev_month} 月没有找到加班归档记录') export_data = [] for active in active_records: # 复用 open_record 的 domain final_domain = [ ('over_line_id.vacation_id.employee_id', '=', active.employee_id.id), ('is_void', '=', False), ('carryover_type', '=', 'fee'), ('carry_forward_date', '=', active.carry_forward_date), ] carryover_records = self.env['overtime.carryover'].search(final_domain, order='start_time asc') if not carryover_records: continue # 复用 open_record 的累减逻辑,实时计算每条的 last_month/this_month current_remaining = active.vacation_sum for carry in carryover_records: last_month_val = current_remaining this_month_val = round(current_remaining - carry.carry_forward_quantity, 3) current_remaining = this_month_val over_line = carry.over_line_id export_data.append({ 'employee_id': active.employee_id.name if active.employee_id else '', 'department_id': carry.department_id.name if carry.department_id else '', 'start_time': (over_line.start_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if over_line and over_line.start_time else '', 'end_time': (over_line.end_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if over_line and over_line.end_time else '', 'carry_forward_date': carry.carry_forward_date, 'carry_forward_quantity1': carry.carry_forward_quantity, 'carry_forward_state': carry_forward_state_map.get(carry.carry_forward_state, carry.carry_forward_state or ''), }) if not export_data: raise UserError(f'在 {prev_year} 年 {prev_month} 月没有找到符合条件的加班归档记录') fields_name = ['employee_id', 'department_id', 'start_time', 'end_time', 'carry_forward_date', 'carry_forward_quantity1', 'carry_forward_state'] field_label = ['姓名', '部门', '加班开始时间', '加班结束时间', '结转日期', '结转时长(小时)', '结转状态'] data = { 'report_name': '加班归档明细报表', 'model_name': 'overtime.carryover', 'fields_name': fields_name, 'field_label': field_label, 'export_data': export_data, 'date_field': None, } 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 _export_overtime_carryover(self, start_date, end_date): """导出加班结转明细(overtime.carryover.tree1 视图数据)""" active_ids = json.loads(self.active_ids) if self.active_ids else [] if active_ids: records = self.env['overtime.carryover'].browse(active_ids) else: records = self.env['overtime.carryover'].search([ ('carry_forward_date', '>=', start_date.date()), ('carry_forward_date', '<', end_date.date()), ]) if not records: raise UserError(f'在 {self.year} 年 {self.month} 月没有找到符合条件的加班结转记录') export_data = [] for record in records: over_line = record.over_line_id export_data.append({ 'employee_id': record.employee_id.name if record.employee_id else '', 'department_id': record.department_id.name if record.department_id else '', 'start_time': (over_line.start_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if over_line and over_line.start_time else '', 'end_time': (over_line.end_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if over_line and over_line.end_time else '', 'carry_forward_date': record.carry_forward_date, 'last_month': record.last_month, 'carry_forward_quantity1': record.carry_forward_quantity1, 'this_month': record.this_month, 'carry_result': '成功', }) fields_name = ['employee_id', 'department_id', 'start_time', 'end_time', 'carry_forward_date', 'last_month', 'carry_forward_quantity1', 'this_month', 'carry_result'] field_label = ['姓名', '部门', '加班开始时间', '加班结束时间', '结转日期', '上期剩余', '结转时长', '本期结余', '结转结果'] data = { 'report_name': '加班结转明细报表', 'model_name': 'overtime.carryover', 'fields_name': fields_name, 'field_label': field_label, 'export_data': export_data, 'date_field': None, } 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 _export_meal(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)] if active_ids: domain = [('id', 'in', active_ids)] + domain filtered_records = self.env['vacation.meal.line'].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.line', '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', } def _export_meal_active(self, start_date, end_date): """导出误餐归档明细""" active_ids = json.loads(self.active_ids) if self.active_ids else [] domain = [('carry_forward_date', '>=', start_date.date()), ('carry_forward_date', '<', end_date.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} 月没有找到符合条件的误餐归档记录') export_data = [] for record in filtered_records: export_data.append({ 'employee_id': record.employee_id.name if record.employee_id else '', 'department_id': record.employee_id.department_id.name if record.employee_id and record.employee_id.department_id else '', 'workflow_id': record.workflow_id.name if record.workflow_id else '', 'start_time': (record.start_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if record.start_time else '', 'end_time': (record.end_time + timedelta(hours=8)).strftime('%Y-%m-%d %H:%M:%S') if record.end_time else '', 'duration': record.duration, 'update_lines': record.update_lines, 'reasons': record.reasons or '', 'meal': record.meal, }) fields_name = ['employee_id', 'department_id', 'workflow_id', 'start_time', 'end_time', 'duration', 'update_lines', 'reasons', 'meal'] field_label = ['发起人', '部门', '审批类型', '开始时间', '结束时间', '时长', '额度', '事由', '加班餐费'] data = { 'report_name': '误餐归档明细报表', 'model_name': 'vacation.meal.active', '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', }