You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

76 lines
5.5 KiB

4 hours ago
# -*- coding: utf-8 -*-
"""票 01 收尾落盘:票据翻 resolved + Answer 存证;map.md Decisions 补行。
二进制读写保留原行尾写后校验 BOM/锚点/关键标记"""
import io, sys
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
# ---------- 1) ticket 01: Status: open -> resolved + Answer ----------
ticket = r'.scratch/a7-heavy/issues/01-sync-task-module.md'
raw = open(ticket, 'rb').read()
assert not raw.startswith(b'\xef\xbb\xbf'), 'ticket has BOM'
nl = b'\r\n' if b'\r\n' in raw else b'\n'
assert raw.count(b'Status: open') == 1, 'status anchor count=%d' % raw.count(b'Status: open')
raw = raw.replace(b'Status: open', b'Status: resolved', 1)
ANSWER = """
## Answer
2026-09-08 落地resolved10 项需求全部实现验收基线全过`mvn test -pl crm-auth -am` 全量单测 EXIT=0BOM 扫描 0编译过
**实现清单**
- /实体`dt_sync_task`任务编号 `SYNC-MMdd-序号`/触发方式/执行状态/起止时间/部门·人员·异常三计数/进度/失败明细 JSON+ `dt_sync_settings` 单例行启停/每日执行时间/重试间隔·次数/并发/最近修改人·时间
- 端点 5 `SyncTaskController` ADMIN tagA7 后台管理/用户管理/同步任务POST /sync/dingtalk任务化POST /sync/tasks/pageGET /sync/tasks/detail?id=GET /sync/settingsPOST /sync/settings
- 服务`DtSyncTaskServiceImpl`launch 并发占位61023单线程 daemon dt-sync-task 异步执行进度 5/45/90/100settings 边界校验61022page 默认 startTime 倒序+ `DtSyncScheduleJob`@Scheduled 每分钟心跳 dt_sync_settings 派生判定发起触发方式=auto
- **组织拉取 SPI执行中用户新增拍板**`OrgSourcePort` 接口fetchDepartments/fetchDeptUsers + 嵌套 Dept/User 值对象+ `DingTalkOrgSource` 实现SDK/HTTP/token 失效重试/解析/限流全迁入DtErrorException 翻译为业务异常+ 装配缝 `crm.auth.org-source`缺省 dingtalk后续接企微/自有 OA 新增实现类标注 @ConditionalOnProperty 即可编排层 `DtOrgSyncServiceImpl` 平台无关化 SDK 依赖
- lastSyncTime`UserStatsDTO.lastSyncTime` 改由 dt_sync_task 最新成功行 endTime 推导
- 错误码 61022 同步设置参数非法 / 61023 已有同步任务运行中 / 61024 同步任务不存在README 已登记权限种子同步任务菜单 @Order(25)
- 测试`DtSyncTaskServiceH2Test`11 用例/ `DtOrgSyncServiceImplTest`5 用例/ `DingTalkOrgSourceTest`4 用例
- bruno 文档 5 文档仓 `A7 后台管理/用户管理/同步任务/`钉钉组织同步改型 / 同步任务分页 / 同步任务详情 / 读取同步设置 / 保存同步设置
**拍板项实际取舍与偏差**
| 票面 | 实际 |
| --- | --- |
| 返任务编号计数改走 detail | 采纳并加强 `DtSyncLaunchDTO{taskId, taskNo}` 两者都给前端轮询 detail id |
| tasks/page POST | 已落实表单绑定筛选ADR-0017 |
| lastSyncTime 直接切换 | 采纳Redis 兼容读取未保留一期无存量调用方同步成功即有任务行 |
| 进度已处理/总数逐条粒度 | 未做阶段粒度 5/45/90/100listener 回报 45/90原型无逐条强诉求 |
| partial_failed 状态 | 枚举保留对齐原型文案一期同步粒度失败即整体 all_failed不产生该状态 |
| 重试规则 | 仅落设置 + 边界校验61022自动重试执行未实现票面 2/6 项未显式要求执行行为 |
| 组织拉取解耦用户新增拍板 | 见实现清单 SPI 条目独立接口多实现不写死钉钉 |
"""
ans = ANSWER.encode('utf-8').replace(b'\n', nl)
if not raw.endswith(nl):
raw += nl
raw += ans
open(ticket, 'wb').write(raw)
raw2 = open(ticket, 'rb').read()
assert b'Status: resolved' in raw2 and b'## Answer' in raw2 and b'OrgSourcePort' in raw2
assert not raw2.startswith(b'\xef\xbb\xbf')
print('ticket01 OK bytes=%d' % len(raw2))
# ---------- 2) map.md: Decisions so far 追加一行 ----------
mpath = r'.scratch/a7-heavy/map.md'
raw = open(mpath, 'rb').read()
assert not raw.startswith(b'\xef\xbb\xbf'), 'map has BOM'
nl = b'\r\n' if b'\r\n' in raw else b'\n'
anchor = '执行票 01–05 开放。'.encode('utf-8')
assert raw.count(anchor) == 1, 'map anchor count=%d' % raw.count(anchor)
DECISION = ('- 2026-09-08 票 01 执行落地(resolved):同步任务 5 端点任务化全量实现——契约改型返 '
'`DtSyncLaunchDTO{taskId, taskNo}`、launch 并发占位 61023、单线程池异步、进度 5/45/90/100;'
'**组织拉取抽离 `OrgSourcePort` SPI(执行中用户拍板)**:`DingTalkOrgSource` 实现 + 装配缝 '
'`crm.auth.org-source`(缺省 dingtalk),编排层 `DtOrgSyncServiceImpl` 平台无关化零 SDK 依赖,'
'后续企微/自有 OA 新增实现类即可;lastSyncTime 直接切换 dt_sync_task 最新成功行(Redis 兼容读取未做);'
'重试规则仅落设置+边界校验不实现自动重试;partial_failed 枚举保留但不产生;'
'错误码 61022-61024、权限种子 @Order(25)、bruno 5 份落文档仓;验证 crm-auth 全量单测 EXIT=0 + BOM 0。').encode('utf-8')
raw = raw.replace(anchor, anchor + nl + DECISION, 1)
open(mpath, 'wb').write(raw)
raw2 = open(mpath, 'rb').read()
assert '票 01 执行落地'.encode('utf-8') in raw2 and not raw2.startswith(b'\xef\xbb\xbf')
print('map.md OK bytes=%d' % len(raw2))