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.
75 lines
5.5 KiB
75 lines
5.5 KiB
# -*- 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 落地(resolved)。10 项需求全部实现,验收基线全过:`mvn test -pl crm-auth -am` 全量单测 EXIT=0、BOM 扫描 0、编译过。
|
|
|
|
**实现清单**
|
|
|
|
- 表/实体:`dt_sync_task`(任务编号 `SYNC-MMdd-序号`/触发方式/执行状态/起止时间/部门·人员·异常三计数/进度/失败明细 JSON)+ `dt_sync_settings` 单例行(启停/每日执行时间/重试间隔·次数/并发/最近修改人·时间)。
|
|
- 端点 5 个(`SyncTaskController`,全 ADMIN,深 tag「A7 后台管理/用户管理/同步任务」):POST /sync/dingtalk(任务化)、POST /sync/tasks/page、GET /sync/tasks/detail?id=、GET /sync/settings、POST /sync/settings。
|
|
- 服务:`DtSyncTaskServiceImpl`(launch 并发占位→61023、单线程 daemon 池 dt-sync-task 异步执行、进度 5/45/90/100、settings 边界校验→61022、page 默认 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/100(listener 回报 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))
|
|
|