feat:报名
This commit is contained in:
@@ -21,6 +21,47 @@ router = APIRouter(tags=["ecosystem"])
|
||||
_ANY = ("opc_member", "carrier", "operator")
|
||||
|
||||
|
||||
def _normalize_activity_ext(payload: dict) -> dict:
|
||||
"""运营方/成员端发布活动:校验并规范化报名体系升级字段(JSON 列)。"""
|
||||
from ..training import main as training_main
|
||||
if payload.get("form_fields_json") is not None:
|
||||
try:
|
||||
raw = json.loads(payload["form_fields_json"]) if isinstance(payload["form_fields_json"], str) else payload["form_fields_json"]
|
||||
except (TypeError, ValueError):
|
||||
raise HTTPException(status_code=400, detail="form_fields_json 不是合法 JSON")
|
||||
fields, err = training_main.normalize_form_fields(raw)
|
||||
if err:
|
||||
raise HTTPException(status_code=400, detail=err)
|
||||
payload["form_fields_json"] = json.dumps(fields, ensure_ascii=False)
|
||||
if payload.get("activity_admins_json") is not None:
|
||||
try:
|
||||
raw = json.loads(payload["activity_admins_json"]) if isinstance(payload["activity_admins_json"], str) else payload["activity_admins_json"]
|
||||
except (TypeError, ValueError):
|
||||
raise HTTPException(status_code=400, detail="activity_admins_json 不是合法 JSON")
|
||||
admins, err = training_main.normalize_activity_admins(raw)
|
||||
if err:
|
||||
raise HTTPException(status_code=400, detail=err)
|
||||
payload["activity_admins_json"] = json.dumps(admins, ensure_ascii=False)
|
||||
if payload.get("secret_json") is not None:
|
||||
try:
|
||||
raw = json.loads(payload["secret_json"]) if isinstance(payload["secret_json"], str) else payload["secret_json"]
|
||||
except (TypeError, ValueError):
|
||||
raise HTTPException(status_code=400, detail="secret_json 不是合法 JSON")
|
||||
payload["secret_json"] = json.dumps(training_main.normalize_secret(raw), ensure_ascii=False)
|
||||
if payload.get("remind_before_min") is not None:
|
||||
remind = int(payload["remind_before_min"] or 0)
|
||||
if remind < 0 or remind > 10080:
|
||||
raise HTTPException(status_code=400, detail="提醒提前量须在 0~10080 分钟之间")
|
||||
payload["remind_before_min"] = remind
|
||||
if payload.get("signup_deadline") is not None:
|
||||
dl = str(payload["signup_deadline"] or "").strip()
|
||||
if dl:
|
||||
from ..training.main import _parse_ms
|
||||
if _parse_ms(dl) <= 0:
|
||||
raise HTTPException(status_code=400, detail="报名截止时间格式不正确")
|
||||
return payload
|
||||
|
||||
|
||||
# ── 通知中心(实时通知体系)──────────────────────────────────────────────
|
||||
@router.get("/notifications", summary="我的通知(分页/分类/已读筛选)")
|
||||
async def list_notifications(
|
||||
@@ -280,6 +321,7 @@ class BookingCreateRequest(BaseModel):
|
||||
name: str = ""
|
||||
contact: str = ""
|
||||
want: str = ""
|
||||
form_data: dict | None = None # 自定义报名表单值 {f_id: value}
|
||||
|
||||
|
||||
@router.get("/activities", summary="活动列表(OPC 用户)")
|
||||
@@ -315,7 +357,7 @@ async def create_activity_public(
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles(*_ANY)),
|
||||
):
|
||||
payload = req.model_dump(exclude_none=True)
|
||||
payload = _normalize_activity_ext(req.model_dump(exclude_none=True))
|
||||
payload["publisher_id"] = actor["id"]
|
||||
payload["publisher_name"] = actor.get("username") or actor.get("nickname") or ""
|
||||
payload["review_status"] = "approved" # 演示环境直接通过;如需审核可改为 pending
|
||||
@@ -336,6 +378,9 @@ async def register_activity_public(
|
||||
booking = training.register_event(event_id, actor["id"], actor.get("username") or "", req.model_dump(exclude_none=True))
|
||||
if booking is None:
|
||||
raise HTTPException(status_code=404, detail="活动不存在")
|
||||
if booking.get("error"):
|
||||
err = booking["error"]
|
||||
raise HTTPException(status_code=400, detail=err)
|
||||
await write_audit(db, action="booking.create", resource="booking", resource_id=booking["booking"]["id"],
|
||||
detail=event_id, user=actor, request=request)
|
||||
return booking
|
||||
@@ -349,9 +394,8 @@ async def update_activity_owned(
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_roles(*_ANY)),
|
||||
):
|
||||
activity = training.update_activity_owned(
|
||||
event_id, req.model_dump(exclude_unset=True, exclude_none=True), actor["id"],
|
||||
)
|
||||
payload = _normalize_activity_ext(req.model_dump(exclude_unset=True, exclude_none=True))
|
||||
activity = training.update_activity_owned(event_id, payload, actor["id"])
|
||||
if activity is None:
|
||||
raise HTTPException(status_code=404, detail="活动不存在或无权修改")
|
||||
await write_audit(db, action="activity.update", resource="activity", resource_id=event_id,
|
||||
|
||||
@@ -1108,6 +1108,19 @@ async def update_booking(
|
||||
db: Database = Depends(get_db),
|
||||
actor: dict = Depends(require_permission("action:booking.manage")),
|
||||
):
|
||||
# 审核动作:走训练域统一审核入口(状态联动 + 审核记录 + 短信通知,幂等)
|
||||
if req.audit_status in ("approved", "rejected"):
|
||||
from ..training import main as training_main
|
||||
payload = {"user_id": actor.get("id", ""), "username": actor.get("username", ""),
|
||||
"sub": actor.get("id", "")}
|
||||
bk = await training_main._apply_booking_audit(
|
||||
booking_id, req.audit_status, req.audit_comment or req.note or "", payload, dict(actor))
|
||||
if bk is None:
|
||||
raise HTTPException(status_code=404, detail="Booking not found")
|
||||
await write_audit(db, action="booking.review", resource="booking", resource_id=booking_id,
|
||||
detail=f"decision={req.audit_status} comment={req.audit_comment or ''}",
|
||||
user=actor, request=request)
|
||||
return training.pack_booking(bk)
|
||||
booking = training.update_booking(booking_id, req.model_dump(exclude_none=True))
|
||||
if booking is None:
|
||||
raise HTTPException(status_code=404, detail="Booking not found")
|
||||
|
||||
@@ -243,6 +243,12 @@ class ActivityCreateRequest(BaseModel):
|
||||
notice: str | None = None # 活动注意事项
|
||||
external_url: str | None = None
|
||||
signup_mode: str | None = None # mini|external
|
||||
# ── 报名体系升级 ──
|
||||
form_fields_json: str | None = None # 报名表单字段定义(JSON 数组)
|
||||
activity_admins_json: str | None = None # 活动管理员(JSON 数组,手机号绑定)
|
||||
secret_json: str | None = None # 审核通过后可见信息(JSON 对象)
|
||||
signup_deadline: str | None = None # 报名截止(ISO;空=不限制)
|
||||
remind_before_min: int | None = None # 活动前提醒提前分钟数(0=关闭)
|
||||
|
||||
|
||||
class ActivityStatusRequest(BaseModel):
|
||||
@@ -265,12 +271,22 @@ class ActivityUpdateRequest(BaseModel):
|
||||
duration_min: int | None = None
|
||||
capacity: int | None = None
|
||||
price_fen: int | None = None # 定价(分),0=免费
|
||||
# ── 报名体系升级 ──
|
||||
form_fields_json: str | None = None
|
||||
activity_admins_json: str | None = None
|
||||
secret_json: str | None = None
|
||||
signup_deadline: str | None = None
|
||||
remind_before_min: int | None = None
|
||||
|
||||
|
||||
class BookingUpdateRequest(BaseModel):
|
||||
status: str | None = None
|
||||
audit_status: str | None = None
|
||||
note: str | None = None
|
||||
# ── 报名体系升级 ──
|
||||
audit_comment: str | None = None # 审核备注(与小程序 /review 口径一致)
|
||||
contact: str | None = None
|
||||
name: str | None = None
|
||||
|
||||
|
||||
class TestCreateRequest(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user