节点确认与稿件上传优化 PRD

状态:PRD(产品决策已确认,见 §17);后端已于 2026-09-18 实现,见 §19 实现记录 范围:pipipen-api(后端);同步影响 pipipen-front(只读,仅出接口约定) 关联接口变更记录:docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md 发布配置契约变更(2026-09-29):发布接口改用显式 stage_confirm_mode,见 §9.2;接口变更记录已并入 docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md 原型依据:Milestone Confirmation Flow 交互原型(画师上传 → 关联节点 → 请求确认 → 确认/退回修改 → 超时自动确认)


1. 背景

业务希望把「commission(worktask)的节点推进」从当前「客户端手动点确认」的单一动作, 升级为一套更完整、可自动化的确认流程:

  1. 画师上传稿件时显式关联到某个节点,并可选择「请求确认」。
  2. 节点新增 待确认(awaiting confirmation) 状态。
  3. 客户端长时间未确认的节点,到达时限后自动确认。
  4. 自动确认的时限,在 service / Open Call(project)发布时由发布方设定。

原型 index-jsfiddle-w6Lbrzjx-2.html 完整描述了这套交互(上传→关联节点→请求确认→确认/退回修改→倒计时自动确认),本 PRD 将其落成后端需求。


2. 现状调研(代码证据)

本节的代码证据描述的是改造前的现状,保持历史原样,不回改。

2.1 领域模型现状

概念模型 / 表关键字段说明
commissionWorkTask / work_tasksstatus(pending/working/finished…)、busable_type(service|project)、reqable_type(service_request|project_request)、paid_amount、priceOpen Call 与 service 的订单都落成 worktask
节点WorkTaskStage / work_task_stageswork_status(pending/working/finished)、is_paid、percent、amount、name节点 = 分期里程碑
稿件WorkTaskFile / work_task_fileswork_task_id、work_task_stage_id、artist_id、user_id、user_is_read已有关联节点字段
稿件附件UploadFile(morph uploadFiles)url_og、state、mime、size一个稿件可挂多个文件
修改意见WorkTaskFileChangeRequest / work_task_file_change_requestswork_task_file_id、content(translatable)、morph uploadImages按「单个稿件」给修改意见

证据文件:

  • app/Models/WorkTask.php、app/Models/WorkTaskStage.php
  • app/Models/WorkTaskFile.php、app/Models/WorkTaskFileChangeRequest.php
  • database/migrations/2025_06_24_080719_create_work_tasks_table.php 等

2.2 节点状态现状

app/Enums/WorkTaskStageWorkStatus.php 只有三态:

enum WorkTaskStageWorkStatus: string
{
    case Pending = 'pending';   // 未开始
    case Working = 'working';    // 进行中
    case Finished = 'finished'; // 已完成
}

work_task_stages.work_status 数据库列也是 enum('pending','working','finished')。 WorkTaskStage 仅提供 markAsFinished() / markAsWorking() 两个状态迁移方法。

2.3 现有上传流程

画师上传稿件是两步:

  1. POST /api/user/upload_file(Api\User\UserController@uploadFile)——上传原始文件,得到 upload_files.id。
  2. POST /api/artist_center/work_task_files/create(Api\Artist\WorkTaskFileController@create)—— 传 upload_file_id + work_task_id + work_task_stage_id,创建 work_task_files 记录并 morph 挂上 UploadFile。

现状问题(与需求相关):

  • create 没有校验 work_task_stage_id 是否属于 work_task_id(可直接传入其它 commission 的节点)。
  • 上传不改变节点状态,也不存在「请求确认」语义;节点推进仍靠客户端手动确认。
  • 一次只挂一个 upload_file_id,多文件需前端循环调用。

2.4 现有节点确认流程

POST /api/work_tasks/confrim_stage_work_status (Api\User\WorkTaskController@confirmStageWorkStatus,路由见 routes/api/userApi.php):

  1. 加锁(Cache::lock)+ 事务 + lockForUpdate。
  2. 找到 is_paid=1 && work_status=working 的当前节点 → markAsFinished()。
  3. 下一个 is_paid=1 && work_status=pending 节点 → markAsWorking()。
  4. 全部节点 finished 且付清 → worktask finished,触发 WorkTaskCompletionService 结算打款给画师。
  5. 写 WorktaskPageEventService::eventConfirmStage / eventFinish,发通知。

关键:确认入口只认 working 节点,没有「待确认」概念;无任何定时自动确认。

2.5 现有修改(revision)流程

POST /api/work_task_file_change_requests/create (Api\User\WorkTaskFileChangeRequestController@create): 按「单个稿件」创建修改意见(content + uploadImages),通知画师,不改变节点状态、没有倒计时语义。

2.6 差距总结

需求点现状差距
节点增加「待确认」状态只有 pending/working/finished需扩展枚举 + 迁移
上传稿件关联节点work_task_stage_id 字段已存在缺校验、缺「请求确认」、缺一次多文件
长时间未确认自动确认无需新增 deadline + 定时任务
确认时间在发布时设置无需在 service/project 增加配置字段并快照
修改退回后暂停确认倒计时无需在节点状态机中体现

3. 设计目标

  1. 节点状态机:在 pending → working → finished 基础上插入 awaiting_confirmation(待确认)与 revision(需修改)。
  2. 上传即确认请求:画师上传稿件时选择节点、可选「请求确认」,触发节点进入待确认并开始倒计时。
  3. 自动确认:到达确认时限未确认的节点,由定时任务自动确认,复用与手动确认一致的结算逻辑。
  4. 时限可配置:service / Open Call 发布时设置确认时限(小时),commission 创建时快照,避免发布后修改影响已存在订单。
  5. 最终节点永不自动确认:与原型一致(Final Delivery always requires manual confirmation),最终节点只能手动确认。

4. 非目标

  • 不改变现有的支付/分期扣款逻辑(full pay / stage pay 与 is_paid 的推进保持不变)。
  • 不改变退款、取消(commission cancellation / refund)逻辑。
  • 不引入「节点级」的审批/加签等更复杂的工作流。
  • 画师不能主动撤回「请求确认」:节点进入 awaiting_confirmation 后不能退回 working(见 §17 决策 5)。
  • 本次不重构前端交互实现(pipipen-front 只读),只约定后端接口与数据。

5. 领域模型变更

5.1 枚举扩展:WorkTaskStageWorkStatus

enum WorkTaskStageWorkStatus: string
{
    case Pending = 'pending';                 // 未开始
    case Working = 'working';                 // 进行中(画师创作中)
    case AwaitingConfirmation = 'awaiting_confirmation'; // 待确认(画师已提交并请求确认)
    case Revision = 'revision';               // 需修改(客户退回,暂停确认倒计时)
    case Finished = 'finished';               // 已完成
}

命名沿用现有 pending/working/finished 小写下划线风格;对外展示文案「待确认 / 需修改」。

5.2 数据库变更(迁移)

work_task_stages 新增列:

列类型说明
confirm_deadline_attimestamp null进入待确认时的自动确认截止时间;revision/finished/working 下为 null;0/最终节点也为 null
confirmed_attimestamp null节点被确认(手动/自动)的时间
confirm_sourceenum('manual','auto') null确认来源,用于审计与展示
confirm_reminder_sent_attimestamp null到期前提醒已发送时间;幂等,避免重复提醒

同时把 work_status 枚举扩展为 ['pending','working','awaiting_confirmation','revision','finished']。

全局默认时限(管理后台设置): 不改表结构,复用 SystemSetting(key worktask.stage_confirm)+ SystemSettingChangeLog 审计:

value 字段类型默认说明
versionint0乐观锁版本号(expected_version 比对)
default_hoursint48全局默认自动确认时限(小时)
reminder_hoursint24到期前提前提醒的提前量(小时)
updated_at_utcstringnull最近更新时间

services / projects 新增列:

列类型说明
stage_confirm_hoursinteger null节点自动确认时限(小时)。null 表示用全局默认值

work_tasks 新增列(快照,保证既有 commission 不受发布方后续修改影响):

列类型说明
stage_confirm_hoursinteger null创建 commission 时从 service/project 快照的确认时限(小时)

快照时机:ServiceRequestController@accept、ProjectRequestController 创建 worktask 时写入。 默认值来源:service/project 的 stage_confirm_hours 为 null 时回退到全局默认(SystemSetting 的 default_hours,默认 48);0 表示不自动确认,也照常快照。

5.3 模型方法扩展(WorkTaskStage)

public function markAsAwaitingConfirmation(?Carbon $deadlineAt = null): void;
public function markAsRevision(): void;       // 清空 confirm_deadline_at
public function markAsWorking(): void;        // 已存在
// 保持既有两个可选参数,确认来源作为第三个可选参数(兼容 OrderService 等既有调用点)
public function markAsFinished(
    ?WorkTask $workTask = null,
    ?bool $isLastStage = null,
    string $confirmSource = self::ConfirmSourceManual,
): void;

markAsFinished 需要新增记录 confirmed_at、confirm_source,并保持现有 WorkTaskStageCompleted 事件与 isLastStage 判定不变。

实现补充(见 §19):

  • markAsAwaitingConfirmation / markAsRevision 同时清空 confirm_reminder_sent_at,保证重新提交后提醒可再次触发。
  • 模型新增 remaining_seconds accessor:awaiting_confirmation 且 confirm_deadline_at 非空时返回 max(0, deadline - now);已过期返回 0,其它状态或 deadline 为空返回 null。
  • confirm_deadline_at/confirmed_at/confirm_reminder_sent_at 增加 datetime cast。

6. 节点状态机

(支付推进) (画师提交+请求确认) pending ────────────────▶ working ───────────────────────────▶ awaiting_confirmation ▲ │ ▲ │ │ │ 客户退回(revision) │ (画师重新提交) ▼ │ └────────────────────────────── revision ─┘ │ (手动确认 / 超时自动确认) ▼ finished

规则:

  1. working → awaiting_confirmation:画师上传稿件并请求确认时触发;写入 confirm_deadline_at = now + stage_confirm_hours。
  2. awaiting_confirmation → revision:客户对稿件发起修改意见时触发;清空/暂停 confirm_deadline_at。
  3. revision → awaiting_confirmation:画师上传修改稿并再次请求确认时触发;重置为完整时限(重新计算 confirm_deadline_at = now + stage_confirm_hours)。
  4. awaiting_confirmation → finished:
    • 手动:客户调确认接口,confirm_source=manual;
    • 自动:定时任务发现 confirm_deadline_at <= now(),confirm_source=auto。
  5. 最终节点(按 percent 最大判定,同 WorkTaskStage::isLastStage())不允许自动确认,只能手动确认;自动任务跳过它。
  6. 确认后仍沿用现状:下一个 is_paid=1 && pending 节点 → working;全部 finished 且付清 → worktask finished + 结算。

7. 核心业务流程

7.1 上传稿件并请求确认(画师)

入口:改造 POST /api/artist_center/work_task_files/create。

请求参数(新增/调整):

参数类型说明
work_task_idint必填
work_task_stage_idint必填,必须属于该 work_task
upload_file_idsint[]改为数组,一次挂多个文件(保留单值兼容)
upload_file_idint兼容单值入参;与 upload_file_ids 互为备选,未使用时允许显式 null
request_confirmationbool默认 false;true 时进入待确认并开始倒计时

规则:

  • 校验 work_task_stage_id 属于 work_task_id(新增)。
  • 仅允许在 working(或 revision 重新提交)节点上「请求确认」;finished 节点不可再挂稿件。
  • request_confirmation=true:
    • 节点 → awaiting_confirmation;
    • 若 workTask->stage_confirm_hours > 0:confirm_deadline_at = now()->addHours(workTask->stage_confirm_hours);
    • 若 stage_confirm_hours = 0 或最终节点:不写 deadline,仅手动确认(前端展示「需手动确认」,不展示倒计时)。
  • request_confirmation=false:仅挂稿件,不改变节点状态(与现状一致)。
  • 写 WorktaskPageEventService 新事件、通知用户。

实现说明(见 §19):

  • 归属校验失败返回错误码 20006;节点状态不允许「请求确认」返回 20007。
  • 目前仅对 request_confirmation=true 施加「非 working/revision 不可请求确认」的守卫; request_confirmation=false 时挂载到 finished 节点的行为保持改造前现状,是否收紧见 §19.4 遗留。
  • 接口返回结构由原来的单个稿件对象改为稿件数组(data 为列表)。

7.2 手动确认(客户)

入口:改造 POST /api/work_tasks/confrim_stage_work_status(路由拼写沿用历史)。

规则(在现有锁定/事务基础上):

  • 可确认的节点优先级:awaiting_confirmation > working(兼容存量无待确认态的 commission)。
  • 确认时写 confirmed_at、confirm_source=manual。
  • 后续「下一节点 → working / 全部完成 → 结算」逻辑不变。

实现说明:共享服务 WorkTaskStageConfirmationService 的选节点查询为 is_paid=1 AND work_status IN (awaiting_confirmation, working) ORDER BY percent ASC; 正常推进顺序下与「awaiting 优先」等价,异常推进场景的排序收紧见 §19.4 遗留。

7.3 自动确认(定时任务)

见 §8。

7.4 退回修改(客户)

入口:复用 POST /api/work_task_file_change_requests/create,并新增节点状态联动:

  • 若当前节点处于 awaiting_confirmation,创建修改意见后节点 → revision,并暂停(置空)confirm_deadline_at。
  • 若节点已是 revision,继续追加修改意见,状态不变。

画师查看修改意见后,通过 §7.1 的 request_confirmation=true 重新提交修改稿,节点 revision → awaiting_confirmation 并重置为完整时限。

实现补充:该接口在本次一并补上了稿件归属校验(work_task_file_id 必须属于当前用户,越权返回 404)。


8. 自动确认机制

8.1 定时任务

新增命令 app/Console/Commands/AutoConfirmWorkTaskStage.php:

// 伪代码
WorkTaskStage::query()
    ->where('work_status', WorkTaskStageWorkStatus::AwaitingConfirmation)
    ->whereNotNull('confirm_deadline_at')
    ->where('confirm_deadline_at', '<=', now())
    // 排除最终节点:percent 为该 worktask 最大者
    ->get()
    ->each(fn ($stage) => $this->confirmStage($stage, 'auto'));

注册到调度(routes/console.php,参考 DispatchPendingCommissionResolutionsCommand):

Schedule::command(AutoConfirmWorkTaskStage::class)
    ->everyFiveMinutes()
    ->withoutOverlapping();

实现补充:提醒与自动确认两个查询都额外限定 workTask.status = working, 避免 commission 取消后残留的 awaiting_confirmation 节点持续发提醒或刷错误日志。

8.2 复用确认逻辑(重要)

将 Api\User\WorkTaskController@confirmStageWorkStatus 的核心(加锁、事务、结算、事件、通知)抽取为共享服务 App\Service\WorkTask\WorkTaskStageConfirmationService::confirm(WorkTask $workTask, string $source, ?int $specificStageId = null), 手动接口与自动任务都调用它,保证:

  • 幂等(重复执行不重复结算);
  • 锁定顺序、取消/退款冲突检查、WorkTaskCompletionService 打款逻辑完全一致;
  • 自动确认跳过最终节点。

8.3 最终节点永不自动确认

  • 判定:WorkTaskStage::isLastStage()(按 percent 最大)。
  • 自动任务在查询阶段即排除「最终节点」,并在 confirm 内二次防御(双保险)。

8.4 到期前提醒(前置通知)

  • 进入 awaiting_confirmation 且 confirm_deadline_at 非空后,在 confirm_deadline_at - reminder_hours(默认 24h)向客户发送「即将自动确认」提醒。
  • 幂等:confirm_reminder_sent_at 为空且已到提醒时点才发送,发送后写回该字段,避免重复提醒。
  • 实现:并入 AutoConfirmWorkTaskStage 命令(同一条命令先发提醒、再做自动确认),减少调度点。
  • stage_confirm_hours = 0 或最终节点无 deadline → 无需提醒。

实现补充:

  • 提醒同时写 SystemNotificationScene::WorktaskStageAutoConfirmReminder 系统通知与 WorktaskPageEventListType::STAGE_AUTO_CONFIRM_REMINDER 页面事件,写回 flag 前完成,正常路径下只写一次。
  • 提醒属 best-effort:通知 / 事件 / flag 在同一 try 内,任一失败只记日志,下一轮调度重试(极端情况可能重复提醒一次)。
  • 提醒阶段整体失败不会阻断同一轮自动确认(命令内双层 try/catch 隔离)。

9. 确认时限配置

9.1 配置位置

端接口字段
画师发布 serviceApi\Artist\ServiceController@create / @updatestage_confirm_mode(三态)+ stage_confirm_hours(仅 enable 可提交)
用户发布 Open Call(project)Api\User\ProjectController@create / @updatestage_confirm_mode(三态)+ stage_confirm_hours(仅 enable 可提交)
管理后台(全局默认)Api\Internal\StageConfirmSettingControllerdefault_hours / reminder_hours

9.2 规则

发布 / 更新时用 stage_confirm_mode 显式表达选择,后端换算为既有的 stage_confirm_hours 落库(2026-09-29 变更,接口变更记录已并入 docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md):

stage_confirm_modestage_confirm_hours 请求约束落库 stage_confirm_hours
default不传该字段null(沿用全局默认)
enable必填,整数 1 ~ 720自定义值
disable不传该字段0(不允许自动确认)
  • create 必须传 stage_confirm_mode;update 不传该字段表示不修改,显式 null 非法。
  • 非 enable 模式出现 stage_confirm_hours 键(无论 null、0、空串、数组或其他值)返回 422;enable 缺少、越界或类型非法同样 422,不新增业务错误码(见 §11)。
  • 落库字段 stage_confirm_hours 编码不变:null → 使用全局默认 default_hours(默认 48);0 → 不允许自动确认(该 service / project 的节点只接受手动确认,见 §17 决策 2);1 ~ 720 → 该 service / project 专属时限,覆盖全局默认。
  • 读取响应新增 stage_confirm_mode:stage_confirm_hours 为 null → default、0 → disable、正数 → enable;stage_confirm_hours 字段保留且含义不变(disable 仍输出 0)。
  • 快照:commission 创建时把生效时限写入 work_tasks.stage_confirm_hours(0 也照常写入),后续发布方修改 service/project 不影响已存在 commission。default 在创建时物化为当时的全局默认时限(可为 0),非 null 快照冻结;既有 null 快照的运行时回退保持现状。

9.3 全局默认值(管理后台)

默认确认时限 48 小时,通过后台内部接口维护(参考 Open Call 手续费减免的实现):

端接口说明
管理后台POST /api/internal/system_settings/stage_confirm/detail读取当前默认时限与提醒提前量
管理后台POST /api/internal/system_settings/stage_confirm/update更新默认时限,走乐观锁 + 变更日志
  • 存储:SystemSetting(key worktask.stage_confirm)+ SystemSettingChangeLog 审计;实现参照 App\Service\Settings\OpenCallFeeWaiverSettingService、Api\Internal\OpenCallFeeWaiverSettingController。
  • 权限:AuthInternalApiRequestMiddleware + X-Admin-Id/X-Admin-Name/X-Request-Id 头(见 routes/api/internalApi.php)。
  • 乐观锁:expected_version 不一致报 SystemSettingVersionConflict(30022),参数非法报 SystemSettingInvalid(30023)。
  • default_hours 与 reminder_hours 均限制在 0 ~ 720。

9.4 对前端可见

  • POST /api/work_tasks/info、POST /api/artist_center/work_tasks/info(本次由 GET 改为 POST,见 §13)返回的 workTaskStages 需带上新状态值及 confirm_deadline_at、confirmed_at、confirm_source, 并附带 remaining_seconds(已实现,随模型序列化自动输出)。
  • 前端需能区分「0 = 不自动确认」(不展示倒计时/提醒)。

10. 对外接口变更(汇总)

接口变更记录已落地:docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md。

10.1 artist

10.2 user

10.3 artist_center(service 发布)

10.4 internal(管理后台,参考 Open Call 手续费减免)

10.5 admin(可选)

  • service / project 管理接口同步支持 stage_confirm_hours(如后台需要代发代改),本次未实现。

上述接口锚点与最终路由以实现为准;正式变更记录见 pipipen-docs/docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md; 发布配置契约的 2026-09-29 变更已并入 pipipen-docs/docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md。


11. ErrorCode

沿用 work_task 区间 20000 - 29999(app/Enums/ErrorCode.php)。PRD 初稿的建议值与最终实现:

枚举值语义实现状态
WorkTaskStageNotBelongsToWorkTask20006稿件关联的节点不属于该 commission已实现
WorkTaskStageCannotRequestConfirmation20007当前节点状态不允许请求确认已实现
WorkTaskStageConfirmDeadlineMissing20008请求确认时缺少确认时限配置未实现:stage_confirm_hours 为 null 时回退全局默认,无需要报错
WorkTaskStageCannotRevise20009当前节点状态不允许发起修改未实现:修改联动保持幂等,无守卫
WorkTaskStageConfirmHoursInvalid20010stage_confirm_hours 越界(非 0~720)未实现:用 validate min:0|max:720 返回 422

规则:业务校验失败统一 ValidationException::throw(ErrorCode::..., ...),不传 ->value;变更需同步文档。 后台全局默认时限的更新沿用已有 SystemSettingVersionConflict(30022)/ SystemSettingInvalid(30023)。


12. 通知与页面事件

12.1 通知场景(SystemNotificationScene)

场景值说明
WorktaskStageAwaitingConfirmationworktask.stage_awaiting_confirmation画师请求确认,通知用户
WorktaskStageAutoConfirmReminderworktask.stage_auto_confirm_reminder到期前提醒客户
WorktaskStageAutoConfirmedworktask.stage_auto_confirmed自动确认,通知双方
(复用)WorkTaskFileChangeRequestCreated已有修改意见通知画师

三个新场景均已实现。注意:notification_templates 目前没有这三个场景的模板行,且未登记到 NotificationSettingService 通知开关;上线前需运营补模板/文案,否则邮件通知内容为空且用户无法关闭(见 §19.4)。

12.2 页面事件(WorktaskPageEventListType)

类型值说明
STAGE_AWAITING_CONFIRMATIONstage_awaiting_confirmation待确认事件(to: user)
STAGE_AUTO_CONFIRM_REMINDERstage_auto_confirm_reminder到期前提醒(to: user)
STAGE_AUTO_CONFIRMEDstage_auto_confirmed自动确认事件(to: both)
STAGE_REVISIONstage_revision退回修改事件(to: artist)

四个事件类型均已实现,通过 WorktaskPageEventService 统一写入,保持与现有 eventConfirmStage / eventFileUpload 一致的 to / is_close 语义。

实现注意:worktask_page_event_list.type 是 MySQL ENUM 列,新增取值必须在迁移里同步 ALTER TABLE ... MODIFY type ENUM(...),否则严格模式下写入会被拒绝(本次已并入节点状态迁移)。


13. 兼容性策略

  1. 存量 commission:work_status 无新枚举值、confirm_deadline_at 为空 → 自动任务天然跳过;手动确认走「working 兜底」路径,行为与现在一致。
  2. 接口兼容:work_task_files/create 的 upload_file_id 单值入参保留,新增 upload_file_ids 数组为推荐形态;两者互为备选且都允许显式 null;request_confirmation 默认 false,不改变既有调用语义。
  3. 字段兼容:新增列全部 nullable,旧数据不迁移值;stage_confirm_hours null → 回退默认值。
  4. 确认来源:confirm_source 仅新增列,不改变既有 WorkTaskStageCompleted 事件与结算判定。
  5. 破坏性变更:/api/work_tasks/info 与 /api/artist_center/work_tasks/info 由 GET 改为 POST, id 由 query 改为 JSON body;旧调用将返回 405,所有调用方(含 pipipen-front)需同版本升级。

14. 迁移实施计划

阶段内容说明
1枚举 + 迁移:扩展 work_status,新增 confirm_deadline_at/confirmed_at/confirm_source/confirm_reminder_sent_at、services.stage_confirm_hours、projects.stage_confirm_hours、work_tasks.stage_confirm_hours纯增列,可回滚;同一迁移内同步扩展 worktask_page_event_list.type ENUM
2WorkTaskStage 状态迁移方法 + markAsFinished 记录来源保持事件兼容
3抽取 WorkTaskStageConfirmationService,改造手动确认接口手动/自动共用
4上传接口改造(多文件、归属校验、请求确认)含节点状态联动
5修改意见联动 revision + 暂停倒计时复用 change request 接口
6定时任务 AutoConfirmWorkTaskStage + 调度注册最终节点排除、提醒 + 自动确认
7service/project 发布接口增加 stage_confirm_hours + 快照创建 commission 时写入 work_tasks
8通知、页面事件、ErrorCode、info 返回字段补齐前端对接依据
9测试 + 文档同步api-changes 单独提交

回滚:阶段 1 迁移可 down();业务代码按阶段小步提交,避免一次性大改。

回滚注意:down() 会把 work_status 收窄回三态,若库中仍有 awaiting_confirmation/revision 行, 严格模式下 ALTER 会报 1265 Data truncated;回滚前需先处理这些行。


15. 验收标准

  1. 画师上传稿件时可选择节点并勾选「请求确认」,节点进入 awaiting_confirmation,前端可见截止时间。
  2. 客户手动确认 awaiting_confirmation 节点后,节点 → finished,下一节点 → working,最终节点确认后触发结算。
  3. 超过确认时限未确认的非最终节点,被定时任务自动确认,结算/事件/通知与手动一致,且不重复触发。
  4. 最终节点在任何情况下不会被自动确认。
  5. 客户发起修改意见后节点进入 revision,倒计时暂停;画师重新提交后重置为完整时限并恢复倒计时。
  6. service / Open Call 发布时可选择自动确认模式(default / enable / disable,disable = 不自动确认;2026-09-29 起由 stage_confirm_mode 表达,见 §9.2),且修改发布配置不影响已存在 commission(快照生效)。
  7. 稿件与节点归属校验生效,无法把稿件挂到其它 commission 的节点;稿件与节点保持一对一。
  8. 到期前按 reminder_hours 提前量向客户发送提醒,且同一节点只提醒一次。
  9. 画师不能撤回「请求确认」;客户在无稿件时仍可手动确认节点。

16. 测试清单

  • 枚举/迁移:work_status 新值可写可读;新列默认 null;迁移 down 可回滚。
  • 上传:多文件挂载;request_confirmation=true/false;节点归属校验(传其它 commission 节点报 20006);finished 节点不可请求确认(20007);单值/数组字段显式 null 兼容。
  • 确认:手动确认 awaiting_confirmation;存量 working 兜底;最终节点完成触发结算;取消/退款冲突检查复用;无稿件时手动确认可用。
  • 自动确认:定时任务只处理过期 awaiting_confirmation;最终节点被跳过;幂等(重复跑不重复结算);confirm_source=auto 落库;stage_confirm_hours=0 不进入自动确认;非进行中 commission 被跳过;提醒阶段失败不阻断自动确认。
  • 提醒:到 confirm_deadline_at - reminder_hours 发提醒;confirm_reminder_sent_at 写回后不再重复;stage_confirm_hours=0 不发提醒;同时写入页面事件且只写一次。
  • 修改联动:awaiting_confirmation → revision 且 deadline 置空;revision → awaiting_confirmation 重置为完整时限;越权稿件返回 404。
  • 配置快照:service/project 设置 stage_confirm_hours(含 0);创建 commission 快照到 work_tasks;修改发布配置不影响旧 commission。
  • 后台设置:stage_confirm 设置读写;expected_version 冲突报 30022;非法参数报 422;变更写入 SystemSettingChangeLog。
  • 通知/事件:各场景事件与通知写入正确,to/is_close 语义与现状一致。
  • 全链路:上传请求确认 → 退回修改 → 重新提交重置倒计时 → 超时自动确认推进。

遵循 pipipen-api/AGENTS.md:测试仅在 local/testing 环境、使用名称含 testing 的数据库执行。 后端测试落地见 §19.2(3 个测试文件、34 个用例)。


17. 已确认产品决策

  1. 默认确认时限:48 小时;可在管理后台设置全局默认值(参考 Open Call 手续费减免的 SystemSetting 实现)。
  2. stage_confirm_hours = 0:不允许自动确认(该 service / project 只接受手动确认),不是「立即自动确认」。
  3. 修改后倒计时:重置为完整时限(画师重新提交后重算 confirm_deadline_at)。
  4. 稿件与节点关系:一对一,沿用 work_task_files.work_task_stage_id 单值,不做多对多。
  5. 画师撤回「请求确认」:不支持,节点进入 awaiting_confirmation 后不能退回 working。
  6. 无稿件时手动确认:允许,保留现状语义(客户端可无稿件确认节点)。
  7. 到期前提醒:需要,按 reminder_hours(默认 24h)提前提醒客户,且只提醒一次。
  8. 时限上限:接受 0 ~ 720 小时(0 表示不自动确认;720 = 30 天)。

实现阶段新增决策(2026-09-18)

  1. 接口风格:本需求涉及的 work_tasks/info 由 GET 改为 POST;pipipen-api/AGENTS.md 固化 「新增/修改接口默认 POST」的约定(仅第三方回跳、下载/流、图片代理、监控/健康检查允许 GET),存量 GET 不追溯。
  2. 越界校验:stage_confirm_hours 越界用 validate min:0|max:720 返回 422,不新增业务错误码。
  3. 越权修复:修改意见接口必须校验 work_task_file_id 属于当前用户,越权返回 404(与 list/update 口径一致)。
  4. 取消联动:提醒与自动确认都跳过非 working 状态的 commission,避免取消后残留节点持续提醒/报错。
  5. 提醒语义:提醒为 best-effort(通知 + 页面事件 + flag 同批处理,失败下轮重试), 正常路径保证「只提醒一次、只写一次事件」。
  6. 入参兼容:upload_file_id 与 upload_file_ids 互为备选且都允许显式 null,两者都缺失/为空仍 422。

发布契约变更(2026-09-29)

  1. 显式模式:发布接口改用 stage_confirm_mode(default / enable / disable)表达选择,stage_confirm_hours 仅 enable 可提交;mode 不落库,由既有时限推导;破坏性更新,不提供旧请求兼容层,API 与 C 端须配套发布。依据:2026-09-29 需求(任务 09-29-stage-confirm-mode-explicit),接口变更记录已并入 docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md。

遗留待确认(实现层面,不阻塞开发)

  • 到期前提醒的提前量默认值(本 PRD 建议 24h,已纳入后台设置 reminder_hours,可再调)。
  • 提醒文案与通知渠道样式由前端/文案侧确认;上线前需补 notification_templates 与通知开关(见 §19.4)。

18. 术语对照

原型 / 界面代码 / 数据库
Milestone(节点)WorkTaskStage / work_task_stages
commissionWorkTask / work_tasks
稿件 / WorkWorkTaskFile / work_task_files + UploadFile
修改意见WorkTaskFileChangeRequest
待确认work_status = awaiting_confirmation
需修改work_status = revision
Final Delivery按 percent 最大的节点(WorkTaskStage::isLastStage())
自动确认时限stage_confirm_hours(service/project 配置 → work_tasks 快照)

19. 实现记录(后端,2026-09-18)

实现仓库:pipipen-api,分支 feature/refund-preview-calculation。 关联接口变更记录:docs/pipipen/api-changes/2026-09-22_worktask_revision_batch.md。

19.1 提交清单

提交内容
42ae291f扩展节点状态与确认时限字段(迁移 + 模型 + 页面事件 ENUM)
2afd5a0e新增节点自动确认全局默认时限后台设置
ac85aac9补齐通知页面事件与错误码
7c2cd0ab抽取节点确认共享服务并支持自动确认
286b2633稿件上传关联节点并支持请求确认
d7ebb955修改意见联动节点需修改状态
f2bf7a7cservice 与 project 发布支持确认时限并快照
8ef3e6ac节点确认与自动确认测试
e875dbd4兼容稿件上传接口单值字段显式为 null
c14a8450自动确认命令隔离提醒失败并跳过非进行中 commission
59afb7b1修改意见接口校验稿件归属
a5bcd0b6到期前提醒写入页面事件
819befbb节点详情接口统一为 POST

19.2 验证

  • 全量测试:vendor/bin/phpunit → 928 tests / 5846 assertions 全绿。
  • 迁移在测试库 pipipen_api_testing 上验证 migrate → migrate:rollback --step=2 → migrate 可回滚。
  • 新增 3 个测试文件、34 个用例,覆盖上传、手动/自动确认、提醒、修改联动、发布快照、后台设置与全链路。

19.3 实现期修正的 PRD 事实错误

  • worktask_page_event_list.type 是 MySQL ENUM,新增 4 个页面事件值必须同步 ALTER(已并入迁移)。
  • remaining_seconds 取 deadline - now(未到时为正);过期返回 0,非待确认态或 deadline 为空返回 null。
  • markAsFinished 保持既有两个可选参数($workTask、$isLastStage)以兼容 OrderService 等调用点, 确认来源作为第三个可选参数。
  • PRD 初稿中的接口 path 与最终路由不一致,已按实现修正: /api/work_tasks/confrim_stage_work_status(非 /api/user/worktask/...)、 /api/work_task_file_change_requests/create、/api/projects/create。

19.4 遗留 / 跟进

级别事项
高(发布协同)pipipen-front 需把两个 work_tasks/info 调用从 GET + query 改为 POST + JSON body,与后端同版本发布;否则调用方 405
高(上线前置)3 个新通知场景缺 notification_templates 行且未登记通知开关(NotificationSettingService),邮件通知内容为空且用户无法关闭;上线前需补模板/文案
中确认通知目前在事务内同步发送,邮件失败可能回滚确认与结算;建议后续改为提交后发送或走队列
中OrderService 阶段支付只认 working 节点,不识别 awaiting_confirmation;自动确认失败会每 5 分钟重试并刷日志
中finished 节点在 request_confirmation=false 时仍可挂稿件(PRD §7.1 原意是 finished 不可挂稿件),需产品确认后再收紧
低markAsFinished 在支付推进路径也会写 confirm_source=manual,建议后续区分来源
低手动确认选节点未按「awaiting 优先于 working」显式排序(正常推进流程下等价)
低提醒/自动确认的幂等基于 flag 与调度 withoutOverlapping,手工并发执行可能重复提醒一次