diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index b0a35037d..70a3e513c 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -353,7 +353,7 @@ class HolidaysRequestBatch(models.Model): column1='leave_id', column2='attachment_id' ) - holiday_allowance = fields.Char(string="可用额度", compute='_compute_holiday_allowance', store=True) + holiday_allowance = fields.Text(string="可用额度", compute='_compute_holiday_allowance', store=True) active = fields.Boolean('Active', default=True, tracking=True) def _get_default_name_title(self): @@ -365,7 +365,7 @@ class HolidaysRequestBatch(models.Model): @api.onchange('is_day') def _onchange_is_day(self): if not self.is_day: - self.leave_ids = [(5, 0, 0)] # 清空One2many字段 + self.leave_ids = [(5, 0, 0)] @api.onchange('is_hour') def _onchange_is_hour(self): @@ -411,7 +411,7 @@ class HolidaysRequestBatch(models.Model): other_leave_types = [ '产假', '事假', '法定假', '调休', '补休', '值班调休', '婚假', '陪产假', '计生假', '丧假', '探亲假', - '家长会假', '病假(普通)', '病假(住院期)', + '家长会假', '病假(普通)', '病假(住院期)', '育儿假', '病假(出院恢复期)', '考试假' ] for leave_type in other_leave_types: @@ -855,9 +855,9 @@ class HolidaysRequestBatch(models.Model): ('vacation_date', '=', vacation_date.replace(day=1)) ], limit=1) if reports_id: - leave_days = reports_id.leave - overtime_days = reports_id.working_overtime - record.holiday_allowance = f"年假{leave_days:.3f}天, 补休{overtime_days:.3f}天" + record.holiday_allowance = (f"本年剩余(年假){reports_id.remainder_year:.3f}天, 上年剩余(年假){reports_id.previous_year:.3f}天\n" + f"本年剩余(补休){reports_id.remainder_year1:.3f}天, 上年剩余(补休){reports_id.previous_year1:.3f}天\n " + f"育儿假{reports_id.remaining_parental_leave:.3f}天") def action_print_leave_batch(self): """按钮触发打印请假单""" diff --git a/yuthon_hr_attendance/__manifest__.py b/yuthon_hr_attendance/__manifest__.py index 4a41593da..f775b316b 100644 --- a/yuthon_hr_attendance/__manifest__.py +++ b/yuthon_hr_attendance/__manifest__.py @@ -58,6 +58,7 @@ 'views/card_remind_form.xml', 'views/yuthon_customer_planning_views.xml', 'views/planning_slot_template_views.xml', + 'views/vacation_overtime_active_views.xml', 'views/hr_attendance_month_views.xml', 'views/wifi_message_view.xml', 'views/back_approval.xml', @@ -65,6 +66,7 @@ 'views/meal_approval.xml', 'views/applet_address_views.xml', 'views/yuthon_holiday_statement.xml', + 'views/yuthon_parenting_allocate_views.xml', 'views/all_attendance_menu.xml', 'views/overtime_approval_inherit_header.xml', diff --git a/yuthon_hr_attendance/models/__init__.py b/yuthon_hr_attendance/models/__init__.py index 3dfc96f27..c401c8c4e 100644 --- a/yuthon_hr_attendance/models/__init__.py +++ b/yuthon_hr_attendance/models/__init__.py @@ -34,7 +34,8 @@ from . import staff_dining_report from . import message_reminder_tasks from . import yuthon_holiday_statement from . import yuthon_meal_case - +from . import yuthon_parenting_allocate +from . import vacation_overtime_active # from odoo.tools import SQL # import odoo # odoo.models.READ_GROUP_AGGREGATE.update({ diff --git a/yuthon_hr_attendance/models/vacation_overtime_active.py b/yuthon_hr_attendance/models/vacation_overtime_active.py new file mode 100644 index 000000000..e9bed3cad --- /dev/null +++ b/yuthon_hr_attendance/models/vacation_overtime_active.py @@ -0,0 +1,24 @@ +from datetime import datetime, timedelta, time +from email.policy import default +from operator import index +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +import calendar +from datetime import date +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError, ValidationError + + +class VacationOvertimeActive(models.Model): + _name = 'vacation.overtime.active' + _description = '加班情况归档' + + employee_id = fields.Many2one('hr.employee', string='员工') + department_id = fields.Many2one('hr.department', related='employee_id.department_id', string="部门") + company_id = fields.Many2one('res.company', related='employee_id.company_id', string="公司") + workflow_record_id = fields.Many2one('soong.workflow.record', string='流程') + overtime_name = fields.Char(string="标题") + carry_forward_date = fields.Date(string="结转日期") + carry_forward_quantity = fields.Float(string="结转时长") + carry_result = fields.Selection([('success', '成功'), ('failed', '失败')], string="结转结果") + remaining_quantity = fields.Float(string="剩余数") diff --git a/yuthon_hr_attendance/models/yuthon_parenting_allocate.py b/yuthon_hr_attendance/models/yuthon_parenting_allocate.py new file mode 100644 index 000000000..b607a5108 --- /dev/null +++ b/yuthon_hr_attendance/models/yuthon_parenting_allocate.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +from odoo import api, fields, models, tools, _ +from datetime import datetime, time, timedelta +from odoo.exceptions import UserError + + +class YuthonParentingAllocate(models.Model): + _name = 'yuthon.parenting.allocate' + _description = '育儿额度分配规则' + + company_id = fields.Many2one("res.company", string="公司", default=lambda self: self.env.company) + cycle_years = fields.Integer(string='周期年数', default=1, help='按出生日计算的周期年数,如1表示1年') + cycle_unit = fields.Integer(string='周期单位', default=1, help='周期的描述单位,如“1”周期') + valid_limit = fields.Integer(string='有效期限(岁)', default=1, help='规则生效的截止条件') + distribute = fields.Integer(string='年/发放额度') + start_date = fields.Date(string="生效日期", copy=False, default=fields.Date.today()) + end_date = fields.Date(string="失效日期", copy=False, default=fields.Date.from_string('2099-01-01')) + is_enable = fields.Boolean(string="是否启用", compute="compute_is_enable", store=True) + birth_certificate = fields.Text(string='备注', default='需要在花名册配置紧急联系人才能生效') + + @api.depends('start_date', 'end_date') + def compute_is_enable(self): + for rec in self: + if not rec.end_date: + rec.is_enable = True + else: + if rec.start_date and rec.end_date: + if rec.start_date <= fields.Date.today() < rec.end_date: + rec.is_enable = True + else: + rec.is_enable = False \ No newline at end of file diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py index db17df426..acdb08682 100644 --- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py +++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py @@ -31,6 +31,7 @@ class YuthonVacationReports(models.Model): year_holiday = fields.Float(string="本年公休日", digits=(16, 3)) year_workday = fields.Float(string="本年工作日", digits=(16, 3)) year_sum = fields.Float(string="本年调休合计", digits=(16, 3), compute="_compute_year_sum") + previous_year1 = fields.Float(string="上年剩余(补休)", digits=(16, 3)) used_this_year1 = fields.Float(string="本年已休", digits=(16, 3)) remainder_year1 = fields.Float(string="剩余(补休)", digits=(16, 3), compute="_compute_year_sum") @@ -40,6 +41,7 @@ class YuthonVacationReports(models.Model): overtime_ids = fields.One2many('vacation.over.line', 'vacation_id', string='加班明细') meal_fee_ids = fields.One2many('vacation.meal.line', 'vacation_id', string='误餐明细') medical_fee_ids = fields.One2many('vacation.medical.line', 'vacation_id', string='体检费明细') + parenting_line_ids = fields.One2many('vacation.parenting.line', 'vacation_id', string='育儿假明细') other_fee_ids = fields.One2many('vacation.other.line', 'vacation_id', string='其他明细') vacation_date = fields.Date(string='日期', default=lambda self: fields.Date.today().replace(day=1)) display_date = fields.Char(string='日期', compute='_compute_display_date') @@ -68,6 +70,8 @@ class YuthonVacationReports(models.Model): general_sick_leave = fields.Float(string='病假(普通)', digits=(16, 3), compute='_compute_other_leave', store=True, group_operator=None) hospital_sick_leave = fields.Float(string='病假(住院期)', digits=(16, 3), compute='_compute_other_leave', store=True, group_operator=None) recovery_sick_leave = fields.Float(string='病假(出院恢复期)', digits=(16, 3), compute='_compute_other_leave', store=True, group_operator=None) + parental_leave = fields.Float(string='育儿假', digits=(16, 3), compute='_compute_parental_leave', store=True, group_operator=None) + remaining_parental_leave = fields.Float(string='剩余育儿假', digits=(16, 3), compute='_compute_parental_leave', store=True, group_operator=None) exam_leave = fields.Integer(string='考试假', compute='_compute_other_leave', store=True, group_operator=None) right_days = fields.Float(string='转正天数', group_operator=None, compute='compute_attendance_fields') internship_days = fields.Float(string='实习天数', group_operator=None) @@ -97,6 +101,33 @@ class YuthonVacationReports(models.Model): tracking=3, default='not_sent') + @api.depends('other_fee_ids', 'other_fee_ids.holiday_type', 'other_fee_ids.lines', + 'other_fee_ids.is_void', 'other_fee_ids.update_type', 'parenting_line_ids', + 'parenting_line_ids.update_date', 'parenting_line_ids.end_date', 'parenting_line_ids.lines') + def _compute_parental_leave(self): + today = date.today() + for rec in self: + used_amount = 0.0 + deduct_fee_lines = rec.other_fee_ids.filtered( + lambda l: + l.holiday_type.name == '育儿假' + and l.update_type == 'reduce' + and not l.is_void + and l.lines > 0 + ) + if deduct_fee_lines: + used_amount = sum(line.lines for line in deduct_fee_lines) + rec.parental_leave = used_amount + total_amount = 0.0 + valid_parenting_lines = rec.parenting_line_ids.filtered( + lambda l: + l.update_date <= today + and (l.end_date >= today if l.end_date else True) + ) + if valid_parenting_lines: + total_amount = sum(line.lines for line in valid_parenting_lines) + rec.remaining_parental_leave = max(total_amount - used_amount, 0.0) + def update_vacation_state(self): records = self.env["yuthon.vacation.reports"].browse( self._context.get('active_ids', self._context.get('active_id', self.ids)) @@ -142,6 +173,7 @@ class YuthonVacationReports(models.Model): for record in self: record.year_sum = record.last_holiday + record.last_workday + record.year_holiday + record.year_workday record.remainder_year1 = record.year_sum - record.used_this_year1 + record.previous_year1 = record.last_holiday + record.last_workday @api.constrains('employee_id', 'vacation_date') def _check_duplicate_monthly_record(self): @@ -993,8 +1025,22 @@ class VacationOverline(models.Model): ) remaining_quantity = fields.Float(string='剩余数', compute="_compute_remaining_quantity", store=True) + def open_record(self): + tree_id = self.env.ref("yuthon_vacation_reports.view_yuthon_vacation_overtime_tree").id + return { + 'type': 'ir.actions.act_window', + 'name': 'Overtime Record', + 'res_model': 'vacation.meal.line', + 'view_mode': 'tree', + 'views': [[tree_id, 'tree']], + 'domain': [('employee_id', '=', self.employee_id.id), + ('carry_forward_state', '=', 'pending') + ], + } + @api.model def confirm(self): + # 1. 构建筛选域 domain = [ '|', ['is_initial_number', '=', True], @@ -1012,11 +1058,39 @@ class VacationOverline(models.Model): filtered_records = self.search(domain) if not filtered_records: return - records_to_update = filtered_records.filtered( - lambda r: fields.float_compare(r.duration, r.carry_forward_quantity) == 0 + + valid_records = filtered_records.filtered( + lambda r: r.carry_forward_quantity and r.carry_forward_quantity != 0.0 ) - if records_to_update: - records_to_update.write({'is_filing': True}) + if not valid_records: + return + + active_model = self.env['vacation.overtime.active'] + for rec in valid_records: + if rec.remaining_quantity == 0: + rec.is_filing = True + + match_domain = [ + ('employee_id', '=', rec.employee_id.id if rec.employee_id else False), + ('workflow_record_id', '=', rec.workflow_record_id.id if rec.workflow_record_id else False), + ('carry_forward_date', '=', rec.carry_forward_date or fields.Date.today()), + ('overtime_name', '=', rec.overtime_name or '') + ] + existing_active = active_model.search(match_domain, limit=1) + active_vals = { + 'employee_id': rec.employee_id.id if rec.employee_id else False, + 'workflow_record_id': rec.workflow_record_id.id if rec.workflow_record_id else False, + 'overtime_name': rec.overtime_name or '', + 'remaining_quantity': rec.remaining_quantity, + 'carry_forward_date': rec.carry_forward_date or fields.Date.today(), + 'carry_forward_quantity': rec.carry_forward_quantity, + 'carry_result': 'success' + } + + if existing_active: + existing_active.write(active_vals) + else: + active_model.create(active_vals) def update_is_visible(self): over_ids = self.env["vacation.over.line"].browse( @@ -1320,6 +1394,19 @@ class VacationMealLine(models.Model): return super(VacationMealLine, self).unlink() +class VacationParentingLine(models.Model): + _name = 'vacation.parenting.line' + _description = '育儿明细' + + vacation_id = fields.Many2one('yuthon.vacation.reports', string='关联假期额度记录') + employee_id = fields.Many2one('hr.employee', string='员工') + update_date = fields.Date(string='发放日期') + end_date = fields.Date(string='失效日期') + lines = fields.Float(string='额度') + text = fields.Text(string='备注') + is_confirm = fields.Boolean(string="是否确认") + + class VacationMedicalLine(models.Model): _name = 'vacation.medical.line' _description = '体检费明细' diff --git a/yuthon_hr_attendance/security/ir.model.access.csv b/yuthon_hr_attendance/security/ir.model.access.csv index cdc58f6eb..b6c9c65d3 100644 --- a/yuthon_hr_attendance/security/ir.model.access.csv +++ b/yuthon_hr_attendance/security/ir.model.access.csv @@ -85,4 +85,7 @@ access_applet_security_quick_perm_wizard,access_applet_security_quick_perm_wizar access_vacation_reports_wizard,vacation.reports.wizard,model_vacation_reports_wizard,base.group_user,1,1,1,1 access_yuthon_holiday_statement,access_yuthon_holiday_statement,model_yuthon_holiday_statement,base.group_user,1,1,1,1 access_yuthon_meal_case,access_yuthon_meal_case,model_yuthon_meal_case,base.group_user,1,1,1,1 -access_pay_carryover_wizard,pay.carryover.wizard,model_pay_carryover_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_pay_carryover_wizard,pay.carryover.wizard,model_pay_carryover_wizard,base.group_user,1,1,1,1 +access_vacation_parenting_line,vacation.parenting.line,model_vacation_parenting_line,base.group_user,1,1,1,1 +access_yuthon_parenting_allocate,yuthon.parenting.allocate,model_yuthon_parenting_allocate,base.group_user,1,1,1,1 +access_vacation_overtime_active,vacation.overtime.active,model_vacation_overtime_active,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_hr_attendance/static/src/js/button_meal_confirm.js b/yuthon_hr_attendance/static/src/js/button_meal_confirm.js index 3e3e82531..303a5fd09 100644 --- a/yuthon_hr_attendance/static/src/js/button_meal_confirm.js +++ b/yuthon_hr_attendance/static/src/js/button_meal_confirm.js @@ -7,17 +7,10 @@ export class CustomAttendanceMealConfirmController extends ListController { setup() { super.setup(); } - async getAction(lock_type) { - const action = await this.orm.call('vacation.meal.line', 'confirm', [lock_type], {context: this.props.context}); - action["context"] = { - ...action.context, - ...this.model.root.context, - } - return action + await this.orm.call('vacation.meal.line', 'confirm', []); } } - registry.category("views").add("custom_vacation_meal_button", { ...listView, Controller: CustomAttendanceMealConfirmController, diff --git a/yuthon_hr_attendance/static/src/js/button_statement_confirm.js b/yuthon_hr_attendance/static/src/js/button_statement_confirm.js index f598e288e..4c08e982e 100644 --- a/yuthon_hr_attendance/static/src/js/button_statement_confirm.js +++ b/yuthon_hr_attendance/static/src/js/button_statement_confirm.js @@ -8,13 +8,9 @@ export class CustomAttendanceStatementConfirmController extends ListController { super.setup(); } + async getAction(lock_type) { - const action = await this.orm.call('hr.leave', 'confirm', [lock_type], {context: this.props.context}); - action["context"] = { - ...action.context, - ...this.model.root.context, - } - return action + await this.orm.call('hr.leave', 'confirm', []); } } diff --git a/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js index 00223dbe0..d70f42708 100644 --- a/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js +++ b/yuthon_hr_attendance/static/src/js/button_vacation_confirm.js @@ -9,12 +9,7 @@ export class CustomAttendanceVacationConfirmController extends ListController { } async getAction(lock_type) { - const action = await this.orm.call('vacation.over.line', 'confirm', [lock_type], {context: this.props.context}); - action["context"] = { - ...action.context, - ...this.model.root.context, - } - return action + await this.orm.call('vacation.over.line', 'confirm', []); } } diff --git a/yuthon_hr_attendance/views/vacation_overtime_active_views.xml b/yuthon_hr_attendance/views/vacation_overtime_active_views.xml new file mode 100644 index 000000000..4f793552b --- /dev/null +++ b/yuthon_hr_attendance/views/vacation_overtime_active_views.xml @@ -0,0 +1,24 @@ + + + + vacation.overtime.active.tree + vacation.overtime.active + + + + + + + + + + + + + + 加班情况(归档) + vacation.overtime.active + tree + + + \ No newline at end of file diff --git a/yuthon_hr_attendance/views/yuthon_parenting_allocate_views.xml b/yuthon_hr_attendance/views/yuthon_parenting_allocate_views.xml new file mode 100644 index 000000000..cd1105f19 --- /dev/null +++ b/yuthon_hr_attendance/views/yuthon_parenting_allocate_views.xml @@ -0,0 +1,22 @@ + + + + yuthon.parenting.allocate.tree + yuthon.parenting.allocate + + + + + + + + + + + + + 育儿假规则 + yuthon.parenting.allocate + tree + + \ No newline at end of file diff --git a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml index 6fad46c79..9adc5a914 100644 --- a/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml +++ b/yuthon_hr_attendance/views/yuthon_vacation_reports_views.xml @@ -75,6 +75,7 @@ + @@ -124,6 +125,7 @@ + @@ -183,7 +185,9 @@ - + + + @@ -323,6 +327,19 @@ + + + + + + + + + + + + + @@ -486,6 +503,19 @@ + + + + + + + + + + + + + @@ -548,22 +578,7 @@ - - - - - vacation.over.line.overtime.tree - vacation.over.line - - - - - - - - - - +