827 lines
38 KiB
Python
827 lines
38 KiB
Python
# -*- coding: utf-8 -*-
|
|
import datetime
|
|
import calendar
|
|
from odoo import api, fields, models, tools, _
|
|
from odoo.tools.float_utils import float_round
|
|
from dateutil.relativedelta import relativedelta
|
|
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
|
from datetime import timedelta
|
|
from datetime import date
|
|
|
|
|
|
class HrAttendanceYear(models.Model):
|
|
_name = "hr.attendance.year"
|
|
_description = "出勤年"
|
|
_rec_name = "year"
|
|
_order = 'year desc'
|
|
|
|
year = fields.Char(string='年份')
|
|
hr_attendance_month_ids = fields.One2many(comodel_name='hr.attendance.month', inverse_name='attendance_year_id', string="出勤月度汇总")
|
|
hr_attendance_month_detail_ids = fields.One2many(comodel_name='hr.attendance.month.detail', inverse_name='attendance_year_id', string="出勤月度汇总明细")
|
|
|
|
|
|
class HrAttendanceMonth(models.Model):
|
|
_name = "hr.attendance.month"
|
|
_description = "出勤月度汇总"
|
|
_order = "statistics_time desc"
|
|
|
|
name = fields.Char(compute='_compute_name')
|
|
attendance_year_id = fields.Many2one('hr.attendance.year', string="统计年份")
|
|
company_id = fields.Many2one('res.company', string="公司", default=lambda self: self.env.user.company_id)
|
|
current_year = fields.Char(string='统计年份', related="attendance_year_id.year")
|
|
current_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="考勤月份")
|
|
is_statistics = fields.Boolean(string="是否已统计", store=True, default=False)
|
|
statistics_time = fields.Datetime(string="统计时间")
|
|
attendance_month_detail_ids = fields.One2many(string="出勤月度汇总明细", comodel_name='hr.attendance.month.detail', inverse_name='hr_attendance_month_id')
|
|
is_state = fields.Boolean(string='是否已全部确认', compute='_compute_is_state', store=True)
|
|
|
|
@api.depends('attendance_month_detail_ids.state')
|
|
def _compute_is_state(self):
|
|
for record in self:
|
|
record.is_state = all(detail.state == 'confirmed' for detail in record.attendance_month_detail_ids)
|
|
|
|
def _compute_name(self):
|
|
for record in self:
|
|
record.write({'name': self.current_year+'年'+self.current_month+'月'})
|
|
|
|
def action_show_details(self):
|
|
self.ensure_one()
|
|
view = self.env.ref("yuthon_hr_attendance.view_hr_attendance_month_detail_tree")
|
|
form_view = self.env.ref("yuthon_hr_attendance.view_hr_attendance_month_detail_form")
|
|
return {
|
|
"name": _("月度汇总明细"),
|
|
"type": "ir.actions.act_window",
|
|
"view_mode": "tree,form",
|
|
"res_model": "hr.attendance.month.detail",
|
|
"views": [(view.id, "tree"), (form_view.id, "form")],
|
|
"view_id": view.id,
|
|
"target": "current",
|
|
"res_id": self.id,
|
|
"domain": [('hr_attendance_month_id', '=', self.id)],
|
|
"context": {'hr_attendance_month_id': self.id}
|
|
}
|
|
|
|
|
|
class HrAttendanceMonthDetail(models.Model):
|
|
_name = "hr.attendance.month.detail"
|
|
_inherit = ['portal.mixin', 'mail.thread', 'mail.activity.mixin']
|
|
_description = "考勤确认单"
|
|
|
|
name = fields.Char(related='employee_id.name')
|
|
employee_id = fields.Many2one('hr.employee', string="员工", readonly=True)
|
|
company_id = fields.Many2one(related="employee_id.company_id", string="公司", store=True)
|
|
department_id = fields.Many2one('hr.department', string="部门", related="employee_id.department_id", readonly=True, store=True)
|
|
# userid = fields.Char(string='userid')
|
|
attendance_year_id = fields.Many2one('hr.attendance.year', string="统计年份")
|
|
current_year = fields.Char(string='统计年份', related="attendance_year_id.year", store=True)
|
|
current_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="考勤月份", store=True)
|
|
required_attendance_days = fields.Integer(string="应出勤天数")
|
|
actual_attendance_days = fields.Integer(string="实际出勤天数")
|
|
compensatory_leave = fields.Integer(string="调休")
|
|
leave_of_absence = fields.Integer(string="事假", digits=(16, 3))
|
|
statutory_leave = fields.Integer(string="加班时(法定)")
|
|
work_overtime = fields.Integer(string="加班")
|
|
entry_date = fields.Date(related='employee_id.entry_date', string="入职日期")
|
|
expected_date = fields.Date(string="离职日期")
|
|
cop_leave = fields.Float(string='补休', digits=(16, 3))
|
|
annual_leave = fields.Float(string='年假', digits=(16, 3))
|
|
duty_off_leave = fields.Integer(string='值班调休')
|
|
marriage_leave = fields.Integer(string='婚假')
|
|
general_off_leave = fields.Integer(string='调休')
|
|
maternity_leave = fields.Integer(string='产假')
|
|
paternity_leave = fields.Integer(string='陪产假')
|
|
family_planning_leave = fields.Integer(string='计生假')
|
|
bereavement_leave = fields.Float(string='丧假')
|
|
home_visit_leave = fields.Integer(string='探亲假')
|
|
parents_meeting_leave = fields.Integer(string='家长会假')
|
|
general_sick_leave = fields.Float(string='病假(普通)')
|
|
hospital_sick_leave = fields.Float(string='病假(住院期)')
|
|
recovery_sick_leave = fields.Float(string='病假(出院恢复期)')
|
|
exam_leave = fields.Integer(string='考试假')
|
|
holiday_id = fields.Many2one('hr.leave.type', string="假期种类")
|
|
active = fields.Boolean('Active', default=True, tracking=True)
|
|
# todo 需要什么字段往上加什么
|
|
# 打卡
|
|
come_late = fields.Integer(string="迟到")
|
|
is_early_card = fields.Integer(string="早退")
|
|
is_no_card = fields.Integer(string="缺卡")
|
|
is_work = fields.Integer(string='旷工')
|
|
# 假期
|
|
unpaid_leave = fields.Integer(string="事假")
|
|
|
|
|
|
note = fields.Char(string='备注')
|
|
hr_attendance_month_id = fields.Many2one('hr.attendance.month', string="出勤月度汇总", ondelete="cascade", readonly=True)
|
|
signature = fields.Image(string="签名", copy=False, attachment=True, max_width=1024, max_height=1024)
|
|
signed_by = fields.Char(string="签名者", copy=False)
|
|
signed_on = fields.Datetime(string="签名时间", copy=False)
|
|
state = fields.Selection(
|
|
selection=[
|
|
('unconfirmed', "未确认"),
|
|
('confirmed', "已确认"),
|
|
('pending' , "待定"),
|
|
],
|
|
string="状态",
|
|
readonly=True, copy=False, index=True,
|
|
tracking=3,
|
|
default='unconfirmed')
|
|
# company_area = fields.Selection(related='department_id.company_area', string='* 所属区域')
|
|
def workflow_record(self):
|
|
start_date = datetime.datetime.strptime(f"{self.current_year}-{self.current_month}-01", "%Y-%m-%d")
|
|
end_date = start_date + relativedelta(months=1) - relativedelta(days=1)
|
|
end_date = end_date.replace(hour=23, minute=59, second=59)
|
|
domain = [
|
|
('create_uid', '=', self.employee_id.user_id.id),
|
|
('write_date2', '>=', start_date),
|
|
('write_date2', '<=', end_date),
|
|
('approvals_group_id.name', '=', '考勤'),
|
|
('approvals_group_id.name', '=', '人力资源'),
|
|
]
|
|
return {
|
|
'name': '工作流记录',
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'soong.workflow.record',
|
|
'view_mode': 'tree',
|
|
'domain': domain,
|
|
}
|
|
|
|
def details_record(self):
|
|
start_datetime = datetime.datetime.strptime(f"{self.current_year}-{self.current_month}-01", "%Y-%m-%d")
|
|
start_date = start_datetime.date()
|
|
|
|
end_datetime = start_datetime + relativedelta(months=1) - relativedelta(days=1)
|
|
end_date = end_datetime.date()
|
|
|
|
domain = [
|
|
('employee_id', '=', self.employee_id.id),
|
|
('current_data', '>=', start_date),
|
|
('current_data', '<=', end_date),
|
|
]
|
|
return {
|
|
'name': '详情',
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'hr.attendance',
|
|
'view_mode': 'tree',
|
|
'domain': domain,
|
|
}
|
|
|
|
def send_confirm_attendance(self):
|
|
for record in self:
|
|
partner = record.employee_id.user_id.partner_id
|
|
lang = record.env.lang
|
|
partner_ids = partner.ids
|
|
# 发送消息
|
|
record.with_context(lang=lang)._message_auto_subscribe_notify(partner_ids, 'yuthon_hr_attendance.mail_action_hr_attendance_message')
|
|
# 给企业微信发送消息
|
|
user_id = record.employee_id.user_id.oauth_uid
|
|
menu_id = self.env['ir.ui.menu'].search([('name', '=', '考勤确认单')])
|
|
url = f'https://oa.thtzjt.com/web?login={record.employee_id.user_id.login}&oauth_uid={record.employee_id.wecom_userid}#id={self.id}&cids=2&action={menu_id.action.id}&model={self._name}&view_type=form'
|
|
attendance_url = "{0}/my/attendance/month/detail/{1}".format(str(url), str(record.id))
|
|
message = '您的{0}年{1}月的考勤已生成 \n <a href=\"{2}\"> 点击查看 </a>'.format(str(record.current_year), str(record.current_month), str(attendance_url))
|
|
# self.env['wx.work.api'].sudo().send_text_message(user_id, message)
|
|
self.sudo().env["wecom.apps"].sync_send_message(category='att', user_id=user_id, message=message)
|
|
|
|
def attendance_confirm(self):
|
|
detail_ids = self.env["hr.attendance.month.detail"].browse(
|
|
self._context.get('active_ids', self._context.get('active_id')))
|
|
for record in detail_ids:
|
|
record.state = 'confirmed'
|
|
|
|
|
|
def _compute_access_url(self):
|
|
super()._compute_access_url()
|
|
for record in self:
|
|
record.access_url = f'/my/attendance/month/detail/{record.id}'
|
|
|
|
def _has_to_be_signed(self):
|
|
if self.state == 'confirmed' and self.signature:
|
|
# 不需要签字确认
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def _get_planning_required_attendance_days(self, employee):
|
|
"""
|
|
排班的应出勤天数
|
|
:param employee: 员工
|
|
:return: 应出勤天数
|
|
"""
|
|
self.ensure_one()
|
|
days = calendar.monthrange(int(self.current_year), int(self.current_month))[1]
|
|
tz = employee.resource_calendar_id.tz # 时区
|
|
required_attendance_days = 0
|
|
for n in range(days):
|
|
current_date = datetime.date(int(self.current_year), int(self.current_month), n + 1)
|
|
current_week = calendar.weekday(int(self.current_year), int(self.current_month), n + 1)
|
|
# 判断当天是否有排班计划
|
|
current_datetime_start = datetime.datetime.combine(datetime.datetime.strptime(current_date.strftime("%Y-%m-%d"), '%Y-%m-%d'), datetime.datetime.min.time())
|
|
current_datetime_end = datetime.datetime.combine(datetime.datetime.strptime(current_date.strftime("%Y-%m-%d"), '%Y-%m-%d'), datetime.datetime.max.time())
|
|
# 判断是否是 排版打卡
|
|
planning_slot_ids = self.env['planning.slot'].search(
|
|
[('resource_id', '=', self.employee_id.resource_id.id),
|
|
('start_datetime', '>=', current_datetime_start - datetime.timedelta(hours=8)),
|
|
('start_datetime', '<=', current_datetime_end - datetime.timedelta(hours=8))])
|
|
if planning_slot_ids:
|
|
required_attendance_days += 1
|
|
return required_attendance_days
|
|
|
|
def _get_required_attendance_days(self, employee):
|
|
"""
|
|
应出勤天数
|
|
:param employee: 员工
|
|
:return: 应出勤天数
|
|
"""
|
|
self.ensure_one()
|
|
days = calendar.monthrange(int(self.current_year), int(self.current_month))[1]
|
|
tz = employee.resource_calendar_id.tz # 时区
|
|
required_attendance_days = 0
|
|
for n in range(days):
|
|
current_date = datetime.date(int(self.current_year), int(self.current_month), n + 1)
|
|
current_week = calendar.weekday(int(self.current_year), int(self.current_month), n + 1)
|
|
# 判断当天是否应该出勤
|
|
work_time_result = self.env["hr.attendance"].sudo().get_employee_work_time(employee, current_week, current_date, tz)
|
|
if work_time_result:
|
|
required_attendance_days += 1
|
|
return required_attendance_days
|
|
|
|
|
|
def _get_actual_attendance_days(self, employee):
|
|
"""
|
|
实际出勤天数
|
|
:param employee: 员工
|
|
:return: 实际出勤天数
|
|
"""
|
|
self.ensure_one()
|
|
days = calendar.monthrange(int(self.current_year), int(self.current_month))[1]
|
|
actual_attendance_days = 0
|
|
for n in range(days):
|
|
# 根据出勤时间判断是否打卡
|
|
current_date = datetime.date(int(self.current_year), int(self.current_month), n + 1)
|
|
attendance_obj = self.env["hr.attendance"].sudo().search([('employee_id', '=', employee.id), ("current_data", '=', current_date)])
|
|
is_check_in = False
|
|
for obj in attendance_obj:
|
|
if obj.check_in or obj.check_out:
|
|
# 当天有打卡记录说明已经出勤
|
|
is_check_in = True
|
|
if is_check_in is True:
|
|
actual_attendance_days += 1
|
|
return actual_attendance_days
|
|
|
|
def _get_come_late(self, employee):
|
|
"""
|
|
判断是否迟到
|
|
:param employee: 员工
|
|
:return: 迟到次数
|
|
"""
|
|
self.ensure_one()
|
|
days = calendar.monthrange(int(self.current_year), int(self.current_month))[1]
|
|
is_late_card = 0
|
|
is_early_card = 0
|
|
is_no_card = 0
|
|
for n in range(days):
|
|
# 根据出勤时间判断是否打卡
|
|
current_date = datetime.date(int(self.current_year), int(self.current_month), n + 1)
|
|
attendance_obj = self.env["hr.attendance"].sudo().search([('employee_id', '=', employee.id), ("current_data", '=', current_date)])
|
|
for obj in attendance_obj:
|
|
# 迟到
|
|
if obj.is_late_card:
|
|
is_late_card += 1
|
|
# 早退
|
|
if obj.is_early_card:
|
|
is_early_card += 1
|
|
# 缺卡
|
|
if obj.is_no_card:
|
|
is_no_card += 1
|
|
return is_late_card, is_early_card, is_no_card
|
|
|
|
def _hr_attendance_month_detail_action(self, processed_employee_ids):
|
|
"""
|
|
返回视图 view_hr_attendance_month_detail_tree
|
|
:return: ir.actions.act_window 视图 view_hr_attendance_month_detail_tree
|
|
"""
|
|
action = {
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'hr.attendance.month.detail',
|
|
'name': "考勤统计",
|
|
'view_type': 'tree',
|
|
'view_mode': 'list,form',
|
|
'domain': [('employee_id', 'in', processed_employee_ids)] if processed_employee_ids else [],
|
|
'views': [[self.env.ref('yuthon_hr_attendance.view_hr_attendance_month_detail_tree').id, 'list'],
|
|
[False, 'form']],
|
|
}
|
|
return action
|
|
|
|
def _get_overtime_annual_summary(self, employee):
|
|
"""
|
|
调休(小时):加班(小时):取员工表上字段
|
|
:param employee: 员工
|
|
:return: 调休(小时):加班(小时)
|
|
"""
|
|
self.ensure_one()
|
|
work_overtime = 0
|
|
compensatory_leave = 0
|
|
overtime_annual_summary = self.env['overtime.annual.summary'].sudo().search([('user_id', '=', employee.id)], limit=1)
|
|
overtime_summarys = overtime_annual_summary.overtime_summary_ids.filtered(
|
|
lambda s: s.annual == int(self.current_year) and s.month == int(self.current_month))
|
|
for overtime_summary in overtime_summarys:
|
|
work_overtime = overtime_summary.overtime_this_month_user
|
|
compensatory_leave = overtime_summary.compensatory_leave
|
|
return work_overtime, compensatory_leave
|
|
|
|
def _get_hr_leave_type(self, employee_id, type_list):
|
|
"""
|
|
:param type_list: 请假类型列表
|
|
:return: 天
|
|
"""
|
|
self.ensure_one()
|
|
leave_days = {}
|
|
year = int(self.current_year)
|
|
month = int(self.current_month)
|
|
if month == 12:
|
|
date_to = date(year, 12, 31)
|
|
else:
|
|
next_month = month + 1
|
|
date_to = date(year, next_month, 1) - datetime.timedelta(days=1)
|
|
|
|
date_from = date(year, month, 1)
|
|
for leave_type in type_list:
|
|
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', leave_type)])
|
|
leave_records = self.env['hr.leave'].search([
|
|
('employee_id', '=', employee_id.id),
|
|
('holiday_status_id', '=', holiday_status_id.id),
|
|
('date_from', '<', date_to),
|
|
('date_to', '>', date_from),
|
|
('state', '=', 'validate')
|
|
])
|
|
leave_days[leave_type] = 0
|
|
for leave in leave_records:
|
|
leave_start = leave.date_from.date()
|
|
leave_end = leave.date_to.date()
|
|
start_in_month = max(leave_start, date_from)
|
|
end_in_month = min(leave_end, date_to)
|
|
# 判断是否跨月
|
|
is_cross_month = leave_start.year != leave_end.year or leave_start.month != leave_end.month
|
|
if is_cross_month:
|
|
if leave_start.year == year and leave_start.month == month:
|
|
start_date = leave_start
|
|
if leave_end.year == year and leave_end.month == month:
|
|
end_date = leave_end
|
|
else:
|
|
end_date = date(year, month + 1, 1) if month < 12 else date(year + 1, 1, 1)
|
|
end_date = end_date - datetime.timedelta(days=1) # 取到当月最后一天
|
|
elif leave_end.year == year and leave_end.month == month:
|
|
start_date = date(year, month, 1)
|
|
end_date = leave_end
|
|
else:
|
|
start_date = start_in_month
|
|
end_date = end_in_month
|
|
days = self._compute_duration(holiday_status_id, start_date, end_date)
|
|
else:
|
|
days = self._compute_duration(holiday_status_id, start_in_month, end_in_month)
|
|
|
|
if leave_type in leave_days:
|
|
leave_days[leave_type] += days
|
|
else:
|
|
leave_days[leave_type] = days
|
|
if leave_type not in leave_days:
|
|
leave_days[leave_type] = 0
|
|
|
|
return leave_days
|
|
|
|
def _compute_duration(self, holiday_status_id, start_date, end_date):
|
|
"""
|
|
计算请假时长
|
|
:param holiday_status_id: 请假类型
|
|
:param start_date: 开始时间
|
|
:param end_date: 结束时间
|
|
:return:天
|
|
"""
|
|
# 如果是自然日:
|
|
if holiday_status_id.type_of_work == "1":
|
|
if start_date and end_date:
|
|
days = (end_date - start_date).days + 1
|
|
else:
|
|
days = 0
|
|
return days
|
|
if holiday_status_id.type_of_work == 0:
|
|
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
|
query_start = datetime.datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
|
query_end = datetime.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 = []
|
|
# 提取节假日和调休日
|
|
for leave in calendar_leaves:
|
|
if leave.holidays:
|
|
holidays_list.extend(leave.holidays.split(','))
|
|
if leave.alternate_rest_day:
|
|
holidays_list.extend(leave.alternate_rest_day.split(','))
|
|
if leave.shift_replacement_day:
|
|
shift_days_list.extend(leave.shift_replacement_day.split(','))
|
|
|
|
holidays_list = [self.remove_leading_zero(date) for date in holidays_list]
|
|
shift_days_list = [self.remove_leading_zero(date) for date in shift_days_list]
|
|
|
|
days = 0 # 初始化工作日计数
|
|
current_date = start_date
|
|
|
|
while current_date <= end_date:
|
|
current_date_str = current_date.strftime('%m月%d日')
|
|
current_date_str = self.remove_leading_zero(current_date_str)
|
|
is_weekend = current_date.weekday() in [5, 6]
|
|
|
|
if current_date_str in holidays_list:
|
|
# 如果是法定节假日,不计为工作日
|
|
pass
|
|
elif current_date_str in shift_days_list:
|
|
# 如果是调休日,无论是否周末都算作工作日
|
|
days += 1
|
|
elif not is_weekend:
|
|
# 如果不是周末且不是节假日,算作工作日
|
|
days += 1
|
|
|
|
current_date += timedelta(days=1)
|
|
else:
|
|
delta = end_date - start_date
|
|
days = delta.days + 1
|
|
return days
|
|
|
|
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}日'
|
|
|
|
def analysis_attendance_month_detail(self):
|
|
"""
|
|
更新用户的考勤明细
|
|
:return:
|
|
"""
|
|
self.ensure_one()
|
|
if self.state == 'confirmed' and self.signature:
|
|
return False
|
|
# 排班的进行统计
|
|
if self.employee_id.default_planning_role_id:
|
|
# todo 需要什么字段往上加什么
|
|
# 迟到, 早退, 缺卡
|
|
self.come_late, self.is_early_card, self.is_no_card = self._get_come_late(self.employee_id)
|
|
# 无薪假:hr.leave表
|
|
days, = self._get_hr_leave_type(self.employee_id, ['事假', '年假'])
|
|
self.unpaid_leave = days
|
|
|
|
# 行政的进行统计
|
|
if not self.employee_id.default_planning_role_id:
|
|
# todo 需要什么字段往上加什么
|
|
# 迟到, 早退, 缺卡
|
|
self.come_late, self.is_early_card, self.is_no_card = self._get_come_late(self.employee_id)
|
|
# 无薪假:hr.leave表
|
|
leave_days = self._get_hr_leave_type(self.employee_id,['事假', '年假', '产假'])
|
|
self.leave_of_absence = leave_days.get('事假', 0)
|
|
self.annual_leave = leave_days.get('年假', 0)
|
|
self.maternity_leave = leave_days.get('产假', 0)
|
|
|
|
return True
|
|
|
|
def update_attendance_month(self):
|
|
"""
|
|
调用用户的考勤明细
|
|
:return:
|
|
"""
|
|
for record in self:
|
|
# 更新用户的考勤明细
|
|
record.analysis_attendance_month_detail()
|
|
|
|
def button_put(self):
|
|
menu_id = self.env['ir.ui.menu'].search([('name', '=', '考勤确认'), ('parent_id', '!=', False)])
|
|
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=hr.attendance.month.detail&view_type=list&menu_id={menu_id.id}'
|
|
we_id = self.env['hr.employee'].search([('id', '=', self.employee_id.id)]).wecom_userid
|
|
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='default', user_id=we_id, title="考勤确认",
|
|
description=self.employee_id.name + '请确认你的考勤:',
|
|
url=url, btntxt="详细信息")
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'type': 'success',
|
|
'message': '确认通知已发送',
|
|
'sticky': False,
|
|
}
|
|
}
|
|
|
|
def button_all_put(self):
|
|
detail_ids = self.env["hr.attendance.month.detail"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
|
for detail in detail_ids:
|
|
menu_id = self.env['ir.ui.menu'].search([('name', '=', '考勤确认'), ('parent_id', '!=', False)])
|
|
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=hr.attendance.month.detail&view_type=list&menu_id={menu_id.id}'
|
|
we_id = self.env['hr.employee'].search([('id', '=', detail.employee_id)]).wecom_userid
|
|
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='attendance', user_id=we_id, title="考勤确认",
|
|
description=detail.employee_id.name + '请确认你的考勤:',
|
|
url=url, btntxt="详细信息")
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'type': 'success',
|
|
'message': '确认通知已发送',
|
|
'sticky': False,
|
|
}
|
|
}
|
|
|
|
def button_confirm(self):
|
|
current_employee = self.env['hr.employee'].search([('user_id', '=', self.env.user.id)], limit=1)
|
|
range1_start, range1_end, range2_start, range2_end= self._get_days(current_employee)
|
|
leave = self._create_leave_record(range1_start, range1_end)
|
|
day = leave.number_of_days
|
|
leave.unlink()
|
|
leave2 = self._create_leave_record(range2_start, range2_end)
|
|
day2 = leave2.number_of_days
|
|
leave2.unlink()
|
|
work_day = self._process_shift_days(day2,range2_start,range2_end) # 当月的工作日
|
|
for record in self:
|
|
current_year = record.current_year
|
|
current_month = int(record.current_month)
|
|
record.state = 'confirmed'
|
|
# 计算该月的天数
|
|
if current_month in [1, 3, 5, 7, 8, 10, 12]:
|
|
month_days = 31
|
|
elif current_month in [4, 6, 9, 11]:
|
|
month_days = 30
|
|
elif current_month == 2:
|
|
if (int(current_year) % 4 == 0 and int(current_year) % 100 != 0) or (int(current_year) % 400 == 0):
|
|
month_days = 29
|
|
else:
|
|
month_days = 28
|
|
# 日期拼接
|
|
payslip_start_date = datetime.date(int(current_year), current_month, 1)
|
|
payslip_end_date = datetime.date(int(current_year), current_month, month_days)
|
|
|
|
payslip_record = self.env['hr.payslip'].search([
|
|
('employee_id', '=', current_employee.id),
|
|
('date_from', '>=', payslip_start_date),
|
|
('date_to', '<=', payslip_end_date)
|
|
])
|
|
if payslip_record:
|
|
# 初始值
|
|
base_deduction = 0
|
|
performance_deduction = 0
|
|
commuting_deduction = 0
|
|
# 获取当前人员的 对应的月份的基本薪资 绩效奖金 和 通信补贴
|
|
base_salary = payslip_record.base_salary
|
|
performance_bonuses = payslip_record.performance_bonuses
|
|
commuting_allowance = payslip_record.commuting_allowance
|
|
|
|
# 普通病假
|
|
common_sick_leave = record.general_sick_leave
|
|
base_deduction += common_sick_leave * base_salary * 0.01
|
|
performance_deduction += (performance_bonuses / 21.75) * common_sick_leave
|
|
|
|
# 住院恢复期
|
|
hospital_recovery_leave = record.recovery_sick_leave
|
|
base_deduction += hospital_recovery_leave * base_salary * 0.01
|
|
performance_deduction += (performance_bonuses / 21.75) * hospital_recovery_leave
|
|
|
|
# 住院假
|
|
hospital_leave = record.hospital_sick_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * hospital_leave
|
|
|
|
# 事假
|
|
personal_leave = record.leave_of_absence
|
|
base_deduction += personal_leave * base_salary * 0.025
|
|
performance_deduction += (performance_bonuses / 21.75) * personal_leave
|
|
|
|
# 产假
|
|
commuting_deduction += (commuting_allowance / day) * work_day
|
|
performance_deduction += (performance_bonuses / day) * work_day
|
|
|
|
# 陪产假
|
|
paternity_leave = record.paternity_leave
|
|
commuting_deduction += (commuting_allowance / month_days) * paternity_leave
|
|
performance_deduction += (performance_bonuses / month_days) * paternity_leave
|
|
|
|
# 计生假
|
|
family_planning_leave = record.family_planning_leave
|
|
commuting_deduction += (commuting_allowance / month_days) * family_planning_leave
|
|
performance_deduction += (performance_bonuses / month_days) * family_planning_leave
|
|
|
|
# 丧假
|
|
bereavement_leave = record.bereavement_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * bereavement_leave
|
|
|
|
# 探亲假
|
|
home_visit_leave = record.home_visit_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * home_visit_leave
|
|
|
|
# 婚假
|
|
marriage_leave = record.marriage_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * marriage_leave
|
|
|
|
# 考试假
|
|
exam_leave = record.exam_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * exam_leave
|
|
|
|
# 家长会假
|
|
parents_meeting_leave = record.parents_meeting_leave
|
|
performance_deduction += (performance_bonuses / 21.75) * parents_meeting_leave
|
|
|
|
current_employee.write({
|
|
'last_month_base_salary_deduction': base_deduction,
|
|
'last_month_performance_deduction': performance_deduction,
|
|
'last_month_commuting_deduction': commuting_deduction
|
|
})
|
|
|
|
# 行政考勤 调休日期
|
|
def _process_shift_days(self, days , start_date, end_date):
|
|
calendar_leaves = self.env['resource.calendar.leaves'].search([])
|
|
all_days = []
|
|
for calendar in calendar_leaves:
|
|
shift_days = calendar.shift_replacement_day.split(',') if calendar.shift_replacement_day else []
|
|
for day_str in shift_days:
|
|
day = datetime.datetime.strptime(day_str, '%m月%d日')
|
|
day = day.replace(year=start_date.year).date()
|
|
all_days.append(day)
|
|
unique_days = list(set(all_days))
|
|
total_days = days
|
|
for day in unique_days:
|
|
if start_date <= day <= end_date:
|
|
total_days += 1
|
|
return total_days
|
|
|
|
# 创建用户记录传入日期来获取天数(自然日中工作日的逻辑)
|
|
def _create_leave_record(self, start_date, end_date):
|
|
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', '年假')])
|
|
admin_id = self.env['hr.employee'].search([("name", "=", 'Administrator')])
|
|
return self.env['hr.leave'].sudo().create({
|
|
'employee_id': admin_id.id,
|
|
'holiday_status_id': holiday_status_id.id,
|
|
'request_date_from': start_date,
|
|
'request_date_to': end_date,
|
|
'state': 'confirm',
|
|
})
|
|
|
|
def _get_days(self, employee_id):
|
|
"""
|
|
:param employee_id: 员工id
|
|
"""
|
|
year = int(self.current_year)
|
|
month = int(self.current_month)
|
|
if month == 12:
|
|
date_to = date(year, 12, 31)
|
|
else:
|
|
next_month = month + 1
|
|
date_to = date(year, next_month, 1) - datetime.timedelta(days=1)
|
|
|
|
date_from = date(year, month, 1)
|
|
holiday_status_id = self.env['hr.leave.type'].search([('name', '=', '产假')])
|
|
leave_records = self.env['hr.leave'].search([
|
|
('employee_id', '=', employee_id.id),
|
|
('holiday_status_id', '=', holiday_status_id.id),
|
|
('date_from', '<', date_to),
|
|
('date_to', '>', date_from),
|
|
('state', '=', 'validate')
|
|
])
|
|
for leave in leave_records:
|
|
leave_start = leave.date_from.date()
|
|
leave_end = leave.date_to.date()
|
|
range1_start = None
|
|
range1_end = None
|
|
range2_start = None
|
|
range2_end = None
|
|
# 判断是否跨月
|
|
# is_cross_month = leave_start.year != leave_end.year or leave_start.month != leave_end.month
|
|
# if is_cross_month:
|
|
if leave_start.year == year and leave_start.month == month:
|
|
# 请假开始在当前月
|
|
range1_start = date(year, month, 1)
|
|
range1_end = date_to
|
|
range2_start = leave_start
|
|
range2_end = date_to
|
|
elif leave_end.year == year and leave_end.month == month:
|
|
# 请假结束在当前月
|
|
range1_start = date(year, month, 1)
|
|
range1_end = date_to
|
|
range2_start = date(year, month, 1)
|
|
range2_end = leave_end
|
|
elif (year, month) > (leave_start.year, leave_start.month) and (year, month) < (leave_end.year, leave_end.month):
|
|
# 当前月在请假期间内
|
|
range1_start = date(year, month, 1)
|
|
range1_end = date_to
|
|
range2_start = date(year, month, 1)
|
|
range2_end = date_to
|
|
|
|
return range1_start, range1_end, range2_start, range2_end
|
|
|
|
def _get_attendance_month_obj(self):
|
|
"""
|
|
获取出勤月度汇总记录结果表
|
|
:return: obj
|
|
"""
|
|
attendance_month_obj = self.env["hr.attendance.month"].search([
|
|
('current_year', '=', self.current_year),
|
|
('current_month', '=', self.current_month)
|
|
], limit=1)
|
|
if not attendance_month_obj:
|
|
attendance_month_obj = self.env["hr.attendance.month"].sudo().create({
|
|
"attendance_year_id": self.attendance_year_id.id,
|
|
"current_year": self.current_year,
|
|
"current_month": self.current_month,
|
|
"is_statistics": True,
|
|
"statistics_time": fields.Datetime.now(),
|
|
})
|
|
return attendance_month_obj
|
|
|
|
def _get_attendance_month_detail(self, employee, current_year, current_month):
|
|
"""
|
|
判断当前是否已经创建了考勤明细
|
|
:param employee: 员工
|
|
:return: 考情明细
|
|
"""
|
|
attendance_month_detail_obj = self.env["hr.attendance.month.detail"].search(
|
|
[('employee_id', '=', employee.id),
|
|
('current_year', '=', current_year),
|
|
('current_month', '=', current_month),
|
|
], limit=1)
|
|
if not attendance_month_detail_obj:
|
|
attendance_month_detail_obj = self.env["hr.attendance.month.detail"].sudo().create({
|
|
"employee_id": employee.id,
|
|
"attendance_year_id": self.attendance_year_id.id,
|
|
"current_year": current_year,
|
|
"current_month": current_month,
|
|
})
|
|
return attendance_month_detail_obj
|
|
|
|
def button_compute_all(self):
|
|
processed_employee_ids = []
|
|
attendance_month_obj = self._get_attendance_month_obj()
|
|
# 查询所有的当前员工? 是否需要判断当前员工是否关联企业微信??预留
|
|
calendars = self.env['resource.calendar'].search([])
|
|
all_employee_ids = set()
|
|
for calendar in calendars:
|
|
all_employee_ids.update(calendar.resource_user_ids.ids)
|
|
all_employee_ids = list(all_employee_ids)
|
|
detail_ids = self.env["hr.attendance.month.detail"].browse(self._context.get('active_ids', self._context.get('active_id')))
|
|
for detail_id in detail_ids:
|
|
if detail_id.employee_id not in all_employee_ids:
|
|
# 判断当前是否已经创建了考勤明细
|
|
attendance_month_detail_obj = self._get_attendance_month_detail(detail_id.employee_id, detail_id.current_year, detail_id.current_month)
|
|
attendance_month_detail_obj.hr_attendance_month_id = attendance_month_obj.id
|
|
# 判断当前用户是否已经确认签字,签字后不再更新
|
|
if attendance_month_detail_obj.state == 'confirmed' and attendance_month_detail_obj.signature:
|
|
continue
|
|
# 更新用户的考勤明细
|
|
attendance_month_detail_obj.analysis_attendance_month_detail()
|
|
if detail_id.employee_id.id not in processed_employee_ids:
|
|
processed_employee_ids.append(detail_id.employee_id.id)
|
|
# 返回包含处理过的员工ID的action动作
|
|
action = self._hr_attendance_month_detail_action(processed_employee_ids)
|
|
return action
|
|
|
|
def attendance_month_detail(self):
|
|
attendance_details = self.env['hr.attendance.month.detail'].search([])
|
|
for detail in attendance_details:
|
|
detail.write({'state': 'confirmed'})
|
|
|
|
def button_pending(self):
|
|
for record in self:
|
|
record.write({'state': 'pending'})
|
|
|
|
def button_all_attendance_month(self):
|
|
attendance_details = self.env['hr.attendance.month.detail'].search([])
|
|
for detail in attendance_details:
|
|
menu_id = self.env['ir.ui.menu'].search([('name', '=', '我的确认'), ('parent_id', '!=', False)])
|
|
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&#action={menu_id.action.id}&model=hr.attendance.month.detail&view_type=list&menu_id={menu_id.id}'
|
|
we_id = self.env['hr.employee'].search([('id', '=', detail.employee_id)]).wecom_userid
|
|
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='attendance', user_id=we_id, title="考勤确认",
|
|
description=detail .employee_id.name + '请确认你的考勤:',
|
|
url=url, btntxt="详细信息")
|