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.
430 lines
22 KiB
430 lines
22 KiB
"""票 03 — 修复运行时验证(D-13 四路径 owner_dept_id + D-10/11/12 三族编辑发布中 + U03 生效层)。
|
|
前置:服务运行中(含修复 jar);存量回填已 apply(affected=13,残留 0)。
|
|
复用 e2e-rules.py 的 Sess 封装模式(中文 body 用 Python requests;Result<Void> data=null 非失败)。
|
|
账号:ADMIN 罗伟健 / A 赖永利 / B 肖琴 / C 曾偲青(dept 映射开头 DB 直查打印)。
|
|
风险控制:三族自建规则测完即删草稿/停用发布中;U03 部门专用规则不占默认位、测完停用;
|
|
不碰种子模板(OPP_STAGE_TPL_01)/兜底方案卡/e2e-seed公海规则。
|
|
用法:python t03-verify.py [B|A|U|CLEANUP ...] # 无参全量
|
|
"""
|
|
import io, json, os, sys
|
|
from datetime import datetime
|
|
import requests
|
|
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8')
|
|
|
|
BASE = 'http://localhost:8080'
|
|
UID = {'ADMIN': '739564171091247104', # 罗伟健 管理员
|
|
'A': '744842565802524672', # 赖永利
|
|
'B': '744842566742048768', # 肖琴
|
|
'C': '744842318024015872'} # 曾偲青
|
|
TAG = 't03-' + datetime.now().strftime('%H%M%S')
|
|
|
|
results, fails = [], []
|
|
|
|
def rec(case, ok, ev=''):
|
|
v = '✅' if ok else '❌'
|
|
results.append((case, ok, ev))
|
|
if not ok:
|
|
fails.append((case, ev))
|
|
print(f" [{v}] {case}" + (f" —— {ev}" if ev else ''))
|
|
|
|
|
|
class Sess:
|
|
def __init__(self, uid):
|
|
r = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': uid}, timeout=15)
|
|
b = r.json()
|
|
assert b.get('code') == 0, f'debug token 失败 {b}'
|
|
self.h = {'Authorization': f"Bearer {b['data']}"}
|
|
|
|
def raw(self, method, path, form=None, params=None, _retry=True):
|
|
headers = dict(self.h)
|
|
data = None
|
|
if form is not None:
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
data = form
|
|
r = requests.request(method, BASE + path, headers=headers, data=data, params=params, timeout=30)
|
|
if r.status_code == 401 and _retry:
|
|
r = requests.request(method, BASE + path, headers=headers, data=data, params=params, timeout=30)
|
|
r.encoding = 'utf-8'
|
|
return r
|
|
|
|
def api(self, method, path, form=None, params=None, step=''):
|
|
try:
|
|
b = self.raw(method, path, form, params).json()
|
|
except Exception as e:
|
|
print(f" [EXC] {step or path}: {e}")
|
|
return None
|
|
if b.get('code') != 0:
|
|
print(f" [ERR] {step or path}: code={b.get('code')} msg={b.get('message')}")
|
|
return None
|
|
return b.get('data')
|
|
|
|
def api_void(self, method, path, form=None, params=None, step=''):
|
|
try:
|
|
b = self.raw(method, path, form, params).json()
|
|
except Exception as e:
|
|
print(f" [EXC] {step or path}: {e}")
|
|
return False
|
|
if b.get('code') != 0:
|
|
print(f" [ERR] {step or path}: code={b.get('code')} msg={b.get('message')}")
|
|
return False
|
|
return True
|
|
|
|
|
|
SES = {}
|
|
def sess(k):
|
|
if k not in SES:
|
|
SES[k] = Sess(UID[k])
|
|
return SES[k]
|
|
|
|
|
|
def db():
|
|
import pymysql
|
|
return pymysql.connect(host='8.129.84.155', port=3306, user='root', password='Itc@123456',
|
|
database='crm', charset='utf8mb4', autocommit=True)
|
|
|
|
def db_one(sql, args=None):
|
|
conn = db()
|
|
try:
|
|
cur = conn.cursor()
|
|
cur.execute(sql, args or ())
|
|
return cur.fetchone()
|
|
finally:
|
|
conn.close()
|
|
|
|
def opp_dept(opp_id):
|
|
row = db_one('SELECT owner_user_id, owner_dept_id FROM opportunity WHERE id=%s', (int(opp_id),))
|
|
return (str(row[0]) if row and row[0] else None,
|
|
str(row[1]) if row and row[1] else None)
|
|
|
|
|
|
ST_PATH = '/api/rule/opp-stage-template'
|
|
SC_PATH = '/api/rule/opp-scheme-template'
|
|
PR_PATH = '/api/rule/opp-pool-rule'
|
|
|
|
def form_nodes(items):
|
|
f = {}
|
|
for i, n in enumerate(items):
|
|
for k, v in n.items():
|
|
if v is not None:
|
|
f[f'nodes[{i}].{k}'] = v
|
|
return f
|
|
|
|
def form_fields(items):
|
|
f = {}
|
|
for i, n in enumerate(items):
|
|
for k, v in n.items():
|
|
if v is not None:
|
|
f[f'fields[{i}].{k}'] = v
|
|
return f
|
|
|
|
def page_rows(s, path, keyword=None, size=100):
|
|
form = {'current': 1, 'size': size}
|
|
if keyword:
|
|
form['keyword'] = keyword
|
|
d = s.api('POST', f'{path}/page', form=form, step=f'{path} page')
|
|
return ((d or {}).get('content') or [], int((d or {}).get('total') or 0))
|
|
|
|
def by_name(rows, name, status=None, nmkey=None):
|
|
for r in rows:
|
|
nm = r.get(nmkey) if nmkey else (r.get('templateName') or r.get('ruleName'))
|
|
if nm == name and (status is None or r.get('status') == status):
|
|
return r
|
|
return None
|
|
|
|
|
|
# ================= Part 0 预检 =================
|
|
DEPT = {}
|
|
def part0():
|
|
print('\n== Part 0 预检(账号部门映射 / 回填残留 / 部门专用规则现场) ==')
|
|
conn = db()
|
|
cur = conn.cursor()
|
|
cur.execute('SELECT id, username, dept_id FROM crm_auth_user WHERE id IN (%s,%s,%s,%s)',
|
|
tuple(int(v) for v in UID.values()))
|
|
for uid, uname, dept in cur.fetchall():
|
|
DEPT[str(uid)] = str(dept) if dept else None
|
|
print(f" 账号 {uname} uid={uid} dept_id={dept}")
|
|
conn.close()
|
|
# 管理员罗伟健 crm_auth_user.dept_id=None 是数据现实(管理员无部门),不参与快照断言
|
|
tri = [k for k in UID if k != 'ADMIN']
|
|
rec('预检·A/B/C 部门映射齐备(管理员无部门为数据现实)', all(DEPT.get(UID[k]) for k in tri), f'map={DEPT}')
|
|
row = db_one('SELECT COUNT(*) FROM opportunity WHERE owner_dept_id IS NULL AND owner_user_id IS NOT NULL AND deleted=0')
|
|
rec('预检·存量回填残留(应为 0)', int(row[0]) == 0, f'残留 NULL 行={int(row[0])}')
|
|
a = sess('ADMIN')
|
|
dirty = []
|
|
for path in (ST_PATH, SC_PATH, PR_PATH):
|
|
rows, _ = page_rows(a, path)
|
|
dirty += [(path, r.get('templateName') or r.get('ruleName')) for r in rows
|
|
if r.get('status') == 2 and (r.get('templateName') or r.get('ruleName') or '').startswith('t03-')]
|
|
rec('预检·无 t03- 残留发布中规则', not dirty, f'{dirty or "无"}')
|
|
|
|
|
|
# ================= Part B 四路径 owner_dept_id =================
|
|
def part_b():
|
|
print('\n== Part B D-13 四路径 owner_dept_id(创建 / assign / handover / claim) ==')
|
|
a, b_, c = sess('A'), sess('B'), sess('C')
|
|
deptA, deptB, deptC = DEPT[UID['A']], DEPT[UID['B']], DEPT[UID['C']]
|
|
xa, xb = f'{TAG}-四路径X', f'{TAG}-四路径Y'
|
|
|
|
# B1 创建:服务端按创建人(owner)部门落快照
|
|
xid = a.api('POST', '/api/opportunity',
|
|
form={'opportunityName': xa, 'oppSource': 'opp_source_02', 'industryCode': 'gov',
|
|
'localityType': 'locality_type_01', 'bidForm': 'bid_form_01',
|
|
'partyAClear': 1, 'partyA': '票03甲方X', 'remark': '票03 D-13 四路径验证'},
|
|
step='创建 X')
|
|
if not xid:
|
|
rec('B1 创建→owner_dept_id=A 部门', False, '创建失败,后续中断')
|
|
return
|
|
xid = str(xid)
|
|
ou, od = opp_dept(xid)
|
|
det = a.api('GET', '/api/opportunity/detail', params={'id': xid}, step='X detail') or {}
|
|
rec('B1 创建→owner_dept_id=A 部门', ou == UID['A'] and od == deptA,
|
|
f"DB=({ou},{od}) 期望=({UID['A']},{deptA}) detail.ownerDeptId={det.get('ownerDeptId')}")
|
|
|
|
# B2 抛池→assign:E2 分配语义=待领取(1)→推进中(2),前置=公海态(跟进中 assign 会 66003)
|
|
ok = a.api_void('POST', '/api/opportunity/release-pool',
|
|
form={'id': xid, 'poolReason': 'pool_reason_02'}, step='X 抛池进公海')
|
|
ok = ok and a.api_void('POST', '/api/opportunity/assign', form={'id': xid, 'userId': UID['B']}, step='assign B')
|
|
ou, od = opp_dept(xid)
|
|
rec('B2 抛池→assign→B:快照跟人走', ok and ou == UID['B'] and od == deptB,
|
|
f"DB=({ou},{od}) 期望=({UID['B']},{deptB})")
|
|
|
|
# B3 handover:CAS 守卫 expectedOwnerUserId=B;ownerNameSnapshot 传参已删签名(ADR-0017 静默忽略)
|
|
ok = b_.api_void('POST', '/api/opportunity/handover',
|
|
form={'id': xid, 'expectedOwnerUserId': UID['B'], 'newOwnerUserId': UID['C'],
|
|
'ownerNameSnapshot': '残留参数应被忽略'},
|
|
step='handover B→C')
|
|
ou, od = opp_dept(xid)
|
|
rec('B3 handover→C:部门完整过户', ok and ou == UID['C'] and od == deptC,
|
|
f"DB=({ou},{od}) 期望=({UID['C']},{deptC})")
|
|
|
|
# B4 抛公海 → claim:新负责人 B 部门
|
|
yid = a.api('POST', '/api/opportunity',
|
|
form={'opportunityName': xb, 'oppSource': 'opp_source_02', 'industryCode': 'gov',
|
|
'localityType': 'locality_type_01', 'bidForm': 'bid_form_01',
|
|
'partyAClear': 1, 'partyA': '票03甲方Y', 'remark': '票03 D-13 claim 路径'},
|
|
step='创建 Y')
|
|
if not yid:
|
|
rec('B4 release-pool→claim→快照=B 部门', False, 'Y 创建失败')
|
|
return
|
|
yid = str(yid)
|
|
r = a.raw('POST', '/api/opportunity/release-pool', form={'id': yid, 'poolReason': 'pool_reason_02'})
|
|
bb = r.json()
|
|
if bb.get('code') != 0:
|
|
rec('B4 release-pool→claim→快照=B 部门', False, f"release-pool 被拦: code={bb.get('code')} msg={bb.get('message')}")
|
|
return
|
|
ok = b_.api_void('POST', '/api/opportunity/claim', params={'id': yid}, step='B claim')
|
|
ou, od = opp_dept(yid)
|
|
rec('B4 release-pool→claim→快照=B 部门', ok and ou == UID['B'] and od == deptB,
|
|
f"DB=({ou},{od}) 期望=({UID['B']},{deptB})")
|
|
|
|
|
|
# ================= Part A 三族编辑发布中→新草稿(不 50001) =================
|
|
def edit_published(s, path, pub_id, form, code_key, nmkey, name):
|
|
"""对发布中行 save-draft(带 id)→ 断言新草稿生成且不撞 50001。返回 (ok, ev)"""
|
|
r = s.raw('POST', f'{path}/save-draft', form=form)
|
|
b = r.json()
|
|
if b.get('code') != 0:
|
|
return False, f"编辑发布中被拒: code={b.get('code')} msg={b.get('message')}"
|
|
rows, _ = page_rows(s, path, keyword=name)
|
|
new = by_name(rows, name, status=1, nmkey=nmkey)
|
|
if not new:
|
|
return False, 'save-draft code=0 但找不到新草稿'
|
|
d = s.api('GET', f'{path}/detail', params={'id': new['id']}, step='新草稿 detail') or {}
|
|
ev = (f"newId={new['id']} ≠ srcId={pub_id};{code_key}={d.get(code_key)}(不变);"
|
|
f"versionNo={d.get('versionNo')}(V1.0→顺延);status={d.get('status')}(1=草稿)")
|
|
ok = (str(new['id']) != str(pub_id)) and d.get('versionNo') == 'V1.1' and d.get('status') == 1
|
|
return ok, ev
|
|
|
|
def part_a():
|
|
print('\n== Part A D-10/11/12 三族「编辑发布中→新草稿」实测(不再 50001) ==')
|
|
a = sess('ADMIN')
|
|
|
|
# --- PR 公海规则 ---
|
|
print(' -- PR 公海规则 --')
|
|
name = f'{TAG}-PR规则'
|
|
srow = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=1, nmkey='ruleName')
|
|
ok = a.api_void('POST', f'{PR_PATH}/save-draft',
|
|
form={'ruleName': name, 'applyScope': 1, 'isDefault': 0, 'ruleDesc': '票03',
|
|
'allowManualPool': 1, 'allowFreeClaim': 1, 'autoRecycleEnabled': 1,
|
|
'recycleDays': 30, 'recycleRemindEnabled': 1, 'remindDays': 7}, step='PR 新建草稿')
|
|
srow = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=1, nmkey='ruleName')
|
|
a.api_void('POST', f'{PR_PATH}/publish',
|
|
form={'id': (srow or {}).get('id'), 'ruleName': name, 'applyScope': 1, 'isDefault': 0,
|
|
'allowManualPool': 1, 'allowFreeClaim': 1, 'autoRecycleEnabled': 1,
|
|
'recycleDays': 30, 'recycleRemindEnabled': 1, 'remindDays': 7}, step='PR publish')
|
|
prow = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=2, nmkey='ruleName')
|
|
if prow:
|
|
f = {'id': prow['id'], 'ruleName': name, 'applyScope': 1, 'isDefault': 0,
|
|
'allowManualPool': 0, 'allowFreeClaim': 0, 'autoRecycleEnabled': 1,
|
|
'recycleDays': 15, 'recycleRemindEnabled': 1, 'remindDays': 3}
|
|
ok, ev = edit_published(a, PR_PATH, prow['id'], f, 'ruleCode', 'ruleName', name)
|
|
rec('PR 编辑发布中→新草稿(不 50001,V1.1 顺延)', ok, ev)
|
|
nd = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=1, nmkey='ruleName')
|
|
if nd:
|
|
a.api_void('POST', f'{PR_PATH}/delete', params={'id': nd['id']}, step='PR 删新草稿')
|
|
a.api_void('POST', f'{PR_PATH}/disable', params={'id': prow['id']}, step='PR 停用发布中')
|
|
else:
|
|
rec('PR 编辑发布中→新草稿(不 50001,V1.1 顺延)', False, 'publish 未生成发布中行')
|
|
|
|
# --- ST 阶段模板 ---
|
|
print(' -- ST 阶段模板 --')
|
|
name = f'{TAG}-ST模板'
|
|
nodes = [{'stageDictCode': 'OPP_STAGE_01', 'workGoal': '需求确认'},
|
|
{'stageDictCode': 'OPP_STAGE_02', 'customNodeName': '方案深化'}]
|
|
a.api_void('POST', f'{ST_PATH}/save-draft',
|
|
form={'templateName': name, 'applyScope': 1, 'isDefault': 0, 'templateDesc': '票03',
|
|
**form_nodes(nodes)}, step='ST 新建草稿')
|
|
srow = by_name(page_rows(a, ST_PATH, keyword=name)[0], name, status=1)
|
|
a.api_void('POST', f'{ST_PATH}/publish',
|
|
form={'id': (srow or {}).get('id'), 'templateName': name, 'applyScope': 1, 'isDefault': 0,
|
|
**form_nodes(nodes)}, step='ST publish')
|
|
prow = by_name(page_rows(a, ST_PATH, keyword=name)[0], name, status=2)
|
|
if prow:
|
|
f = {'id': prow['id'], 'templateName': name, 'applyScope': 1, 'isDefault': 0,
|
|
**form_nodes([{'stageDictCode': 'OPP_STAGE_01', 'workGoal': '需求确认(改)'},
|
|
{'stageDictCode': 'OPP_STAGE_02'},
|
|
{'stageDictCode': 'OPP_STAGE_03'},
|
|
{'stageDictCode': 'OPP_STAGE_04'}])}
|
|
ok, ev = edit_published(a, ST_PATH, prow['id'], f, 'templateCode', 'templateName', name)
|
|
rec('ST 编辑发布中→新草稿(不 50001,V1.1 顺延)', ok, ev)
|
|
nd = by_name(page_rows(a, ST_PATH, keyword=name)[0], name, status=1)
|
|
if nd:
|
|
a.api_void('POST', f'{ST_PATH}/delete', params={'id': nd['id']}, step='ST 删新草稿')
|
|
a.api_void('POST', f'{ST_PATH}/disable', params={'id': prow['id']}, step='ST 停用发布中')
|
|
else:
|
|
rec('ST 编辑发布中→新草稿(不 50001,V1.1 顺延)', False, 'publish 未生成发布中行')
|
|
|
|
# --- SC 方案卡模板 ---
|
|
print(' -- SC 方案卡模板 --')
|
|
name = f'{TAG}-SC模板'
|
|
defs = a.api('GET', f'{SC_PATH}/field-defs', step='field-defs') or []
|
|
f3 = [f.get('fieldKey') for f in defs[:3]]
|
|
fields = [{'fieldKey': f3[0], 'isVisible': 1, 'isRequired': 1},
|
|
{'fieldKey': f3[1], 'isVisible': 1, 'isRequired': 0},
|
|
{'fieldKey': f3[2], 'isVisible': 0}]
|
|
a.api_void('POST', f'{SC_PATH}/save-draft',
|
|
form={'templateName': name, 'applyScope': 1, 'isDefault': 0, 'templateDesc': '票03',
|
|
**form_fields(fields)}, step='SC 新建草稿')
|
|
srow = by_name(page_rows(a, SC_PATH, keyword=name)[0], name, status=1)
|
|
a.api_void('POST', f'{SC_PATH}/publish',
|
|
form={'id': (srow or {}).get('id'), 'templateName': name, 'applyScope': 1, 'isDefault': 0,
|
|
**form_fields(fields)}, step='SC publish')
|
|
prow = by_name(page_rows(a, SC_PATH, keyword=name)[0], name, status=2)
|
|
if prow:
|
|
f = {'id': prow['id'], 'templateName': name, 'applyScope': 1, 'isDefault': 0,
|
|
**form_fields([{'fieldKey': f3[0], 'isVisible': 1, 'isRequired': 1},
|
|
{'fieldKey': f3[1], 'isVisible': 1, 'isRequired': 1}])}
|
|
ok, ev = edit_published(a, SC_PATH, prow['id'], f, 'templateCode', 'templateName', name)
|
|
rec('SC 编辑发布中→新草稿(不 50001,V1.1 顺延)', ok, ev)
|
|
nd = by_name(page_rows(a, SC_PATH, keyword=name)[0], name, status=1)
|
|
if nd:
|
|
a.api_void('POST', f'{SC_PATH}/delete', params={'id': nd['id']}, step='SC 删新草稿')
|
|
a.api_void('POST', f'{SC_PATH}/disable', params={'id': prow['id']}, step='SC 停用发布中')
|
|
else:
|
|
rec('SC 编辑发布中→新草稿(不 50001,V1.1 顺延)', False, 'publish 未生成发布中行')
|
|
|
|
|
|
# ================= Part U U03 生效层 =================
|
|
def part_u():
|
|
print('\n== Part U U03 生效层:部门专用 allowManualPool=0 真拦截(修复前恒放行) ==')
|
|
a = sess('ADMIN')
|
|
deptC = DEPT[UID['C']]
|
|
name = f'{TAG}-PR禁手动'
|
|
a.api_void('POST', f'{PR_PATH}/save-draft',
|
|
form={'ruleName': name, 'applyScope': 2, 'isDefault': 0, 'deptIds': deptC,
|
|
'allowManualPool': 0, 'allowFreeClaim': 1, 'autoRecycleEnabled': 0,
|
|
'recycleRemindEnabled': 0}, step='部门专用禁手动草稿')
|
|
drow = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=1, nmkey='ruleName')
|
|
if not drow:
|
|
rec('U03 部门专用规则发布', False, '草稿未找到')
|
|
return
|
|
a.api_void('POST', f'{PR_PATH}/publish',
|
|
form={'id': drow['id'], 'ruleName': name, 'applyScope': 2, 'isDefault': 0, 'deptIds': deptC,
|
|
'allowManualPool': 0, 'allowFreeClaim': 1, 'autoRecycleEnabled': 0,
|
|
'recycleRemindEnabled': 0}, step='publish 部门专用')
|
|
dpub = by_name(page_rows(a, PR_PATH, keyword=name)[0], name, status=2, nmkey='ruleName')
|
|
rec('U03 部门专用规则发布(deptIds 绑定)', bool(dpub), f"deptIds={deptC} allowManualPool=0")
|
|
|
|
# X 的 owner=C(deptC)→ release-pool 应被拦截;对照 Y owner=B 不受影响(B 部门无专用规则)
|
|
conn = db()
|
|
cur = conn.cursor()
|
|
cur.execute("SELECT id, owner_dept_id FROM opportunity WHERE opp_name LIKE %s ORDER BY create_time DESC",
|
|
(f'{TAG}%',))
|
|
t03_opps = cur.fetchall()
|
|
conn.close()
|
|
xid = next((str(r[0]) for r in t03_opps if str(r[1]) == deptC), None)
|
|
yid = next((str(r[0]) for r in t03_opps if str(r[1]) == DEPT[UID['B']]), None)
|
|
if not xid:
|
|
rec('U03 生效层:拦截手动抛公海', False, '找不到 owner=C 部门的 t03 商机(先跑 B)')
|
|
else:
|
|
oc = sess('C') # C 是 X 当前 owner,由其发起抛池
|
|
r = oc.raw('POST', '/api/opportunity/release-pool', form={'id': xid, 'poolReason': 'pool_reason_02'})
|
|
b = r.json()
|
|
if b.get('code') != 0:
|
|
rec('U03 生效层:allowManualPool=0 拦截手动抛公海(修复前恒放行)', True,
|
|
f"被拒: code={b.get('code')} msg={b.get('message')}(修复前 owner_dept_id 恒空不命中部门索引而放行)")
|
|
else:
|
|
rec('U03 生效层:allowManualPool=0 拦截手动抛公海(修复前恒放行)', False,
|
|
'未被拦截!部门索引仍未命中,需回查')
|
|
c_sess = sess('C')
|
|
c_sess.api_void('POST', '/api/opportunity/claim', params={'id': xid}, step='X 领回原位')
|
|
# 对照:B 部门商机不受该部门专用规则影响
|
|
if yid:
|
|
ob = sess('B')
|
|
r = ob.raw('POST', '/api/opportunity/release-pool', form={'id': yid, 'poolReason': 'pool_reason_02'})
|
|
b = r.json()
|
|
if b.get('code') == 0:
|
|
sess('B').api_void('POST', '/api/opportunity/claim', params={'id': yid}, step='Y 领回原位')
|
|
rec('U03 对照:非绑定部门(B)商机不受影响', b.get('code') == 0,
|
|
f"code={b.get('code')} msg={b.get('message')}")
|
|
|
|
a.api_void('POST', f'{PR_PATH}/disable', params={'id': drow['id']}, step='停用部门专用规则(恢复现场)')
|
|
|
|
|
|
# ================= CLEANUP =================
|
|
def cleanup():
|
|
a = sess('ADMIN')
|
|
print('\n== CLEANUP:t03- 自建规则不留脏状态 ==')
|
|
for path, nmkey in ((ST_PATH, 'templateName'), (SC_PATH, 'templateName'), (PR_PATH, 'ruleName')):
|
|
rows, _ = page_rows(a, path, keyword='t03-')
|
|
for r in rows:
|
|
nm = r.get(nmkey) or ''
|
|
if not nm.startswith('t03-'):
|
|
continue
|
|
rid, st = r['id'], r.get('status')
|
|
if st == 2:
|
|
a.api_void('POST', f'{path}/disable', params={'id': rid}, step=f'disable {nm}')
|
|
elif st == 1:
|
|
a.api_void('POST', f'{path}/delete', params={'id': rid}, step=f'delete {nm}')
|
|
dirty = []
|
|
for path, nmkey in ((ST_PATH, 'templateName'), (SC_PATH, 'templateName'), (PR_PATH, 'ruleName')):
|
|
rows, _ = page_rows(a, path, keyword='t03-')
|
|
dirty += [r.get(nmkey) for r in rows if r.get('status') in (1, 2)]
|
|
rec('清理后 t03- 无草稿/发布中残留', not dirty, f'残留={dirty or "无"}')
|
|
|
|
|
|
FLOWS = {'B': part_b, 'A': part_a, 'U': part_u, 'P': part0, 'CLEANUP': cleanup}
|
|
|
|
if __name__ == '__main__':
|
|
want = [x.upper() for x in sys.argv[1:]] or ['P', 'B', 'A', 'U', 'CLEANUP']
|
|
if 'P' in want:
|
|
part0()
|
|
if 'B' in want:
|
|
part_b()
|
|
if 'A' in want:
|
|
part_a()
|
|
if 'U' in want:
|
|
part_u()
|
|
if 'CLEANUP' in want:
|
|
cleanup()
|
|
print(f'\n===== 汇总:✅ {sum(1 for r in results if r[1])} / ❌ {sum(1 for r in results if not r[1])} =====')
|
|
if fails:
|
|
print('失败项:')
|
|
for c, ev in fails:
|
|
print(f' ❌ {c} —— {ev}')
|
|
base = os.path.dirname(os.path.abspath(__file__))
|
|
with open(os.path.join(base, 't03-verify-result.json'), 'w', encoding='utf-8') as fp:
|
|
json.dump([{'case': c, 'ok': ok, 'evidence': ev} for c, ok, ev in results], fp, ensure_ascii=False, indent=1)
|
|
print('结果已写 t03-verify-result.json')
|
|
|