Skip to content

Commit

Permalink
#866 添加对单日预约时长和同时段预约的限制,修改前端的提示信息 (#868)
Browse files Browse the repository at this point in the history
* 修复订阅管理中的变量名称,确保使用正确的取消订阅列表

* 优化组织订阅查询逻辑,减少数据库查询次数;添加生成数据脚本以便于测试

* 为预约系统增加单人预约时长检查,限制单日预约时长和同时段的多次预约

添加个人预约时长检查,优化预约逻辑以限制单日预约时长

* 修复取消的预约也被计入的错误,修复前端细节
  • Loading branch information
HelloWorldZTR authored Jan 20, 2025
1 parent 90364bf commit bcc48bb
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 8 deletions.
15 changes: 13 additions & 2 deletions Appointment/appoint/manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from Appointment.config import appointment_config as CONFIG
from Appointment.models import Participant, Room, Appoint
from Appointment.utils.utils import get_conflict_appoints
from Appointment.utils.utils import get_conflict_appoints, get_total_appoint_time
from Appointment.utils.log import logger, get_user_logger
from Appointment.appoint.jobs import set_scheduler, cancel_scheduler
from Appointment.extern.wechat import MessageType, notify_appoint
Expand Down Expand Up @@ -93,6 +93,9 @@ def _check_conflict(appoint: Appoint):
conflict_appoints = get_conflict_appoints(appoint, lock=True)
assert len(conflict_appoints) == 0, '预约时间段与已有预约冲突!'

def _check_total_time(appointer: Participant, start: datetime, finish: datetime):
total_time = get_total_appoint_time(appointer, start.date(), lock=True)
assert total_time + finish - start <= CONFIG.max_appoint_time, '单日预约时长超限!'

def _attend_require_num(room: Room, type: Appoint.Type, start: datetime, finish: datetime) -> int:
'''实际监控检查要求的人数'''
Expand Down Expand Up @@ -163,6 +166,15 @@ def create_appoint(
_check_create_num(room, type, inner_num, outer_num)
_check_num_constraint(room, type, inner_num, outer_num)

# 个人预约需要检查总时长
user = appointer.Sid
if (
user.is_person()
and type != Appoint.Type.LONGTERM
and type != Appoint.Type.INTERVIEW
):
_check_total_time(appointer, start, finish)

_check_credit(appointer)
appoint = Appoint(
major_student=appointer, Room=room,
Expand All @@ -185,7 +197,6 @@ def create_appoint(
get_user_logger(appointer).info(f"发起预约,预约号{appoint.pk}")

# 如果预约者是个人,解锁成就-完成地下室预约 该部分尚未测试
user = appointer.Sid
if user.is_person():
unlock_achievement(user, '完成地下室预约')

Expand Down
6 changes: 5 additions & 1 deletion Appointment/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from utils.config import Config, LazySetting
from utils.config.cast import str_to_time

from datetime import timedelta

__all__ = [
'appointment_config',
Expand Down Expand Up @@ -43,6 +44,9 @@ class AppointmentConfig(Config):
allow_newstu_appoint = True
# 是否限制开始前的预约取消时间
restrict_cancel_time = False

# 单人单日预约总时长上限
max_appoint_time = timedelta(hours=6)
# 是否允许单人同一时段预约两个房间
allow_overlap = False

appointment_config = AppointmentConfig(ROOT_CONFIG, 'underground')
31 changes: 29 additions & 2 deletions Appointment/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
from datetime import timedelta
from datetime import timedelta, date, datetime

from django.http import HttpRequest
from django.db.models import Q, QuerySet

from Appointment.models import Room, Appoint
from Appointment.models import Room, Appoint, Participant

'''
YWolfeee:
Expand Down Expand Up @@ -103,6 +103,33 @@ def door2room(door):
def check_temp_appoint(room: Room) -> bool:
return '研讨' in room.Rtitle

# 获取当日预约总时长,不含长期预约和面试
def get_total_appoint_time(appointer: Participant, day: date, lock=False) -> timedelta:
# 获取当日预约,不含长期预约和面试
appoints = appointer.appoint_list.filter(Astart__date=day).exclude(
Atype__in=(Appoint.Type.LONGTERM, Appoint.Type.INTERVIEW)
).exclude(
Astatus=Appoint.Status.CANCELED
)
if lock:
appoints = appoints.select_for_update()
# 计算总时长
total_time = timedelta()
for appoint in appoints:
total_time += appoint.Afinish - appoint.Astart
return total_time

# 获取预约者同时进行的预约,不含长期预约和面试
def get_overlap_appoints(appointer: Participant, start_time: datetime, finish_time: datetime) -> QuerySet[Appoint]:
parallel_appoints = appointer.appoint_list.exclude(
Atype__in=(Appoint.Type.LONGTERM, Appoint.Type.INTERVIEW)
).exclude(
Astatus=Appoint.Status.CANCELED
).exclude(
Q(Afinish__lte=start_time) | Q(Astart__gte=finish_time)
).all()
return parallel_appoints


def get_conflict_appoints(appoint: Appoint, times: int = 1,
interval: int = 1, week_offset: int = 0,
Expand Down
38 changes: 37 additions & 1 deletion Appointment/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@
)
from Appointment.extern.wechat import MessageType, notify_appoint
from Appointment.utils.utils import (
get_conflict_appoints, to_feedback_url,
get_conflict_appoints,
to_feedback_url,
get_total_appoint_time,
get_overlap_appoints
)
from Appointment.utils.log import logger, get_user_logger
import Appointment.utils.web_func as web_func
Expand Down Expand Up @@ -416,6 +419,11 @@ def arrange_time(request: HttpRequest):
# 判断当前用户是否可以进行长期预约
has_longterm_permission = get_participant(request.user).longterm

# 用于前端使用
allow_overlap = CONFIG.allow_overlap
max_appoint_time = int(CONFIG.max_appoint_time.total_seconds() // 3600) # 以小时计算
is_person = request.user.is_person()

# 获取房间编号
Rid = request.GET.get('Rid')
try:
Expand Down Expand Up @@ -447,6 +455,13 @@ def arrange_time(request: HttpRequest):
dayrange_list, start_day, end_next_day = web_func.get_dayrange(
day_offset=start_week * 7)

# 获取每天剩余的可预约时长
available_hours = {}
for day in [start_day + timedelta(days=i) for i in range(7)]:
used_time = get_total_appoint_time(get_participant(request.user), day)
available_hour = CONFIG.max_appoint_time - used_time
available_hours[day.strftime('%a')] = int(available_hour.total_seconds() // 3600) # 以小时计算

# 获取预约时间的最大时间块id
max_stamp_id = web_func.get_time_id(room, room.Rfinish, mode="leftopen")

Expand Down Expand Up @@ -535,6 +550,7 @@ class TimeStatus:

# 转换成方便前端使用的形式
js_dayrange_list = json.dumps(dayrange_list)
js_available_hours = json.dumps(available_hours)

# 获取房间信息,以支持房间切换的功能
function_room_list = Room.objects.function_rooms().order_by('Rid')
Expand Down Expand Up @@ -895,6 +911,26 @@ def checkout_appoint(request: UserRequest):
# TODO: 隔周预约的处理可优化,根据start_week调整实际预约时间
start_time += timedelta(weeks=start_week)
end_time += timedelta(weeks=start_week)

# 预约时间检查
if (
applicant.Sid.is_person()
and not is_longterm
and not is_interview
and get_total_appoint_time(applicant, start_time.date()) + (end_time - start_time) > CONFIG.max_appoint_time
):
wrong('您预约的时长已超过每日最大预约时长', render_context)

# 检查预约者是否有同时段的预约
if (
applicant.Sid.is_person()
and not CONFIG.allow_overlap
and not is_longterm
and not is_interview
and get_overlap_appoints(applicant, start_time, end_time).exists()
):
wrong(f'您在该时间段已经有预约', render_context)

if my_messages.get_warning(render_context)[0] is None:
# 参数检查全部通过,下面开始创建预约
appoint_type = Appoint.Type.NORMAL
Expand Down
16 changes: 14 additions & 2 deletions templates/Appointment/booking.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,21 @@ <h4 class="breadcrumb-title">
{% comment %} replace js alert with bootstrap alert {% endcomment %}
<div class="col-12">
<div class="alert alert-info alert-dismissible fade show" role="alert">
<strong>温馨提示:</strong> 点击【房间名称】可以选择【其他房间】进行预约。
<strong>温馨提示:</strong>
点击【房间名称】可以选择【其他房间】进行预约。
{% if not allow_overlapse and is_person %}
不可以预约同时段的多个房间。
{% endif %}
<button type="button" class="close" data-dismiss="alert">
<span >&times;</span>
</button>
</div>
<div class="alert alert-info alert-dismissible fade show" role="alert">
<strong>温馨提示:</strong> 每个【时间块】表示自显示时刻起的 30 分钟使用时间,长于 30 分钟的预约只需点击【起始时间块】和【结束时间块】即可。
<strong>温馨提示:</strong>
每个【时间块】表示自显示时刻起的 30 分钟使用时间,长于 30 分钟的预约只需点击【起始时间块】和【结束时间块】即可。
{% if is_person %}
每人每天常规预约时间不得超过 {{ max_appoint_time }} 小时。
{% endif %}
<button type="button" class="close" data-dismiss="alert">
<span >&times;</span>
</button>
Expand Down Expand Up @@ -413,6 +421,7 @@ <h5 class="doc-name">{{ room }}</h5>
//document.getElementById(btn.id).textContent="hehe";
}
function submitfunc() {
let available_hours = {{ available_hours|safe }};
if (!(timestatus >= 1)) {
alert("请选择预约时段!");
}
Expand All @@ -430,6 +439,9 @@ <h5 class="doc-name">{{ room }}</h5>
if(endid - startid > 5){
return alert("常规预约时间不能超过3小时!");
}
if({{ is_person | yesno:"true, false" }} && endid - startid + 1 > available_hours[weekday] * 2) {
return alert("每人每天常规预约时间不得超过" + {{ max_appoint_time }} + "小时!");
}
if(endid - startid === 1){
if(!confirm("时长30分钟的预约只需点击开始时间块即可。您确定要发起时长1小时的预约吗?")) return;
}
Expand Down

0 comments on commit bcc48bb

Please sign in to comment.