添加功能

This commit is contained in:
李鹏宇
2026-03-07 21:52:26 +08:00
parent ee11cb9317
commit 023ce5eefa
9 changed files with 255 additions and 30 deletions
+1
View File
@@ -30,6 +30,7 @@
'views/inherit_planning_role_views.xml',
'views/inherit_planning_slot_views.xml',
'views/attendance_message_log.xml',
'views/duty_adjustment_schedule_views.xml',
'views/inherit_planning_slot_template_views.xml',
'views/yuthon_customer_approval_views.xml',
'views/yuthon_shift_planning_views.xml',
+1
View File
@@ -39,6 +39,7 @@ from . import vacation_overtime_active
from . import yuthon_overtime_line
from . import overtime_active_line
from . import vacation_meal_active
from . import duty_adjustment_schedule
# from odoo.tools import SQL
# import odoo
# odoo.models.READ_GROUP_AGGREGATE.update({
@@ -0,0 +1,133 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools, _
from datetime import datetime, time, timedelta
from odoo.exceptions import UserError
from datetime import date, timedelta
class DutyAdjustmentSchedule(models.Model):
_name = 'duty.adjustment.schedule'
_description = '值班调休表'
vacation_id = fields.Many2one('yuthon.vacation.reports', string="关联假期额度日志")
employee_id = fields.Many2one('hr.employee', string="员工")
is_adjustment = fields.Boolean(string="已调休")
hide_selection = fields.Selection([('legal_date', '法定日'), ('adjust_holidays', '调班日'), ('workday', '工作日')], string="", compute="_compute_is_hide")
is_hide = fields.Boolean(string="隐藏", compute='_compute_is_hide')
company_id = fields.Many2one(related='employee_id.company_id', string="公司")
duty_date = fields.Date(string="值班日期")
week_day = fields.Char(string='星期', compute='_compute_week_day', store=True)
@api.depends('duty_date')
def _compute_is_hide(self):
"""
最终规则(周末/调休日不隐藏):
- 法定日 → 隐藏(is_hide=True),原因=法定日
- 调班日 → 隐藏(is_hide=True),原因=调班日
- 平常工作日 → 隐藏(is_hide=True),原因=工作日
- 调休日/周末 → 不隐藏(is_hide=False),无原因
"""
for record in self:
record.is_hide = False
record.hide_selection = False
if not record.duty_date:
continue
date_type = record._process_shift_days(record.duty_date)
if date_type == 'legal_date':
record.is_hide = True
record.hide_selection = 'legal_date'
elif date_type == 'adjust_holidays':
record.is_hide = True
record.hide_selection = 'adjust_holidays'
elif date_type == 'workday':
record.is_hide = True
record.hide_selection = 'workday'
@api.depends('duty_date')
def _compute_week_day(self):
week_map = {'0': '周一', '1': '周二', '2': '周三', '3': '周四', '4': '周五', '5': '周六', '6': '周日'}
for record in self:
if record.duty_date:
record.week_day = week_map[str(record.duty_date.weekday())]
else:
record.week_day = ''
def compensation(self):
for record in self:
if record.vacation_id.vacation_state == 'confirmed':
raise UserError('该假期额度已确认,无法进行调休操作')
record.is_adjustment = True
over_record = self.env['vacation.over.line'].search([('employee_id', '=', record.employee_id.id), ('update_date', '=', record.duty_date),
('vacation_id', '=', record.vacation_id.id)], limit=1)
if not over_record:
self.env['vacation.over.line'].create({
'employee_id': record.employee_id.id,
'update_date': record.duty_date,
'vacation_id': record.vacation_id.id,
'valid_date': date(record.duty_date.year + 1, 6, 30),
'workday': 8,
'update_type': 'add',
'text': '值班调休'
})
record.vacation_id.update_line()
def revoke(self):
for record in self:
record.is_adjustment = False
over_record = self.env['vacation.over.line'].search([('employee_id', '=', record.employee_id.id), ('update_date', '=', record.duty_date),
('vacation_id', '=', record.vacation_id.id), ('text', '=', '值班调休')], limit=1)
if over_record:
over_record.unlink()
record.vacation_id.update_line()
def _process_shift_days(self, current_data):
"""
判断指定日期的类型:
- 法定日(holidays)→ 'legal_date'
- 调班日(shift_replacement_day)→ 'adjust_holidays'
- 平常工作日(1-5)→ 'workday'
- 调休日(alternate_rest_day)/周末 → None
"""
if not current_data:
return None
current_year = current_data.year
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
calendar_leaves = self.env['resource.calendar.leaves'].sudo().search([
("calendar_id.name", "=", "行政考勤工作日"),
("date_from", ">=", query_start),
("date_from", "<=", query_end)
])
holidays_list = [] # 法定日
shift_days_list = [] # 调班日(对应adjust_holidays)
adjust_days_list = [] # 调休日(不隐藏,无原因)
for leave in calendar_leaves:
if leave.holidays:
holidays_list.extend(leave.holidays.split(','))
if leave.shift_replacement_day: # 调班日
shift_days_list.extend(leave.shift_replacement_day.split(','))
if leave.alternate_rest_day: # 调休日(仅判断,不赋值原因)
adjust_days_list.extend(leave.alternate_rest_day.split(','))
holidays_list = [self.remove_leading_zero(d) for d in holidays_list if d]
shift_days_list = [self.remove_leading_zero(d) for d in shift_days_list if d]
current_date_str = f"{current_data.month}月{current_data.day}日"
if current_date_str in holidays_list:
return 'legal_date' # 法定日
elif current_date_str in shift_days_list:
return 'adjust_holidays' # 调班日
elif current_data.weekday() in [0, 1, 2, 3, 4]:
return 'workday' # 平常工作日(周一到周五)
else:
return None # 调休日/周末(不隐藏,无原因)
# 移除日期字符串中的前导零
def remove_leading_zero(self, date_str):
month, day = date_str.split('月')
month = month.lstrip('0')
day = day.rstrip('日')
day = day.lstrip('0')
return f'{month}月{day}日'
@@ -88,19 +88,6 @@ class PlanningSlotTemplate(models.Model):
}
return action
# @api.depends("planning_name")
# def _compute_display_name(self):
# super()._compute_display_name()
# for record in self:
# if record.planning_name and record.role_id:
# record.display_name = f"{record.role_id.name}-{record.planning_name}"
# elif record.planning_name:
# record.display_name = record.planning_name
# elif record.role_id:
# record.display_name = record.role_id.name
# else:
# record.display_name = "Unknown"
@api.depends('role_id','planning_name')
def _compute_display_name(self):
for shift_template in self:
@@ -57,6 +57,7 @@ class YuthonVacationReports(models.Model):
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='其他明细')
duty_ids = fields.One2many('duty.adjustment.schedule', 'vacation_id', string='值班调休明细', compute='_compute_duty_ids')
vacation_date = fields.Date(string='日期', default=lambda self: fields.Date.today().replace(day=1))
show_date = fields.Date(string='日期', compute='_compute_display_date', store=True)
display_date = fields.Char(string='日期', compute='_compute_display_date')
@@ -68,13 +69,14 @@ class YuthonVacationReports(models.Model):
digits=(16, 3), group_operator=None)
remain_missed_meals = fields.Float(string="剩余误餐次数", compute='_compute_meal_fee', store=True, group_operator=None)
medical_fee = fields.Float(string="体检费", compute='_compute_medical_fee', store=True, group_operator=None)
duty = fields.Float(string="值班", compute='_compute_duty_fee', store=True, group_operator=None)
duty_off_leave = fields.Float(string='值班调休', compute='_compute_duty_fee', store=True, group_operator=None)
compensatory_leave = fields.Float(string="调休d", compute='_compute_other_leave', store=True, group_operator=None)
leave_of_absence = fields.Float(string="事假d", digits=(16, 3), compute='_compute_other_leave', store=True,
group_operator=None)
statutory_leave = fields.Float(string="加班时(法定)d", compute='_compute_other_leave', store=True, group_operator=None, digits=(16, 3))
cop_leave = fields.Float(string='补休d', digits=(16, 3), store=True, compute='_compute_working_overtime',
group_operator=None)
duty_off_leave = fields.Float(string='值班调休d', compute='_compute_other_leave', store=True, group_operator=None)
marriage_leave = fields.Float(string='婚假d', compute='_compute_other_leave', store=True, group_operator=None)
maternity_leave = fields.Float(string='产假d', compute='_compute_other_leave', store=True, group_operator=None)
paternity_leave = fields.Float(string='陪产假d', compute='_compute_other_leave', store=True, group_operator=None)
@@ -117,6 +119,77 @@ class YuthonVacationReports(models.Model):
tracking=3,
default='not_sent')
@api.depends('employee_id', 'vacation_date')
def _compute_duty_ids(self):
duty_schedule_env = self.env['duty.adjustment.schedule']
planning_slot_env = self.env['planning.slot']
for record in self:
if not record.employee_id or not record.vacation_date:
record.duty_ids = False
continue
emp_id = record.employee_id.id
year = record.vacation_date.year
month = record.vacation_date.month
month_start = date(year, month, 1)
last_day = calendar.monthrange(year, month)[1]
month_end = date(year, month, last_day)
all_duty_records = duty_schedule_env.search([
('vacation_id', '=', record.id),
('duty_date', '>=', month_start),
('duty_date', '<=', month_end)
])
# 过滤出:非已调休的记录 → 批量删除
non_adjusted_duties = all_duty_records.filtered(lambda d: not d.is_adjustment)
if non_adjusted_duties:
non_adjusted_duties.unlink()
white_duty_slots = planning_slot_env.search([
('employee_id', '=', emp_id),
('planning_name', '=', '白值'),
('start_datetime', '>=', datetime.combine(month_start, datetime.min.time())),
('start_datetime', '<=', datetime.combine(month_end, datetime.max.time()))
])
existing_duty_dates = {
d.duty_date for d in duty_schedule_env.search([
('vacation_id', '=', record.id),
('duty_date', '>=', month_start),
('duty_date', '<=', month_end)
])
}
create_vals = []
for slot in white_duty_slots:
if not slot.start_datetime:
continue
duty_date = slot.start_datetime.date()
if duty_date in existing_duty_dates:
continue
create_vals.append({
'employee_id': emp_id,
'vacation_id': record.id,
'duty_date': duty_date,
'is_adjustment': False
})
# 批量创建(效率更高)
if create_vals:
duty_schedule_env.create(create_vals)
record.duty_ids = duty_schedule_env.search([
('vacation_id', '=', record.id),
('duty_date', '>=', month_start),
('duty_date', '<=', month_end)
])
@api.depends('duty_ids.is_adjustment')
def _compute_duty_fee(self):
for record in self:
total_duty_count = len(record.duty_ids)
duty_off_count = len([line for line in record.duty_ids if line.is_adjustment])
record.duty_off_leave = duty_off_count
record.duty = max(total_duty_count - duty_off_count, 0)
@api.depends('vacation_date', 'employee_id')
def _compute_number_card(self):
for record in self:
@@ -181,7 +254,6 @@ class YuthonVacationReports(models.Model):
'context': {'tree_view_ref': "yuthon_hr_attendance.view_hr_leave_statement_tree"},
}
def import_initial_data(self):
return {
'type': 'ir.actions.act_window',
@@ -312,7 +384,7 @@ class YuthonVacationReports(models.Model):
rec.used_this_year1 += deduct_from_current1 # 累加到本年已休
remaining_overtime_deduct -= deduct_from_current1
add_overtime_lines = rec.overtime_ids.filtered(lambda o: o.workflow_record_id and o.update_type == 'add' and o.is_void != True)
add_overtime_lines = rec.overtime_ids.filtered(lambda o: o.text != '上月额度继承' and o.update_type == 'add' and o.is_void != True)
# 计算补休新增总天数
workday_overtime_add = sum((line.workday or 0) for line in add_overtime_lines) / 8
holidays_overtime_add = sum((line.public_holidays or 0) for line in add_overtime_lines) / 8
@@ -1134,7 +1206,6 @@ class YuthonVacationReports(models.Model):
'调休': 'compensatory_leave',
'事假': 'leave_of_absence',
'法定假': 'statutory_leave',
'值班调休': 'duty_off_leave',
'婚假': 'marriage_leave',
'产假': 'maternity_leave',
'陪产假': 'paternity_leave',
@@ -1158,7 +1229,7 @@ class YuthonVacationReports(models.Model):
current_value = getattr(rec, field_name) or 0
setattr(rec, field_name, current_value + line.lines)
int_fields = [
'compensatory_leave', 'statutory_leave', 'duty_off_leave',
'compensatory_leave', 'statutory_leave',
'marriage_leave', 'maternity_leave', 'paternity_leave',
'family_planning_leave', 'home_visit_leave',
'parents_meeting_leave', 'exam_leave'
@@ -95,4 +95,5 @@ access_overtime_active_line,overtime.active.line,model_overtime_active_line,base
access_export_date_wizard,export.date.wizard,model_export_date_wizard,base.group_user,1,1,1,1
access_vacation_meal_active,vacation.meal.active,model_vacation_meal_active,base.group_user,1,1,1,1
access_yuthon_vacation_import_wizard,yuthon.vacation.import.wizard,model_yuthon_vacation_import_wizard,base.group_user,1,1,1,1
access_overtime_change_wizard,overtime.change.wizard,model_overtime_change_wizard,base.group_user,1,1,1,1
access_overtime_change_wizard,overtime.change.wizard,model_overtime_change_wizard,base.group_user,1,1,1,1
access_duty_adjustment_schedule,duty.adjustment.schedule,model_duty_adjustment_schedule,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
95
96
97
98
99
@@ -0,0 +1,20 @@
<odoo>
<record id="view_duty_adjustment_schedule_tree" model="ir.ui.view">
<field name="name">duty.adjustment.schedule.tree</field>
<field name="model">duty.adjustment.schedule</field>
<field name="arch" type="xml">
<tree editable="bottom" create="false" delete="false" edit="false">
<field name="employee_id"/>
<field name="company_id"/>
<field name="duty_date"/>
<field name="week_day"/>
<field name="is_hide" column_invisible="1"/>
<field name="hide_selection" string=" "/>
<field name="vacation_id" column_invisible="1"/>
<field name="is_adjustment" column_invisible="1"/>
<button name="compensation" string="调休" type="object" class="btn-primary" invisible="is_adjustment or is_hide"/>
<button name="revoke" string="撤销" type="object" class="btn-danger" invisible="not is_adjustment"/>
</tree>
</field>
</record>
</odoo>
@@ -225,13 +225,4 @@
<field name="groups_id"
eval="[(4, ref('yuthon_hr_attendance.group_he_attendance_sign_manager'))]" />
</record>
<record id="action_update_abnormal_shift" model="ir.actions.server">
<field name="name">批量更新班次名称</field>
<field name="model_id" ref="yuthon_hr_attendance.model_hr_attendance" />
<field name="binding_model_id" ref="yuthon_hr_attendance.model_hr_attendance" />
<field name="state">code</field>
<field name="code">action = model.update_abnormal()</field>
<field name="groups_id"
eval="[(4, ref('yuthon_hr_attendance.group_he_attendance_sign_manager'))]" />
</record>
</odoo>
@@ -65,6 +65,8 @@
<field name="expatriation" options='{ "fg_color": "white: expatriation == False"}'/>
<field name="right_days" options='{ "fg_color": "white: right_days == False"}'/>
<field name="test_days" options='{ "fg_color": "white: test_days == False"}'/>
<field name="duty" options='{ "fg_color": "white: duty == False"}'/>
<field name="duty_off_leave" options='{ "fg_color": "white: duty_off_leave == False"}'/>
<field name="is_late_card" options='{ "fg_color": "white: is_late_card == False"}'/>
<field name="is_early_card" options='{ "fg_color": "white: is_early_card == False"}'/>
<field name="is_no_card" options='{ "fg_color": "white: is_no_card == False"}'/>
@@ -81,7 +83,6 @@
options='{ "fg_color": "white: statutory_leave == False"}'/>
<field name="cop_leave" options='{ "fg_color": "white: cop_leave == False"}'/>
<field name="parental_leave" options='{ "fg_color": "white: parental_leave == False"}'/>
<field name="duty_off_leave" options='{ "fg_color": "white: duty_off_leave == False"}'/>
<field name="marriage_leave" options='{ "fg_color": "white: marriage_leave == False"}'/>
<field name="maternity_leave" options='{ "fg_color": "white: maternity_leave == False"}'/>
<field name="paternity_leave" options='{ "fg_color": "white: paternity_leave == False"}'/>
@@ -129,8 +130,12 @@
<field name="remainder_year"/>
<field name="should_attendance" options='{ "fg_color": "white: should_attendance == False"}'/>
<field name="actual_attendance" options='{ "fg_color": "white: actual_attendance == False"}'/>
<field name="expatriation" options='{ "fg_color": "white: expatriation == False"}'/>
<field name="right_days" options='{ "fg_color": "white: right_days == False"}'/>
<field name="test_days" options='{ "fg_color": "white: test_days == False"}'/>
<field name="duty" options='{ "fg_color": "white: duty == False"}'/>
<field name="duty_off_leave" options='{ "fg_color": "white: duty_off_leave == False"}'/>
<field name="compensatory_leave" options='{ "fg_color": "white: compensatory_leave == False"}'/>
<field name="is_late_card" options='{ "fg_color": "white: is_late_card == False"}'/>
<field name="is_early_card" options='{ "fg_color": "white: is_early_card == False"}'/>
<field name="is_no_card" options='{ "fg_color": "white: is_no_card == False"}'/>
@@ -148,7 +153,6 @@
options='{ "fg_color": "white: statutory_leave == False"}'/>
<field name="cop_leave" options='{ "fg_color": "white: cop_leave == False"}'/>
<field name="parental_leave" options='{ "fg_color": "white: parental_leave == False"}'/>
<field name="duty_off_leave" options='{ "fg_color": "white: duty_off_leave == False"}'/>
<field name="marriage_leave" options='{ "fg_color": "white: marriage_leave == False"}'/>
<field name="maternity_leave" options='{ "fg_color": "white: maternity_leave == False"}'/>
<field name="paternity_leave" options='{ "fg_color": "white: paternity_leave == False"}'/>
@@ -371,6 +375,22 @@
</tree>
</field>
</page>
<page string="值班明细">
<field name="duty_ids" context="{'default_employee_id': employee_id}">
<tree editable="bottom" delete="false" default_order="duty_date desc">
<field name="vacation_id" column_invisible="1"/>
<field name="employee_id" column_invisible="1"/>
<field name="company_id" column_invisible="1"/>
<field name="duty_date"/>
<field name="week_day"/>
<field name="hide_selection" string=" "/>
<field name="is_hide" column_invisible="1"/>
<field name="is_adjustment" column_invisible="1"/>
<button name="compensation" string="调休" type="object" class="btn-primary" invisible="is_adjustment or is_hide"/>
<button name="revoke" string="撤销" type="object" class="btn-danger" invisible="not is_adjustment"/>
</tree>
</field>
</page>
<page string="管理员修改字段" groups="base.group_system">
<group col="2">
<group>