|
|
|
@@ -3,6 +3,8 @@ from odoo import models, fields, api, http, SUPERUSER_ID, _
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
import math
|
|
|
|
|
import logging
|
|
|
|
|
from geopy.distance import geodesic
|
|
|
|
|
import json
|
|
|
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
@@ -12,13 +14,15 @@ class AppletAttendance(Controller):
|
|
|
|
|
小程序考勤接口
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
@route('/yuthon/att/today', methods=['POST'], type='http', auth='public', cors='*', csrf=False)
|
|
|
|
|
@route('/yuthon/att/today', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
|
|
|
|
def apple_yuthon_att_today(self, **kw):
|
|
|
|
|
"""
|
|
|
|
|
小程序获取当前用户打卡信息
|
|
|
|
|
:return:
|
|
|
|
|
{
|
|
|
|
|
'location': 经纬度-打卡地点,
|
|
|
|
|
'att_longitude': '经度'
|
|
|
|
|
'att_latitude': '纬度'
|
|
|
|
|
'class': [班次,开始时间-结束时间]
|
|
|
|
|
'user_id': 用户id
|
|
|
|
|
'check_in': [上班卡,正常]
|
|
|
|
@@ -26,39 +30,38 @@ class AppletAttendance(Controller):
|
|
|
|
|
'state': 是否可以打卡['yes', 'no']
|
|
|
|
|
'state_result': 不可打卡原因 'xxx'
|
|
|
|
|
}
|
|
|
|
|
# 0.判断是否有用户,否则返回“此用户无权限”
|
|
|
|
|
# 1.根据经纬度判断是否可以打卡,如果不可以就直接返回“超出范围,不允许打卡”
|
|
|
|
|
# 2.把这个人的地址location,class,user_id,state-yes,state_result=''
|
|
|
|
|
# 2.check_in "YYYY-MM-DD HH:MM:SS" ['', ''] 如果没有传 ''
|
|
|
|
|
# 2.check_out "YYYY-MM-DD HH:MM:SS" ['', ''] 如果没有传 ''
|
|
|
|
|
"""
|
|
|
|
|
user_id = int(kw.get('id'))
|
|
|
|
|
current_data = kw.get('current_data')
|
|
|
|
|
current_data = datetime.strptime(current_data, "%Y-%m-%d")
|
|
|
|
|
user_id = int(kw['user_id'])
|
|
|
|
|
current_data = fields.Date.today()
|
|
|
|
|
user_id = request.env.user.search([('id', '=', user_id)])
|
|
|
|
|
if not user_id:
|
|
|
|
|
return {"Msg": "此用户没有权限,请联系管理员配置。%s" % (user_id.name), 'state': 'no', 'state_result': '无权限'}
|
|
|
|
|
# 1.根据经纬度判断是否可以打卡,如果不可以就直接返回“超出范围,不允许打卡”
|
|
|
|
|
employee = request.env["hr.employee"].search([('user_id', '=', user_id)])
|
|
|
|
|
company_id = employee.company_id
|
|
|
|
|
address_id = request.env["applet.address"].search([('name', '=', company_id.name)])
|
|
|
|
|
att_longitude = kw.get('att_longitude')# 打卡经度
|
|
|
|
|
att_latitude = kw.get('att_latitude') # 打卡纬度
|
|
|
|
|
longitude = address_id.longitude # 要求经度
|
|
|
|
|
latitude = address_id.latitude # 要求纬度
|
|
|
|
|
if not self.is_in_range(att_latitude, att_longitude, latitude, longitude, threshold=0.3):
|
|
|
|
|
result = "此用户没有权限,请联系管理员配置。%s" % (user_id.name)
|
|
|
|
|
return {'state': 'no', 'state_result': result}
|
|
|
|
|
user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', user_id.id)])
|
|
|
|
|
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_id.name)])
|
|
|
|
|
address_id = request.env(user=SUPERUSER_ID)["applet.address"].search([('address_name', '=', employee_id.company_id.name)])
|
|
|
|
|
if not address_id:
|
|
|
|
|
return {'state': 'no', 'state_result': "该打卡地址没有配置"}
|
|
|
|
|
att_longitude = kw['att_longitude'] # 打卡经度
|
|
|
|
|
att_latitude = kw['att_latitude'] # 打卡纬度
|
|
|
|
|
longitude = address_id.longitude # 要求经度
|
|
|
|
|
latitude = address_id.latitude # 要求纬度
|
|
|
|
|
if not att_longitude or not att_latitude:
|
|
|
|
|
return {"Msg": "经纬度必填"}
|
|
|
|
|
card_rice = address_id.card_rice * 0.001 # 打卡范围
|
|
|
|
|
if not self.is_within_radius_geopy(att_latitude, att_longitude, latitude, longitude, card_rice):
|
|
|
|
|
return {"Msg": "超出范围,不允许打卡", 'state': 'no', 'state_result': '超出范围'}
|
|
|
|
|
else:
|
|
|
|
|
attendance_id = request.env["hr.attendance"].search([('employee_id', '=', employee.id), ('current_data', '=', current_data)])
|
|
|
|
|
attendance_id = request.env(user=SUPERUSER_ID)["hr.attendance"].search([('employee_id', '=', employee_id.id), ('current_data', '=', current_data)])
|
|
|
|
|
check_in = attendance_id.check_in
|
|
|
|
|
check_out = attendance_id.check_out
|
|
|
|
|
attendance_class = attendance_id.shift_name
|
|
|
|
|
class_start_time = '2025-6-5 11:36' #
|
|
|
|
|
class_end_time = '2025-6-5 11:36' #
|
|
|
|
|
is_early_card = attendance_id.is_early_card
|
|
|
|
|
is_late_card = attendance_id.is_late_card
|
|
|
|
|
# 预处理 check_in 和 check_out 的状态
|
|
|
|
|
check_in_status = '未打卡' if not check_in else ('迟到' if is_late_card else '正常')
|
|
|
|
|
check_out_status = '未打卡' if not check_out else ('早退' if is_early_card else '正常')
|
|
|
|
|
# 确定最终状态描述
|
|
|
|
|
state_result = '正常'
|
|
|
|
|
if is_late_card and is_early_card:
|
|
|
|
|
state_result = '迟到+早退'
|
|
|
|
@@ -68,20 +71,31 @@ class AppletAttendance(Controller):
|
|
|
|
|
state_result = '早退'
|
|
|
|
|
elif not check_in or not check_out:
|
|
|
|
|
state_result = '未打卡'
|
|
|
|
|
# 格式化时间字符串
|
|
|
|
|
check_in_str = check_in.strftime('%H:%M:%S') if check_in else ' '
|
|
|
|
|
check_out_str = check_out.strftime('%H:%M:%S') if check_out else ' '
|
|
|
|
|
|
|
|
|
|
# 统一返回结构
|
|
|
|
|
return {
|
|
|
|
|
'location': '%s' % address_id.address_name2,
|
|
|
|
|
'user_id': user_id,
|
|
|
|
|
'state': 'yes', # 无论是否打卡,state 始终为 'yes'(根据业务调整)
|
|
|
|
|
'class': attendance_class,
|
|
|
|
|
'state_result': state_result,
|
|
|
|
|
'check_in': [check_in_str, check_in_status],
|
|
|
|
|
'check_out': [check_out_str, check_out_status],
|
|
|
|
|
return_data = {
|
|
|
|
|
'data': {
|
|
|
|
|
'location': '%s' % address_id.address_name,
|
|
|
|
|
'user_id': user_id.id,
|
|
|
|
|
'state': 'yes',
|
|
|
|
|
'class': [attendance_class, class_start_time, class_end_time],
|
|
|
|
|
'state_result': state_result,
|
|
|
|
|
'check_in': check_in_str + ' ' + check_in_status,
|
|
|
|
|
'check_out': check_out_str + ' ' + check_out_status,
|
|
|
|
|
},
|
|
|
|
|
'code': '200',
|
|
|
|
|
'message': 'OK',
|
|
|
|
|
}
|
|
|
|
|
return json.dumps(return_data)
|
|
|
|
|
|
|
|
|
|
def is_within_radius_geopy(self, lat1, lon1, lat2, lon2, radius_km):
|
|
|
|
|
"""
|
|
|
|
|
使用geopy库判断是否在范围内
|
|
|
|
|
"""
|
|
|
|
|
point1 = (lat1, lon1)
|
|
|
|
|
point2 = (lat2, lon2)
|
|
|
|
|
distance = geodesic(point1, point2).kilometers
|
|
|
|
|
return distance <= radius_km
|
|
|
|
|
|
|
|
|
|
@route('/yuthon/att/record', methods=['POST'], type='json', auth='none', csrf=False)
|
|
|
|
|
def apple_yuthon_att_record(self, **kw):
|
|
|
|
@@ -100,25 +114,4 @@ class AppletAttendance(Controller):
|
|
|
|
|
'checkin_time': checkin_time, # 打卡时间
|
|
|
|
|
'source': 'mini_programs', # 打卡来源
|
|
|
|
|
'location_title': location_title, # 打卡地点
|
|
|
|
|
})
|
|
|
|
|
print("---------------------------------------------测试成功---------------------------------------")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def calculate_distance(self, lat1, lon1, lat2, lon2):
|
|
|
|
|
"""计算两个经纬度点之间的球面距离(公里)"""
|
|
|
|
|
# 将角度转换为弧度
|
|
|
|
|
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
|
|
|
|
|
# Haversine公式
|
|
|
|
|
dlat = lat2 - lat1
|
|
|
|
|
dlon = lon2 - lon1
|
|
|
|
|
a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
|
|
|
|
|
c = 2 * math.asin(math.sqrt(a))
|
|
|
|
|
# 地球半径(公里)
|
|
|
|
|
earth_radius = 6371
|
|
|
|
|
return c * earth_radius
|
|
|
|
|
# 判断是否在范围内(例如允许范围为0.5公里)
|
|
|
|
|
|
|
|
|
|
def is_in_range(self, att_lat, att_lon, req_lat, req_lon, threshold=0.5):
|
|
|
|
|
distance = self.calculate_distance(att_lat, att_lon, req_lat, req_lon)
|
|
|
|
|
return distance <= threshold
|
|
|
|
|
})
|