1358 lines
62 KiB
Python
1358 lines
62 KiB
Python
from dateutil.relativedelta import relativedelta
|
||
|
||
from odoo.http import Controller, request, route
|
||
from odoo import models, fields, api, http, SUPERUSER_ID, _
|
||
from datetime import datetime, date
|
||
import logging
|
||
from geopy.distance import geodesic
|
||
from datetime import timedelta
|
||
from ..until import base
|
||
import json
|
||
import requests
|
||
|
||
from odoo.tools import float_round, float_repr
|
||
|
||
import math
|
||
import json
|
||
|
||
|
||
def clean_floats(obj):
|
||
if isinstance(obj, float):
|
||
if math.isnan(obj) or math.isinf(obj):
|
||
return None
|
||
return obj
|
||
elif isinstance(obj, dict):
|
||
return {k: clean_floats(v) for k, v in obj.items()}
|
||
elif isinstance(obj, list):
|
||
return [clean_floats(v) for v in obj]
|
||
else:
|
||
return obj
|
||
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AppletAttendance(Controller):
|
||
"""
|
||
小程序考勤接口
|
||
"""
|
||
|
||
@http.route('/ro/base/user/login/v1', type='http', auth="public", csrf=False, cors='*')
|
||
def ro_base_user_login_v1(self, **kw):
|
||
_logger.info("----------------/ro/base/user/login/v1---------接口收到响应-------")
|
||
password = kw.get('password')
|
||
login = kw.get('login')
|
||
try:
|
||
uid = request.session.authenticate(request.env.cr.dbname, login, password)
|
||
except Exception as e:
|
||
_logger.info(f"接口登录失败: {e}")
|
||
property_tenant_information = request.env['property.tenant.information'].sudo().search(
|
||
[('phone', '=', login)])
|
||
if not property_tenant_information:
|
||
return_data = {'data': {}, 'code': 401, 'message': '用户名或密码错误'}
|
||
else:
|
||
return_data = {'data': {'name': property_tenant_information.name, 'id': property_tenant_information.id,
|
||
'phone': property_tenant_information.phone,
|
||
'access': ['物业账单']}, 'code': 200, 'message': '用户名或密码错误'}
|
||
return json.dumps(return_data)
|
||
|
||
user_id = request.env['res.users'].sudo().browse(uid)
|
||
# 创建token
|
||
token = base.set_token(user_id.id, user_id.name)
|
||
employee_id = user_id.with_company(user_id.company_id.id).employee_id
|
||
applet_security_setting_ids = request.env['applet.security'].search(
|
||
[('employee_id', '=', employee_id.id)]).applet_security_setting_ids
|
||
access_list = [access.name for access in applet_security_setting_ids] + ['日程']
|
||
# 查询一下用户的手机号phone, 承租人关联的
|
||
|
||
data = {'name': employee_id.name, 'id': user_id.id, 'access': access_list, 'phone': employee_id.mobile_phone, 'token': token}
|
||
_logger.info(f"接口人{employee_id.name}权限{access_list}__用户id: {user_id}________")
|
||
return_data = {'data': data, 'code': 200, 'message': ''}
|
||
return json.dumps(return_data)
|
||
|
||
# 验证token就在url下面加这个装饰器
|
||
# @base.authorize
|
||
|
||
@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': [上班卡,正常]
|
||
'check_out': [下班卡,早退]
|
||
'state': 是否可以打卡['yes', 'no']
|
||
'state_result': 不可打卡原因 'xxx'
|
||
}
|
||
"""
|
||
_logger.info("----------------/yuthon/att/today---------接口收到响应-------------------------------")
|
||
current_datetime = fields.Datetime.now() + timedelta(hours=8)
|
||
current_date = fields.Date.today() + timedelta(hours=8)
|
||
|
||
# 转换为字符串格式以便日志显示
|
||
current_datetime_str = fields.Datetime.to_string(current_datetime)
|
||
current_date_str = fields.Date.to_string(current_datetime.date())
|
||
|
||
# 日志输出
|
||
_logger.info(f'当前完整时间(含8小时偏移):{current_datetime_str}')
|
||
_logger.info(f'当前日期(含8小时偏移):{current_date_str}')
|
||
user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', int(kw['user_id']))])
|
||
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_id.name)])
|
||
attendance_id = request.env(user=SUPERUSER_ID)["hr.attendance"].search(
|
||
[('employee_id', '=', employee_id.id), ('current_data', '=', current_date)])
|
||
check_in = attendance_id.check_in + timedelta(hours=8) if attendance_id.check_in else ''
|
||
check_out = attendance_id.check_out + timedelta(hours=8) if attendance_id.check_out else ''
|
||
if attendance_id.shift_name:
|
||
shift_name = attendance_id.shift_name
|
||
else:
|
||
shift_name = '行政班次'
|
||
|
||
def get_all_upper_depts(dept):
|
||
"""递归获取部门及其所有上级部门的ID列表"""
|
||
dept_ids = [dept.id]
|
||
if dept.parent_id:
|
||
dept_ids.extend(get_all_upper_depts(dept.parent_id))
|
||
return dept_ids
|
||
|
||
all_dept_ids = get_all_upper_depts(employee_id.department_id)
|
||
|
||
slot_start = datetime.combine(current_date, datetime.min.time())
|
||
slot_end = datetime.combine(current_date + timedelta(days=1), datetime.min.time())
|
||
# 查找员工对应的班次
|
||
planning_slots = request.env(user=SUPERUSER_ID)['planning.slot'].search([
|
||
('employee_id', '=', employee_id.id),
|
||
('start_datetime', '>=', slot_start),
|
||
('start_datetime', '<', slot_end),
|
||
], limit=1)
|
||
planning_name = planning_slots.planning_name if planning_slots else None
|
||
template_domain = []
|
||
if planning_name:
|
||
template_domain.append(('planning_name', '=', planning_name))
|
||
template_domain.append(('department_id', 'in', all_dept_ids))
|
||
template = request.env(user=SUPERUSER_ID)['planning.slot.template'].search(template_domain, limit=1)
|
||
if not template:
|
||
role_templates = [
|
||
{'role_name': '厨卫(集团)', 'template': None},
|
||
{'role_name': '项目运营岗', 'template': None}
|
||
]
|
||
|
||
for item in role_templates:
|
||
item['template'] = request.env(user=SUPERUSER_ID)['planning.slot.template'].search(
|
||
[('role_id.name', '=', item['role_name'])], )
|
||
|
||
for item in role_templates:
|
||
if item['template']:
|
||
employee_ids = item['template'].role_id.resource_ids.employee_id.ids or []
|
||
employee_list = list(employee_ids)
|
||
if employee_id.id in employee_list:
|
||
template = item['template']
|
||
break
|
||
if not template:
|
||
template = request.env(user=SUPERUSER_ID)['planning.slot.template'].search(
|
||
[('role_id.name', '=', '集团行政')],
|
||
limit=1
|
||
)
|
||
sch_check_in_time, sch_check_out_time = request.env(user=SUPERUSER_ID)[
|
||
'hr.attendance.detail'].normal_clock_in_time(template, current_date)
|
||
start_time = sch_check_in_time + timedelta(hours=8)
|
||
end_time = sch_check_out_time + timedelta(hours=8)
|
||
start_time_str = start_time.strftime('%H:%M')
|
||
end_time_str = end_time.strftime('%H:%M')
|
||
is_early_card = attendance_id.is_early_card
|
||
is_late_card = attendance_id.is_late_card
|
||
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:
|
||
state_result = '迟到'
|
||
if is_early_card:
|
||
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 ' '
|
||
check_in_html = f'<p style="color:red">{check_in_str}</p>' if check_in_status in ['迟到', '早退',
|
||
'缺卡'] else f'<p style="color:green">{check_in_str}</p>'
|
||
check_out_html = f'<p style="color:red">{check_out_str}</p>' if check_out_status in ['迟到', '早退',
|
||
'缺卡'] else f'<p style="color:green">{check_out_str}</p>'
|
||
state = "no"
|
||
message = "超出范围或wifi未连接,不允许打卡"
|
||
address_ids = request.env(user=SUPERUSER_ID)["applet.address"].search(
|
||
[('company_id', '=', employee_id.company_id.id)])
|
||
if kw['att_latitude'] != kw['att_longitude']:
|
||
self_address = self.get_address_from_coords(kw['att_latitude'], kw['att_longitude'])
|
||
else:
|
||
self_address = {'formatted_address': 'formatted_address'}
|
||
wifi_name_list = []
|
||
for wifi_mac in address_ids:
|
||
for mac in wifi_mac.wifi_name_ids:
|
||
wifi_name_list.append(mac.mac)
|
||
for address_id in address_ids:
|
||
if '天成大厦' in self_address['formatted_address']:
|
||
message = '可以打卡'
|
||
state = 'yes'
|
||
_logger.info(f"天成大厦在{self_address['formatted_address']}")
|
||
break
|
||
card_rice = address_id.card_rice * 0.001 # 打卡范围
|
||
if self.is_within_radius_geopy(kw['att_latitude'], kw['att_longitude'], address_id.latitude,
|
||
address_id.longitude, card_rice) or kw['wifi_name'] in wifi_name_list:
|
||
message = '可以打卡'
|
||
state = 'yes'
|
||
break
|
||
_logger.info(
|
||
f"\n人员: {employee_id.name} {employee_id.company_id.name} \n当前所在地址: {self_address['formatted_address']} \n是否可以打卡: {state}\n {wifi_name_list}\n wifi:{kw['wifi_name']}_________")
|
||
if self.is_workday(current_date):
|
||
attendance_id_class = f"{shift_name} {start_time_str}-{end_time_str}"
|
||
else:
|
||
attendance_id_class = f"{shift_name}休息"
|
||
data = {
|
||
'location': self_address['formatted_address'],
|
||
'user_id': user_id.id,
|
||
'class': f"{attendance_id_class}",
|
||
'state_result': state_result,
|
||
'check_in': check_in_html,
|
||
'check_in_status': check_in_status,
|
||
'check_out': check_out_html,
|
||
'check_out_status': check_out_status,
|
||
'state': state,
|
||
},
|
||
return_data = {'data': data, 'code': '200', 'message': message, }
|
||
return json.dumps(return_data)
|
||
|
||
@route('/yuthon/att/totday_info', methods=['POST'], csrf=False, type='http', auth='none', cors='*')
|
||
def apple_totday_info(self):
|
||
'''
|
||
获取当日打卡的信息
|
||
'''
|
||
|
||
current_date = fields.Date.today() + timedelta(hours=8)
|
||
user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', int(kw['user_id']))])
|
||
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', user_id.name)])
|
||
attendance_id = request.env(user=SUPERUSER_ID)["hr.attendance"].search(
|
||
[('employee_id', '=', employee_id.id), ('current_data', '=', current_date)])
|
||
check_in = attendance_id.check_in + timedelta(hours=8) if attendance_id.check_in else ''
|
||
check_out = attendance_id.check_out + timedelta(hours=8) if attendance_id.check_out else ''
|
||
start_time_str = ''
|
||
end_time_str = ''
|
||
if attendance_id.shift_name:
|
||
shift_name = attendance_id.shift_name
|
||
else:
|
||
shift_name = '行政班次'
|
||
if attendance_id:
|
||
if not attendance_id.shift_id:
|
||
year = attendance_id.current_data.year
|
||
month = attendance_id.current_data.month
|
||
day = attendance_id.current_data.day
|
||
current_date = datetime.today()
|
||
min_hour_from = attendance_id.employee_id.resource_id.calendar_id.attendance_ids[0].hour_from
|
||
min_hour = int(min_hour_from)
|
||
min_minute = int(min_hour_from * 10 % min_hour_from)
|
||
will_datetime_start = current_date.replace(year=year, month=month, day=day, hour=min_hour,
|
||
minute=min_minute)
|
||
max_hour_to = attendance_id.employee_id.resource_id.calendar_id.attendance_ids[-1].hour_to
|
||
max_hour = int(max_hour_to)
|
||
max_minute = int(max_hour_to * 10 % max_hour_to)
|
||
will_datetime_end = current_date.replace(year=year, month=month, day=day, hour=max_hour,
|
||
minute=max_minute)
|
||
start_time_str = will_datetime_start.strftime('%H:%M')
|
||
end_time_str = will_datetime_end.strftime('%H:%M')
|
||
else:
|
||
check_in_start = attendance_id.shift_id.template_id.check_in_start
|
||
check_out_end = attendance_id.shift_id.template_id.check_out_end
|
||
start_minute = int((check_in_start - int(check_in_start)) * 60)
|
||
end_minute = int((check_out_end - int(check_out_end)) * 60)
|
||
start_time_str = f"{int(check_in_start):02d}-{start_minute:02d}"
|
||
end_time_str = f"{int(check_out_end):02d}-{end_minute:02d}"
|
||
is_early_card = attendance_id.is_early_card
|
||
is_late_card = attendance_id.is_late_card
|
||
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:
|
||
state_result = '迟到'
|
||
if is_early_card:
|
||
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 ' '
|
||
check_in_html = f'<p style="color:red">{check_in_str}</p>' if check_in_status in ['迟到', '早退',
|
||
'缺卡'] else f'<p style="color:green">{check_in_str}</p>'
|
||
check_out_html = f'<p style="color:red">{check_out_str}</p>' if check_out_status in ['迟到', '早退',
|
||
'缺卡'] else f'<p style="color:green">{check_out_str}</p>'
|
||
|
||
if self.is_workday(current_date):
|
||
attendance_id_class = f"{shift_name} {start_time_str}-{end_time_str}"
|
||
else:
|
||
attendance_id_class = f"{shift_name}休息"
|
||
data = {
|
||
'user_id': user_id.id,
|
||
'class': f"{attendance_id_class}",
|
||
'state_result': state_result,
|
||
'check_in': check_in_html,
|
||
'check_in_status': check_in_status,
|
||
'check_out': check_out_html,
|
||
'check_out_status': check_out_status
|
||
},
|
||
return_data = {'data': data, 'code': '200', 'message': '成功', }
|
||
return json.dumps(return_data)
|
||
|
||
def is_workday(self, date):
|
||
"""判断指定日期是否为工作日(考虑法定节假日和调休)
|
||
如果是工作日返回True
|
||
如果是休息日返回False
|
||
"""
|
||
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
calendar_leaves = self.env['resource.calendar.leaves'].sudo().search([
|
||
("calendar_id.name", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)
|
||
])
|
||
holidays_set = set()
|
||
shift_days_set = set()
|
||
for leave in calendar_leaves:
|
||
if leave.holidays:
|
||
holidays_set.update(leave.holidays.split(','))
|
||
if leave.alternate_rest_day:
|
||
holidays_set.update(leave.alternate_rest_day.split(','))
|
||
if leave.shift_replacement_day:
|
||
shift_days_set.update(leave.shift_replacement_day.split(','))
|
||
|
||
date_str = date.strftime('%m月%d日')
|
||
date_str = self.remove_leading_zero(date_str)
|
||
is_weekend = date.weekday() in [5, 6]
|
||
|
||
if date_str in holidays_set:
|
||
return False
|
||
elif date_str in shift_days_set:
|
||
return True
|
||
else:
|
||
return not is_weekend
|
||
|
||
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 is_within_radius_geopy(self, lat1, lon1, lat2, lon2, radius_km):
|
||
"""
|
||
使用geopy库判断是否在范围内
|
||
"""
|
||
point1 = (float(lat1), float(lon1))
|
||
point2 = (float(lat2), float(lon2))
|
||
distance = geodesic(point1, point2).kilometers
|
||
return distance <= radius_km
|
||
|
||
def get_address_from_coords(self, latitude, longitude):
|
||
api_key = 'BHXBZ-RHJC3-Y7R3G-OOMZ2-N6FBE-NQFIB' # 替换为你的实际密钥
|
||
url = f'https://apis.map.qq.com/ws/geocoder/v1?location={latitude},{longitude}&key={api_key}'
|
||
response = requests.get(url)
|
||
data = response.json()
|
||
if data['status'] == 0:
|
||
address = data['result']['address']
|
||
formatted_address = data['result']['formatted_addresses']['recommend']
|
||
return {'address': address, 'formatted_address': formatted_address}
|
||
else:
|
||
print(f"Error: {data['message']}")
|
||
return None
|
||
|
||
@route('/yuthon/att/record', methods=['POST'], type='http', auth='none', csrf=False)
|
||
def apple_yuthon_att_record(self, **kw):
|
||
"""
|
||
小程序创建打卡记录
|
||
:return:
|
||
"""
|
||
_logger.info("-------触发打卡接口/yuthon/att/record---------")
|
||
res_user_id = request.env(user=SUPERUSER_ID)["res.users"].search([('id', '=', kw.get('user_id'))])
|
||
_logger.info(
|
||
f"传过来的user_id:{kw.get('user_id')},名称{res_user_id.name}, 时间{fields.Datetime.now() + timedelta(hours=8)}")
|
||
employee_id = request.env(user=SUPERUSER_ID)["hr.employee"].search([('name', '=', res_user_id.name)])
|
||
if res_user_id and employee_id:
|
||
# 初始化变量
|
||
checkin_type = None
|
||
current_datetime = fields.Datetime.now() + timedelta(hours=8) # 打卡时间
|
||
current_date = current_datetime.date()
|
||
|
||
department = employee_id.department_id
|
||
slot_template = None
|
||
groupname = ''
|
||
if department:
|
||
slot_template = request.env(user=SUPERUSER_ID)["planning.slot.template"].search(
|
||
[('department_id', '=', department.id)], limit=1)
|
||
if not slot_template:
|
||
slot_template = request.env(user=SUPERUSER_ID)["planning.slot.template"].search(
|
||
[('planning_name', '=', '集团行政')], limit=1)
|
||
if slot_template:
|
||
if slot_template.planning_name == "集团行政":
|
||
groupname = "集团行政"
|
||
elif slot_template.role_id and slot_template.planning_name:
|
||
groupname = f"{slot_template.role_id.name} - {slot_template.planning_name}"
|
||
|
||
# 时间转换辅助函数(考虑day2次日标记)
|
||
def get_schedule_datetime(float_hour, is_check_out_end=False, is_day2=False):
|
||
if float_hour is None:
|
||
return None
|
||
# 只有下班结束时间且是次日时才加一天
|
||
base_date = current_date
|
||
if is_check_out_end and is_day2:
|
||
base_date = current_date + timedelta(days=1)
|
||
hours = int(float_hour)
|
||
minutes = int((float_hour - hours) * 60)
|
||
return datetime.combine(base_date, datetime.min.time()) + timedelta(hours=hours, minutes=minutes)
|
||
|
||
# 解析班次时间
|
||
check_in_start = None
|
||
check_in_end = None
|
||
check_out_start = None
|
||
check_out_end = None
|
||
if slot_template:
|
||
if slot_template:
|
||
check_in_start = get_schedule_datetime(slot_template.check_in_start)
|
||
check_in_end = get_schedule_datetime(slot_template.check_in_end)
|
||
check_out_start = get_schedule_datetime(slot_template.check_out_start)
|
||
check_out_end = get_schedule_datetime(slot_template.check_out_end, is_check_out_end=True,
|
||
is_day2=slot_template.day2)
|
||
# 检查该员工是否已有上班打卡记录
|
||
has_checkin = False
|
||
if employee_id:
|
||
# 查询今天是否有上班打卡记录
|
||
checkin_records = request.env(user=SUPERUSER_ID)["hr.attendance.detail"].search([
|
||
('employee_id', '=', employee_id.id),
|
||
('checkin_type', '=', '上班打卡'),
|
||
('checkin_time', '>=', fields.Datetime.to_string(current_date)),
|
||
('checkin_time', '<', fields.Datetime.to_string(current_date + timedelta(days=1)))
|
||
])
|
||
has_checkin = bool(checkin_records)
|
||
sch_checkin_time = check_in_start
|
||
# 判断打卡类型并设置标准打卡时间
|
||
if check_in_start and check_in_end and check_in_start <= current_datetime <= check_in_end:
|
||
checkin_type = "上班打卡"
|
||
sch_checkin_time = check_in_start
|
||
elif has_checkin and check_out_start and check_out_end and check_out_start <= current_datetime <= check_out_end:
|
||
checkin_type = "下班打卡"
|
||
sch_checkin_time = check_out_end
|
||
|
||
# 创建考勤记录
|
||
detail_id = request.env(user=SUPERUSER_ID)["hr.attendance.detail"].create({
|
||
'employee_id': employee_id.id,
|
||
'checkin_time': fields.datetime.now(),
|
||
'source': 'mini_programs',
|
||
'groupname': groupname,
|
||
'checkin_type': checkin_type,
|
||
'sch_checkin_time': sch_checkin_time,
|
||
})
|
||
# now_time = fields.Datetime.to_datetime(fields.Date.today()) - timedelta(hours=8, days=1)
|
||
# start_time = now_time - timedelta(days=2)
|
||
# request.env["hr.attendance.detail"].normal_clock_in_time(employee_id, now_time, start_time)
|
||
return_data = {'code': '200', 'message': '打卡成功'}
|
||
_logger.info(f"打卡记录创建成功 id = {detail_id.id} checkin_time = {fields.datetime.now()}")
|
||
else:
|
||
return_data = {'code': '200', 'message': '未识别到员工'}
|
||
return json.dumps(return_data)
|
||
|
||
@route('/yuthon/app/att', methods=['POST'], type='http', auth='none', csrf=False)
|
||
def yuthon_app_att(self, **kw):
|
||
"""
|
||
:param kw:
|
||
:return:
|
||
{
|
||
"code": "200", // 状态码,200表示成功
|
||
"message": "OK", // 状态描述
|
||
"data": {
|
||
"current_month": { // 当月考勤数据
|
||
"holiday_data": { // 假期数据
|
||
"holiday_list": ["2025-01-01", "2025-01-02"], // 法定节假日,调休休息日列表(日期字符串)
|
||
"shift_days_list": ["2025-02-10", "2025-02-11"] // 调休补班日列表(日期字符串)
|
||
},
|
||
"banner_rote": { // 异常统计
|
||
"is_no_card": 2, // 缺卡次数
|
||
"is_early_card": 1, // 早退次数
|
||
"is_late_card": 3, // 迟到次数
|
||
"is_abnormal": 5 // 异常总次数
|
||
"is_no_work" : 2 // 旷工次数
|
||
|
||
},
|
||
"attendance_data": [ // 每日考勤记录列表
|
||
{
|
||
"date": "2025-06-15", // 日期
|
||
"class": ["早班", "08:30-17:30"], // 班次信息 [班次名称, 标准时间范围]
|
||
"user_id": 1001, // 用户ID
|
||
"check_in": ["08:45:20", "迟到"], // 签到信息 [时间, 状态]
|
||
"check_out": ["17:20:15", "正常"] // 签退信息 [时间, 状态]
|
||
},
|
||
{
|
||
"date": "2025-06-16",
|
||
"class": ["晚班", "14:00-22:00"],
|
||
"user_id": 1001,
|
||
"check_in": ["缺卡", "缺卡"],
|
||
"check_out": ["缺卡", "缺卡"]
|
||
}
|
||
]
|
||
},
|
||
"previous_month": { // 上月考勤数据(结构同current_month)
|
||
"holiday_data": { ... },
|
||
"banner_rote": { ... },
|
||
"attendance_data": [ ... ]
|
||
}
|
||
}
|
||
}
|
||
"""
|
||
user_id = int(kw['user_id'])
|
||
month = kw['selected_month'] # 2015-01
|
||
year = int(month[0:4])
|
||
month = int(month[-2:])
|
||
# 当月日期范围
|
||
current_month_first = datetime(year, month, 1)
|
||
if month == 12:
|
||
current_month_last = datetime(year + 1, 1, 1) - timedelta(days=1)
|
||
else:
|
||
current_month_last = datetime(year, month + 1, 1) - timedelta(days=1)
|
||
# calendar_leaves = request.env(user=SUPERUSER_ID)['resource.calendar.leaves'].search(
|
||
# [("calendar_id", "=", "行政考勤工作日")]) # 少一个search
|
||
# 本月的节假日
|
||
holiday_data = self.get_holiday_data(current_month_first.date(), current_month_last.date())
|
||
# 本月考勤记录
|
||
leave_days = 0
|
||
current_attendance = request.env(user=SUPERUSER_ID)['hr.attendance'].sudo().search([
|
||
('employee_id.user_id', '=', user_id),
|
||
('current_data', '>=', current_month_first.date()),
|
||
('current_data', '<=', current_month_last.date()),
|
||
])
|
||
# 本月请假天数
|
||
leave_ids = request.env(user=SUPERUSER_ID)['hr.leave'].sudo().search([
|
||
('employee_id.user_id', '=', user_id),
|
||
('date_from', '>=', current_month_first),
|
||
('date_to', '<=', current_month_last),
|
||
('state', 'in', ['validate'])
|
||
])
|
||
for leave in leave_ids:
|
||
leave_days += leave.number_of_days
|
||
|
||
# 本月异常次数
|
||
current_stats = {
|
||
'is_no_card': sum(1 for rec in current_attendance if rec.is_no_card),
|
||
'is_early_card': sum(1 for rec in current_attendance if rec.is_early_card),
|
||
'is_late_card': sum(1 for rec in current_attendance if rec.is_late_card),
|
||
'is_abnormal': sum(1 for rec in current_attendance if rec.is_abnormal),
|
||
'is_holiday': leave_days,
|
||
'is_no_work': sum(1 for rec in current_attendance if rec.absenteeism_card),
|
||
}
|
||
current_records = self.process_attendances(current_attendance, user_id)
|
||
return_data = {
|
||
'code': '200',
|
||
'message': 'OK',
|
||
'data': {
|
||
'current_month': {
|
||
'holiday_data': holiday_data,
|
||
'banner_rote': current_stats,
|
||
'attendance_data': current_records,
|
||
},
|
||
}
|
||
}
|
||
return json.dumps(return_data)
|
||
|
||
@route('/yuthon/vacation/get', methods=['POST'], type='http', auth='none', csrf=False)
|
||
def yuthon_vacation_get(self, **kw):
|
||
"""
|
||
:param kw:
|
||
:return:
|
||
{
|
||
"code": "200", // 状态码,200表示成功
|
||
"message": "OK", // 状态描述
|
||
"data": {
|
||
"current_month": { // 当月考勤数据
|
||
"holiday_data": { // 假期数据
|
||
"holiday_list": ["2025-01-01", "2025-01-02"], // 法定节假日,调休休息日列表(日期字符串)
|
||
"shift_days_list": ["2025-02-10", "2025-02-11"] // 调休补班日列表(日期字符串)
|
||
},
|
||
|
||
"""
|
||
id = int(kw['id'])
|
||
|
||
record = request.env['yuthon.vacation.reports'].sudo().browse(id)
|
||
result = {
|
||
'vacation_date': '',
|
||
'employee': '',
|
||
'items': []
|
||
}
|
||
if record:
|
||
items = []
|
||
fields_list = [
|
||
('应到', 'should_attendance'),
|
||
('实到', 'actual_attendance'),
|
||
('迟到', 'is_late_card'),
|
||
('早退', 'is_early_card'),
|
||
('缺卡', 'is_no_card'),
|
||
('旷工', 'is_work'),
|
||
('异常', 'is_abnormal'),
|
||
('出勤天数', 'att_mount'),
|
||
('允许迟到次数', 'late_allowed'),
|
||
('转正天数', 'right_days'),
|
||
('实习天数', 'internship_days'),
|
||
('试用天数', 'test_days'),
|
||
('年假', 'done_leave'),
|
||
('加班时', 'done_working_overtime'),
|
||
('误餐次数', 'meal_fee'),
|
||
('体检费', 'medical_fee'),
|
||
('调休', 'compensatory_leave'),
|
||
('事假', 'leave_of_absence'),
|
||
('加班时(法定)', 'statutory_leave'),
|
||
('补休', 'cop_leave'),
|
||
('值班调休', 'duty_off_leave'),
|
||
('婚假', 'marriage_leave'),
|
||
('产假', 'maternity_leave'),
|
||
('陪产假', 'paternity_leave'),
|
||
('计生假', 'family_planning_leave'),
|
||
('丧假', 'bereavement_leave'),
|
||
('探亲假', 'home_visit_leave'),
|
||
('家长会假', 'parents_meeting_leave'),
|
||
('病假(普通)', 'general_sick_leave'),
|
||
('病假(住院期)', 'hospital_sick_leave'),
|
||
('病假(出院恢复期)', 'recovery_sick_leave'),
|
||
('考试假', 'exam_leave'),
|
||
]
|
||
for label, field in fields_list:
|
||
value = getattr(record, field, None)
|
||
if value is not None:
|
||
items.append({'key': label, 'value': value})
|
||
|
||
result = {
|
||
'vacation_date': fields.Date.to_string(record.vacation_date),
|
||
'employee': record.employee_id.name,
|
||
'items': items
|
||
}
|
||
|
||
return_data = {
|
||
'code': '200',
|
||
'message': 'OK',
|
||
'data': result
|
||
}
|
||
return json.dumps(return_data)
|
||
|
||
@route('/yuthon/vacation/confirm', methods=['POST'], type='http', auth='none', csrf=False)
|
||
def yuthon_vacation_confirm(self, **kw):
|
||
"""
|
||
:param kw:
|
||
:return:
|
||
{
|
||
"code": "200", // 状态码,200表示成功
|
||
"message": "OK", // 状态描述
|
||
"data": {
|
||
"current_month": { // 当月考勤数据
|
||
"holiday_data": { // 假期数据
|
||
"holiday_list": ["2025-01-01", "2025-01-02"], // 法定节假日,调休休息日列表(日期字符串)
|
||
"shift_days_list": ["2025-02-10", "2025-02-11"] // 调休补班日列表(日期字符串)
|
||
},
|
||
|
||
"""
|
||
id = int(kw['id'])
|
||
record = request.env['yuthon.vacation.reports'].sudo().browse(id)
|
||
record.manage_reject()
|
||
|
||
# 这里需要确认提交
|
||
|
||
return_data = {
|
||
'code': '200',
|
||
'message': 'OK',
|
||
'data': id
|
||
}
|
||
return json.dumps(return_data)
|
||
|
||
def process_attendances(self, attendances, user_id):
|
||
records = []
|
||
for att in attendances:
|
||
is_weekend = att.current_data.weekday() in [5, 6]
|
||
|
||
if not att.shift_id:
|
||
year = att.current_data.year
|
||
month = att.current_data.month
|
||
day = att.current_data.day
|
||
current_date = datetime.today()
|
||
min_hour_from = att.employee_id.resource_id.calendar_id.attendance_ids[0].hour_from
|
||
min_hour = int(min_hour_from)
|
||
min_minute = int(min_hour_from * 10 % min_hour_from)
|
||
will_datetime_start = current_date.replace(year=year, month=month, day=day, hour=min_hour,
|
||
minute=min_minute)
|
||
max_hour_to = att.employee_id.resource_id.calendar_id.attendance_ids[-1].hour_to
|
||
max_hour = int(max_hour_to)
|
||
max_minute = int(max_hour_to * 10 % max_hour_to)
|
||
will_datetime_end = current_date.replace(year=year, month=month, day=day, hour=max_hour,
|
||
minute=max_minute)
|
||
standard_check_in_str = will_datetime_start.strftime('%H:%M')
|
||
standard_check_out_str = will_datetime_end.strftime('%H:%M')
|
||
else:
|
||
# 获取班次信息
|
||
check_in_start = att.shift_id.template_id.check_in_start if att.shift_id and att.shift_id.template_id else 0
|
||
check_out_end = att.shift_id.template_id.check_out_end if att.shift_id and att.shift_id.template_id else 0
|
||
start_hour = int(check_in_start)
|
||
start_minute = int((check_in_start - start_hour) * 60)
|
||
end_hour = int(check_out_end)
|
||
end_minute = int((check_out_end - end_hour) * 60)
|
||
# 格式化标准时间
|
||
standard_check_in_str = f"{start_hour:02d}:{start_minute:02d}"
|
||
standard_check_out_str = f"{end_hour:02d}:{end_minute:02d}"
|
||
|
||
# 实际打卡时间
|
||
check_in = att.check_in
|
||
check_out = att.check_out
|
||
is_early_card = att.is_early_card
|
||
is_late_card = att.is_late_card
|
||
|
||
base_check_in_status = '缺卡' if not check_in else ('迟到' if is_late_card else '正常')
|
||
base_check_out_status = '缺卡' if not check_out else ('早退' if is_early_card else '正常')
|
||
|
||
if is_weekend:
|
||
check_in_status = '正常' if check_in else ''
|
||
check_out_status = '正常' if check_out else ''
|
||
else:
|
||
check_in_status = base_check_in_status
|
||
check_out_status = base_check_out_status
|
||
|
||
if check_in:
|
||
check_in_time = (check_in + timedelta(hours=8)).strftime('%H:%M:%S')
|
||
check_in_str = f"{check_in_status} {check_in_time}" if check_in_status in ['迟到',
|
||
'早退'] else check_in_time
|
||
else:
|
||
check_in_str = '' if is_weekend else '缺卡'
|
||
|
||
if check_out:
|
||
check_out_time = (check_out + timedelta(hours=8)).strftime('%H:%M:%S')
|
||
check_out_str = f"{check_out_status} {check_out_time}" if check_out_status in ['迟到',
|
||
'早退'] else check_out_time
|
||
else:
|
||
check_out_str = '' if is_weekend else '缺卡'
|
||
|
||
if not check_in_status and not check_out_status:
|
||
color_select = 3
|
||
else:
|
||
has_exception = (check_in_status in ['迟到', '早退', '缺卡']) or (
|
||
check_out_status in ['迟到', '早退', '缺卡'])
|
||
|
||
color_select = 2 if has_exception else 1
|
||
|
||
records.append({
|
||
'date': att.current_data.strftime('%Y/%m/%d'),
|
||
'class': [f"{att.shift_name} ", f"{standard_check_in_str}-{standard_check_out_str}"],
|
||
'user_id': user_id,
|
||
'check_in': check_in_str,
|
||
'check_out': check_out_str,
|
||
'color_select': color_select,
|
||
'is_weekend': is_weekend
|
||
})
|
||
return records
|
||
|
||
def get_holiday_data(self, start_date, end_date):
|
||
"""
|
||
获取指定日期范围内的假期和调休数据
|
||
|
||
参数:
|
||
start_date: 开始日期,date类型
|
||
end_date: 结束日期,date类型
|
||
返回:
|
||
包含假期和调休信息的字典
|
||
"""
|
||
# 从start_date中提取年份
|
||
target_year = start_date.year
|
||
target_month = start_date.month
|
||
current_year = (fields.Date.today() + timedelta(hours=8)).year
|
||
query_start = datetime(current_year, 1, 1, 0, 0, 0) - timedelta(hours=8)
|
||
query_end = datetime(current_year, 12, 31, 23, 59, 59) - timedelta(hours=8)
|
||
domain = [("calendar_id", "=", "行政考勤工作日"),
|
||
("date_from", ">=", query_start),
|
||
("date_from", "<=", query_end)]
|
||
if start_date and end_date:
|
||
# 将date对象转换为datetime对象
|
||
start_datetime = datetime.combine(start_date, datetime.min.time())
|
||
end_datetime = datetime.combine(end_date, datetime.max.time())
|
||
domain.extend([("date_from", "<=", end_datetime), ("date_to", ">=", start_datetime)])
|
||
calendar_leaves = request.env(user=SUPERUSER_ID)['resource.calendar.leaves'].search(domain)
|
||
# 根据name 来进行去重
|
||
unique_leaves = {}
|
||
for leave in calendar_leaves:
|
||
if leave.name not in unique_leaves:
|
||
unique_leaves[leave.name] = leave
|
||
unique_leave_records = request.env['resource.calendar.leaves']
|
||
for leave in unique_leaves.values():
|
||
unique_leave_records += leave
|
||
|
||
holidays_list = [] # 假期
|
||
shift_days_list = [] # 调休
|
||
for leaves in unique_leave_records:
|
||
# 处理法定节假日和调休日- 直接从date_from和date_to获取
|
||
if leaves.date_from and leaves.date_to:
|
||
# 将datetime类型转换为date类型并格式化为YYYY-MM-DD
|
||
date_from = (leaves.date_from + timedelta(hours=8)).date().isoformat()
|
||
date_to = (leaves.date_to + timedelta(hours=8)).date().isoformat()
|
||
# 处理日期范围,添加从date_from到date_to的所有日期
|
||
start_date_obj = datetime.strptime(date_from, '%Y-%m-%d').date()
|
||
end_date_obj = datetime.strptime(date_to, '%Y-%m-%d').date()
|
||
delta = end_date_obj - start_date_obj
|
||
|
||
for i in range(delta.days + 1):
|
||
day = start_date_obj + timedelta(days=i)
|
||
# 只添加年份和月份匹配的日期
|
||
if day.year == target_year and day.month == target_month:
|
||
formatted_date = f"{day.year}-{day.month}-{day.day}"
|
||
holidays_list.append(formatted_date)
|
||
|
||
# 处理调班补班
|
||
if leaves.shift_replacement_day:
|
||
day_strings = leaves.shift_replacement_day.split(',')
|
||
for day_str in day_strings:
|
||
month_str, day_str = day_str.strip().replace('日', '').split('月')
|
||
month = int(month_str)
|
||
day = int(day_str)
|
||
# 只添加月份匹配的日期
|
||
if month == target_month:
|
||
formatted_date = f"{target_year}-{month}-{day}"
|
||
shift_days_list.append(formatted_date)
|
||
|
||
return {
|
||
"holiday_list": holidays_list,
|
||
"shift_days_list": shift_days_list,
|
||
}
|
||
|
||
# 水电表接口
|
||
@route('/yuthon/meter/reading', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_meter_reading(self, **kw):
|
||
"""
|
||
水电表抄表
|
||
:return:
|
||
table_list:[
|
||
{
|
||
building:'楼盘名称1', 楼盘固定为天河商贸大厦
|
||
floor_list:[
|
||
{
|
||
floor:'楼层1',
|
||
data_list:[
|
||
{
|
||
address: "地址address",
|
||
code: "8817298788",
|
||
employee_name: "xxx,xxx",
|
||
last_dosage: 0,
|
||
last_mater_data: "上次抄表日期",
|
||
mater_data: "抄表日期str",
|
||
meter_type: "electric",
|
||
room:'101'
|
||
},
|
||
{
|
||
address: "地址address",
|
||
code: "8817298788",
|
||
employee_name: "xxx,xxx",
|
||
last_dosage: 0,
|
||
last_mater_data: "上次抄表日期",
|
||
mater_data: "抄表日期str",
|
||
meter_type: "electric",
|
||
room:'102'
|
||
},
|
||
{
|
||
address: "地址address",
|
||
code: "8817298788",
|
||
employee_name: "xxx,xxx",
|
||
last_dosage: 0,
|
||
last_mater_data: "上次抄表日期",
|
||
mater_data: "抄表日期str",
|
||
meter_type: "electric",
|
||
room:'103'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
floor:'楼层2',
|
||
data_list:[
|
||
{
|
||
address: "地址address",
|
||
code: "8817298788",
|
||
employee_name: "xxx,xxx",
|
||
last_dosage: 0,
|
||
last_mater_data: "上次抄表日期",
|
||
mater_data: "抄表日期str",
|
||
meter_type: "electric",
|
||
room:'201'
|
||
},
|
||
{
|
||
address: "地址address",
|
||
code: "8817298788",
|
||
employee_name: "xxx,xxx",
|
||
last_dosage: 0,
|
||
last_mater_data: "上次抄表日期",
|
||
mater_data: "抄表日期str",
|
||
meter_type: "electric",
|
||
room:'202'
|
||
},
|
||
"""
|
||
_logger.info("----------------/yuthon/meter/reading---------接口收到请求-------------------------------")
|
||
today = datetime.now()
|
||
first_day = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
last_day = (today.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
|
||
last_day = last_day.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
|
||
first_day_str = first_day.strftime('%Y-%m-%d')
|
||
last_day_str = last_day.strftime('%Y-%m-%d')
|
||
two_months_ago = datetime.now().date() - relativedelta(months=2)
|
||
# 去掉 数据为 0 的数据.
|
||
valid_archives = request.env(user=SUPERUSER_ID)['property.meter.archives'].search([
|
||
('is_virtual_meter', '!=', True),
|
||
'|',
|
||
('end_time', '=', False),
|
||
('end_time', '<=', two_months_ago)
|
||
])
|
||
electric_records = request.env(user=SUPERUSER_ID)['meter.archives.line'].search([
|
||
('archives_state', '!=', 'stop'),
|
||
('archives_id', 'in', valid_archives.ids),
|
||
('meter_date', '>=', first_day_str),
|
||
('meter_date', '<=', last_day_str),
|
||
])
|
||
|
||
valid_water_ids = request.env(user=SUPERUSER_ID)['property.meter.water'].search([
|
||
'|',
|
||
('end_time', '=', False),
|
||
('end_time', '<=', two_months_ago)
|
||
]).ids
|
||
water_records = request.env(user=SUPERUSER_ID)['meter.water.line'].search([
|
||
('water_state', '!=', 'stop'),
|
||
('meter_date', '>=', first_day_str),
|
||
('meter_date', '<=', last_day_str),
|
||
('water_id', 'in', valid_water_ids)
|
||
])
|
||
|
||
# 接口调整, 电表,水表数据拆分.
|
||
# 先根据状态,再根据楼层
|
||
|
||
# 电表数据
|
||
electric_result_dict = {}
|
||
|
||
# 处理电表
|
||
for electric in electric_records:
|
||
property_infos = electric.yuthon_property_ids if electric.yuthon_property_ids else [None]
|
||
for property_info in property_infos:
|
||
# 处理空物业信息的情况,所有关联字段设为空
|
||
if property_info:
|
||
building = '天河商贸大厦'
|
||
floor = electric.epitope_values_id.name if electric.epitope_values_id else ''
|
||
room = property_info.units or ''
|
||
address = electric.unit_number or ''
|
||
employee_name = property_info.lessee_id.name if property_info.lessee_id else ''
|
||
else:
|
||
building = '天河商贸大厦'
|
||
floor = electric.epitope_values_id.name if electric.epitope_values_id else ''
|
||
room = ''
|
||
address = ''
|
||
employee_name = ''
|
||
state = electric.state
|
||
electric_value = {
|
||
'id': electric.id,
|
||
'current_read': electric.current_read,
|
||
'code': electric.archives_id.name or '',
|
||
'last_dosage': electric.last_dosage or 0,
|
||
'mater_data': str(electric.meter_date) if electric.meter_date else '',
|
||
'last_mater_data': str(electric.last_meter) if electric.last_meter else '',
|
||
'meter_type': 'electric',
|
||
'employee_name': employee_name,
|
||
'address': address,
|
||
'building': '天河商贸大厦',
|
||
'floor': floor,
|
||
'room': room,
|
||
'epitope_values': electric.epitope_values_id.name,
|
||
'unit_number': electric.unit_number,
|
||
'state': electric.state, # 状态 reading unreading
|
||
'last_read': electric.last_read,
|
||
}
|
||
|
||
electric_result_dict.setdefault(state, {}).setdefault(building, {}).setdefault(floor, []).append(
|
||
electric_value)
|
||
|
||
electric_result = []
|
||
for state, building_dict in electric_result_dict.items():
|
||
building_list = []
|
||
for building, floor_dict in building_dict.items():
|
||
floor_list = []
|
||
for floor, data_list in floor_dict.items():
|
||
floor_list.append({
|
||
'floor': floor,
|
||
'data_list': data_list
|
||
})
|
||
building_list.append({
|
||
'building': building,
|
||
'floor_list': floor_list
|
||
})
|
||
electric_result.append({
|
||
'state': state,
|
||
'building_list': building_list
|
||
})
|
||
|
||
water_result_dict = {}
|
||
# 处理水表
|
||
for water in water_records:
|
||
property_infos = water.yuthon_property_ids if water.yuthon_property_ids else [None]
|
||
|
||
for property_info in property_infos:
|
||
# 处理空物业信息的情况,所有关联字段设为空
|
||
if property_info:
|
||
building = '天河商贸大厦'
|
||
floor = water.epitope_values_id.name if water.epitope_values_id else ''
|
||
room = property_info.units or ''
|
||
address = water.unit_number or ''
|
||
employee_name = property_info.lessee_id.name if property_info.lessee_id else ''
|
||
else:
|
||
building = '天河商贸大厦'
|
||
floor = water.epitope_values_id.name if water.epitope_values_id else ''
|
||
room = ''
|
||
address = ''
|
||
employee_name = ''
|
||
state = water.state
|
||
water_value = {
|
||
'id': water.id,
|
||
'current_read': water.current_read,
|
||
'code': water.water_id.name,
|
||
'last_dosage': water.last_dosage or 0,
|
||
'mater_data': str(water.meter_date) if water.meter_date else '',
|
||
'last_mater_data': str(water.last_meter) if water.last_meter else '',
|
||
'meter_type': 'water',
|
||
'employee_name': employee_name,
|
||
'address': address,
|
||
'building': building,
|
||
'floor': floor,
|
||
'room': room,
|
||
'epitope_values': water.epitope_values_id.name,
|
||
'unit_number': water.unit_number,
|
||
'state': water.state, # 状态 reading unreading
|
||
'last_read': water.last_read,
|
||
}
|
||
water_result_dict.setdefault(state, {}).setdefault(building, {}).setdefault(floor, []).append(
|
||
water_value)
|
||
|
||
water_result = []
|
||
for state, building_dict in water_result_dict.items():
|
||
building_list = []
|
||
for building, floor_dict in building_dict.items():
|
||
floor_list = []
|
||
for floor, data_list in floor_dict.items():
|
||
floor_list.append({
|
||
'floor': floor,
|
||
'data_list': data_list
|
||
})
|
||
building_list.append({
|
||
'building': building,
|
||
'floor_list': floor_list
|
||
})
|
||
water_result.append({
|
||
'state': state,
|
||
'building_list': building_list
|
||
})
|
||
|
||
return json.dumps({
|
||
'electric': electric_result,
|
||
'water': water_result,
|
||
})
|
||
|
||
# 获取楼层列表
|
||
@route('/yuthon/meter/reading/floor', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_meter_reading_floor(self, **kw):
|
||
'''获取楼层列表'''
|
||
result = request.env['yuthon.epitope.values'].sudo().search_read([], fields=['name', 'type'])
|
||
return json.dumps(result)
|
||
|
||
# 根据楼层,状态(reading, unreading) 类型, 获取 列表数据
|
||
@route('/yuthon/meter/reading/list', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_meter_reading_list(self, **kw):
|
||
reading_state = kw.get('state')
|
||
type = kw.get('type')
|
||
floor_id = int(kw.get('floor_id'))
|
||
_logger.info(f'楼层id{floor_id}, ---{reading_state}, ---{type}')
|
||
today = datetime.now()
|
||
first_day = today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
||
last_day = (today.replace(day=1) + timedelta(days=32)).replace(day=1) - timedelta(days=1)
|
||
last_day = last_day.replace(hour=23, minute=59, second=59, microsecond=999999)
|
||
first_day_str = first_day.strftime('%Y-%m-%d')
|
||
last_day_str = last_day.strftime('%Y-%m-%d')
|
||
floor_name = request.env['yuthon.epitope.values'].sudo().search([('id', '=', floor_id)]).name
|
||
_logger.info(f'楼层{floor_name}')
|
||
# domain = [('state', '=', reading_state),
|
||
# ('epitope_values_id.name', '=', floor_name),
|
||
# ('meter_date', '>=', first_day_str),
|
||
# ('meter_date', '<=', last_day_str)]
|
||
|
||
domain = [
|
||
('epitope_values_id.name', '=', floor_name),
|
||
('meter_date', '>=', first_day_str),
|
||
('meter_date', '<=', last_day_str)
|
||
]
|
||
two_months_ago = datetime.now().date() - relativedelta(months=2)
|
||
if type == 'electric':
|
||
domain.append(('archives_state', '!=', 'stop'))
|
||
model = 'meter.archives.line'
|
||
domain.extend([
|
||
('archives_id.is_virtual_meter', '!=', True),
|
||
'|',
|
||
('archives_id.end_time', '=', False),
|
||
('archives_id.end_time', '<=', two_months_ago)
|
||
])
|
||
else:
|
||
domain.append(('water_state', '!=', 'stop'))
|
||
model = 'meter.water.line'
|
||
records = request.env[model].sudo().search(domain)
|
||
# _logger.info(f'任务数据{records}')
|
||
result = []
|
||
for record in records:
|
||
property_infos = record.yuthon_property_ids if record.yuthon_property_ids else [None]
|
||
for property_info in property_infos:
|
||
# 处理空物业信息的情况,所有关联字段设为空
|
||
if property_info:
|
||
building = '天河商贸大厦'
|
||
floor = record.epitope_values_id.name if record.epitope_values_id else ''
|
||
room = property_info.units or ''
|
||
address = property_info.name or ''
|
||
employee_name = property_info.lessee_id.name if property_info.lessee_id else ''
|
||
else:
|
||
building = '天河商贸大厦'
|
||
floor = record.epitope_values_id.name if record.epitope_values_id else ''
|
||
room = ''
|
||
address = ''
|
||
employee_name = ''
|
||
state = record.state
|
||
tvalue = {
|
||
'id': record.id,
|
||
'code': record.water_id.name if type == 'water' else record.archives_id.name,
|
||
'last_dosage': record.last_dosage or 0,
|
||
'mater_data': str(record.meter_date) if record.meter_date else '',
|
||
'last_mater_data': str(record.last_meter) if record.last_meter else '',
|
||
'meter_type': state,
|
||
'employee_name': employee_name,
|
||
'address': address,
|
||
'building': building,
|
||
'floor': floor,
|
||
'room': room,
|
||
'epitope_values': record.epitope_values_id.name,
|
||
'unit_number': record.unit_number,
|
||
'state': record.state, # 状态 reading unreading
|
||
'last_read': record.last_read,
|
||
'current_read': clean_floats(record.current_read),
|
||
'current_meter': clean_floats(record.current_meter)
|
||
}
|
||
result.append(tvalue)
|
||
return json.dumps(result, allow_nan=False)
|
||
|
||
@route('/yuthon/meter/dosage', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_meter_dosage(self, **kw):
|
||
"""
|
||
更新仪表用量
|
||
:param code: 编号
|
||
:param dosage: 用量
|
||
:param meter_type: 仪表类型
|
||
"""
|
||
id = kw.get('id')
|
||
code = kw.get('code') # 编号
|
||
current_read = kw.get('current_read') # 本次读数
|
||
last_reading_time = kw.get('last_reading_time') # 本次读数时间
|
||
models_config = [
|
||
{'model_name': 'meter.archives.line', 'type_name': '电表'},
|
||
{'model_name': 'meter.water.line', 'type_name': '水表'}
|
||
]
|
||
record = None
|
||
meter_type_name = None
|
||
for config in models_config:
|
||
current_record = request.env(user=SUPERUSER_ID)[config['model_name']].browse(int(id))
|
||
if current_record.exists():
|
||
record = current_record
|
||
meter_type_name = config['type_name']
|
||
break
|
||
if record:
|
||
record.write({'current_read': current_read, 'last_reading_time': last_reading_time, 'state': 'reading'})
|
||
message = f"成功更新{meter_type_name}用量: {code}"
|
||
else:
|
||
message = f"未找到{meter_type_name}记录: {code}"
|
||
return_data = {
|
||
'data': {},
|
||
'code': '200',
|
||
'message': message,
|
||
}
|
||
_logger.info(message)
|
||
return json.dumps(return_data)
|
||
|
||
@route('/yuthon/peoperty/accounts/receivable', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_peoperty_accounts_receivable(self, **kw):
|
||
_logger.info("-----/yuthon/peoperty/accounts/receivable---------接口收到请求-----")
|
||
phone = kw.get('phone')
|
||
if not phone:
|
||
return json.dumps({
|
||
'data': {},
|
||
'code': '400',
|
||
'message': '请输入手机号'
|
||
})
|
||
lessee_records = request.env(user=SUPERUSER_ID)['property.tenant.information'].search([('phone', '=', phone)])
|
||
if not lessee_records:
|
||
return json.dumps({
|
||
'data': {},
|
||
'code': '400',
|
||
'message': '未找到该手机号对应的承租方'
|
||
})
|
||
lessee_id = lessee_records[0].id
|
||
|
||
# 查询最近三个月记录
|
||
today = datetime.today()
|
||
three_months_ago = today - relativedelta(months=3)
|
||
start_of_month = three_months_ago.replace(day=1).date()
|
||
|
||
_logger.info(f'月初数据:{start_of_month}')
|
||
records = request.env(user=SUPERUSER_ID)['property.accounts.receivable'].search(
|
||
[('lessee_id', '=', lessee_id)], order='due_date desc')
|
||
|
||
data = {}
|
||
for record in records:
|
||
month = record.due_date.strftime('%Y-%m')
|
||
value = data.get(month)
|
||
if not value:
|
||
value = {
|
||
'total': 0,
|
||
'property_dict': {}
|
||
}
|
||
data.update({month: value})
|
||
|
||
# 金额合计
|
||
value['total'] += record.submitted
|
||
|
||
for prop in record.property_ids:
|
||
property_id = prop.id
|
||
property_dict = value.get('property_dict').get(property_id)
|
||
if not property_dict:
|
||
property_dict = {
|
||
'property_name': prop.name,
|
||
'total': 0,
|
||
'id': property_id,
|
||
'list': []
|
||
}
|
||
value['property_dict'].update({property_id: property_dict})
|
||
|
||
property_dict['total'] += record.submitted
|
||
property_dict['list'].append({
|
||
'id': record.id,
|
||
'submitted': float_repr(float_round(record.submitted, precision_rounding=0.01), 2),
|
||
'original_amount': float_repr(
|
||
float_round(record.original_amount - record.discount_amount, precision_rounding=0.01), 2),
|
||
'management_type': record.management_type_id.name,
|
||
'start_date': fields.Date.to_string(record.due_date),
|
||
'end_date': fields.Date.to_string(record.due_date_end) if record.due_date_end else '----'
|
||
})
|
||
|
||
result = []
|
||
for k, v in data.items():
|
||
value = {
|
||
'month': k,
|
||
'total': float_repr(float_round(v.get('total'), precision_digits=2), 2),
|
||
'property_list': [
|
||
{
|
||
'id': k1,
|
||
'property_name': v1.get('property_name'),
|
||
'total': float_repr(float_round(v1.get('total'), precision_rounding=0.01), 2),
|
||
'list': v1.get('list')
|
||
} for k1, v1 in v.get('property_dict').items()
|
||
]
|
||
}
|
||
result.append(value)
|
||
|
||
return json.dumps({
|
||
'data': result,
|
||
'code': '200',
|
||
'message': '成功'
|
||
})
|
||
|
||
@route('/yuthon/wifi/message/create', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_wifi_message_create(self, **kwargs):
|
||
# mac, name, bssid, company_id
|
||
# 获取参数
|
||
mac = kwargs.get('mac')
|
||
name = kwargs.get('name')
|
||
bssid = kwargs.get('bssid')
|
||
company_id = kwargs.get('company_id')
|
||
|
||
# 判断是否为空
|
||
if not all([mac, name, bssid, company_id]):
|
||
return json.dumps({
|
||
'code': 400,
|
||
'message': '参数不完整'
|
||
})
|
||
|
||
wifi_message = request.env['wifi.message'].sudo().search([
|
||
('mac', '=', mac),
|
||
('company_id', '=', company_id)
|
||
], limit=1)
|
||
|
||
if wifi_message:
|
||
wifi_message.sudo().write({
|
||
'name': name,
|
||
'bssid': bssid,
|
||
})
|
||
else:
|
||
request.env['wifi.message'].sudo().create({
|
||
'mac': mac,
|
||
'name': name,
|
||
'bssid': bssid,
|
||
'company_id': company_id
|
||
})
|
||
|
||
return json.dumps({
|
||
'code': 200,
|
||
'message': '成功'
|
||
})
|
||
|
||
@route('/yuthon/company/list', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_company_list(self, **kwargs):
|
||
return json.dumps({
|
||
'code': 200,
|
||
'message': '成功',
|
||
'data': request.env['res.company'].sudo().search_read([], ['id', 'name'])
|
||
})
|
||
|
||
@route('/yuthon/applet/address/create', methods=['POST'], type='http', auth='none', cors='*', csrf=False)
|
||
def yuthon_applet_address_create(self, **kwargs):
|
||
# address_name, address_name2, company_id, longitude,latitude,card_rice
|
||
# 获取参数
|
||
address_name = kwargs.get('address_name')
|
||
address_name2 = kwargs.get('address_name2')
|
||
company_id = kwargs.get('company_id')
|
||
longitude = kwargs.get('longitude')
|
||
latitude = kwargs.get('latitude')
|
||
card_rice = kwargs.get('card_rice')
|
||
|
||
# 判断是否为空
|
||
if not all([address_name, address_name2, longitude, latitude, card_rice, company_id]):
|
||
return json.dumps({
|
||
'code': 400,
|
||
'message': '参数不完整'
|
||
})
|
||
applet_address = request.env['applet.address'].sudo().search([
|
||
('address_name', '=', address_name),
|
||
('longitude', '=', longitude),
|
||
('latitude', '=', latitude),
|
||
('company_id', '=', company_id)
|
||
], limit=1)
|
||
|
||
if applet_address:
|
||
applet_address.sudo().write({
|
||
'address_name2': address_name2,
|
||
'card_rice': card_rice,
|
||
})
|
||
else:
|
||
request.env['applet.address'].sudo().create({
|
||
'address_name': address_name,
|
||
'address_name2': address_name2,
|
||
'longitude': longitude,
|
||
'latitude': latitude,
|
||
'card_rice': card_rice,
|
||
'company_id': company_id
|
||
})
|
||
return json.dumps({
|
||
'code': 200,
|
||
'message': '成功'
|
||
})
|