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.
 
 
 
 
 

195 lines
8.6 KiB

# -*- coding: utf-8 -*-
"""票 06 · (d) 类修复主脚本:错误码节回捞 + createTime 行补齐 + 详情公共头部截断修复 + jobTitleLevel 值域。
纪律:说明文字单一真源 = 源码 @Schema(jobTitleLevel 源码注解由独立编辑先行回写,本脚本写文档侧)。
产物:直接写回 D:/code/crm-api-docs 工作树(不提交),全部动作逐文件打印可审计。
"""
from __future__ import annotations
import io
import re
import subprocess
import sys
from pathlib import Path
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
DOCS = Path('D:/code/crm-api-docs')
A4 = DOCS / 'A4 客户管理'
WORKLIST = Path('.scratch/customer-integration-ready/assets/doc-gap-worklist.md')
report: list[str] = []
def log(msg):
print(msg)
report.append(msg)
# ---------- 0) 解析 worklist:错误码丢失清单 ----------
wl = WORKLIST.read_text(encoding='utf-8').splitlines()
ec_files: list[str] = []
for line in wl:
if not line.startswith('| 客户'):
continue
cols = [c.strip() for c in line.split('|')[1:-1]]
if len(cols) >= 7 and '错误码丢失' in cols[6]:
ec_files.append(cols[0])
log(f'[0] worklist 错误码丢失标注 {len(ec_files)} 文件')
# ---------- 1) 错误码节回捞 ----------
VALID_CODE = re.compile(r'\b\d{5}\b')
recovered = skipped = missing_head = 0
for rel in ec_files:
wt = A4 / rel
txt = wt.read_text(encoding='utf-8')
if '## 错误码' in txt:
log(f'[1] 跳过(现版已有错误码节): {rel}')
skipped += 1
continue
git_path = f'A4 客户管理/{rel}'
proc = subprocess.run(['git', '-C', str(DOCS), 'show', f'HEAD:{git_path}'],
capture_output=True)
head = proc.stdout.decode('utf-8', errors='replace')
lines = head.splitlines()
try:
start = next(i for i, l in enumerate(lines) if re.match(r'^\s*## 错误码', l))
except StopIteration:
log(f'[1] ⚠ HEAD 无错误码节: {rel}')
missing_head += 1
continue
end = start + 1
while end < len(lines) and not re.match(r'^\s*## ', lines[end]):
end += 1
sec = lines[start:end]
# 尾部空行并入(下一段标题前的空行不带走)
while sec and sec[-1].strip() == '':
sec.pop()
codes = sorted(set(VALID_CODE.findall('\n'.join(sec))))
anchor = ' ## 响应示例'
if anchor not in txt:
log(f'[1] ⚠ 工作树无响应示例锚点: {rel}')
missing_head += 1
continue
insert = '\n' + '\n'.join(sec) + '\n\n'
txt = txt.replace(anchor, insert + anchor, 1)
wt.write_text(txt, encoding='utf-8', newline='')
recovered += 1
log(f'[1] 回捞 {len(sec)} 行 错误码节 codes={codes}: {rel}')
log(f'[1] 错误码回捞:恢复 {recovered},现版已有 {skipped},异常 {missing_head}')
# ---------- 2) createTime 行补齐 ×9 ----------
CT_PLAN = [ # (文件, 形态, 说明=源码 @Schema 原文)
('客户交割/交接记录分页.bru', 'content', '发起时间'),
('客户交割/交接详情.bru', 'flat', '发起时间'),
('客户交割/发起交接.bru', 'flat', '发起时间'),
('客户公海/任务结果.bru', 'flat', '提交时间'),
('我的客户/客户导入/任务结果.bru', 'flat', '提交时间'),
('客户详情/联系人导入/任务结果.bru', 'flat', '提交时间'),
('客户公海/最近导入记录.bru', 'content', '提交时间'),
('我的客户/客户导入/最近导入记录.bru', 'content', '提交时间'),
('客户详情/联系人导入/最近导入记录.bru', 'content', '提交时间'),
]
ct_done = 0
for rel, form, desc in CT_PLAN:
wt = A4 / rel
txt = wt.read_text(encoding='utf-8')
prefix = 'content[].' if form == 'content' else ''
row = f' | {prefix}createTime | LocalDateTime | {desc} |'
if row in txt:
log(f'[2] 跳过(已有): {rel}')
continue
lines = txt.splitlines()
# 数据结构表范围:## 响应 data 结构 → ## 响应示例 之间
try:
t0 = next(i for i, l in enumerate(lines) if l.strip() == '## 响应 data 结构')
except StopIteration:
log(f'[2] ⚠ 无响应 data 结构节: {rel}')
continue
t1 = next((i for i in range(t0 + 1, len(lines)) if lines[i].strip() == '## 响应示例'), len(lines))
# 锚点:transfer=状态行后;import=creatorName 行后
field = 'status' if desc == '发起时间' else 'creatorName'
anchor_idx = None
for i in range(t0 + 1, t1):
m = re.match(rf'^\s*\|\s*(content\[\]\.)?{field}\s*\|', lines[i])
if m:
anchor_idx = i
break
if anchor_idx is None:
log(f'[2] ⚠ 未找到锚点行 {field}: {rel}')
continue
lines.insert(anchor_idx + 1, row)
wt.write_text('\n'.join(lines) + ('\n' if txt.endswith('\n') else ''), encoding='utf-8', newline='')
ct_done += 1
log(f'[2] 补行 {row.strip()} @ {rel}(锚点 {field} 后)')
log(f'[2] createTime 补行 {ct_done}/9')
# ---------- 3) 详情公共头部 7 行截断修复(原文=源码 @Schema) ----------
DH = A4 / '客户详情/详情公共头部.bru'
DH_ROWS = {
'opportunityCount': '关联商机数(外部反查,失败场景前端显 "--"',
'projectCount': '关联项目数(A5 项目 seam,恒 null 显 "--"',
'wonProjectAmount': '汇总卡①已中标项目总额(占位恒 null:A5 项目/投标 seam 未接入,显 "--"',
'planEstimateAmount': '汇总卡②跟进中方案预估总额(占位恒 null:方案卡 seam 未接入,显 "--"',
'ongoingOpportunityAmount': '汇总卡③进行中商机总额(占位恒 null:TODO 可经 CustomerOpportunityQueryPort 扩展汇总查询后切真值,显 "--"',
'contractAmount': '汇总卡④合同总额(占位恒 null:合同模块未建,显 "--"',
'paidAmount': '汇总卡④累计回款(占位恒 null:合同/回款模块未建,与合同总额同卡,显 "--"',
}
txt = DH.read_text(encoding='utf-8')
fixed = 0
for field, desc in DH_ROWS.items():
pat = re.compile(rf'^(\s*\|\s*{field}\s*\|\s*\w+\s*\|).*$', re.M)
m = pat.search(txt)
if not m:
log(f'[3] ⚠ 未找到行: {field}')
continue
if '"--"' in m.group(0):
log(f'[3] 跳过(已完整): {field}')
continue
txt = pat.sub(lambda mm: f'{mm.group(1)} {desc} |', txt, count=1)
fixed += 1
DH.write_text(txt, encoding='utf-8', newline='')
log(f'[3] 详情公共头部截断行修复 {fixed}/7')
# ---------- 4) jobTitleLevel 值域 ×3(说明文字=源码 @Schema 新文本) ----------
LEVEL_DESC = '职务层级(等级真值 1–10,可空,null=未设置等级落右侧栏)'
JT_FILES = ['联系人/联系人详情.bru', '联系人/联系人全局搜索.bru', '联系人/顶级联系人列表.bru']
jt_done = 0
for rel in JT_FILES:
wt = A4 / rel
txt = wt.read_text(encoding='utf-8')
pat = re.compile(rf'^(\s*\|\s*(\[\]\.)?(content\[\]\.)?jobTitleLevel\s*\|\s*Integer\s*\|)\s*职务层级(可空)\s*\|$', re.M)
if LEVEL_DESC in txt:
log(f'[4] 跳过(已有值域): {rel}')
continue
new_txt, n = pat.subn(lambda mm: f'{mm.group(1)} {LEVEL_DESC} |', txt)
if n == 0:
log(f'[4] ⚠ 未匹配 jobTitleLevel 行: {rel}')
continue
wt.write_text(new_txt, encoding='utf-8', newline='')
jt_done += 1
log(f'[4] 值域补全 ×{n} 行: {rel}')
log(f'[4] jobTitleLevel 值域 {jt_done}/3')
# ---------- 5) 验证 ----------
log('== 验证 ==')
n_ec = sum(1 for f in A4.rglob('*.bru') if f.name != 'folder.bru' and '## 错误码' in f.read_text(encoding='utf-8'))
log(f'[5] 含错误码节 .bru = {n_ec}(目标 ≥ 78)')
dh_txt = (A4 / '客户详情/详情公共头部.bru').read_text(encoding='utf-8')
dangle = [l for l in dh_txt.splitlines() if l.rstrip().endswith('\\ |') or l.rstrip().endswith('\\')]
log(f'[5] 详情公共头部 dangling 行 = {len(dangle)}')
bad_ct = [rel for rel, _, _ in CT_PLAN
if not any(('createTime' in l and l.startswith(' | '))
for l in (A4 / rel).read_text(encoding='utf-8').splitlines())]
log(f'[5] createTime 仍缺 = {bad_ct}')
bad_jt = [rel for rel in JT_FILES if LEVEL_DESC not in (A4 / rel).read_text(encoding='utf-8')]
log(f'[5] jobTitleLevel 仍缺 = {bad_jt}')
# BOM 扫描(本脚本触碰的文件)
touched = set(ec_files) | {r for r, _, _ in CT_PLAN} | {'客户详情/详情公共头部.bru'} | set(JT_FILES)
bom = []
for rel in sorted(touched):
p = A4 / rel
if p.exists() and p.read_bytes()[:3] == b'\xef\xbb\xbf':
bom.append(rel)
log(f'[5] BOM = {bom if bom else ""}')
log('== 票 06 脚本完成 ==')