Merge branch 'refs/heads/dev' into dev_zhou
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import models
|
||||
from . import utils
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '出勤拓展',
|
||||
'version': '17.0.0.1',
|
||||
'category': '出勤拓展',
|
||||
'author': 'pengyuthon->ljm',
|
||||
'sequence': 190,
|
||||
'description': """
|
||||
功能介绍
|
||||
==============
|
||||
|
||||
* 新增 定时任务 和 抓取企业微信的出勤数据的核心模块
|
||||
* 新增 企业微信出勤记录的原始数据
|
||||
* 新增 企业微信出勤记录和出勤模块的融合数据
|
||||
* 新增 月度汇总明细结果表
|
||||
* 新增 月度汇总明细表,附加权限
|
||||
* 新增 月度汇总向导,年份维护表
|
||||
* 新增 员工出勤以及判断是否应该出勤的逻辑等
|
||||
""",
|
||||
'depends': ['hr_attendance', 'yuthon_wechat'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/hr_attendance_time.xml',
|
||||
'views/hr_attendance_views.xml',
|
||||
'views/hr_attendance_detail_views.xml',
|
||||
'views/wx_work_views.xml',
|
||||
],
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record forcecreate="True" id="ir_cron_get_or_update_attendance_time" model="ir.cron">
|
||||
<field name="name">定时任务:定时抓取上下班打卡信息</field>
|
||||
<field name="model_id" ref="model_hr_attendance_detail"/>
|
||||
<field name="state">code</field>
|
||||
<field name="code">model.get_hr_attendance_time()</field>
|
||||
<field eval="False" name="active"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">120</field>
|
||||
<field name="interval_type">months</field>
|
||||
<field name="nextcall" eval="(DateTime.now()).strftime('%Y-%m-%d 00:00:00')"/>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -1,5 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import hr_attendance
|
||||
from . import hr_attendance_detail
|
||||
from . import wx_work
|
||||
@@ -1,342 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import pytz
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import api, fields, models, tools, _
|
||||
|
||||
|
||||
class HrAttendance(models.Model):
|
||||
_inherit = "hr.attendance"
|
||||
_order = 'current_data desc, check_in desc'
|
||||
|
||||
userid = fields.Char(string='userid')
|
||||
groupname = fields.Char(string='打卡规则名称')
|
||||
current_data = fields.Date(string='打卡日期', tracking=True)
|
||||
sch_check_in_time = fields.Datetime(string='上班正常时间')
|
||||
sch_check_out_time = fields.Datetime(string='下班正常时间')
|
||||
sch_check_noon_time = fields.Datetime(string='中午正常时间')
|
||||
check_noon = fields.Datetime(string='中午打卡', tracking=True)
|
||||
check_in = fields.Datetime(string="上班打卡", required=False, default=False, tracking=True)
|
||||
check_out = fields.Datetime(string="下班打卡", tracking=True)
|
||||
attendance_type = fields.Selection([('wx_work', '企业微信')], readonly=True, string='来源')
|
||||
# field_work_check = fields.Datetime(string="外勤打卡")
|
||||
|
||||
attendance_detail_ids = fields.One2many(string="打卡详情", comodel_name='hr.attendance.detail', inverse_name='hr_attendance_id')
|
||||
replacement_approval_ids = fields.One2many(string="补卡详情", comodel_name='approval2.request', compute='_compute_replacement_approval_ids')
|
||||
go_out_approval_ids = fields.One2many(string="外出详情", comodel_name='approval2.request', compute='_compute_go_out_approval_ids')
|
||||
work_overtime_approval_ids = fields.One2many(string="加班详情", comodel_name='approval2.request', compute='_compute_work_overtime_approval_ids')
|
||||
hr_leave_approval_ids = fields.One2many(string="请假详情", comodel_name='hr.leave', compute='_compute_hr_leave_approval_ids')
|
||||
|
||||
def action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("hr_attendance_ext.view_hr_attendance_detail_tree")
|
||||
form_view = self.env.ref("hr_attendance_ext.view_hr_attendance_detail_form")
|
||||
return {
|
||||
"name": _("出勤详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "hr.attendance.detail",
|
||||
"views": [(view.id, "tree"), (form_view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"target": "current",
|
||||
"res_id": self.id,
|
||||
"domain": [('hr_attendance_id', '=', self.id)],
|
||||
"context": {'hr_attendance_id': self.id}
|
||||
}
|
||||
|
||||
def replacement_approval_action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("approvals2.approval_request_view_tree")
|
||||
form_view = self.env.ref("approvals2.approval_request_view_form")
|
||||
if self.current_data:
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", self.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "card_replacement"),
|
||||
("replacement_date", "=", self.current_data),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
else:
|
||||
approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)])
|
||||
return {
|
||||
"name": _("补卡详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "approval2.request",
|
||||
"views": [(view.id, "tree"), (form_view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"target": "current",
|
||||
"res_id": self.id,
|
||||
"domain": [('id', 'in', approval_request.ids)],
|
||||
"context": {'id': approval_request.ids}
|
||||
}
|
||||
|
||||
def go_out_approval_action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("approvals2.approval_request_view_tree")
|
||||
form_view = self.env.ref("approvals2.approval_request_view_form")
|
||||
if self.current_data:
|
||||
date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", self.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "go_out_approval"),
|
||||
("approval_end_time", ">=", date_start),
|
||||
("approval_start_time", "<=", date_end),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
else:
|
||||
approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)])
|
||||
|
||||
return {
|
||||
"name": _("外出详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "approval2.request",
|
||||
"views": [(view.id, "tree"), (form_view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"target": "current",
|
||||
"res_id": self.id,
|
||||
"domain": [('id', 'in', approval_request.ids)],
|
||||
"context": {'id': approval_request.ids}
|
||||
}
|
||||
|
||||
def work_overtime_action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("approvals2.approval_request_view_tree")
|
||||
form_view = self.env.ref("approvals2.approval_request_view_form")
|
||||
if self.current_data:
|
||||
date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", self.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "work_overtime"),
|
||||
("approval_end_time", ">=", date_start),
|
||||
("approval_start_time", "<=", date_end),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
else:
|
||||
approval_request = self.env["approval2.request"].sudo().search([('id', '<', 0)])
|
||||
return {
|
||||
"name": _("加班详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "approval2.request",
|
||||
"views": [(view.id, "tree"), (form_view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"target": "current",
|
||||
"res_id": self.id,
|
||||
"domain": [('id', 'in', approval_request.ids)],
|
||||
"context": {'id': approval_request.ids}
|
||||
}
|
||||
|
||||
def hr_leave_action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("hr_holidays.hr_leave_view_tree_my")
|
||||
form_view = self.env.ref("hr_holidays.hr_leave_view_form")
|
||||
if self.current_data:
|
||||
date_start = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(fields.Date.to_date(self.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["hr.leave"].sudo().search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('date_from', '<=', date_end),
|
||||
('date_to', '>=', date_start),
|
||||
# ("state", "=", "validate")
|
||||
])
|
||||
else:
|
||||
approval_request = self.env["hr.leave"].sudo().search([('id', '<', 0)])
|
||||
|
||||
return {
|
||||
"name": _("请假详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": "hr.leave",
|
||||
"views": [(view.id, "tree"), (form_view.id, "form")],
|
||||
"view_id": view.id,
|
||||
"target": "current",
|
||||
"res_id": self.id,
|
||||
"domain": [('id', 'in', approval_request.ids)],
|
||||
"context": {'id': approval_request.ids, 'short_name': 1, 'search_default_active_time_off': 1}
|
||||
}
|
||||
|
||||
@api.constrains('check_in', 'check_out', 'employee_id')
|
||||
def _check_validity(self):
|
||||
for attendance in self:
|
||||
if attendance.attendance_type != 'wx_work':
|
||||
super(HrAttendance, attendance)._check_validity()
|
||||
|
||||
# 审批补卡详情 approval2
|
||||
def _compute_replacement_approval_ids(self):
|
||||
for record in self:
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", record.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "card_replacement"),
|
||||
("replacement_date", "=", record.current_data),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
record.replacement_approval_ids = approval_request.ids if approval_request.ids else None
|
||||
|
||||
# 审批请假详情 hr.leave
|
||||
def _compute_hr_leave_approval_ids(self):
|
||||
for record in self:
|
||||
record.hr_leave_approval_ids = None
|
||||
if record.current_data:
|
||||
date_start = fields.Datetime.from_string(
|
||||
fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(
|
||||
fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["hr.leave"].sudo().search([
|
||||
('employee_id', '=', record.employee_id.id),
|
||||
('date_from', '<=', date_end),
|
||||
('date_to', '>=', date_start),
|
||||
# ("state", "=", "validate")
|
||||
])
|
||||
record.hr_leave_approval_ids = approval_request.ids if approval_request.ids else None
|
||||
|
||||
# 审批外出详情 approval2
|
||||
def _compute_go_out_approval_ids(self):
|
||||
for record in self:
|
||||
record.go_out_approval_ids = None
|
||||
if record.current_data:
|
||||
date_start = fields.Datetime.from_string(fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", record.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "go_out_approval"),
|
||||
("approval_end_time", ">=", date_start),
|
||||
("approval_start_time", "<=", date_end),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
record.go_out_approval_ids = approval_request.ids if approval_request.ids else None
|
||||
|
||||
# 审批加班详情 approval2
|
||||
def _compute_work_overtime_approval_ids(self):
|
||||
for record in self:
|
||||
record.work_overtime_approval_ids = None
|
||||
if record.current_data:
|
||||
date_start = fields.Datetime.from_string(
|
||||
fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 00:00:00')) - timedelta(hours=8)
|
||||
date_end = fields.Datetime.from_string(
|
||||
fields.Date.to_date(record.current_data).strftime('%Y-%m-%d 23:59:59')) - timedelta(hours=8)
|
||||
|
||||
approval_request = self.env["approval2.request"].sudo().search([
|
||||
("request_owner_id", "=", record.employee_id.user_id.id),
|
||||
("approvals_request_type", "=", "work_overtime"),
|
||||
("approval_end_time", ">=", date_start),
|
||||
("approval_start_time", "<=", date_end),
|
||||
# ("state", "=", "approved")
|
||||
])
|
||||
record.work_overtime_approval_ids = approval_request.ids if approval_request.ids else None
|
||||
|
||||
def get_work_time(self, attendance_data, current_date, tz):
|
||||
"""
|
||||
当前时间的工作时间段
|
||||
:param attendance_data:
|
||||
:param current_date:
|
||||
:param tz:
|
||||
:return:
|
||||
"""
|
||||
work_time_list = []
|
||||
for work_time in attendance_data:
|
||||
work_time_list.append(self.time_to_utc_time(self.str_to_time(current_date, work_time.hour_from), tz))
|
||||
work_time_list.append(self.time_to_utc_time(self.str_to_time(current_date, work_time.hour_to), tz))
|
||||
work_time_sorted = sorted(work_time_list)
|
||||
go_from, go_to, off_from, off_to = work_time_sorted
|
||||
return go_from, go_to, off_from, off_to, current_date
|
||||
|
||||
@api.depends('check_in', 'sch_check_in_time')
|
||||
def is_come_late(self):
|
||||
self.ensure_one()
|
||||
if self.check_in and self.sch_check_in_time and self.check_in > self.sch_check_in_time:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_employee_work_time(self, employee, current_week, current_date, tz):
|
||||
"""
|
||||
获取员工的当天是否应该出勤以及出勤时间
|
||||
:param employee: 员工
|
||||
:param current_week: 当天周
|
||||
:param current_date: 当前天
|
||||
:param tz: 时区
|
||||
:return: 出勤时间
|
||||
"""
|
||||
# 获取员工的工作信息
|
||||
resource_calendar = employee.resource_calendar_id
|
||||
# 获取工作表
|
||||
attendance_ids = resource_calendar.attendance_ids
|
||||
# 获取公休息日表
|
||||
global_leave = resource_calendar.global_leave_ids
|
||||
# 有没有工作规则
|
||||
if not attendance_ids:
|
||||
return False
|
||||
# 是不是工作时间
|
||||
attendance_data = resource_calendar.attendance_ids.filtered(lambda s: s.dayofweek == str(current_week))
|
||||
if not attendance_data:
|
||||
return False
|
||||
# 获取工作时间
|
||||
work_time = self.get_work_time(attendance_data, current_date, tz)
|
||||
if not work_time:
|
||||
return False
|
||||
go_from, go_to, off_from, off_to, current_date = work_time
|
||||
# 早上和下午上班 判断当天 是不是公休日
|
||||
if self.get_leave_day(global_leave, go_from, tz) and self.get_leave_day(global_leave, off_from, tz):
|
||||
return False
|
||||
return go_from, go_to, off_from, off_to, current_date
|
||||
|
||||
def get_leave_day(self, global_leave, current_time, tz):
|
||||
"""
|
||||
判断是否是公休日
|
||||
:param global_leave:
|
||||
:param current_time:
|
||||
:param tz:
|
||||
:return:
|
||||
"""
|
||||
if not global_leave:
|
||||
return False
|
||||
for leave in global_leave:
|
||||
if self.utc_time_to_tz(tz, leave.date_from) <= current_time.strftime("%Y-%m-%d %H:%M:%S") <= self.utc_time_to_tz(tz, leave.date_to):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def utc_time_to_tz(tz, chin):
|
||||
"""
|
||||
utc时间的时区转换tz时间
|
||||
:param tz: 时区
|
||||
:param chin: 时间
|
||||
:return: 转换时间
|
||||
"""
|
||||
time_timezone = pytz.timezone('utc').localize(chin, is_dst=False)
|
||||
time_to_utc = time_timezone.astimezone(pytz.timezone(tz))
|
||||
utc_time_str = time_to_utc.strftime("%Y-%m-%d %H:%M:%S")
|
||||
return utc_time_str
|
||||
|
||||
@staticmethod
|
||||
def time_to_utc_time(data, tz):
|
||||
"""
|
||||
本地时间转换成tz时间
|
||||
:param data: 时间
|
||||
:param tz: 时区
|
||||
:return: 转换时间
|
||||
"""
|
||||
time_timezone = pytz.timezone(tz).localize(data, is_dst=False)
|
||||
time_to_utc = time_timezone.astimezone(pytz.timezone("utc"))
|
||||
utc_time_str = time_to_utc.strftime("%Y-%m-%d %H:%M:%S")
|
||||
utc_time = datetime.strptime(utc_time_str, "%Y-%m-%d %H:%M:%S")
|
||||
return utc_time
|
||||
|
||||
@staticmethod
|
||||
def str_to_time(current_data, data):
|
||||
"""
|
||||
%d:%d 的 str类型的时间转换成时间
|
||||
:param current_data: 当前日期
|
||||
:param data: str类型时间
|
||||
:return: 转换后的时间
|
||||
"""
|
||||
from_time = "%d:%d" % (int(data), round((data - int(data)) * 60))
|
||||
from_times = str(current_data) + " " + from_time
|
||||
from_ti = datetime.strptime(from_times, "%Y-%m-%d %H:%M")
|
||||
return from_ti
|
||||
@@ -1,318 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import base64
|
||||
import datetime
|
||||
import logging
|
||||
import traceback
|
||||
from odoo import api, fields, models, tools, _
|
||||
from ...hr_attendance_ext.utils.common import utils_list_page
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HrAttendanceDetailImage(models.Model):
|
||||
_name = "hr.attendance.detail.image"
|
||||
_description = "打卡原始抓取数据图片"
|
||||
|
||||
attendance_detail_id = fields.Many2one('hr.attendance.detail', string="原始打卡记录", ondelete="cascade", readonly=True)
|
||||
media_url = fields.Char(string='附件地址')
|
||||
media_file = fields.Binary(string='附件文件', compute="_compute_media_file")
|
||||
|
||||
@api.depends('media_url')
|
||||
def _compute_media_file(self):
|
||||
for record in self:
|
||||
record.media_file = None
|
||||
wx_work_auth = self.env['auth.oauth.provider'].search([('name', '=', 'Wechat Card')], limit=1)
|
||||
if record.media_url:
|
||||
image_path = os.path.join(wx_work_auth.wx_work_image_path, record.media_url)
|
||||
record.media_file = base64.b64encode(open(image_path, 'rb').read()) if image_path else False
|
||||
|
||||
|
||||
class HrAttendanceDetail(models.Model):
|
||||
_name = "hr.attendance.detail"
|
||||
_description = "打卡原始抓取数据"
|
||||
_order = 'checkin_time desc'
|
||||
|
||||
name = fields.Char(related='employee_id.name')
|
||||
employee_id = fields.Many2one('hr.employee', string="员工", readonly=True)
|
||||
department_id = fields.Many2one('hr.department', string="部门", related="employee_id.department_id", readonly=True)
|
||||
userid = fields.Char(string='userid')
|
||||
groupname = fields.Char(string='打卡规则名称')
|
||||
checkin_type = fields.Char(string='打卡类型')
|
||||
exception_type = fields.Char(string='异常类型')
|
||||
checkin_time = fields.Datetime(string='打卡时间')
|
||||
location_title = fields.Char(string='打卡地点')
|
||||
location_detail = fields.Char(string='打卡地点详情')
|
||||
wifiname = fields.Char(string='打卡wifi名称')
|
||||
notes = fields.Char(string='打卡备注')
|
||||
wifimac = fields.Char(string='打卡的MAC地址')
|
||||
mediaids = fields.Char(string='打卡的附件media_id')
|
||||
lat = fields.Integer(string='位置打卡地点纬度')
|
||||
lng = fields.Integer(string='位置打卡地点经度')
|
||||
deviceid = fields.Char(string='打卡设备id')
|
||||
sch_checkin_time = fields.Datetime(string='标准打卡时间')
|
||||
groupid = fields.Integer(string='规则id')
|
||||
schedule_id = fields.Char(string='班次id')
|
||||
timeline_id = fields.Integer(string='时段id')
|
||||
|
||||
attendance_detail_image_ids = fields.One2many(string="打卡记录图片", comodel_name='hr.attendance.detail.image', inverse_name='attendance_detail_id')
|
||||
hr_attendance_id = fields.Many2one('hr.attendance', string="出勤记录", ondelete="cascade", readonly=True)
|
||||
|
||||
def action_show_details(self):
|
||||
self.ensure_one()
|
||||
view = self.env.ref("hr_attendance_ext.view_hr_attendance_detail_image_tree")
|
||||
return {
|
||||
"name": _("图片详情"),
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "form",
|
||||
"res_model": "hr.attendance.detail.image",
|
||||
"views": [(view.id, "tree")],
|
||||
"view_id": view.id,
|
||||
"target": "new",
|
||||
"res_id": self.id,
|
||||
"domain": [('attendance_detail_id', '=', self.id)],
|
||||
"context": {'default_attendance_detail_id': self.id}
|
||||
}
|
||||
|
||||
def _get_wx_attendance_user_list(self):
|
||||
"""
|
||||
获取企业微信的关联用户
|
||||
:return: 用户列表
|
||||
"""
|
||||
# userid 从用户列表里获取企业微信的用户
|
||||
user_obj = self.env['res.users'].search([])
|
||||
# 暂时先用for循环,如果后期加大频率,需改成并发
|
||||
user_list = []
|
||||
for user in user_obj:
|
||||
if user.oauth_uid:
|
||||
user_list.append(user.oauth_uid)
|
||||
return user_list
|
||||
|
||||
@staticmethod
|
||||
def _get_wx_attendance_data(userid_limit):
|
||||
"""
|
||||
获取企业微信的打卡信息
|
||||
:param wx_work_checkin_api: 企业微信api
|
||||
:param userid_limit: 用户列表
|
||||
:return:
|
||||
"""
|
||||
# 先按照天抓取,时间戳
|
||||
now_time = datetime.datetime.now() + datetime.timedelta(hours=8)
|
||||
start_time = now_time - datetime.timedelta(days=30)
|
||||
start_time_stamp = int(start_time.timestamp())
|
||||
end_time_stamp = int(now_time.timestamp())
|
||||
# 整理请求信息
|
||||
data = {
|
||||
"opencheckindatatype": 3,
|
||||
"starttime": start_time_stamp,
|
||||
"endtime": end_time_stamp,
|
||||
"need_photo": 1,
|
||||
"useridlist": userid_limit
|
||||
}
|
||||
# 重写一个类处理result获取想要的结果
|
||||
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def _check_wx_attendance_data(response, userid_limit):
|
||||
"""
|
||||
检查请求返回的信息是否正确
|
||||
:param response: 请求返回信息
|
||||
:param userid_limit: 用户列表
|
||||
:return: 检查结果
|
||||
"""
|
||||
# 对返回信息进行整理
|
||||
if response.status_code != 200:
|
||||
_logger.error('抓取出勤数据请求失败,异常:{0},用户列表{1}'.format(str(response.json()), str(userid_limit)))
|
||||
return []
|
||||
result = response.json()
|
||||
if result.get('errcode') != 0:
|
||||
_logger.error('获取外出打卡数据失败,异常:{0},用户列表{1}'.format(str(result.get('errmsg')), str(userid_limit)))
|
||||
return []
|
||||
checkin_data = result.get('checkindata', [])
|
||||
if not checkin_data:
|
||||
_logger.error('没有找到外出打卡记录,异常:{0},用户列表{1}'.format(str(response.json()), str(userid_limit)))
|
||||
return []
|
||||
return checkin_data
|
||||
|
||||
def _check_wx_attendance_time(self, data, userid_limit):
|
||||
"""
|
||||
检查保存之前的数据是否准确,并且分析出想要的打卡时间
|
||||
:param data: 原始数据
|
||||
:param userid_limit: 用户列表
|
||||
:return: 分析后的打卡数据
|
||||
"""
|
||||
userid = data.get("userid", "")
|
||||
checkin_time_stamp = data.get("checkin_time", "")
|
||||
sch_checkin_time_stamp = data.get("sch_checkin_time", None)
|
||||
if not userid or not checkin_time_stamp:
|
||||
_logger.error('没有抓取到用户出勤信息,异常:{0},用户列表{1}'.format(str(data), str(userid_limit)))
|
||||
return False
|
||||
checkin_time = datetime.datetime.fromtimestamp(checkin_time_stamp) if checkin_time_stamp else None
|
||||
sch_checkin_time = datetime.datetime.fromtimestamp(sch_checkin_time_stamp) if sch_checkin_time_stamp else None
|
||||
return checkin_time, sch_checkin_time
|
||||
|
||||
def _check_wx_attendance_media(self, data):
|
||||
"""
|
||||
分析返回来的数据获取图片原图
|
||||
:param data: 原始数据
|
||||
:param userid_limit: 用户列表
|
||||
:return: 获取后的图片
|
||||
"""
|
||||
media_list = []
|
||||
userid = data.get("userid", "")
|
||||
mediaids_list = data.get("mediaids", [])
|
||||
if not mediaids_list:
|
||||
return media_list
|
||||
|
||||
for media_id in mediaids_list:
|
||||
data = {
|
||||
"media_id": media_id
|
||||
}
|
||||
try:
|
||||
result, response = self.sudo().env["wecom.apps"].sync_media_get(data)
|
||||
if result:
|
||||
filename = response.headers['Content-Disposition'].split('; ')[2].replace('filename=', '').replace('"', '')
|
||||
wx_work_auth = self.env['auth.oauth.provider'].search([('name', '=', 'Wechat Card')], limit=1)
|
||||
wx_work_image = os.path.join(wx_work_auth.wx_work_image_path, str(filename))
|
||||
with open(wx_work_image, 'wb') as f:
|
||||
f.write(response.content)
|
||||
media_list.append((0, 0, {
|
||||
'media_url': filename,
|
||||
}))
|
||||
else:
|
||||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1}'.format(str(response), str(data)))
|
||||
except Exception as e:
|
||||
_logger.error('请求原始图片失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data), str(traceback.format_exc())))
|
||||
|
||||
return media_list
|
||||
|
||||
def _save_wx_attendance_detail_data(self, data, checkin_time, sch_checkin_time):
|
||||
"""
|
||||
保存相关的企业微信的原始打卡信息
|
||||
:param data: 请求返回后的处理信息
|
||||
:param checkin_time: 打卡时间
|
||||
:param sch_checkin_time: 应打卡时间
|
||||
:param media_list: 图片列表
|
||||
:return: 保存结果
|
||||
"""
|
||||
userid = data.get("userid", "")
|
||||
# 查询是否存在数据
|
||||
if self.env['hr.attendance.detail'].sudo().search_count([("userid", "=", userid), ("checkin_time", "=", checkin_time)]) <= 0:
|
||||
res_user_obj = self.env['res.users'].sudo().search([("oauth_uid", "=", userid)], limit=1)
|
||||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id", "=", res_user_obj.id)], limit=1)
|
||||
if not res_user_obj or not hr_employee_obj:
|
||||
_logger.error('检查保存之前的数据异常,异常:无关联的员工数据,数据信息{0}'.format(str(data)))
|
||||
return False
|
||||
attendance_detail_obj = self.env['hr.attendance.detail'].sudo().create({
|
||||
'employee_id': hr_employee_obj.id,
|
||||
"userid": userid,
|
||||
"groupname": data.get("groupname", ""),
|
||||
"checkin_type": data.get("checkin_type", ""),
|
||||
"exception_type": data.get("exception_type", ""),
|
||||
"checkin_time": checkin_time,
|
||||
"location_title": data.get("location_title", ""),
|
||||
"location_detail": data.get("location_detail", ""),
|
||||
"wifiname": data.get("wifiname", ""),
|
||||
"notes": data.get("notes", ""),
|
||||
"wifimac": data.get("wifimac", ""),
|
||||
"mediaids": str(data.get("mediaids", "")),
|
||||
"lat": data.get("lat", 0),
|
||||
"lng": data.get("lng", 0),
|
||||
"deviceid": data.get("deviceid", ""),
|
||||
"sch_checkin_time": sch_checkin_time,
|
||||
"groupid": data.get("groupid", 0),
|
||||
"schedule_id": data.get("schedule_id", ""),
|
||||
"timeline_id": data.get("timeline_id", 0),
|
||||
# "attendance_detail_image_ids": media_list
|
||||
})
|
||||
|
||||
# 分析返回来的数据获取图片原图
|
||||
try:
|
||||
media_list = self._check_wx_attendance_media(data)
|
||||
attendance_detail_obj.attendance_detail_image_ids = media_list
|
||||
except Exception as e:
|
||||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data), str(traceback.format_exc())))
|
||||
|
||||
userid = data.get("userid", "")
|
||||
# 区分当天的上班,下班,中午打卡,合并一个
|
||||
checkin_time_now = checkin_time + datetime.timedelta(hours=8) if checkin_time else None
|
||||
sch_checkin_time_now = sch_checkin_time + datetime.timedelta(hours=8) if sch_checkin_time else None
|
||||
current_data = (sch_checkin_time + datetime.timedelta(hours=8)).strftime("%Y-%m-%d") if sch_checkin_time_now else checkin_time_now.strftime("%Y-%m-%d")
|
||||
# 同步整理当天出勤数据
|
||||
attendance_obj = self.env['hr.attendance'].search([("userid", "=", userid), ("current_data", "=", current_data)], limit=1)
|
||||
if not attendance_obj:
|
||||
res_user_obj = self.env['res.users'].search([("oauth_uid", "=", userid)], limit=1)
|
||||
hr_employee_obj = self.env['hr.employee'].sudo().search([("user_id", "=", res_user_obj.id)], limit=1)
|
||||
attendance_obj = self.env['hr.attendance'].sudo().create({
|
||||
'employee_id': hr_employee_obj.id,
|
||||
"userid": userid,
|
||||
"current_data": current_data,
|
||||
"attendance_type": "wx_work"
|
||||
})
|
||||
# 赋值企业微信打卡数据
|
||||
attendance_obj.attendance_type = "wx_work"
|
||||
|
||||
checkin_type = data.get("checkin_type", "")
|
||||
exception_type = data.get("exception_type", "")
|
||||
if sch_checkin_time_now and str(sch_checkin_time_now.strftime("%H")) == "13" and checkin_type == "上班打卡" and exception_type != "未打卡":
|
||||
attendance_obj.sudo().write({
|
||||
"sch_check_noon_time": sch_checkin_time,
|
||||
"check_noon": checkin_time
|
||||
})
|
||||
elif checkin_type == "上班打卡" and exception_type != "未打卡":
|
||||
attendance_obj.sudo().write({
|
||||
"sch_check_in_time": sch_checkin_time,
|
||||
"check_in": checkin_time
|
||||
})
|
||||
elif checkin_type == "下班打卡" and exception_type != "未打卡":
|
||||
attendance_obj.sudo().write({
|
||||
"sch_check_out_time": sch_checkin_time,
|
||||
"check_out": checkin_time
|
||||
})
|
||||
# elif checkin_type == "外出打卡":
|
||||
# attendance_obj.sudo().write({
|
||||
# "field_work_check": checkin_time,
|
||||
# })
|
||||
# 进行关联
|
||||
attendance_detail_obj.hr_attendance_id = attendance_obj.id
|
||||
return True
|
||||
|
||||
def get_hr_attendance_time(self):
|
||||
"""
|
||||
获取企业微信打卡数据的核心逻辑
|
||||
:return: 处理结果
|
||||
"""
|
||||
# 从用户列表里获取企业微信的用户
|
||||
user_list = self._get_wx_attendance_user_list()
|
||||
# 企业微信获取打卡数据,要求用户分组100,分批获取打卡记录
|
||||
for userid_limit in utils_list_page(user_list, 100):
|
||||
# 获取企业微信的打卡信息
|
||||
try:
|
||||
attendance_data = self._get_wx_attendance_data(userid_limit)
|
||||
attendance_result, response = self.sudo().env["wecom.apps"].sync_checkin_data(attendance_data)
|
||||
except Exception as e:
|
||||
_logger.error('获取企业微信的打卡信息失败,异常:{0},用户列表{1},异常详情:{2}'.format(str(e), str(userid_limit), str(traceback.format_exc())))
|
||||
continue
|
||||
# 检查请求返回的信息是否正确
|
||||
try:
|
||||
checkin_data = self._check_wx_attendance_data(response, userid_limit)
|
||||
except Exception as e:
|
||||
_logger.error('检查请求返回的信息失败,异常:{0},用户列表{1},异常详情:{2}'.format(str(e), str(userid_limit), str(traceback.format_exc())))
|
||||
continue
|
||||
# 将原始数据存储
|
||||
for data in checkin_data:
|
||||
# 检查保存之前的数据是否准确,并且分析出想要的打卡时间
|
||||
try:
|
||||
checkin_time, sch_checkin_time = self._check_wx_attendance_time(data, userid_limit)
|
||||
except Exception as e:
|
||||
_logger.error('检查保存之前的数据分析出想要的数据失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data), str(traceback.format_exc())))
|
||||
continue
|
||||
# 保存相关的企业微信的原始打卡信息
|
||||
try:
|
||||
self._save_wx_attendance_detail_data(data, checkin_time, sch_checkin_time)
|
||||
except Exception as e:
|
||||
_logger.error('保存相关的企业微信的原始打卡信息失败,异常:{0},数据信息{1},异常详情:{2}'.format(str(e), str(data), str(traceback.format_exc())))
|
||||
return True
|
||||
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
import logging
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from odoo import models, fields
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AuthOAuthProvider(models.Model):
|
||||
_inherit = "auth.oauth.provider"
|
||||
|
||||
wx_work_image_path = fields.Char(string="企业微信图片保存地址")
|
||||
@@ -1,7 +0,0 @@
|
||||
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
|
||||
|
||||
access_hr_attendance_detail_user,hr.attendance.detail.user,model_hr_attendance_detail,base.group_user,1,0,0,0
|
||||
access_hr_attendance_detail_manager,hr.attendance.detail.manager,model_hr_attendance_detail,base.group_user,1,1,1,1
|
||||
|
||||
access_hr_attendance_detail_image_user,hr.attendance.detail.image.user,model_hr_attendance_detail_image,base.group_user,1,0,0,0
|
||||
access_hr_attendance_detail_image_manager,hr.attendance.detail.image.manager,model_hr_attendance_detail_image,base.group_user,1,1,1,1
|
||||
|
Binary file not shown.
|
Before Width: | Height: | Size: 5.9 KiB |
@@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import common
|
||||
@@ -1,31 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
|
||||
def utils_list_page(data, limit):
|
||||
"""
|
||||
列表平分
|
||||
:param data: list
|
||||
:param limit: 每组多少个
|
||||
:return: 平分结果
|
||||
"""
|
||||
for start in range(0, len(data), limit):
|
||||
if start + limit > len(data):
|
||||
end = len(data)
|
||||
else:
|
||||
end = start + limit
|
||||
yield data[start:end]
|
||||
|
||||
|
||||
def os_path_join(path, filename):
|
||||
"""
|
||||
默认传入的是debian的路径 /xx/xx/xx/转换成多系统兼容
|
||||
:param path:
|
||||
:param filename:
|
||||
:return:
|
||||
"""
|
||||
image_path_list = path.split('/')
|
||||
image_path = ""
|
||||
for path in image_path_list:
|
||||
image_path = os.path.join(str(image_path), str(path))
|
||||
image = os.path.join(image_path, filename)
|
||||
return image
|
||||
@@ -1,80 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_hr_attendance_detail_image_tree" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.detail.image.tree</field>
|
||||
<field name="model">hr.attendance.detail.image</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="hr attendance detail image">
|
||||
<field name="media_url"/>
|
||||
<field name="media_file" widget="image" options='{"size": [80, 80]}' />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_attendance_detail_image_form" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.detail.image.form</field>
|
||||
<field name="model">hr.attendance.detail.image</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="hr attendance detail image">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="media_url"/>
|
||||
<field name="media_file" widget="image"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_attendance_detail_tree" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.detail.tree</field>
|
||||
<field name="model">hr.attendance.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="hr attendance detail">
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="groupname"/>
|
||||
<field name="checkin_type"/>
|
||||
<field name="checkin_time"/>
|
||||
<field name="sch_checkin_time"/>
|
||||
<field name="exception_type" />
|
||||
<field name="location_title"/>
|
||||
<field name="notes"/>
|
||||
<field name="attendance_detail_image_ids" />
|
||||
<button name="action_show_details" string="打卡图片" type="object" title="Show Details" icon="fa-list"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
<record id="view_hr_attendance_detail_form" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.detail.form</field>
|
||||
<field name="model">hr.attendance.detail</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="hr attendance detail">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="employee_id"/>
|
||||
<field name="department_id"/>
|
||||
<field name="groupname"/>
|
||||
<field name="checkin_type"/>
|
||||
<field name="checkin_time"/>
|
||||
<field name="sch_checkin_time"/>
|
||||
<field name="exception_type" />
|
||||
<field name="location_title"/>
|
||||
<field name="notes"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_detail_action" model="ir.actions.act_window">
|
||||
<field name="name">出勤详情</field>
|
||||
<field name="res_model">hr.attendance.detail</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_hr_attendance_attendances_view" name="考勤" parent="hr_attendance.menu_hr_attendance_root" sequence="20"/>
|
||||
<menuitem id="menu_hr_attendance_detail_view_attendances" name="打卡详情" parent="menu_hr_attendance_attendances_view" sequence="20" action="hr_attendance_detail_action"/>
|
||||
<menuitem id="menu_hr_attendance_attendances_setting_view" name="考勤维护" parent="hr_attendance.menu_hr_attendance_root" sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
@@ -1,55 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_attendance_tree_ext_inherited" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.tree.inherited</field>
|
||||
<field name="model">hr.attendance</field>
|
||||
<field name="inherit_id" ref="hr_attendance.view_attendance_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//tree" position="attributes">
|
||||
<attribute name="edit">false</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='check_in']" position="before">
|
||||
<field name="current_data"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='check_in']" position="after">
|
||||
<field name="sch_check_in_time"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='check_out']" position="after">
|
||||
<field name="sch_check_out_time" optional="hide"/>
|
||||
<field name="check_noon" optional="hide"/>
|
||||
<field name="sch_check_noon_time" optional="hide"/>
|
||||
<field name="attendance_type" optional="hide"/>
|
||||
<field name="attendance_detail_ids" optional="hide"/>
|
||||
<button name="action_show_details" string="打卡" type="object" title="Show Details" icon="fa-list"/>
|
||||
<field name="replacement_approval_ids" optional="hide"/>
|
||||
<button name="replacement_approval_action_show_details" string="补卡" type="object" title="Show Details" icon="fa-list"/>
|
||||
<field name="go_out_approval_ids" optional="hide"/>
|
||||
<button name="go_out_approval_action_show_details" string="外出" type="object" title="Show Details" icon="fa-list"/>
|
||||
<field name="work_overtime_approval_ids" optional="hide"/>
|
||||
<button name="work_overtime_action_show_details" string="加班" type="object" title="Show Details" icon="fa-list"/>
|
||||
<field name="hr_leave_approval_ids" optional="hide"/>
|
||||
<button name="hr_leave_action_show_details" string="请假" type="object" title="Show Details" icon="fa-list"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="hr_attendance_view_form_ext_inherited" model="ir.ui.view">
|
||||
<field name="name">hr.attendance.form.inherited</field>
|
||||
<field name="model">hr.attendance</field>
|
||||
<field name="inherit_id" ref="hr_attendance.hr_attendance_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='check_in']" position="before">
|
||||
<field name="current_data"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='check_in']" position="after">
|
||||
<field name="sch_check_in_time"/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='check_out']" position="after">
|
||||
<field name="sch_check_out_time"/>
|
||||
<field name="check_noon"/>
|
||||
<field name="sch_check_noon_time"/>
|
||||
<field name="attendance_type" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_oauth_provider_form_wwb_inherit" model="ir.ui.view">
|
||||
<field name="name">auth.oauth.provider.form.wwb.inherit</field>
|
||||
<field name="model">auth.oauth.provider</field>
|
||||
<field name="inherit_id" ref="auth_oauth.view_oauth_provider_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='data_endpoint']" position="after">
|
||||
<field name="wx_work_image_path" invisible="name != 'Wechat Card'"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -10,14 +10,11 @@
|
||||
'category': 'Human Resources/Attendances',
|
||||
'version': '0.1',
|
||||
'license': 'LGPL-3',
|
||||
'depends': ['hr_contract', 'sale_renting', 'documents'],
|
||||
'depends': ['hr_contract', 'yuthon_hr_employee'],
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/sale_order_data.xml',
|
||||
'views/contract_menu_views.xml',
|
||||
'data/contract_code_data.xml',
|
||||
'views/inherit_hr_contract_views.xml',
|
||||
'views/inherit_sale_order_views.xml',
|
||||
# 'views/inherit_res_partner.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="sale_order_sequence" model="ir.sequence">
|
||||
<record id="contract_code" model="ir.sequence">
|
||||
<field name="name">合同编号</field>
|
||||
<field name="code">sale_order_code</field>
|
||||
<field name="code">contract_code</field>
|
||||
<field name="prefix">HT%(y)s%(month)s%(day)s</field>
|
||||
<field name="padding">3</field>
|
||||
</record>
|
||||
@@ -1,3 +1 @@
|
||||
from . import inherit_hr_contract
|
||||
from . import inherit_res_partner
|
||||
from . import inherit_sale_order
|
||||
|
||||
@@ -8,6 +8,12 @@ class HrContract(models.Model):
|
||||
_description = "员工合同拓展"
|
||||
|
||||
job_lv_id = fields.Many2one(related='employee_id.job_lv_id', string="职级")
|
||||
account = fields.Char("账户")
|
||||
bank = fields.Char("开户行")
|
||||
code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('contract_code'))
|
||||
resource_calendar_id = fields.Many2one('resource.calendar', string='考勤时间表')
|
||||
|
||||
@api.onchange('job_lv_id')
|
||||
def onchange_salary(self):
|
||||
for salary in self:
|
||||
salary.wage = salary.job_lv_id.base_wage
|
||||
salary.name = '劳动合同'
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
_description = "联系人拓展"
|
||||
|
||||
sale_order_id = fields.Many2one("sale.order", string='关联租赁合同')
|
||||
invoicing_type = fields.Char(string='开票类型')
|
||||
billable_header = fields.Char(string='开票抬头')
|
||||
address_delivery = fields.Char(string='送达地址')
|
||||
rental_purposes = fields.Char(string='出租用途')
|
||||
@@ -1,17 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models, tools
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = "sale.order"
|
||||
_description = "租赁合同拓展"
|
||||
|
||||
contract_code = fields.Char(string="合同编号", default=lambda self: self.env['ir.sequence'].next_by_code('sale_order_code'))
|
||||
res_partner_ids = fields.One2many('res.partner', 'sale_order_id', string='承租方明细')
|
||||
contract_documents_ids = fields.Many2many('documents.document', string="附件")
|
||||
contract_confirm_date = fields.Datetime(string="合同签定日期")
|
||||
handover_date = fields.Datetime(string="交接日期")
|
||||
|
||||
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<menuitem
|
||||
id="yuthon_contract_menu_root"
|
||||
name="合同管理"
|
||||
web_icon="yuthon_contract,static/description/icon.png"
|
||||
sequence="1"/>
|
||||
|
||||
<menuitem
|
||||
id="yuthon_contract_hr_menu"
|
||||
parent="yuthon_contract_menu_root"
|
||||
name="员工合同"
|
||||
action="hr_contract.action_hr_contract"
|
||||
sequence="5"/>
|
||||
<menuitem
|
||||
id="yuthon_contract_sale_renting_menu"
|
||||
parent="yuthon_contract_menu_root"
|
||||
name="租赁合同"
|
||||
action="sale_renting.rental_order_action"
|
||||
sequence="10"/>
|
||||
<menuitem
|
||||
id="yuthon_contract_product_manager_menu"
|
||||
parent="yuthon_contract_menu_root"
|
||||
name="物管合同"
|
||||
action="sale_renting.rental_order_action"
|
||||
sequence="15"/>
|
||||
<menuitem
|
||||
id="yuthon_contract_construction_menu"
|
||||
parent="yuthon_contract_menu_root"
|
||||
name="建设工程合同"
|
||||
action="sale_renting.rental_order_action"
|
||||
sequence="20"/>
|
||||
<menuitem
|
||||
id="yuthon_contract_maintenance_menu"
|
||||
parent="yuthon_contract_menu_root"
|
||||
name="维保合同评估"
|
||||
action="sale_renting.rental_order_action"
|
||||
sequence="25"/>
|
||||
</odoo>
|
||||
@@ -6,7 +6,20 @@
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="job_lv_id"/>
|
||||
<field name="job_lv_id" required="1"/>
|
||||
<field name="code"/>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='information']" position="replace">
|
||||
<page name="information2" string="工资信息">
|
||||
<group>
|
||||
<group>
|
||||
<field name="wage_type"/>
|
||||
<field name="schedule_pay"/>
|
||||
<field name="wage"/>
|
||||
</group>
|
||||
<group/>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="contract_res_partner_views_form" model="ir.ui.view">
|
||||
<field name="name">contract.res.partner.views.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_type']" position="after">
|
||||
<field name="invoicing_type"/>
|
||||
<field name="billable_header"/>
|
||||
<field name="address_delivery"/>
|
||||
<field name="rental_purposes"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<odoo>
|
||||
<record id="inherit_sale_renting_yuthon_form" model="ir.ui.view">
|
||||
<field name="name">inherit.sale.renting.yuthon.form</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale_renting.rental_order_primary_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="contract_code"/>
|
||||
<field name="contract_confirm_date"/>
|
||||
<field name="handover_date"/>
|
||||
<field name="contract_documents_ids">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
</tree>
|
||||
</field>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='order_lines']" position="after">
|
||||
<page string="承租方">
|
||||
<field name="res_partner_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="function" optional="show"/>
|
||||
<field name="phone" optional="show"/>
|
||||
<field name="email" optional="show"/>
|
||||
<field name="vat" optional="show"/>
|
||||
<field name="invoicing_type" optional="show"/>
|
||||
<field name="billable_header" optional="show"/>
|
||||
<field name="address_delivery" optional="show"/>
|
||||
<field name="rental_purposes" optional="show"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -29,3 +29,24 @@ class EmployeeBannerRoute(Controller):
|
||||
</div>
|
||||
"""
|
||||
}
|
||||
|
||||
@http.route('/hr/employee/create', type='http', auth="none")
|
||||
def hr_employee_create(self, employee_id=None, **kw):
|
||||
values = {k: v for k, v in request.params.items()}
|
||||
if not employee_id:
|
||||
values['error'] = "缺少员工号"
|
||||
response = request.render('yuthon_hr_employee.website_form_employee', values)
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
|
||||
return response
|
||||
|
||||
values['employee_id'] = int(employee_id)
|
||||
if request.httprequest.method == 'POST':
|
||||
record = request.env['hr.employee'].sudo().browse(values['employee_id'])
|
||||
if "name" in values and values.get("name", ""):
|
||||
record.name = values.get("name", "")
|
||||
values["message"] = "员工信息创建成功"
|
||||
response = request.render('yuthon_hr_employee.website_form_employee', values)
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
|
||||
return response
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="yuthon_hr_employee.frontend_layout" name="Frontend Layout" inherit_id="web.layout" primary="True">
|
||||
<xpath expr="//head/meta[last()]" position="after">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
</xpath>
|
||||
<xpath expr="//head/link[last()]" position="after">
|
||||
<link rel="preload" href="/web/static/src/libs/fontawesome/fonts/fontawesome-webfont.woff2?v=4.7.0" as="font" crossorigin=""/>
|
||||
<t t-call-assets="web.assets_frontend" t-js="false"/>
|
||||
</xpath>
|
||||
<xpath expr="//t[@t-out='0']" position="replace">
|
||||
<div id="wrapwrap" t-attf-class="#{pageName or ''}">
|
||||
<header t-if="not no_header" id="top" data-anchor="true">
|
||||
<img class="img-responsive d-block mx-auto"
|
||||
t-attf-src="/web/binary/company_logo"
|
||||
alt="Logo"/>
|
||||
</header>
|
||||
<main>
|
||||
<t t-out="0"/>
|
||||
</main>
|
||||
<footer t-cache="no_footer,no_copyright" t-if="not no_footer" id="bottom" data-anchor="true" t-attf-class="bg-light o_footer">
|
||||
<div id="footer"/>
|
||||
<div t-if="not no_copyright" class="o_footer_copyright">
|
||||
<div class="container py-3">
|
||||
<div class="row">
|
||||
<div class="col-sm text-center text-sm-start text-muted">
|
||||
<span class="o_footer_copyright_name me-2">Copyright &copy; <span t-field="res_company.name" itemprop="name">Company name</span></span>
|
||||
</div>
|
||||
<div class="col-sm text-center text-sm-end">
|
||||
<t t-call="web.brand_promotion"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
<template id="yuthon_hr_employee.website_form_employee" name="website_form_employee">
|
||||
<t t-call="yuthon_hr_employee.frontend_layout">
|
||||
<div class="container py-5">
|
||||
<form class="oe_website_form_room" role="form" t-attf-action="/hr/employee/create" method="post" onsubmit="this.action = '/hr/employee/create' + location.hash">
|
||||
<input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a href="panel_1">第一页</a></li>
|
||||
<li><a href="panel_2">第二页</a></li>
|
||||
</ul>
|
||||
<div class="tab-content mt-3">
|
||||
<div role="tabpanel" t-att-class="not comments and 'tab-pane fade in show active' or 'tab-pane fade'" id="panel_1">
|
||||
<div class="mb-3 field-room_id">
|
||||
<label for="employee_id" class="form-label">员工号</label>
|
||||
<input type="text" placeholder="会议号" name="employee_id" t-att-value="employee_id" id="employee_id" t-attf-class="form-control" readonly="readonly"/>
|
||||
</div>
|
||||
</div>
|
||||
<div role="tabpanel" t-att-class="comments and 'tab-pane fade in show active' or 'tab-pane fade'" id="panel_2">
|
||||
222
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p class="alert alert-danger" t-if="error" role="alert">
|
||||
<t t-esc="error"/>
|
||||
</p>
|
||||
<p class="alert alert-success" t-if="message" role="status">
|
||||
<t t-esc="message"/>
|
||||
</p>
|
||||
|
||||
<div t-attf-class="clearfix oe_login_buttons text-center gap-1 d-grid mb-1">
|
||||
<button type="submit" class="btn btn-primary">签到</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user