24 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8231 | 配导游 / 配摄影 / 物资取消阶段门——出行前四态均可随时配置;取消成团改为放行并回收导摄配置 | admin | jw(GIT) | 修改接口 | deployed | not_required | verified | mmg | e15dd73bd36a1d27374f9206797243f8a5e4326c | 2026-09-23 | jw 2026-09-23 裁决:配导游 / 配摄影 / 物资三项取消节点限制,只要在出行节点之前都可以随时配置。改前配导游 / 配摄影被 assertFormed 拦在招募中(589552),物资被 assertInMaterialStage 限死在 MATERIAL_PREPARING(589520)——而团期要集齐四项 ready(房 / 车 / 导 / 摄)才推得进 MATERIAL_PREPARING,实际「物资配不了」的根因多半是房车没配齐。本次三处写口统一引用新常量 PRE_DEPARTURE_CONFIGURABLE_STATUSES(RECRUITING / RESOURCE_PREPARING / MATERIAL_PREPARING / PENDING_DEPARTURE),出行后与已取消一律拒成新错误码 589598,未建团仍 589553。连带改的是取消成团:放开招募中可配之后,若导摄仍算「已派单资源」,招募中配过人的团一成团就再也取消不了成团(开工前实测:TEST 团期 2099750660965584898 在房车 ready 全 0、已确认子订单 0 的情况下,仅凭导游一项就返 589502)。故按方案 C,导摄两项移出 R10 判定,取消成团改为放行并在同事务内回收导摄配置(order_batch_staff 整期软删 + 各子订单 order_staff_assignment 中 source=GROUP_BATCH 的扇出行软删),房 / 车两项仍照旧阻断。取证阶段另修一处本单引入的回归:招募中清空导摄配置后 ready 位回不到 false(免闸豁免只对已成团成立,needs_* 招募中恒 0),已随 PR #8237 修复。TEST 实测 2026-09-23 12:15~12:25:物资三端点在四态各放行、在 REVIEWING / TRIP_FINISHED / CANCELLED 各返 589598 且零写入;配导摄在四态各落库成功、在三个出行后态返 589598 且零写入;取消成团对仅导摄 dispatched 的样本返 200 并完成回收,对 hotel_ready=1 / vehicle_ready=1 两个样本仍返 589502 且零写入;连续两次取消成团第二次返 589501、不重复回收。前端侧:配导游 / 配摄影按钮本来就不看 batchStatus,零改动;物资 tab 的操作列在 SuppliesPanel.vue 里按 MATERIAL_PREPARING 前置隐藏,后端放开后前端不改则本单无实际效果,交接件见同批 frontend 条目,故 frontend_status 记 pending。 | 2026-09-23 mmg 交付:SuppliesPanel 拆双判据四态放行三写口,配导摄零改动实证,随配套 frontend 条目同 commit | 2026-09-23 | dev-v3 |
团期资源配置: 配导游 / 配摄影 / 物资放开到出行前四态(管理后台)
服务: hl-order-service-v3(端口 8086/8186) PR: #8234、#8237 Issue: #8231 日期: 2026-09-23 影响范围: 管理后台团期详情页的「配导游 / 配摄影」弹窗、「物资」tab、「取消成团」按钮
⚠️ 关键变化
本次是行为变更,不是纯增字段,三条都与调用方此前的预期不同:
- 可配窗口扩大。以前「招募中配不了导摄」「物资只有到了准备物资阶段才配得了」,现在出行前四态都能配。
- 拒绝时的错误码换了。以前物资拒是
589520、导摄拒是589552,现在统一是新码589598。按 589520 / 589552 做文案分支的前端要改。 - 取消成团的阻塞条件收窄了。以前「已派导游 / 已派摄影」会把取消成团拦住(589502),现在不会;作为代价,取消成团成功后会把该团期的导摄配置一并回收,配人列表会变空。
一、背景
GroupBatchStatus 状态机里,「出行之前」= 前四态:
| 允许配置 | 拒绝 |
|---|---|
RECRUITING 招募中 · RESOURCE_PREPARING 资源准备中 · MATERIAL_PREPARING 物料准备中 · PENDING_DEPARTURE 待出发 |
TRAVELLING 出行中 · TRIP_FINISHED 出行完毕 · REVIEWING 核单中 · SETTLED 已结算 · CANCELLED 已取消 |
CANCELLED 按拒绝处理:它是终态取消,不属于「出行前」语义。
物资的实际约束比表面更紧:团期要先走到 MATERIAL_PREPARING 才开窗,而
RESOURCE_PREPARING → MATERIAL_PREPARING 的推进条件是四项 ready 齐(房 / 车 / 导 / 摄)+ 合同 / 保险。
即今天「物资配不了」的根因多数是房车没配齐。本次放开后,物资配置不再依赖这条链。
状态机推进逻辑本身不动:MATERIAL_PREPARING 这个阶段、以及「四项 ready 齐才推进」那条链一律不变。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 保存团期人员配置 | PUT | /v3/admin/group-batch/{productBatchId}/staff |
修改接口 | 阶段门从「已成团」放宽到出行前四态;拒绝码 589552 → 589598 |
| 2 | 设置报账人等级 | PUT | /v3/admin/group-batch/{productBatchId}/staff/{staffId}/reporter-rank |
修改接口 | 同上,与保存口同窗口 |
| 3 | 新增团期备品行 | POST | /v3/admin/order/group-batch/{groupBatchId}/supplies |
修改接口 | 阶段门从 MATERIAL_PREPARING 放宽到出行前四态;拒绝码 589520 → 589598 |
| 4 | 调整团期备品数量 | PUT | /v3/admin/order/group-batch/supplies/{batchSuppliesId}/quantity |
修改接口 | 同上 |
| 5 | 软删团期备品行 | DELETE | /v3/admin/order/group-batch/supplies/{batchSuppliesId} |
修改接口 | 同上 |
| 6 | 取消成团 | POST | /v3/admin/order/group-batch/{groupBatchId}/cancel-group |
修改接口 | 导摄两项不再阻断;成功时额外回收导摄配置 |
路径、入参结构、权限码、成功响应结构均零变化;网关无改动。
三、接口详情
1. 保存团期人员配置 PUT /v3/admin/group-batch/{productBatchId}/staff
VO: BatchStaffConfigReqVO → BatchStaffConfigRespVO
使用场景
团期详情页「配导游 / 配摄影」弹窗点保存。整期全量覆盖:按 scopeRoles(不传即全量)软删旧配置、
写入新配置、异步扇出到各活跃子订单,并按配置结果回填 guide_ready / photographer_ready。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| productBatchId | path | Long | 是 | 正整数,产品侧排期 ID | 团期所属班期;未建团返 589553 |
| scopeRoles | body | List<String> | 否 | 元素非空 | 限定本次覆盖的角色范围,不传则整期覆盖 |
| staffList | body | List<Item> | 否 | null 按空列表处理 | 传空列表 = 清空覆盖范围内的配置 |
| staffList[].staffId | body | Long | 是 | 须命中候选人员 | 人员 ID |
| staffList[].staffRole | body | String | 是 | 须与人员类型相符,否则 582114 | 角色(GUIDE / LEADER / PHOTOGRAPHER …) |
| staffList[].sortOrder | body | Integer | 否 | — | 展示排序 |
| staffList[].remark | body | String | 否 | — | 备注 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| productBatchId | Long | 回显路径参数 |
| groupBatchId | Long | 团期聚合主键,取守卫阶段那一次既有反查的结果,不额外查库 |
| staffList | List | 保存后的整期最终状态 |
| affectedOrderCount | Integer | 本次扇出触及的活跃子订单数 |
请求示例
PUT /v3/admin/group-batch/2097250420299530242/staff HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
Content-Type: application/json
{"staffList":[{"staffId":1002,"staffRole":"GUIDE","sortOrder":0},{"staffId":1003,"staffRole":"PHOTOGRAPHER","sortOrder":1}]}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"productBatchId": "2097250420299530242",
"groupBatchId": "2097250563497385985",
"affectedOrderCount": 0,
"staffList": [
{"staffId": 1002, "staffRole": "GUIDE", "staffName": "李雪梅", "reporterRank": "NONE"},
{"staffId": 1003, "staffRole": "PHOTOGRAPHER", "staffName": "王强", "reporterRank": "NONE"}
]
}
}
空数据 / 降级响应
staffList 传空列表即清空覆盖范围内的配置,返回 200,staffList 为空数组、affectedOrderCount 为实际扇出订单数。
清空后 ready 位的回落口径本次有修正:已成团且该位 needs_*=false(成团免闸置位)时保持就绪、不回落;
其余情形(含招募中)一律回落为 false。改前招募中清空后 ready 位会卡在 true,与配置行数 0 自相矛盾。
错误响应
{"code": 589598, "message": "出行后不可再配置导游 / 摄影 / 物资", "data": null}
未建团(该班期还没有任何订单,团期行尚未 Lazy 建)仍是另一个码,两者不可合并:
{"code": 589553, "message": "团期尚未创建(该班期还没有任何订单),请先建团并完成成团后再操作", "data": null}
业务边界
- 出行前四态(
RECRUITING/RESOURCE_PREPARING/MATERIAL_PREPARING/PENDING_DEPARTURE)放行 TRAVELLING/TRIP_FINISHED/REVIEWING/SETTLED/CANCELLED返 589598,被拒时一行都不写(闸在写库之前)- 未建团返 589553,且不会为配置动作提前建团期行
- 589552「团期尚未成团」不再由本接口抛出,但该码仍被别处(车辆派单、房务)使用,未退役
2. 设置报账人等级 PUT /v3/admin/group-batch/{productBatchId}/staff/{staffId}/reporter-rank
VO: ReporterRank(枚举入参)
使用场景
团期人员名单里把某个已配人员标为主报账人 / 协助报账人。它与保存口是同构写口——同样写
order_batch_staff 并扇出 order_staff_assignment,故口径必须与保存口完全一致。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| productBatchId | path | Long | 是 | 正整数 | 产品侧排期 ID |
| staffId | path | Long | 是 | 须命中该团期已配人员 | 人员 ID |
| rank | body | String | 是 | PRIMARY / ASSIST / NONE | 报账人等级 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Void | 成功返回 null,判成败看 code |
请求示例
PUT /v3/admin/group-batch/2097250420299530242/staff/1002/reporter-rank HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
Content-Type: application/json
{"rank":"PRIMARY"}
响应示例
{"code": 200, "message": "成功", "data": null}
空数据 / 降级响应
本接口无列表出参,无空数据形态;团期已配人员为空时会先因 staffId 未命中报错,不会静默成功。
错误响应
{"code": 589598, "message": "出行后不可再配置导游 / 摄影 / 物资", "data": null}
业务边界
- 窗口与保存口完全一致,不允许两个方法口径漂移
- 被拒时连「查已派 staff」这一步读都不发生,零写入
- 未建团仍返 589553
3. 新增团期备品行 POST /v3/admin/order/group-batch/{groupBatchId}/supplies
VO: AddSuppliesReqVO
使用场景
团期详情页「物资」tab 手工录入一行备品(产品侧没有、临时采购的)。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 不存在返 GROUP_BATCH_NOT_FOUND |
| suppliesName | body | String | 否 | 传 suppliesResourceId 时可空 | 备品名称;纯手填时必填 |
| suppliesResourceId | body | Long | 否 | — | 备品库资源 ID,传了则名称取库值 |
| category | body | String | 否 | — | 分类 |
| hasCost | body | Boolean | 否 | — | 是否计费 |
| billingType | body | String | 否 | PER_PERSON / PER_QUANTITY | 计费方式 |
| unitPrice | body | BigDecimal | 否 | — | 单价 |
| quantity | body | Integer | 是 | ≥ 1,否则 589522 | 数量 |
| sortOrder | body | Integer | 否 | 默认 0 | 展示排序 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Long | 新建备品行 ID(batchSuppliesId),用于后续改数量 / 软删 |
请求示例
POST /v3/admin/order/group-batch/2099750365778886657/supplies HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
Content-Type: application/json
{"suppliesName":"临时采购的雨衣","quantity":1,"sortOrder":999}
响应示例
{"code": 200, "message": "成功", "data": "2102611411027066881"}
空数据 / 降级响应
本接口是写接口,无空数据形态。创单时由系统固化产品侧备品的 freezeFromProduct 路径不过本阶段门——
它是首单懒建事务内的系统行为,加门会把建团直接打断,该豁免本次不变。
错误响应
{"code": 589598, "message": "出行后不可再配置导游 / 摄影 / 物资", "data": null}
业务边界
- 出行前四态放行,尤其
RESOURCE_PREPARING——改前该态必被 589520 拒 - 出行后与
CANCELLED返 589598,不落库 - 团期不存在返
GROUP_BATCH_NOT_FOUND,不是阶段错误 - 数量非法返 589522 参数错误,不是状态错误
4. 调整团期备品数量 PUT /v3/admin/order/group-batch/supplies/{batchSuppliesId}/quantity
VO: AdjustSuppliesQuantityReqVO
使用场景
物资 tab 行内编辑数量。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| batchSuppliesId | path | Long | 是 | 须命中活跃备品行 | 不存在返 589521 |
| quantity | body | Integer | 是 | ≥ 1,否则 589522 | 新数量 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Void | 成功返回 null |
请求示例
PUT /v3/admin/order/group-batch/supplies/2102611411027066881/quantity HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
Content-Type: application/json
{"quantity":5}
响应示例
{"code": 200, "message": "成功", "data": null}
空数据 / 降级响应
无列表出参,无空数据形态。备品行已被软删时返 589521,不会静默成功。
错误响应
{"code": 589598, "message": "出行后不可再配置导游 / 摄影 / 物资", "data": null}
业务边界
- 阶段门由该备品行反查所属团期后判定,窗口与新增口一致
- 「确认物资之后能不能改」不归本门管:确认后仍可增删改是有意为之,窗口扩大后该结论不变
- 被拒时数量不变、行不被软删
5. 软删团期备品行 DELETE /v3/admin/order/group-batch/supplies/{batchSuppliesId}
VO: Result<Void>(无请求体)
使用场景
物资 tab 删除一行备品。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| batchSuppliesId | path | Long | 是 | 须命中活跃备品行 | 不存在返 589521 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Void | 成功返回 null |
请求示例
DELETE /v3/admin/order/group-batch/supplies/2102611411027066881 HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
响应示例
{"code": 200, "message": "成功", "data": null}
空数据 / 降级响应
软删是幂等的:对已软删的行再调返 589521「备品行不存在」,不会重复写。
错误响应
{"code": 589598, "message": "出行后不可再配置导游 / 摄影 / 物资", "data": null}
业务边界
- 窗口与新增 / 改数量完全一致
- 被拒时行的
deleted_at保持为空
6. 取消成团 POST /v3/admin/order/group-batch/{groupBatchId}/cancel-group
VO: Result<Void>(无请求体)
使用场景
团期从「资源准备中」退回「招募中」。本次没有改它的入参或路径,改的是它的阻塞条件与成功时的副作用。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| groupBatchId | path | Long | 是 | 正整数,团期聚合主键 | 不存在返 GROUP_BATCH_NOT_FOUND;非 RESOURCE_PREPARING 返 589501 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
| data | Void | 成功返回 null,团期状态已回到 RECRUITING |
请求示例
POST /v3/admin/order/group-batch/2099750660965584898/cancel-group HTTP/1.1
Host: api.test.1814.love:9443
Authorization: Bearer <admin token>
响应示例
{"code": 200, "message": "成功", "data": null}
空数据 / 降级响应
成功时无返回体。该团期没有任何导摄配置时,回收步骤是零行软删的空操作,不影响成功。
错误响应
{"code": 589502, "message": "取消成团被阻塞(有已确认子订单或已派单资源)", "data": null}
连续两次调用,第二次因状态已不是 RESOURCE_PREPARING 被 CAS 拒:
{"code": 589501, "message": "团期状态不允许当前操作", "data": null}
业务边界
- 仍阻塞:有已确认子订单(
PENDING_DEPARTURE/TRAVELLING/COMPLETED)、hotel_ready=true、vehicle_ready=true(且非整团免车) - 不再阻塞:已派导游、已派摄影
- 成功时依次:状态 CAS 回
RECRUITING→ 四项 ready 与物资确认全部重置 → 回收导摄配置 → 登记配车释放命令 - 回收范围只含
source=GROUP_BATCH的扇出行,子订单自己单独派的人不受影响 - 幂等:第二次调用被 589501 拒,不重复回收
四、契约约束与正确调用方式
- 判拒绝原因不要再认 589520 / 589552。这两个码不再由上述五个配置接口抛出(589520 保留占位防号段复用,589552 仍由车辆派单、房务等别处使用)。统一认 589598。
- 589598 与 589553 是两件事,不要合并成一个 toast:前者是「这个团已经出发了,配不了了」,后者是「这个班期还没有任何订单,先去建团」。
- 配导摄成功后,若调用方缓存了
guide_ready/photographer_ready,需重新拉取团期详情——本次修正了清空时的回落口径。 - 取消成功后,团期人员名单会变空,调用方若停在配人弹窗需主动刷新。
五、数据库行为
- 配导摄保存:整期(或
scopeRoles范围内)软删旧配置行 + 写入新配置行 + 异步扇出到各活跃子订单;随后按配置结果回填团期行的两个 ready 标志 - 物资三口:分别为插入一行、更新一行的数量、软删一行
- 取消成团:状态 CAS 一行 + 重置该团期四项 ready 与物资确认 + 软删该团期全部导摄配置行 + 软删其各子订单中来源为团期扇出的人员分配行,全部在同一个事务内,任一步失败整体回滚
- 被阶段门拒绝时零写入:闸在所有写库动作之前
六、边界行为
CANCELLED(已流团)按拒绝处理,返 589598- 未建团(团期行尚未 Lazy 建)返 589553,不会为配置动作提前建行——否则会出现「零单可配 → 首单落地反而不可配 → 成团后又可配」这种非单调行为
- 创单时固化产品侧备品的系统路径不过阶段门,该豁免不变
- 「确认物资之后仍可增删改」不变
六.6、修改前后对比
| 维度 | 改前 | 改后 |
|---|---|---|
| 配导摄可配状态 | 已成团各态(招募中被拒) | RECRUITING / RESOURCE_PREPARING / MATERIAL_PREPARING / PENDING_DEPARTURE |
| 配导摄拒绝码 | 589552(未成团)/ 589553(未建团) | 589598(已出行 / 已取消)/ 589553(未建团) |
| 物资三口可配状态 | 仅 MATERIAL_PREPARING |
同上四态 |
| 物资三口拒绝码 | 589520 | 589598 |
| 取消成团:已派导游 / 摄影 | 返 589502 被阻塞 | 放行,且回收导摄配置 |
| 取消成团:房 / 车 ready | 返 589502 被阻塞 | 不变,仍返 589502 |
| 招募中清空导摄后的 ready 位 | 卡在 true(与配置行数 0 矛盾) | 回落为 false |
六.7、影响评估
| 面 | 评估 |
|---|---|
| 管理后台配导摄 | 按钮本来就不看 batchStatus,招募中点得下去、只是被后端拒。后端放开后前端零改动即可生效 |
| 管理后台物资 tab | 操作列按 MATERIAL_PREPARING 前置隐藏,前端不改则本次无实际效果,交接件见同批 frontend 条目 |
| 已有错误文案分支 | 按 589520 / 589552 做文案的地方需改认 589598 |
| 取消成团 | 可取消的团期变多;成功后人员名单会被清空,属预期 |
| 小程序端 | 不受影响,物资清单在小程序端是只读视图 |
| 状态机 | 不变,MATERIAL_PREPARING 阶段与「四项 ready 齐才推进」那条链一律不动 |
七、不影响范围
- 团期状态机的推进逻辑、四项 ready 的计算口径
- 流团(
disband):只校验状态白名单,不看 ready 位,本次完全不受影响 - 子订单自己单独派的人员(
order_staff_assignment中 source 非GROUP_BATCH的行):回收不碰它们 - 车辆派单、房务等仍在用
assertFormed的写口:口径不变 - 权限码、路径、入参结构、成功响应结构、网关配置
八、测试环境已验证
2026-09-23 12:15~12:25,api.test.1814.love:9443,分支 dev-v3(fe752f16c)。
| AC | 内容 | 结果 |
|---|---|---|
| 物资四态放行 | RECRUITING / RESOURCE_PREPARING / MATERIAL_PREPARING / PENDING_DEPARTURE 各跑「增 → 改数量 → 删」三端点 |
12 次调用全 200,自造行自删、零残留 |
| 物资出行后拒 | REVIEWING / TRIP_FINISHED / CANCELLED 各跑三端点 |
全返 589598;既有行的 quantity 未变、未软删、未新增行 |
| 配导摄四态放行 | 四态各保存「1002 GUIDE + 1003 PHOTOGRAPHER」 | 全 200 且落库;逐个还原为改前配置,四组还原判定全 ✔ |
| 配导摄出行后拒 | REVIEWING / TRIP_FINISHED / CANCELLED |
全返 589598;活跃行与表内总行数逐字未变 |
| 未建团 | 不存在的 productBatchId |
返 589553,未被新码吞掉 |
| 取消成团放行 + 回收 | 团期 2099750660965584898(needs_guide=1 & guide_ready=1,房车 ready 全 0,已确认子订单 0)——同一样本开工前实测返 589502 |
返 200;状态 RESOURCE_PREPARING → RECRUITING;guide_ready / photographer_ready 归 0;配置行与子订单扇出行同时间戳软删 |
| 房车仍阻断 | hotel_ready=1 与 vehicle_ready=1 两个样本 |
均返 589502;团期行与两张配置表逐字未变 |
| 幂等 | 对同一团期连调两次取消成团 | 第二次返 589501;三处读数与第一次后完全一致,无重复回收 |
本地单测:assignment + groupbatch + house 4103 条、archunit / core / requirement 等 3456 条、其余 26 个包 4621 条,全绿。
取证用到的写操作及还原:新增的备品行均由本次自行软删;配导摄的四个团期均按改前配置逐条还原;
取消成团样本 2099750660965584898 已按改前值还原(状态、两个 ready 位、配置行与扇出行的软删标记)。
十、相关文档
- Issue #8231(含开工前的 AC-6 实测与方案 C 裁决)
- PR #8234(主体)、PR #8237(ready 回落回归修复)
- 被推翻:#7287 T5(
assertFormed)、#7023 AC-TD-13(物资单一阶段窗口)、wx 2026-09-15「放行但配置行保留」 - 保持不变:#7023 AC-TD-15(确认后仍可改)、#7441 PR-2e(整团免车的 ready 剥离)
关联 / 联系人
- 后端:jw
- 前端:mmg(物资 tab 显隐条件,见同批 frontend 条目)