7ae595c8c2aa25237e37dfd4e0e222fc6026d8c2
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f92a98a3a3 |
子任务跟随父分类,修复父子跨本导致客户端无法显示层级
用户问:「任务是带有子任务功能的,能否通过 caldav,把任务的子任务也同步给客户端?」
## 答案:能,但实测发现父子被分到了不同本
改前数据:
父 #12「轻舟的幼儿园发票」 category_id = 1(家庭事务)→ tasks-1.ics
子 #29「送财务报销」 category_id = NULL(收件箱) → tasks-inbox.ics
客户端渲染「家庭事务」时看不到子任务,渲染「收件箱」时又找不到父任务,
⇒ 不可能显示成层级,只能两条并列。
## 改法
### 1. 子任务跟随父分类(TaskDav::effectiveCat)
有效分类 = 自己的 category_id
↓ 为空且是子任务时
父任务的 category_id
⚠️ **不改数据库**,只在视图层算。
改库等于篡改用户数据,而且单向不可逆。
边界:
| 情况 | 有效分类 |
|---|---|
| 自己无分类 + 父有分类 | 跟父(父子同本) |
| 自己无分类 + 父也无分类 | 都进收件箱(父子同本) |
| 自己有分类 | 用自己的(尊重用户明确设定) |
`tasksIn()` 改为 SQL 联查 `p.category_id`(一次到位,避免 N+1),
再在内存里按有效分类过滤。
### 2. SUMMARY 去掉硬加的「└ 」前缀
原来给子任务 SUMMARY 加「└ 」。这会**污染用户数据** ——
客户端把标题写回来(PUT)时前缀被存进库,用户的标题就多了一坨。
层级改由标准属性表达:
- `RELATED-TO:task-12` —— RFC 5545 §3.8.4.5 标准父子
- `X-FAMILYBOOK-PARENT:轻舟的幼儿园发票` —— 展示用补充,不支持的客户端无视
### 3. 跨本时的降级提示
用户明确给子任务设了别的分类时(尊重其选择),父子仍会跨本。
那种情况在 DESCRIPTION 尾部补一句人话说明,避免客户端把
「有 RELATED-TO 但找不到父」当成数据损坏而不显示这条。
## 验证
改后分布:
| 本 | 总数 | 父 | 子 |
|---|---|---|---|
| 家庭事务 | 7 | 6 | 1(#29) |
| 工作相关 | 19 | 19 | 0 |
| 个人事务 | 2 | 2 | 0 |
| 党支部工作 | 1 | 1 | 0 |
| 轻舟相关 | 1 | 1 | 0 |
| 收件箱 | 2 | 2 | 0 |
- 总数仍 32 条,没丢没重
- **所有父子都在同一本里**
- HTTP 端到端 39 项全过(含跨本隔离、鉴权、日历本回归)
## 客户端支持度说明(服务端无法改变)
| 客户端 | 层级显示 |
|---|---|
| DAVx⁵ + Tasks.org | 认 RELATED-TO,显示缩进子项 |
| 苹果提醒事项 | 有原生子任务 UI,对 RELATED-TO 支持有限 |
| Thunderbird | 不显示层级,两条并列 |
数据是正确的,显示成什么样取决于客户端能力。
想在所有客户端都看到层级,只能靠前端自己渲染 —— 那是网页端的事。
|
||
|
|
c86658966c |
CalDAV 增加待办(VTODO)双向同步
用户原话:「网页端的 caldav 功能,除了能够同步日程外,增加可以同步任务(待办)的功能」
## 三个设计选择(用户已定)
1. 存放位置:作为 /dav/ 内的一个日历本,不另开 /taskdav/ 入口
2. 同步方向:直接双向读写
3. 字段映射:标准映射
## 字段映射
| VTODO | tasks 列 | 说明 |
|---|---|---|
| UID | 派生 task-<id> | 与日历 UID 空间隔离 |
| SUMMARY | title | 子任务加「└ 」前缀(纯展示) |
| DESCRIPTION | note | |
| DUE | due_at | 零点视为整天,用 VALUE=DATE |
| STATUS | status | todo→NEEDS-ACTION,doing→IN-PROCESS,done→COMPLETED |
| COMPLETED | done_at | 仅 done 时输出 |
| PERCENT-COMPLETE | status | doing=50,done=100 |
| CATEGORIES | 看板名 | 让客户端能归类 |
| RELATED-TO | parent_id | 标准父子表达 |
## 改动
### app/Ics.php:并列增加 VTODO 能力
不改 parse()/export() 的任何行为(VEVENT 路径注释里记着多轮踩坑史),
新增 exportTodos() / vtodo() / parseTodos() / normalizeTodo(),复用底层工具。
### app/TaskDav.php(新建,约 380 行)
虚拟日历本:href 固定 /dav/tasks.ics/,BOOK_ID = -1
(calendar_events.calendar_id 是 INT UNSIGNED 恒 ≥1,负数永不撞)。
权限口径与网页端完全一致,复用 Task::visibleTo / Task::canWrite /
Task::create / Task::update / Task::destroy,不另写一套。
### dav.php:前置拦截,原有日历逻辑零改动
PROPFIND / REPORT / GET / PUT / DELETE 各分支开头判断是否待办本,
是则走 TaskDav 并 exit。另外在 /dav/ 的本清单里追加待办本
(否则客户端根本不知道有这一本)。
## 过程中修掉的 4 个真 bug
1. **Class "TaskDav" not found** → 缺 use App\TaskDav;
autoload 要求带上命名空间前缀,漏了会让整个 dav.php 全线 500。
2. **PUT/GET单条/DELETE 全 404** → 原来全用 isTaskHref(),
它只比「整条路径 == /dav/tasks.ics/」,带子资源时必然 false。
新增 isTaskPath() 做前缀匹配(且要求前缀后必须是 / 或结束,
避免 /dav/tasks.icsXXX 被误判)。
3. **PUT 到不存在的资源名返回 403** → 原实现直接抛「任务不存在」。
按 RFC 4918 §9.7,PUT 到不存在的 URI 就是创建资源。
改为:只有确实存在且有权访问时才走更新分支,否则新建。
4. **注释里的 markdown 加粗导致 PHP 语法错**
`作为 **/dav/ 内的一个日历本**` 里 `**/` 形成 `*/`,提前闭合块注释。
PHP 注释里不要写 markdown 加粗。
## 验证
### Ics 层(34 项全过)
生成 → 解析 → 归一往返,含三种客户端完成态表达
(STATUS:COMPLETED / 只有 COMPLETED 时间戳 / PERCENT-COMPLETE:100)。
### TaskDav 层(45 项全过,真实 32 条任务)
读写、ETag 稳定性、权限、事务回滚。
写操作全在事务里做并回滚,不污染真实数据。
### HTTP 端到端(37 项全过)
OPTIONS → PROPFIND 发现 → REPORT → GET 整本 → PUT 新建
→ GET 读回 → PUT 更新 → DELETE,另含回归与越权检查:
- 原有日历本 PROPFIND/GET 仍正常,组件类型仍是 VEVENT
- 无口令 401、错口令 401、不存在的任务 404
测试按 RFC 跟随 Location(新建时服务端分配 id 并告知真实 href),
这也是真实客户端的行为。
### 数据完整性
测试前后 tasks 均为 32 条,测试数据已清理。
测试用 DAV 口令已 revoke(不影响用户原有口令)。
## 部署
app/Ics.php、app/TaskDav.php、dav.php 三个文件已上传并验证。
⚠️ 服务端文件不在客户端仓库,以后改这些要先比对服务器 md5 再验
(本次踩过:改了没上传,拿旧代码验出假结果)。
|
||
|
|
ef574d82a4 |
v51b DAV 例外:时区换算 + RECURRENCE-ID/EXDATE/多 VEVENT 不再被丢
根因三处(此前一个资源只能有一个 VEVENT、只认前 9 个字段): · Ics::normalizeVevent() 固定 9 字段,没有 recurrence_id/exdate; · dav.php 只取 $list[0](覆盖行全丢); · calendar_events 没有对应列。 修法: · 时区 —— 带 Z / TZID 的值按 zone 换算成本地墙钟,浮动值不动; UNTIL 一起归一(必须与 DTSTART 同值类型/同基准);DTSTAMP/CREATED/ LAST-MODIFIED 仍按 RFC 保留 UTC。 · 例外 —— 新增 calendar_event_overrides 卫星表(uk(event_id,recurrence_id), FK CASCADE)存覆盖行;EXDATE 落既有的 calendar_event_skips(与网页端 「只删这一次」共用);PUT 的例外语义 = 权威替换。 · 未建模属性(X-* 等)落 calendar_events.extra 原样回吐, 表单保存不认识时不动它(防网页编辑抹掉客户端的 X-*)。 · 「RECURRENCE-ID + STATUS:CANCELLED」折算成 EXDATE(RFC 5545 §3.8.4.4 与 EXDATE 等价),并保留"正文里只有覆盖行"的退化路径。 · ETag / ctag 必须含例外,否则只改「某一次」别的设备永远拉不到。 · 网页月历 month() 认覆盖行(按新时间画、原时间不留影、不被 EXDATE 那天画)。 Schema 33 → 34(VERSION 人读 + fingerprint() 才是真正判据)。 验证:Ics 离线单测 30/30;端到端 DAV 例外探针 56/56;v45 既有回归 24/24。 反证:退回旧行为 → 6 条断言变红(网页上"已取消"的那次仍被画出来)。 |
||
|
|
4ccbcc3360 |
v45 第 45 批:日程「重复到」换最新控件 + CalDAV 双向同步
用户 2026-10-12 两条:
1. 新建日程里「重复到」的那个日历选择框,改成最新控件。
- templates/calendar/_event_fields.php:裸 <input type=date> 换成
.dt-input.dt-antd-single[data-antd-date=1](AntD 纯日期 DatePicker),
复用既有 mountAntdDate,不新写挂载逻辑。
- 值载体仍是 name=rep_until 的原生框(包在 .dt-native 里),
服务端契约与 Repeat::build($_POST) 一字不改。
2. CalDAV 从「只读」改成「双向同步」。
用户给了一台可用的参照服务器(群晖 DAViCal)要求「完全按参照返回」;
抓参照 + 复刻 Thunderbird / SyncCalendar 的真实请求序列后确认:
参照是可写的,而只读会让两个客户端都判「不可用」。
- dav.php:放行 PUT(新建 201+ETag / 更新 204+ETag / If-Match 不符 412)、
DELETE(204)、MKCALENDAR(201+Location),仍逐本判权;
OPTIONS 的 Allow 与 DAV 头照参照补齐(DAV 头两行,注意 PHP header 追加);
current-user-privilege-set 按实际可写性给,并排除系统派生本(农历等)。
- app/CalDav.php:资源名统一用 UID(原自定义本用 id、派生本用 uid 两种混着,
一开写就表现为「客户端 PUT 到 UID 名、服务端 href 是 id 名 ⇒ 写进去读不回」);
补 owner / getctag / getcontenttype / calendar-description;新增 ctagSeedOf。
- app/Ics.php:修一个一直存在的真 bug —— 导出 RRULE 漏了 `RRULE:` 前缀
(库里存的是去前缀的值),导致所有导出/同步出去的重复日程都变成单次。
- templates/home.php:首页红线补 data-now-min(给探针做零容差判据用)。
验证:本地 e2e 1510/0、TB 探针 35/0、DAV 诊断 28/0、
v43 真机 33/0 + 反向 9/0、v44 真机 41/0 + API 63/0 + 反向 19/0、
v45 重复到 10/0 + 反向 2/0、v45 DAV 双向 24/0 + 反向 5/0;
全流水线 PUSH_RC=0(含部署静态断言第 45 批形态)。
|
||
|
|
be582569dd |
v31 第 31 批:DAV(CalDAV/CardDAV)同步修复 —— 发现链缺失 + ETag 潮汐 + 资源重复
用户原话:「carddav 和 caldav 好像都不好用,没有任何数据被同步出来。
同步的客户端,在 D:/Yongquan/code/SyncCalendar 中有,你可以用这个里面的代码试试同步一下。」
三处真根因**全部靠实测**拿到(不是"看代码猜"):写了 _fb_dav_diag.py,
逐字复刻 SyncCalendar 的 DavClient 请求序列(PROPFIND/REPORT/GET + 它那几个 XML 体),
打线上一次就复现出用户症状(15 条失败),修完线上 28/0。
1. 发现链(discovery)**整段缺失** ← 这就是"没有任何数据"的根因
标准客户端(DAVx⁵ / iOS / SyncCalendar)建账号按 RFC 6764 走:
GET /.well-known/caldav → 302 到 /dav/ ← 原来 404(落到 index.php 去登录页)
PROPFIND <上下文路径> → current-user-principal ← 原来回的是"日历本清单",没有 principal
PROPFIND <principal> → calendar-home-set ← 原来没这个属性
PROPFIND <home> Depth:1 / <book> / REPORT ← 这两步一直是通的
① 就断 ⇒ 客户端**在填完账号点保存那一刻**就失败,一条数据都拉不到。
为什么历次验收全绿:上批只验了 PROPFIND /dav/ 与 /carddav/ —— 恰好是**一直通**的那两步,
断点在其**前面**,从来没被测过。
改:两个入口补 PRINCIPAL_PATH/HOME_PATH + /.well-known/* 301(**在鉴权之前**)+
根/principal/列表三处都给 current-user-principal。.htaccess 用**独立标记块**追加
well-known 重写规则(本仓库第 4 次"独立入口必须独立块")。
2. DTSTAMP 取 time() → ETag 潮汐(增量同步**永远不收敛**)
App\Ics::stampOf() 原为"没给时间就 time()",而**派生条目**(观影/行程/生日/农历/
节假日/节气)没有 updated_at/created_at ⇒ 每生成一次 ICS 就写一个新"现在" ⇒ 正文变
⇒ ETag 变。线上实测:同一本连续两次 REPORT **每一条 ETag 都不同**,整本 GET 的 ETag
也在变 ⇒ 客户端把"增量同步"退化成"每轮全量重拉",且拉回来的对不上、永远不收敛。
改:缺时间写固定值 19700101T000000Z(确定优先于"诚实的当前时间")。
3. 派生本把"日历视图条目"当"CalDAV 资源"
Calendar::exportRows() 对派生本按**日历视图**取数(重复日程展开 N 次、跨天逐日、
生日/农历逐日派生)。线上 /dav/1.ics/ 实测 **2981 个 response / 只有 196 个不同 href**
(anniv-0_familybook.ics 自己重复 **2399** 次)。CalDAV 的资源是**一个 .ics 文件** ⇒
客户端要么把同一条反复入库,要么判定服务器异常而中止本轮同步。
改:CalDav::resources() 按 **UID 去重**(资源名本就是 UID)。
4. 连带两个真 bug(同一段代码顺出来的)
· exportRows() 派生分支**没按本过滤**:month() 里"任务""纪念日"两段不带 kind 判断
(它们 cal=0)⇒ 取"观影本"会把全家所有生日一起带出来(2981 条里绝大多数是 anniv)。
· CalDav::parsePath() 的 $event=(int)$evSeg:派生资源名(movie-100_familybook)
(int) 后全是 0 ⇒ 单条资源请求被当"整本",GET /dav/4.ics/lunar-...ics 实测回 **400 条**。
5. 顺带补全:系统日历本(农历/节假日/传统节日/节气)原来**导出是空的**
它们只走派生、没有 calendar_events 行,而 exportRows 只认 custom/movie/trip ⇒
手机上订阅这几个本一条都没有。改:按 [今年-1, 今年+5] 派生(**封顶**,农历每天一条,
130 年会有 4.7 万条)。实测耗时 lunarRange 31ms / traditional 51ms / Anniversary 30ms。
改:.htaccess、router.php、dav.php、carddav.php、app/CalDav.php、app/CardDav.php、
app/Calendar.php、app/Ics.php、templates/settings/me.php(补"或只填服务器地址"的提示)。
验证(全部 0 失败):DAV 探针 28/0(**线上修前 15 失败 → 修后 28/0**,同一套探针两处都跑)、
DAV 反向验证 8/0(7 处修复各自改回旧行为,断言全变红)、e2e 1478/0、升级 153/0、
城市 32/0、几何 428/0 + 移动 680/0 + 交互 92/0、b24 41/0、b27 34/0、b28 39/0、b29 38/0、
b30_import 26/0、b30_ui2 16/0、b30_notify 23/0;部署静态断言新增 12 条守卫并逐条证伪通过。
线上 7 个文件 SHA256 与本地一致;/.well-known/caldav→301 到 /dav/ 实测生效。
⚠️ 本轮在部署脚本上真踩到并修掉两个坑(已写进 pitfalls.md):
· router.php **不发到线上**,把它写进部署断言 → grep "No such file" → 部署中止在
"改了一半"的状态(迁到 push.sh 第 1 段)。
· `set -euo pipefail` 下 `X=$(... | grep -c ...)` 在 **0 匹配**时退出码 1 ⇒
**整个脚本静默退出**(不打 FAIL,日志停在上一行)。已给全部 7 处补 `|| true`。
|
||
|
|
c3d00ad66a |
v24 第 20 批:拖动即时上传 / 观影图片带出 / 行程保存修复 / 日程红线与删除范围与多提醒 / 悬停统一(用户 9 条)
用户 9 条原话要点:
①「观影模块,编辑观影,已经保存的海报和票根、合影,都没带进来。」
②「个人设置页面,"只影响你自己看到的样子"这个提示删除,不要了。我怎么称呼家人,
这个卡片,不要了。我的资料这个卡片,不要了。」
③「旅程模块,编辑行程,点击保存没有反应。」
④「所有上传图片视频和附件的模块,都允许多次拖动添加文件上传,拖动一个进去,
就开始上传,还可以继续拖动,继续上传,最后统一保存即可。」
⑤「添加行程,线路名、承运方,都不要了。车辆,只在出行方式选择自驾时才出现,
其他情况下默认不出现。出发地和到达地,应该在同一行。」
⑥「日程模块,周视图和日视图,应该在视图中间显示一条红线,用来表示当前进行的时间…
用一个不断从上到下的红线,来表示当前时间在视图上的刻度和位置。」
⑦「对于重复的日程,当点击删除的时候,应该提示是删除这一次,还是全部都删除,
还是删除这一次及以后的。」
⑧「日程的提醒,是可以多选的,就是说,一个日程,我可以选择让它 5 分钟提醒,
10 分钟提醒等等。」
⑨「很多按钮的悬停效果非常奇怪,感觉纯粹是换了一个背景色…应该调整所有按钮的悬停效果,
保持统一,但是不要单纯的换一个不协调的背景色。」
🔴 三个真根因(都是"看着不像 bug"的地方):
1. **③ 点保存没反应 = `<form>` 嵌套**(HTML 非法):`trips/node_form.php` 的照片缩略图上
套了 `<form action=photo_del>`,而整块在**外层行程表单内部**。浏览器解析到内层
`</form>` 时**把外层表单提前关掉** → 「保存」按钮**掉出表单**(真机实测
`button.form === null`)。源码里完全看不出,**只有真浏览器量 `btn.form` 才现形**;
且**只有这段行程已经有照片时才触发** —— 所以表现为"有时候好、有时候坏"。
→ 按钮留在原位(布局不动),用 HTML5 `form="id"` 关联到**表单外**的独立表单。
2. **⑤ 车辆藏不住(第 17 批就写过、一直没生效)**:`.field` 自己声明 `display:flex`,
作者样式压过浏览器默认的 `[hidden]{display:none}` → `hidden` 形同虚设。
**本项目第 4 次栽同一个坑**(.att-item / .pv / .pp-item 各一次)→ 补 `.field[hidden]`。
3. **④ 拖动即时上传的卡片"看不见"**:模板给 `.stage-list` 加了 `hidden`(没人上传时
不留空边框),JS 建卡片时忘了 `list.hidden = false` → 卡片在 DOM 里但整条清单
`display:none`、尺寸 0×0。**HTTP 层断言全绿**(HTML 里确实有卡片),
**只有真机量尺寸才发现** —— 由新写的真机探针抓到。
本批新增能力:
- **拖动即时上传**(第 4 条):新增 `upload_stage.php` + `app/StagedUpload.php` +
`templates/_stage_drop.php`。拖进来 → **立刻** POST 到暂存目录 → 回 `stoken` →
放进隐藏字段 `stage[]` → 保存时在 `attach_save_files()` 里**认领**(移文件 + 建附件行)。
`media.php` 加 `?stage=` 预览(拖完立刻看得见缩略图)。
⚠️ 三个必须踩准:**串行队列**(并发会让缩略图顺序乱跳)、**提交守卫**
(还有没传完就 preventDefault,否则"拖了但没保存上"必然发生)、**卡片清单要 un-hide**。
⚠️ 海报/票根走**独立字段名** `stage_poster`/`stage_ticket`(各自单槽位、要按比例重裁)。
⚠️ 落点**就近取**(`input.closest('.field')`):一个表单里多个上传区不能串到第一个去。
- **观影编辑带出已存图片**(第 1 条):编辑页显示海报 / 票根 / 合影 + 各自的删除入口;
新增 `movies/media_del`(只删不换也能删,槽位走白名单);`movies/update` 收编辑页
新选的照片时**合并**而不是替换(否则一编辑就把已有合影冲掉)。
- **行程表单**(第 5 条):去掉线路名 / 承运方(**库里的值保留**:UPDATE 时不参与,
与 seat_info/gate 同一套);出发地/到达地包进 `.loc-row` 真同一行;车辆按自驾显隐。
- **当前时间红线**(第 6 条):位置在服务端算(与 `.tg-block` 同一套"24h = 100%"坐标),
前端每分钟自己挪(对齐到下一分钟整再起定时器),跨午夜 reload;只画在**今天那一列**。
- **重复日程删除三档**(第 7 条):与「编辑」那三档同名同序(全部 / 这一次及以后 /
只这一次)。「只删这一次」→ `skipOnce`(**绝不删整条系列**);「这一次及以后」→
老系列截 UNTIL,**必须清掉 COUNT**(两者并存时 COUNT 优先,不清就截不断)。
- **提醒多选**(第 8 条):同列存逗号串(`5,15,60`);Schema 把它从 **INT 改 VARCHAR**
(INT 存逗号串会被隐式转换,多档会静默只剩第一档);ICS 导出吐**多个 VALARM**。
- **悬停统一**(第 9 条):原来 14 种互不相干的 hover 底色 → 统一成**两套手法**:
有底色 `filter: brightness(.92)` 同色系加深 + 轻抬升;描边白底 `--hov-veil` 中性薄雾
+ 边框微显。放在 CSS **最末**用一条兜底收口(新按钮自动继承),并把十几条旧规则
清成"交给统一段"。`person_picker.js` 同时改为按需加载。
验证(本地全套):
- e2e **1004/0**(+21 条本批断言)· 升级 149/0 · 桌面几何 428/0 ·
**真机探针 25/0**(连续拖两次 / 红线位置与颜色与几何 / 车辆显隐 / 出发到达同行)。
- **反向验证 5/5**:①删除按钮改回嵌套 form ②卡片清单不 un-hide ③观影编辑不渲染海报
④车辆隐藏失效 ⑤「只删这一次」做成删全部 —— 五种坏法全部被抓住,还原后复绿 1004/0。
过程中还**抓到两条弱断言**(只查 DOM 有无、不查可见性/是否真渲染),已一并加强。
- 部署脚本静态断言段本地空跑 **305 条全过**。
|
||
|
|
8f80f3e835 |
v24 第 2 轮:19 条清单收尾(附件预览/CalDAV/任务分类看板/全局选人/家谱/纪念日/财务)
用户 2026-10-02 提的 19 条需求,本轮完成剩下的 8 条 + CalDAV 服务端:
- ③ 附件:图片/视频给缩略图,PDF/Word/Excel 给**同尺寸**图标;可预览的在本页
弹层里看(不再新开标签页),渲染不了的点了直接下载;预览层右上角恒有下载按钮。
- ⑦b CalDAV:把「我的日历本」按 RFC 4791 只读子集分享出去(OPTIONS/PROPFIND/
REPORT/GET,写方法一律 403)。用**专用口令**(库里只存 sha256),不是登录口令。
- ⑨⑩ 任务:分类与看板都由用户自建;已完成的任务从看板消失,改在「已完成」页
按分类查看/搜索;设置入口在「新建任务」旁的 ☰。
- ⑪ 全局人员选择器:一个片段铺到观影/旅程/资源/日历共享/任务共享/财务共享;
修掉了老组件「名字和拼音都搜不动」的 bug —— .att-item 自己声明了 display:flex,
压过了浏览器 [hidden]{display:none},过滤根本没生效。
- ⑫ 家谱:头像改方形不再裁切;姻亲不在图上的卡片给「⇄ 姻亲」入口,
以 TA 为中心重画 + 「返回我的家谱」。
- ⑬ 纪念日:阴历/阳历都能设且按对应历法重复;名册里每个人的生日默认可见
(实时派生,不复制一份);全部作为全天日程进日历。
- ⑮ 财务:账本可共享,双方都能记账、都能看全部账目,资产−负债=净资产;
金额一律以「分」存,不用浮点。
验证:端到端 724 项通过 / 0 失败;几何 368、交互 97、移动端 516、家谱 24、
升级 123 全绿;SQL 列名静态校验 0 处可疑;部署 11 条线上探针全过。
|