Merge branch 'dev' into dev_zhou

This commit is contained in:
zhou
2025-06-05 14:49:26 +08:00
3 changed files with 54 additions and 61 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ from odoo import api, fields, models, tools
from odoo.exceptions import ValidationError
class AttendanceIotUser(models.Model):
class AttendanceIotUser(models.Model):
_name = 'attendance.iot.user'
attendance_iot_id = fields.Many2one('attendance.iot', string="设备号")
+5 -5
View File
@@ -1,6 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_attendance_iot,attendance_iot,model_attendance_iot,base.group_user,1,1,1,1
access_attendance_iot_manager,attendance_iot_manager,model_attendance_iot_manager,base.group_user,1,1,1,1
access_attendance_iot_manager_wizard,attendance_iot_manager_wizard,model_attendance_iot_manager_wizard,base.group_user,1,1,1,1
access_attendance_iot_datas,attendance_iot_datas,model_attendance_iot_datas,base.group_user,1,1,1,1
access_attendance_iot_user,attendance_iot_user,model_attendance_iot_user,base.group_user,1,1,1,1
access_attendance_iot,attendance.iot,model_attendance_iot,base.group_user,1,1,1,1
access_attendance_iot_manager,attendance.iot.manager,model_attendance_iot_manager,base.group_user,1,1,1,1
access_attendance_iot_manager_wizard,attendance.iot.manager.wizard,model_attendance_iot_manager_wizard,base.group_user,1,1,1,1
access_attendance_iot_datas,attendance.iot.datas,model_attendance_iot_datas,base.group_user,1,1,1,1
access_attendance_iot_user,attendance.iot.user,model_attendance_iot_user,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_attendance_iot attendance_iot attendance.iot model_attendance_iot base.group_user 1 1 1 1
3 access_attendance_iot_manager attendance_iot_manager attendance.iot.manager model_attendance_iot_manager base.group_user 1 1 1 1
4 access_attendance_iot_manager_wizard attendance_iot_manager_wizard attendance.iot.manager.wizard model_attendance_iot_manager_wizard base.group_user 1 1 1 1
5 access_attendance_iot_datas attendance_iot_datas attendance.iot.datas model_attendance_iot_datas base.group_user 1 1 1 1
6 access_attendance_iot_user attendance_iot_user attendance.iot.user model_attendance_iot_user base.group_user 1 1 1 1
@@ -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
})