|
@@ -43,7 +43,10 @@ def parse_hhmm(value: Optional[str]) -> Optional[time]:
|
|
|
parts = raw.split(":")
|
|
parts = raw.split(":")
|
|
|
if len(parts) < 2:
|
|
if len(parts) < 2:
|
|
|
raise DutyBizError("时间格式须为 HH:mm")
|
|
raise DutyBizError("时间格式须为 HH:mm")
|
|
|
- h, m = int(parts[0]), int(parts[1])
|
|
|
|
|
|
|
+ try:
|
|
|
|
|
+ h, m = int(parts[0]), int(parts[1])
|
|
|
|
|
+ except ValueError as exc:
|
|
|
|
|
+ raise DutyBizError("时间格式须为 HH:mm") from exc
|
|
|
if not (0 <= h <= 23 and 0 <= m <= 59):
|
|
if not (0 <= h <= 23 and 0 <= m <= 59):
|
|
|
raise DutyBizError("时间格式须为 HH:mm")
|
|
raise DutyBizError("时间格式须为 HH:mm")
|
|
|
return time(hour=h, minute=m)
|
|
return time(hour=h, minute=m)
|
|
@@ -198,15 +201,32 @@ def _assignment_dict(row: DutyAssignment) -> Dict[str, Any]:
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
-def _slot_dict(slot: DutySlot, assignments: Sequence[DutyAssignment]) -> Dict[str, Any]:
|
|
|
|
|
|
|
+def _slot_dict(
|
|
|
|
|
+ slot: DutySlot,
|
|
|
|
|
+ assignments: Sequence[DutyAssignment],
|
|
|
|
|
+ *,
|
|
|
|
|
+ shift_labels: Dict[str, str],
|
|
|
|
|
+ work_plan_labels: Dict[str, str],
|
|
|
|
|
+) -> Dict[str, Any]:
|
|
|
active = [a for a in assignments if a.del_flag == 0]
|
|
active = [a for a in assignments if a.del_flag == 0]
|
|
|
|
|
+ plans = split_work_plans(getattr(slot, "work_plans", None))
|
|
|
|
|
+ cross = bool(getattr(slot, "cross_day", 0) or 0)
|
|
|
|
|
+ start = getattr(slot, "start_time", None)
|
|
|
|
|
+ end = getattr(slot, "end_time", None)
|
|
|
return {
|
|
return {
|
|
|
"id": slot.id,
|
|
"id": slot.id,
|
|
|
"shiftType": slot.shift_type,
|
|
"shiftType": slot.shift_type,
|
|
|
- "shiftLabel": SHIFT_LABELS.get(slot.shift_type, slot.shift_type),
|
|
|
|
|
|
|
+ "shiftLabel": shift_labels.get(slot.shift_type)
|
|
|
|
|
+ or SHIFT_LABELS.get(slot.shift_type, slot.shift_type),
|
|
|
"capacity": slot.capacity,
|
|
"capacity": slot.capacity,
|
|
|
"taken": len(active),
|
|
"taken": len(active),
|
|
|
|
|
+ "workPlans": plans,
|
|
|
|
|
+ "workPlanLabels": [work_plan_labels.get(v, v) for v in plans],
|
|
|
"remark": (slot.remark or "").strip() or None,
|
|
"remark": (slot.remark or "").strip() or None,
|
|
|
|
|
+ "startTime": format_hhmm(start),
|
|
|
|
|
+ "endTime": format_hhmm(end),
|
|
|
|
|
+ "crossDay": cross,
|
|
|
|
|
+ "timeLabel": format_time_label(start, end, cross),
|
|
|
"assignments": [_assignment_dict(a) for a in active],
|
|
"assignments": [_assignment_dict(a) for a in active],
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -215,9 +235,17 @@ def _day_dict(
|
|
|
day: DutyDay,
|
|
day: DutyDay,
|
|
|
slots: Sequence[DutySlot],
|
|
slots: Sequence[DutySlot],
|
|
|
assignments_by_slot: Dict[str, List[DutyAssignment]],
|
|
assignments_by_slot: Dict[str, List[DutyAssignment]],
|
|
|
|
|
+ *,
|
|
|
|
|
+ shift_labels: Dict[str, str],
|
|
|
|
|
+ work_plan_labels: Dict[str, str],
|
|
|
) -> Dict[str, Any]:
|
|
) -> Dict[str, Any]:
|
|
|
slot_payload = [
|
|
slot_payload = [
|
|
|
- _slot_dict(s, assignments_by_slot.get(s.id, []))
|
|
|
|
|
|
|
+ _slot_dict(
|
|
|
|
|
+ s,
|
|
|
|
|
+ assignments_by_slot.get(s.id, []),
|
|
|
|
|
+ shift_labels=shift_labels,
|
|
|
|
|
+ work_plan_labels=work_plan_labels,
|
|
|
|
|
+ )
|
|
|
for s in sorted(slots, key=lambda x: (x.order_num or 0, x.shift_type or ""))
|
|
for s in sorted(slots, key=lambda x: (x.order_num or 0, x.shift_type or ""))
|
|
|
if s.del_flag == 0
|
|
if s.del_flag == 0
|
|
|
]
|
|
]
|
|
@@ -278,7 +306,15 @@ def _load_assignments_for_slots(
|
|
|
def _serialize_day(db: Session, day: DutyDay) -> Dict[str, Any]:
|
|
def _serialize_day(db: Session, day: DutyDay) -> Dict[str, Any]:
|
|
|
slots = _load_slots(db, day.id)
|
|
slots = _load_slots(db, day.id)
|
|
|
assigns = _load_assignments_for_slots(db, [s.id for s in slots])
|
|
assigns = _load_assignments_for_slots(db, [s.id for s in slots])
|
|
|
- return _day_dict(day, slots, assigns)
|
|
|
|
|
|
|
+ shift_labels = _load_dict_label_map(db, day.tenant_no, DICT_SHIFT_TYPE)
|
|
|
|
|
+ work_plan_labels = _load_dict_label_map(db, day.tenant_no, DICT_WORK_PLAN)
|
|
|
|
|
+ return _day_dict(
|
|
|
|
|
+ day,
|
|
|
|
|
+ slots,
|
|
|
|
|
+ assigns,
|
|
|
|
|
+ shift_labels=shift_labels,
|
|
|
|
|
+ work_plan_labels=work_plan_labels,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
|
|
|
def empty_day_skeleton(duty_date: date) -> Dict[str, Any]:
|
|
def empty_day_skeleton(duty_date: date) -> Dict[str, Any]:
|
|
@@ -430,6 +466,59 @@ def save_day(
|
|
|
order += 1
|
|
order += 1
|
|
|
slot = existing_by_type.get(slot_in.shiftType)
|
|
slot = existing_by_type.get(slot_in.shiftType)
|
|
|
slot_remark = (slot_in.remark or "").strip() or None
|
|
slot_remark = (slot_in.remark or "").strip() or None
|
|
|
|
|
+ plans_raw = list(slot_in.workPlans or [])
|
|
|
|
|
+ for v in plans_raw:
|
|
|
|
|
+ v = (v or "").strip()
|
|
|
|
|
+ if not v:
|
|
|
|
|
+ continue
|
|
|
|
|
+ row = (
|
|
|
|
|
+ db.query(SysDictData)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ SysDictData.tenant_no == tenant_no,
|
|
|
|
|
+ SysDictData.dict_type == DICT_WORK_PLAN,
|
|
|
|
|
+ SysDictData.dict_value == v,
|
|
|
|
|
+ SysDictData.del_flag == 0,
|
|
|
|
|
+ )
|
|
|
|
|
+ .first()
|
|
|
|
|
+ )
|
|
|
|
|
+ if not row:
|
|
|
|
|
+ ensure_dict_data(
|
|
|
|
|
+ db,
|
|
|
|
|
+ tenant_no=tenant_no,
|
|
|
|
|
+ dict_type=DICT_WORK_PLAN,
|
|
|
|
|
+ label=v,
|
|
|
|
|
+ preferred_value=v,
|
|
|
|
|
+ user_id=user_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ slot_work_plans = join_work_plans(plans_raw)
|
|
|
|
|
+
|
|
|
|
|
+ if slot_in.shiftType not in SHIFT_LABELS:
|
|
|
|
|
+ row = (
|
|
|
|
|
+ db.query(SysDictData)
|
|
|
|
|
+ .filter(
|
|
|
|
|
+ SysDictData.tenant_no == tenant_no,
|
|
|
|
|
+ SysDictData.dict_type == DICT_SHIFT_TYPE,
|
|
|
|
|
+ SysDictData.dict_value == slot_in.shiftType,
|
|
|
|
|
+ SysDictData.del_flag == 0,
|
|
|
|
|
+ )
|
|
|
|
|
+ .first()
|
|
|
|
|
+ )
|
|
|
|
|
+ if not row:
|
|
|
|
|
+ ensure_dict_data(
|
|
|
|
|
+ db,
|
|
|
|
|
+ tenant_no=tenant_no,
|
|
|
|
|
+ dict_type=DICT_SHIFT_TYPE,
|
|
|
|
|
+ label=SHIFT_LABELS.get(slot_in.shiftType, slot_in.shiftType),
|
|
|
|
|
+ preferred_value=slot_in.shiftType,
|
|
|
|
|
+ user_id=user_id,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ start_t = parse_hhmm(slot_in.startTime)
|
|
|
|
|
+ end_t = parse_hhmm(slot_in.endTime)
|
|
|
|
|
+ if (start_t is None) ^ (end_t is None):
|
|
|
|
|
+ raise DutyBizError("值班时间须同时填写开始与结束,或全部留空")
|
|
|
|
|
+ cross = 1 if slot_in.crossDay else 0
|
|
|
|
|
+
|
|
|
# 已有 slot 上历史 assign 用户可再保存(D4);新建 slot 必须具备值班人员角色(D7)
|
|
# 已有 slot 上历史 assign 用户可再保存(D4);新建 slot 必须具备值班人员角色(D7)
|
|
|
grandfathered_user_ids: set = set()
|
|
grandfathered_user_ids: set = set()
|
|
|
if slot is None:
|
|
if slot is None:
|
|
@@ -438,6 +527,10 @@ def save_day(
|
|
|
duty_day_id=day.id,
|
|
duty_day_id=day.id,
|
|
|
shift_type=slot_in.shiftType,
|
|
shift_type=slot_in.shiftType,
|
|
|
capacity=slot_in.capacity,
|
|
capacity=slot_in.capacity,
|
|
|
|
|
+ work_plans=slot_work_plans,
|
|
|
|
|
+ start_time=start_t,
|
|
|
|
|
+ end_time=end_t,
|
|
|
|
|
+ cross_day=cross,
|
|
|
remark=slot_remark,
|
|
remark=slot_remark,
|
|
|
tenant_no=tenant_no,
|
|
tenant_no=tenant_no,
|
|
|
order_num=order,
|
|
order_num=order,
|
|
@@ -476,6 +569,10 @@ def save_day(
|
|
|
f"超过名额 {slot_in.capacity},请先删人再缩容"
|
|
f"超过名额 {slot_in.capacity},请先删人再缩容"
|
|
|
)
|
|
)
|
|
|
slot.capacity = slot_in.capacity
|
|
slot.capacity = slot_in.capacity
|
|
|
|
|
+ slot.work_plans = slot_work_plans
|
|
|
|
|
+ slot.start_time = start_t
|
|
|
|
|
+ slot.end_time = end_t
|
|
|
|
|
+ slot.cross_day = cross
|
|
|
slot.remark = slot_remark
|
|
slot.remark = slot_remark
|
|
|
slot.order_num = order
|
|
slot.order_num = order
|
|
|
slot.update_by = user_id
|
|
slot.update_by = user_id
|
|
@@ -673,7 +770,7 @@ def get_public_page(db: Session, token: str) -> Dict[str, Any]:
|
|
|
|
|
|
|
|
def grab(db: Session, token: str, shift_type: str, user_id: str) -> Dict[str, Any]:
|
|
def grab(db: Session, token: str, shift_type: str, user_id: str) -> Dict[str, Any]:
|
|
|
shift_type = (shift_type or "").strip()
|
|
shift_type = (shift_type or "").strip()
|
|
|
- if shift_type not in SHIFT_LABELS:
|
|
|
|
|
|
|
+ if not shift_type:
|
|
|
raise DutyBizError("班次类型无效")
|
|
raise DutyBizError("班次类型无效")
|
|
|
|
|
|
|
|
day = (
|
|
day = (
|
|
@@ -736,10 +833,12 @@ def grab(db: Session, token: str, shift_type: str, user_id: str) -> Dict[str, An
|
|
|
)
|
|
)
|
|
|
db.add(row)
|
|
db.add(row)
|
|
|
db.commit()
|
|
db.commit()
|
|
|
|
|
+ shift_labels = _load_dict_label_map(db, day.tenant_no, DICT_SHIFT_TYPE)
|
|
|
return {
|
|
return {
|
|
|
"dutyDate": day.duty_date.isoformat(),
|
|
"dutyDate": day.duty_date.isoformat(),
|
|
|
"shiftType": shift_type,
|
|
"shiftType": shift_type,
|
|
|
- "shiftLabel": SHIFT_LABELS.get(shift_type, shift_type),
|
|
|
|
|
|
|
+ "shiftLabel": shift_labels.get(shift_type)
|
|
|
|
|
+ or SHIFT_LABELS.get(shift_type, shift_type),
|
|
|
"personName": name,
|
|
"personName": name,
|
|
|
"userId": user.id,
|
|
"userId": user.id,
|
|
|
"status": "ok",
|
|
"status": "ok",
|