Files
FamilyBook/templates/trips/_revise_form.php
T
laoyang 89b956205d v24 第 13 批:旅程模块重构(双轨 → 一条记录 + 状态机)
用户原话:「旅程这个模块,我准备大改…如果按照计划行程和实际行程来分别管理,
会变的很复杂…根据我的想法,重构这个模块。」
—— 这不是加需求,是把「双轨」架构换成「一条记录 + 状态机」。

## 用户确认的三个决定
1. 两层:**活动**(顶层容器)→ **行程**(挂其下,字段沿用原节点)
2. **修正不立即转实际**:修正只是记录,行程仍标"计划(已修正 N 次)",
   要再点一次「标记为实际」才转实际
3. 老数据**按现有轨道自动迁移**(category=reality → stage=actual)

## 状态机(本次重构的心脏)
trip_nodes.stage:
  · plan    计划行程 —— 唯一以「计划时间」上日程的状态,三个动作都能用
  · actual  实际行程 —— 上日程(实际时间)+ 进「往年今日」
  · skipped 未成行   —— **不上日程、不进往年今日**,但旅程里仍显示并标注
                          (用户:"还是要显示,只不过要标记上")
旧的 category(plan/reality) 保留作兼容,业务一律看 stage(saveNode 同步维护两者)。

## 三个动作(Trip.php)
· reviseNode —— 记一次修正。🔴 **第一次修正前先把原计划存成 seq=1 快照**,
  否则用户要的"之前的计划行程数据要保留,为了以后做对比"就落空。**不动 stage**。
  可无限次(seq 递增)。
· completeNode —— 实际=计划,直接转 actual,**不产生修正历史**(没有变化就没有历史)。
· markActual —— 把(已修正过的)计划正式转 actual。
· skipNode —— **不是真删**,只置 stage='skipped'(用户明确"不要真的删除,只是做一个标记")。
· restoreNode —— 撤销未成行(点错了能改回来)。
界面上的「彻底删除」与「标记未成行」是两个并列动作,弹窗文案里写明区别。

## 修正历史(Schema v19,新表 trip_node_revisions)
每次修正一行:实际起止 / 实际地点 / 说明 / 参与人快照。
第 1 条 = 原计划快照,第 2..n 条 = 各次修正 —— 详情页用表格展示,可直接对比。

## 日历与往年今日(用户点名的联动)
· 日历:`n.stage IN ('plan','actual')` —— 去掉旧的 `status<>'cancelled'`
  (那是双轨口径;现在"未成行"是 stage)。实际行程标题加 ✓ 前缀便于区分。
· **活动本身绝不上日程**(用户:"活动因为可能跨越的时间比较长")。
· 往年今日:旅程部分从「整趟 events」改成**实际行程**(stage='actual'),
  标题带活动名前缀;未成行的不出现。

## 验证
新增 _trip_e2e12.py **30/0**:建活动 → 加行程(可选计划/实际)→ 修正(仍计划 /
历史保留原计划 09:00 / 可无限次)→ 标记为实际 → 完成(无历史)→ 标记未成行(未真删)
→ 日历四种情形(已转实际✓ 新建实际✓ 完成✓ 未成行✗ 活动✗)→ 往年今日只看实际。
回归:e2e 787/0、升级 129/0(含 v19 路径)、batch_e 91/0、batch10 46/0、batch9 38/0、
picker 28/0 + 11/0、crop 17/0、formbloat 9/0、semi 27/0、geom 5/5、mobile 688/0、
interact 92/0、drag 5/0。
e2e 两条旧断言("双轨两块/两轨各一条")按重构语义更新为"三组渲染 / stage 各一条
+ category 与 stage 同步"。
部署断言新增 1 大组,含**反退化防线**:skipNode 若被改成 DELETE 立即变红。
线上自检 19/19;线上真实账号验收 16/0。
线上老数据迁移实测:#222 计划3+实际1、#221 实际4、#220 实际13 —— 一条没丢。
2026-10-04 20:57:40 +08:00

76 lines
3.6 KiB
PHP

<?php
/**
* 「修正」表单(v24 第 13 批):记录一条计划行程的**实际**执行情况。
*
* 用户原话:「另外一个按钮,用来记录实际的情况,按钮名字就叫修正,但是记住,
* 这个实际的行程和计划行程,是一条记录,不是两条,点击这个按钮,
* 则让用户选择实际的执行时间,包括开始和结束时间,实际的参与人等信息,
* 就相当于是修改了一下这个行程,只不过,之前的计划行程数据要保留,
* 为了以后做对比。修正,是无限次数的。」
*
* ⚠️ 与「编辑」的区别:
* · 编辑 = 改**计划**本身(排错了计划就改它);
* · 修正 = 记**实际**发生了什么(计划不动,实际值进历史,供对比)。
* 两者都作用在同一条 trip_nodes 记录上(用户明确"是一条记录,不是两条")。
*
* 变量(require 前设好):
* $rvNode 被修正的行程
* $rvTripId 旅程 id
* $rvPeople 人名册(选实际参与人)
* $rvMembers 当前参与人 id
*/
$rvPrefix = 'rv' . (int)$rvNode['id'];
$rvN = (int)($rvNode['revision_n'] ?? 0);
?>
<form class="rev-form" method="post" action="<?= e(url('trips/node_revise')) ?>">
<?= csrf_field() ?>
<input type="hidden" name="trip_id" value="<?= (int)$rvTripId ?>">
<input type="hidden" name="id" value="<?= (int)$rvNode['id'] ?>">
<div class="nf-grid">
<?php /* 实际的起止时间:默认填计划值,方便"只改时间"这种最常见的情况 */ ?>
<div class="field dt-range full">
<label>实际的开始 / 结束</label>
<?php $dtName = 'rev_starts_at'; $dtValue = $rvNode['starts_at'] ?? '';
$dtId = $rvPrefix . '-s'; $dtSeconds = true; $dtRange = 'start';
require __DIR__ . '/../_datetime.php'; ?>
<?php $dtName = 'rev_ends_at'; $dtValue = $rvNode['ends_at'] ?? '';
$dtId = $rvPrefix . '-e'; $dtSeconds = true; $dtRange = 'end';
require __DIR__ . '/../_datetime.php'; ?>
<span class="tip">默认是计划的时间,改成实际发生的即可。</span>
</div>
<div class="field">
<label for="<?= e($rvPrefix) ?>-place">实际地点</label>
<input class="input" id="<?= e($rvPrefix) ?>-place" name="place"
value="<?= e($rvNode['place'] ?? '') ?>" placeholder="没变就留空">
</div>
<div class="field full">
<label for="<?= e($rvPrefix) ?>-detail">这次修正的说明</label>
<input class="input" id="<?= e($rvPrefix) ?>-detail" name="detail"
placeholder="如:航班延误,改到下午才到">
</div>
<?php if (!empty($rvPeople)): ?>
<div class="field full">
<label>实际参与的人</label>
<div class="nf-members">
<?php foreach ($rvPeople as $p): ?>
<label class="check-item">
<input type="checkbox" name="members[]" value="<?= (int)$p['id'] ?>"
<?= in_array((int)$p['id'], $rvMembers, true) ? 'checked' : '' ?>>
<span><?= e(person_name($p)) ?></span>
</label>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
<div class="btn-row">
<button class="btn btn-sm" type="submit">
<?= $rvN > 0 ? '再记一次修正' : '记录这次修正' ?>
</button>
<?php if ($rvN > 0): ?>
<span class="tip" style="align-self:center">之前已记过 <?= $rvN ?> 次;原计划数据都留着。</span>
<?php endif; ?>
</div>
</form>
<?php unset($rvPrefix, $rvN); ?>