| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- // 活动域请求 DTO(端点 22~37 的**入参形状**)。
- //
- // 依据:unified/03-migration/academic-frontend-contract-v1.md
- // - §2.2 活动(/admin/activities)端点 22~37 的「请求要点」列;
- // - §3.4 B 活动端点字段表(响应侧;本文件只放入参 + 少量响应拼装结构);
- // - §1.5 B1/B2(时间与量纲:`startTime`/`endTime` 是业务时间列,金额是两位小数字符串)。
- //
- // ---------------------------------------------------------------------------
- // 三条 DTO 铁律(与 `dto_fund.go` 同源,**不另立一套**)
- // ---------------------------------------------------------------------------
- //
- // 1. **金额一律 `string`**(`"100.00"`),Handler 用 `parseMoney` → `money.Parse`。
- // 不声明 `float64`/`money.Amount`:前者把二进制浮点误差带进对账,后者让 JSON number 也能解码
- // (等于偷偷接受 float —— 见 `parseMoney` 注释的"连宽松接受数字都不做")。
- // 2. **缺参 → 2001 由 `validate` tag 表达;值非法 → 2002 由 Handler 显式判**。
- // 因此**枚举字段不写 `oneof=`**(`oneof` 失败会被 `infra/validator` 归成 2001,
- // 而"传了非法枚举"按设计 §10.1 属 2002)。枚举白名单一律在 Handler 里判。
- // 3. **可选与必填要能区分**:`numberSign` 用 `*int32`(契约 §3.4 B 行 25(v1.6)「传即 2002」,
- // "传了 0"与"没传"必须区分开,只有指针能表达);其它可选字段用值类型 + 空串/0 语义。
- //
- // 命名:camelCase JSON(与契约逐字一致);Go 侧 ID 后缀全大写。
- package handler
- // CreateActivityReq 端点 23 新建活动请求体(`POST /admin/activities`)。
- //
- // 字段 → 目标列(DDL 第 7 表):
- //
- // type→type / campusId→campus_id / title→title / content→content / memo→memo /
- // thumbnail→thumbnail / money→money / startTime→start_time / endTime→end_time /
- // numberMax→number_max / status→status / toped→toped / subjectId→subject_id / gradeId→grade_id
- //
- // **不接受的字段**(写进来也无效,故不声明):
- //
- // numberSign 报名计数列(只能由报名/取消增减;契约行 22 注「活动更新时不可写」)
- // view 浏览量(详情页访问自增)
- // createTime 由 DB 默认写入(业务时间列,勿手改)
- // institutionId 由请求作用域决定(见下方说明)
- type CreateActivityReq struct {
- // Type 活动类型(必填;2 充值赠送 / 3 代金券 / 4 老带新;其它 → 2002)。
- Type int8 `json:"type" validate:"required"`
- // CampusID 校区(0/缺省 = 机构所有自营校区;>0 时校验归属本机构)。
- CampusID int64 `json:"campusId"`
- // InstitutionID 机构(可选:非超管传了但与本作用域不一致 → **1004**;超管可用它指定)。
- InstitutionID int64 `json:"institutionId"`
- // Title 活动标题(必填,契约行 23 的"新建"未列字段,此处按旧系统编辑页的必填项取 title)。
- Title string `json:"title" validate:"required,max=500"`
- // Content 活动内容(HTML;可空)。
- Content string `json:"content"`
- // Memo 备注(可空)。
- Memo string `json:"memo" validate:"max=500"`
- // Thumbnail 缩略图 URL(可空)。
- Thumbnail string `json:"thumbnail" validate:"max=500"`
- // Money 活动金额/额度(字符串;可空 → 0.00;**≤0 不拒**,§3.8-Z1)。
- Money string `json:"money"`
- // StartTime 开始时间(RFC3339 或 `2006-01-02`;可空)。
- StartTime string `json:"startTime"`
- // EndTime 结束时间(同上;给了 StartTime 时不得早于它 → 否则 2002)。
- EndTime string `json:"endTime"`
- // NumberMax 名额上限(0 = 不限;负数 → 2002)。
- NumberMax int32 `json:"numberMax"`
- // Status 活动状态(0 停用 / 1 开启 / 2 已结束;缺省 0 = DDL 默认)。
- Status int8 `json:"status"`
- // Toped 置顶(0/1)。
- Toped int8 `json:"toped"`
- // SubjectID 科目(0 = 混合)。
- SubjectID int32 `json:"subjectId"`
- // GradeID 年级(0 = 混合)。
- GradeID int32 `json:"gradeId"`
- }
- // UpdateActivityReq 端点 25 更新活动请求体(`PUT /admin/activities/:id`,**整体替换**语义)。
- //
- // 与 `CreateActivityReq` 的差异只有两处:
- // - `NumberSign *int32`:**只要出现即 2002**(契约 §3.4 B 行 25,v1.6)。用指针是为了区分
- // "没传"与"传了 0"——`{"numberSign":0}` 同样是调用方试图写计数列,必须拒;
- // - 字段必填性更松:`title` 仍必填(整体替换后不能没有标题),其余同新建。
- type UpdateActivityReq struct {
- // Type 活动类型(2/3/4)。
- Type int8 `json:"type" validate:"required"`
- // CampusID 校区(0 = 机构所有自营校区)。
- CampusID int64 `json:"campusId"`
- // InstitutionID 机构(非超管须与本作用域一致)。
- InstitutionID int64 `json:"institutionId"`
- // Title 活动标题(必填)。
- Title string `json:"title" validate:"required,max=500"`
- // Content 活动内容(可空;PUT 语义下未传即清空)。
- Content string `json:"content"`
- // Memo 备注。
- Memo string `json:"memo" validate:"max=500"`
- // Thumbnail 缩略图。
- Thumbnail string `json:"thumbnail" validate:"max=500"`
- // Money 活动金额(≤0 允许)。
- Money string `json:"money"`
- // StartTime 开始时间。
- StartTime string `json:"startTime"`
- // EndTime 结束时间。
- EndTime string `json:"endTime"`
- // NumberMax 名额上限。
- NumberMax int32 `json:"numberMax"`
- // Status 活动状态。
- Status int8 `json:"status"`
- // Toped 置顶。
- Toped int8 `json:"toped"`
- // SubjectID 科目。
- SubjectID int32 `json:"subjectId"`
- // GradeID 年级。
- GradeID int32 `json:"gradeId"`
- // NumberSign **禁写列**:非 nil 即 2002("传了"的判据,见类型注释)。
- NumberSign *int32 `json:"numberSign"`
- }
- // SignActivityReq 端点 26 报名请求体(`POST /admin/activities/:id/sign`)。
- //
- // 对应 SP19 `sp_courses_activity_signed_add(uid, activityid, status, recommendUid, createUid)`:
- // `activityid` 取路径、`createUid` 取作用域,故 body 只有前三个。
- type SignActivityReq struct {
- // UserID 报名用户(必填;ULID)。
- UserID string `json:"userId" validate:"required,max=26"`
- // Status 报名状态(可空:<1 → 1,dump 762924-762926 同口径)。
- Status int32 `json:"status"`
- // RecommendUserID 推荐人(可空;老带新活动才有意义)。
- RecommendUserID string `json:"recommendUserId" validate:"max=26"`
- }
- // CancelActivitySignReq 端点 27 取消报名请求体(`POST /admin/activities/:id/cancel-sign`)。
- //
- // 对应 SP20 `sp_courses_activity_signed_cancel(uid, activityid)`:`activityid` 取路径。
- type CancelActivitySignReq struct {
- // UserID 报名用户(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- }
- // SettleRecommendReq 端点 29 老带新结算请求体(`POST /admin/activities/:id/recommend-settle`)。
- //
- // 对应 SP18 `sp_courses_activity_use_activity_recommend(uid, order, remark, createUid)`;
- // `createUid` 取作用域。
- type SettleRecommendReq struct {
- // UserID 被推荐人(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- // OrderNo 报课记录 ID 的字符串形式(必填:活动上机构/校区为 0 时靠它回查)。
- OrderNo string `json:"orderNo" validate:"required,max=100"`
- // Remark 备注(可空)。
- Remark string `json:"remark" validate:"max=500"`
- }
- // CancelRechargeReq 端点 30 取消充值活动请求体(`POST /admin/activities/:id/cancel-recharge`)。
- //
- // 对应 SP8 `sp_user_expense_cancel_recharge_activity(uid, activityid, campusid, money, requestid, remark, createUid)`;
- // `activityid` 取路径、`createUid` 取作用域。
- type CancelRechargeReq struct {
- // UserID 用户(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- // CampusID 校区(必填 >0:资金落库必须有明确校区;校区不存在 → 3104)。
- CampusID int64 `json:"campusId" validate:"required,gt=0"`
- // Money 取消金额(必填字符串)。
- Money string `json:"money" validate:"required"`
- // RequestID 幂等请求号(可空 = 不享幂等)。
- RequestID string `json:"requestId" validate:"max=64"`
- // Remark 备注(可空)。
- Remark string `json:"remark" validate:"max=500"`
- }
- // UseActivityDiscountReq 端点 32 使用赠送请求体(`POST /admin/activities/discounts/use`)。
- //
- // 对应 SP15 `sp_courses_activity_user_discount_use(uid, activityid, stuCourseId, amount, memo)`。
- type UseActivityDiscountReq struct {
- // UserID 学生(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- // ActivityID 活动(必填 >0:**单活动**取池,不得跨活动扣减)。
- ActivityID int64 `json:"activityId" validate:"required,gt=0"`
- // CoursesStudentID 报课记录(→ discount_log.courses_student_id)。
- CoursesStudentID int64 `json:"coursesStudentId"`
- // Amount 使用金额(字符串;<=0 → no-op,§3.8-Z1)。
- Amount string `json:"amount"`
- // Memo 备注(可空)。
- Memo string `json:"memo" validate:"max=500"`
- }
- // RefundActivityDiscountReq 端点 33 赠送退回请求体(`POST /admin/activities/discounts/refund`)。
- //
- // 对应 SP16 `sp_courses_activity_user_discount_refund(stuCourseId, amount, memo)`。
- type RefundActivityDiscountReq struct {
- // CoursesStudentID 报课记录(必填 >0:退回对象按流水 `courses_student_id` 定位)。
- CoursesStudentID int64 `json:"coursesStudentId" validate:"required,gt=0"`
- // Amount 应退金额(字符串;<=0 → no-op)。
- Amount string `json:"amount"`
- // Memo 备注(可空 → "退回赠送金额")。
- Memo string `json:"memo" validate:"max=500"`
- }
- // RefundDiscountByActivityReq 端点 34 按活动退回请求体
- // (`POST /admin/activities/discounts/refund-by-activity`)。
- //
- // 对应 SP17 `sp_courses_activity_user_discount_refund_user_activity(uid, activityid, amount, memo)`。
- type RefundDiscountByActivityReq struct {
- // UserID 学生(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- // ActivityID 活动(必填 >0)。
- ActivityID int64 `json:"activityId" validate:"required,gt=0"`
- // Amount 应退金额(字符串;type=3 代金券会被服务端按 A19 改为"最近一条 log 的 ABS")。
- Amount string `json:"amount"`
- // Memo 备注(可空 → "退回活动金额")。
- Memo string `json:"memo" validate:"max=500"`
- }
- // UseRechargeGiftReq 端点 35 充值赠送使用请求体(`POST /admin/activities/recharge-gifts/use`)。
- //
- // 对应 SP12 `sp_courses_activity_use_activity_recharge(uid, activityid, stuCourseId, amount, memo)`。
- type UseRechargeGiftReq struct {
- // UserID 学生(必填)。
- UserID string `json:"userId" validate:"required,max=26"`
- // ActivityID 活动(必填 >0)。
- ActivityID int64 `json:"activityId" validate:"required,gt=0"`
- // CoursesStudentID 报课记录(→ used_log.order_no)。
- CoursesStudentID int64 `json:"coursesStudentId"`
- // Amount 使用金额(字符串;<=0 → no-op)。
- Amount string `json:"amount"`
- // Memo 备注(可空 → "使用充值赠送金额")。
- Memo string `json:"memo" validate:"max=500"`
- }
- // RefundRechargeGiftReq 端点 36 充值赠送退回请求体(`POST /admin/activities/recharge-gifts/refund`)。
- //
- // 对应 SP13 `sp_courses_activity_refund_activity_recharge(stuCourseId, amount, memo)`。
- type RefundRechargeGiftReq struct {
- // CoursesStudentID 报课记录(必填 >0)。
- CoursesStudentID int64 `json:"coursesStudentId" validate:"required,gt=0"`
- // Amount 应退金额(字符串;<=0 → no-op)。
- Amount string `json:"amount"`
- // Memo 备注(可空 → "退回活动金额")。
- Memo string `json:"memo" validate:"max=500"`
- }
- // CreateVisitReq 端点 37 回访登记请求体(`POST /admin/activities/:id/visits`)。
- //
- // 契约 §2.2 行 37 写作 `activitySignedId,memo`(**两个都无问号 ⇒ 必填**)。
- type CreateVisitReq struct {
- // ActivitySignedID 报名行(必填 >0;不存在/跨作用域 → 3001)。
- ActivitySignedID int64 `json:"activitySignedId" validate:"required,gt=0"`
- // Memo 回访内容(必填)。
- Memo string `json:"memo" validate:"required,max=500"`
- }
- // ---------------------------------------------------------------- 响应拼装(只放契约要求的键)
- // activitySignResp 端点 26 响应(契约 §3.4 B 行 26:只要求 `signedId`)。
- type activitySignResp struct {
- // SignedID 报名行 ID。
- SignedID int64 `json:"signedId"`
- }
- // activityCancelledResp 端点 27 / 30 响应(契约 §3.4 B 行 27、30:只要求 `cancelled`)。
- type activityCancelledResp struct {
- // Cancelled 是否已取消(成功恒 true)。
- Cancelled bool `json:"cancelled"`
- }
- // activitySettledResp 端点 29 响应(契约 §3.4 B 行 29:`settled` + `activityId`)。
- type activitySettledResp struct {
- // Settled 本次是否真正结算(false = dump 的 LEAVE 分支:无推荐人/金额≤0/校区回查不到,零写入)。
- Settled bool `json:"settled"`
- // ActivityID 结算的活动 ID。
- ActivityID int64 `json:"activityId"`
- }
- // activityUsedResp 端点 32 / 35 响应(契约 §3.4 B 行 32、35:`used`)。
- type activityUsedResp struct {
- // Used 是否真的发生了使用(金额 ≤0 时服务端 no-op ⇒ false)。
- Used bool `json:"used"`
- }
- // activityRefundedResp 端点 33 / 34 / 36 响应(契约 §3.4 B 行 33、34、36:`refunded`)。
- type activityRefundedResp struct {
- // Refunded 是否真的发生了退回(金额 ≤0 或无可用行 ⇒ false)。
- Refunded bool `json:"refunded"`
- }
|