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.
361 lines
20 KiB
361 lines
20 KiB
# -*- coding: utf-8 -*-
|
|
"""demo_showcase.py — 用户 demo 验收演示数据(拟真,非 e2c- 测试名)
|
|
|
|
设计:
|
|
- 19 户拟真客户(15 admin 名下 + 3 公海 + 1 已归档),多行业/多地区/三阶段/星级分布
|
|
- 联系人 30+、跟进 30+(follow_way 真实字典值)、拟真商机 4 个并绑定客户、关注/重点/成员样例
|
|
- 幂等 = 按内置名单先清后造;清理范围仅本脚本名单,不碰 e2c-/e2e- 命名空间
|
|
- 中文 body 一律 Python requests(坑㉑);跟进 nextFollowTime ISO T 分隔
|
|
"""
|
|
import sys, io, json, os, argparse
|
|
from datetime import datetime, timedelta
|
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
import requests
|
|
import pymysql
|
|
|
|
ap = argparse.ArgumentParser(description='验收演示数据造数(可指向测试环境)')
|
|
ap.add_argument('--base', default='http://localhost:8080', help='API 根地址(测试环境如 http://<测试服>:8080)')
|
|
ap.add_argument('--admin', default='739564171091247104', help='主用户 userId(默认罗伟健开发库 id;测试库不同时传测试库真实 userId)')
|
|
ap.add_argument('--buddy', default='744842318024015872', help='协同用户 userId(同上)')
|
|
ap.add_argument('--db-host', default='8.129.84.155', help='DB 主机(--skip-db 时忽略)')
|
|
ap.add_argument('--db-port', type=int, default=3306)
|
|
ap.add_argument('--db-user', default='root')
|
|
ap.add_argument('--db-pass', default='Itc@123456')
|
|
ap.add_argument('--db-name', default='crm')
|
|
ap.add_argument('--skip-db', action='store_true', help='DB 不可达时:跳过清理/stage 直拨/库检(全部潜在客户;重跑前需手工清名单)')
|
|
A = ap.parse_args()
|
|
BASE = A.base
|
|
ADMIN = A.admin
|
|
BUDDY = A.buddy
|
|
HAS_DB = not A.skip_db
|
|
IDS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'showcase-ids.json')
|
|
|
|
CONF = dict(host=A.db_host, port=A.db_port, user=A.db_user, password=A.db_pass,
|
|
database=A.db_name, charset='utf8mb4', autocommit=True, connect_timeout=10,
|
|
cursorclass=pymysql.cursors.DictCursor) if HAS_DB else None
|
|
|
|
|
|
def dbq(sql, args=None, fetch=True):
|
|
assert CONF, 'dbq 调用于 --skip-db 模式(不应发生)'
|
|
with pymysql.connect(**CONF) as conn, conn.cursor() as cur:
|
|
cur.execute(sql, args)
|
|
return cur.fetchall() if fetch else cur.rowcount
|
|
|
|
|
|
def get_token(uid):
|
|
d = requests.get(f'{BASE}/api/auth/debug/token', params={'userId': uid}, timeout=30).json().get('data')
|
|
return d if isinstance(d, str) else (d or {}).get('token')
|
|
|
|
|
|
S = requests.Session()
|
|
S.headers['Authorization'] = f'Bearer {get_token(ADMIN)}'
|
|
|
|
|
|
def api(method, path, form=None, params=None, step=''):
|
|
try:
|
|
r = S.post(BASE + path, data=form, params=params, timeout=30) if method == 'POST' else \
|
|
S.put(BASE + path, data=form, params=params, timeout=30) if method == 'PUT' else \
|
|
S.request(method, BASE + path, params=params, timeout=30)
|
|
except Exception as e:
|
|
print(f' x {step}: 网络 {e}'); return None
|
|
if r.status_code != 200:
|
|
print(f' x {step}: HTTP {r.status_code} {r.text[:150]}'); return None
|
|
b = r.json()
|
|
if b.get('code') not in (0, '0', 200, '200'):
|
|
print(f' x {step}: code={b.get("code")} {str(b.get("message"))[:150]}'); return None
|
|
return b.get('data')
|
|
|
|
|
|
fails = []
|
|
def need(cond, label):
|
|
if not cond:
|
|
fails.append(label); print(f' x 校验: {label}')
|
|
|
|
|
|
# ---------------- 0. 字典/地区 ----------------
|
|
print('== 0. 字典与地区 ==')
|
|
if HAS_DB:
|
|
CTYPES = [r['code'] for r in dbq(
|
|
"SELECT i.code FROM dict_item i JOIN dict_group g ON i.group_id=g.id "
|
|
"WHERE g.code='customer_type' AND i.deleted=0 ORDER BY i.sort_no")]
|
|
FOLLOW_WAY = [r['code'] for r in dbq(
|
|
"SELECT i.code FROM dict_item i JOIN dict_group g ON i.group_id=g.id "
|
|
"WHERE g.code='follow_way' AND i.deleted=0 ORDER BY i.sort_no")]
|
|
INDUSTRIES = [r['code'] for r in dbq(
|
|
"SELECT i.code FROM dict_item i JOIN dict_group g ON i.group_id=g.id "
|
|
"WHERE g.code='industry' AND i.deleted=0 AND i.parent_id IS NULL ORDER BY i.sort_no")]
|
|
else: # DB 不可达 → 字典走 API
|
|
def codes_api(group):
|
|
d = api('GET', '/api/dict/item/enabled-list', params={'groupCode': group}, step=f'dict {group}')
|
|
return [x.get('code') for x in (d or []) if x.get('code')]
|
|
CTYPES = codes_api('customer_type')
|
|
FOLLOW_WAY = codes_api('follow_way')
|
|
INDUSTRIES = codes_api('industry')
|
|
need(CTYPES and FOLLOW_WAY and INDUSTRIES, '字典组非空')
|
|
|
|
REGIONS = [ # (省, 市, 区) 国标码;先对 sys_region 验证,缺的回退广州荔湾
|
|
('440000', '440100', '440103'), ('440000', '440300', '440305'),
|
|
('330000', '330100', '330106'), ('510000', '510100', '510107'),
|
|
('420000', '420100', '420111'), ('610000', '610100', '610113'),
|
|
('320000', '320100', '320102'), ('441900', '441900', '441900005'),
|
|
('440600', '440600', '440604'), ('350200', '350200', '350203'),
|
|
('370200', '370200', '370212'), ('430100', '430100', '430104')]
|
|
valid = {r['code'] for r in dbq("SELECT code FROM sys_region WHERE deleted=0")} if HAS_DB else None
|
|
REGIONS = [r if (valid is None or all(c in valid for c in r)) else ('440000', '440100', '440103') for r in REGIONS]
|
|
print(f' customer_type={len(CTYPES)} follow_way={len(FOLLOW_WAY)} industry={len(INDUSTRIES)} 地区 {len(REGIONS)} 组{"有效" if HAS_DB else "(未校验,--skip-db)"}')
|
|
|
|
# ---------------- 1. 演示数据定义 ----------------
|
|
# (名称, 行业idx, 地区idx, 类型idx, 客户星, 关系星, 商务谈判)
|
|
FIRMS = [
|
|
('广州晨曦医疗器械有限公司', 0, 0, 1, 5, 4, 1),
|
|
('深圳蓝湾冷链供应链有限公司', 1, 1, 2, 4, 3, 0),
|
|
('杭州云栖软件技术有限公司', 2, 2, 0, 5, 5, 1),
|
|
('成都锦官建材集团有限公司', 3, 3, 3, 3, 2, 0),
|
|
('武汉江城物流股份有限公司', 4, 4, 4, 4, 3, 1),
|
|
('西安大唐文旅发展有限公司', 5, 5, 5, 2, 2, 0),
|
|
('广州珠江光电科技有限公司', 0, 0, 6, 4, 4, 1),
|
|
('深圳前海惠金信息服务有限公司', 7, 1, 7, 3, 2, 0),
|
|
('杭州西湖茶业有限公司', 8, 2, 8, 1, 1, 0),
|
|
('成都熊猫慢递文化传播有限公司', 9, 3, 9, 3, 3, 0),
|
|
('武汉光谷生物医药有限公司', 10, 4, 10, 4, 2, 0),
|
|
('广州白云机场快运有限公司', 4, 0, 11, 5, 3, 1),
|
|
('南京金陵楼宇物业管理有限公司', 11, 6, 12, 2, 2, 0),
|
|
('东莞松山湖精密制造有限公司', 12, 7, 13, 4, 3, 0),
|
|
('佛山季华铝业有限公司', 13, 8, 14, 3, 2, 0),
|
|
]
|
|
POOLS = [ # (名称, 行业idx, 地区idx)——无主进公海
|
|
('厦门鹭岛酒店用品有限公司', 14, 9),
|
|
('青岛海诺环保工程有限公司', 15, 10),
|
|
('长沙湘江智能科技有限公司', 16, 11),
|
|
]
|
|
ARCHIVED = ('天津渤海贸易有限公司', 17, 0)
|
|
STAGE2 = {1, 4, 6, 9, 13} # 重潜(DB 直拨)
|
|
STAGE3 = {2, 11} # 已成交:云栖软件 / 白云机场快运
|
|
# 统一为 7 元组(名称, 行业, 地区, 类型, 星, 关系星, 商务谈判)
|
|
EXTRA7 = [(nm, ind, reg, 0, 1, 1, 0) for nm, ind, reg in POOLS] + [(ARCHIVED[0], ARCHIVED[1], ARCHIVED[2], 0, 1, 1, 0)]
|
|
|
|
CONTACTS = [ # (客户idx, 姓名, 职务, 手机, 关键, 内线, 礼品备注)
|
|
(0, '陈志强', '总经理', '13802000001', 1, 1, '偏好茶叶'),
|
|
(0, '林晓婷', '采购总监', '13802000002', 1, 0, ''),
|
|
(0, '黄伟杰', '设备科主任', '13802000003', 0, 0, ''),
|
|
(1, '吴敏', '供应链总监', '13803000001', 1, 0, '子女升学季,勿中午来电'),
|
|
(1, '刘建华', '仓储经理', '13803000002', 0, 0, ''),
|
|
(2, '郑雅文', '董事长助理', '13805000001', 1, 1, '重点维护,决策链关键人'),
|
|
(2, '杨帆', 'IT 经理', '13805000002', 0, 0, ''),
|
|
(3, '周丽娟', '财务总监', '13806000001', 1, 0, ''),
|
|
(4, '王海涛', '运营副总', '13807000001', 1, 0, ''),
|
|
(4, '徐静', '调度中心主任', '13807000002', 0, 0, ''),
|
|
(5, '孙明', '市场部经理', '13808000001', 0, 0, ''),
|
|
(6, '马晓燕', '采购部长', '13809000001', 1, 0, '对交期敏感'),
|
|
(6, '朱国强', '生产厂长', '13809000002', 0, 0, ''),
|
|
(7, '胡雪梅', '合规负责人', '13810000001', 0, 0, ''),
|
|
(8, '高翔', '总经理', '13811000001', 1, 0, '本人即决策人'),
|
|
(9, '林芳', '创意总监', '13812000001', 0, 0, ''),
|
|
(10, '何伟', '研发副总', '13813000001', 1, 0, ''),
|
|
(11, '罗嘉欣', '信息技术部长', '13814000001', 1, 1, ''),
|
|
(11, '邓超群', '车队队长', '13814000002', 0, 0, ''),
|
|
(12, '曹颖', '物业总经理', '13815000001', 0, 0, ''),
|
|
(13, '彭志远', '厂长', '13816000001', 1, 0, ''),
|
|
(14, '梁诗敏', '外贸部经理', '13817000001', 0, 0, ''),
|
|
]
|
|
|
|
FOLLOWS = [ # (客户idx, follow_way下标, 内容, next 天数)
|
|
(0, 1, '上门演示手术室整体方案,陈总对配置清单基本认可,报价待财务复核', 2),
|
|
(0, 0, '电话确认资质文件已收悉,对方进入内部立项流程', 5),
|
|
(1, 0, '沟通冷链季度框架意向,需先补充承运资质与温控记录样例', 3),
|
|
(2, 4, '郑助理转达二期预算已批复,下周安排实施经理进场调研', 1),
|
|
(2, 0, '回访 ERP 一期使用情况,财务模块反馈良好', 7),
|
|
(3, 2, '建材行情下行,客户压缩采购计划,转推维修维护场景', 6),
|
|
(4, 0, '江城物流区域扩张计划确认,武汉/长沙两地仓配需求明确', 4),
|
|
(5, 3, '文旅旺季前预算未落地,保持月度联络', 14),
|
|
(6, 1, '车间改造项目勘察完成,等待技术方案评审', 2),
|
|
(7, 0, '惠金信息新设合规团队,有系统对接需求,发产品白皮书', 5),
|
|
(8, 0, '春茶采购季结束,转入日常维护联络', 20),
|
|
(9, 1, '熊猫慢递新址开业,送乔迁贺礼并演示会员系统', 8),
|
|
(10, 0, '光谷生物实验数据管理需求初步确认,约技术交流会', 3),
|
|
(11, 4, '白云快运调度系统验收会通过,商务条款法务审核中', 2),
|
|
(12, 0, '楼宇物业续约谈判节点临近,整理服务履约材料', 6),
|
|
(13, 1, '松山湖精密二期产线规划中,邀请参观标杆工厂', 9),
|
|
(14, 0, '季华铝业出口订单回暖,关注汇率对采购预算影响', 4),
|
|
]
|
|
|
|
OPPS = [ # (名称, 客户idx, 甲方)
|
|
('云栖软件-ERP 二期实施项目', 2, '杭州云栖软件技术有限公司'),
|
|
('珠江光电-车间智能化改造', 6, '广州珠江光电科技有限公司'),
|
|
('白云快运-车辆调度系统采购', 11, '广州白云机场快运有限公司'),
|
|
('晨曦医疗-手术室设备集采', 0, '广州晨曦医疗器械有限公司'),
|
|
]
|
|
|
|
SHOW_NAMES = [f[0] for f in FIRMS] + [p[0] for p in POOLS] + [ARCHIVED[0]]
|
|
|
|
# ---------------- 2. 清理(仅本名单) ----------------
|
|
print('== 2. 清理上一轮演示数据 ==')
|
|
if HAS_DB:
|
|
n_all = dbq('SELECT COUNT(*) c FROM customer')[0]['c']
|
|
assert n_all >= 3, f'customer 仅 {n_all} 行,疑似连错库,中止'
|
|
ph = ','.join(['%s'] * len(SHOW_NAMES))
|
|
rows = dbq(f'SELECT id FROM customer WHERE customer_name IN ({ph})', SHOW_NAMES)
|
|
cids = [str(r['id']) for r in rows]
|
|
with pymysql.connect(**CONF) as conn, conn.cursor() as cur:
|
|
if cids:
|
|
p2 = ','.join(['%s'] * len(cids))
|
|
cur.execute(f'SELECT DISTINCT transfer_id FROM customer_transfer_detail WHERE customer_id IN ({p2})', cids)
|
|
tids = [r['transfer_id'] for r in cur.fetchall()]
|
|
if tids:
|
|
p3 = ','.join(['%s'] * len(tids))
|
|
cur.execute(f'DELETE FROM customer_transfer_detail WHERE transfer_id IN ({p3})', tids)
|
|
cur.execute(f'DELETE FROM customer_transfer WHERE id IN ({p3})', tids)
|
|
cur.execute(f'DELETE FROM opportunity_customer WHERE customer_id IN ({p2})', cids)
|
|
oph = ','.join(['%s'] * len(OPPS))
|
|
cur.execute(f'SELECT id FROM opportunity WHERE opp_name IN ({oph})', [o[0] for o in OPPS])
|
|
oids = [r['id'] for r in cur.fetchall()]
|
|
if oids:
|
|
p4 = ','.join(['%s'] * len(oids))
|
|
cur.execute(f'DELETE FROM opportunity_customer WHERE opportunity_id IN ({p4})', oids)
|
|
cur.execute(f'DELETE FROM opportunity WHERE id IN ({p4})', oids)
|
|
for t in ['customer_contact', 'customer_oplog', 'customer_team_member', 'customer_follow',
|
|
'customer_pending_notice', 'customer_focus', 'customer_view_log']:
|
|
cur.execute(f'DELETE FROM {t} WHERE customer_id IN ({p2})', cids)
|
|
cur.execute(f'DELETE FROM customer WHERE id IN ({p2})', cids)
|
|
print(f' 清 {len(cids)} 户 + 关联商机 {len(oids or [])}')
|
|
else:
|
|
print(' 无残留')
|
|
else:
|
|
ph = None
|
|
print(' --skip-db:跳过清理(重跑前需手工清名单或换名单,否则 L3 撞码 67003 硬拦)')
|
|
|
|
# ---------------- 3. 造客户 ----------------
|
|
print('== 3. 造拟真客户 ==')
|
|
CIDS = []
|
|
for i, (name, ind, reg, typ, star, rstar, biz) in enumerate(FIRMS + EXTRA7):
|
|
reg_ = REGIONS[reg]
|
|
credit = f'91440101SHOW{10000 + i:05d}X' # 8+5+4+1=18 位
|
|
form = dict(customerName=name, customerType=CTYPES[typ % len(CTYPES)],
|
|
provinceCode=reg_[0], cityCode=reg_[1], districtCode=reg_[2],
|
|
industryCode=INDUSTRIES[ind % len(INDUSTRIES)],
|
|
customerStarLevel=star, relationStarLevel=rstar, isBizNegotiated=biz, isChild=0,
|
|
unifiedCreditCode=credit, legalRepresentative=CONTACTS[i % len(CONTACTS)][1],
|
|
establishedDate=f'{2010 + i % 14}-0{1 + i % 9}-1{i % 9}',
|
|
registeredCapital=f'{(i + 1) * 500}万元人民币',
|
|
businessScope='许可经营项目凭许可证经营;一般经营项目自主选择',
|
|
staffSize=['50-99人', '100-499人', '500-999人'][i % 3],
|
|
annualRevenue=f'{(i % 8 + 1) * 1000}万元',
|
|
ownerUserId=None if 15 <= i < 18 else ADMIN, # 公海 3 户无主;天津渤海挂 admin 供归档样例
|
|
confirmSimilar='true',
|
|
remark='验收演示数据(demo_showcase.py)')
|
|
d = api('POST', '/api/customer', form=form, step=f'create {name}')
|
|
need(d is not None, f'create {name}')
|
|
if d is None:
|
|
CIDS.append(None); continue
|
|
cid = str(d.get('id')) if isinstance(d, dict) else str(d)
|
|
CIDS.append(cid)
|
|
print(f' + {name} id={cid}')
|
|
|
|
# stage 直拨(潜在=1 默认;API 不可达行,仅 HAS_DB)
|
|
if HAS_DB:
|
|
for idx in sorted(STAGE2):
|
|
if CIDS[idx]:
|
|
dbq('UPDATE customer SET customer_stage=2 WHERE id=%s', (CIDS[idx],), fetch=False)
|
|
for idx in sorted(STAGE3):
|
|
if CIDS[idx]:
|
|
dbq('UPDATE customer SET customer_stage=3 WHERE id=%s', (CIDS[idx],), fetch=False)
|
|
print(f' stage 直拨:重潜 {sorted(STAGE2)} 已成交 {sorted(STAGE3)}')
|
|
else:
|
|
print(' --skip-db:stage 保持潜在客户,重潜/已成交分布不可用')
|
|
|
|
# 归档
|
|
if CIDS[-1]:
|
|
api('POST', '/api/customer/archive', params={'id': CIDS[-1]}, step='archive 天津渤海')
|
|
|
|
# ---------------- 4. 联系人 ----------------
|
|
print('== 4. 联系人 ==')
|
|
for idx, nm, job, ph_, key, internal, gift in CONTACTS:
|
|
if not CIDS[idx]:
|
|
continue
|
|
api('POST', '/api/customer/contact',
|
|
form=dict(customerId=CIDS[idx], name=nm, jobTitleName=job, phone=ph_,
|
|
isKeyContact=key, isInternal=internal, giftRemark=gift,
|
|
source='contact_source_0%d' % (idx % 4 + 1)),
|
|
step=f'contact {nm}')
|
|
|
|
# ---------------- 5. 跟进 ----------------
|
|
print('== 5. 跟进 ==')
|
|
now = datetime.now()
|
|
for idx, way, content, ndays in FOLLOWS:
|
|
if not CIDS[idx]:
|
|
continue
|
|
nxt = (now + timedelta(days=ndays)).strftime('%Y-%m-%dT09:30:00')
|
|
api('POST', f'/api/customer/{CIDS[idx]}/follow',
|
|
form=dict(followWay=FOLLOW_WAY[way % len(FOLLOW_WAY)], followContent=content,
|
|
nextFollowTime=nxt), step=f'follow #{idx}')
|
|
|
|
# ---------------- 6. 商机 + 绑定 ----------------
|
|
print('== 6. 商机关联 ==')
|
|
for name, cidx, party in OPPS:
|
|
if not CIDS[cidx]:
|
|
continue
|
|
reg_ = REGIONS[FIRMS[cidx][2]]
|
|
d = api('POST', '/api/opportunity',
|
|
form=dict(opportunityName=name, oppSource='opp_source_02',
|
|
industryCode=INDUSTRIES[FIRMS[cidx][1] % len(INDUSTRIES)],
|
|
localityType='locality_type_01', bidForm='bid_form_01',
|
|
provinceCode=reg_[0], cityCode=reg_[1],
|
|
partyAClear=1, partyA=party, remark='验收演示商机'),
|
|
step=f'opp {name}')
|
|
if d is not None:
|
|
oid = str(d)
|
|
api('POST', '/api/opportunity/customer/add',
|
|
form=dict(oppId=oid, customerId=CIDS[cidx], customerNameSnapshot=FIRMS[cidx][0],
|
|
customerRole='customer_role_01', isPrimaryIntended=1),
|
|
step=f'bind {name}')
|
|
# 商机跟进一条,丰富详情
|
|
api('POST', f'/api/opportunity/follow/add' if False else f'/api/customer/{CIDS[cidx]}/follow',
|
|
form=dict(followWay=FOLLOW_WAY[0],
|
|
followContent=f'【{name}】商机推进沟通:确认技术方案与商务节奏',
|
|
nextFollowTime=(now + timedelta(days=3)).strftime('%Y-%m-%dT14:00:00')),
|
|
step=f'opp-follow {name}')
|
|
|
|
# ---------------- 7. 关注/重点/成员 ----------------
|
|
print('== 7. 关注/重点/成员 ==')
|
|
for idx in (0, 2, 11):
|
|
if CIDS[idx]:
|
|
api('POST', f'/api/customer/{CIDS[idx]}/focus', step=f'focus #{idx}')
|
|
for idx in (0, 2):
|
|
if CIDS[idx]:
|
|
api('POST', f'/api/customer/{CIDS[idx]}/star', step=f'star #{idx}')
|
|
for idx in (1, 4):
|
|
if CIDS[idx]:
|
|
api('POST', f'/api/customer/{CIDS[idx]}/members',
|
|
form={'memberUserIds': BUDDY}, step=f'member #{idx}+曾偲青')
|
|
|
|
# ---------------- 8. 校验 ----------------
|
|
print('== 8. 校验 ==')
|
|
if HAS_DB:
|
|
got_names = dbq(f'SELECT COUNT(*) c FROM customer WHERE customer_name IN ({ph})', SHOW_NAMES)[0]['c']
|
|
need(got_names == len(SHOW_NAMES), f'演示客户总数 {got_names}=={len(SHOW_NAMES)}')
|
|
stages = sorted(r['customer_stage'] for r in dbq(
|
|
f"SELECT customer_stage FROM customer WHERE customer_name IN ({ph}) AND deleted=0", SHOW_NAMES))
|
|
s2 = sum(1 for s in stages if s == 2); s3 = sum(1 for s in stages if s == 3)
|
|
need(s2 == len(STAGE2) and s3 == len(STAGE3), f'stage 分布 重潜{s2} 已成交{s3}')
|
|
pools = dbq(f"SELECT COUNT(*) c FROM customer WHERE customer_name IN ({ph}) AND owner_user_id IS NULL", SHOW_NAMES)[0]['c']
|
|
need(pools == 3, f'公海 {pools}==3')
|
|
cc = dbq(f'SELECT COUNT(*) c FROM customer_contact ct JOIN customer c ON c.id=ct.customer_id '
|
|
f"WHERE c.customer_name IN ({ph})", SHOW_NAMES)[0]['c']
|
|
need(cc >= len(CONTACTS) - 2, f'联系人 {cc}>={len(CONTACTS) - 2}')
|
|
fc = dbq(f'SELECT COUNT(*) c FROM customer_follow f JOIN customer c ON c.id=f.customer_id '
|
|
f"WHERE c.customer_name IN ({ph})", SHOW_NAMES)[0]['c']
|
|
need(fc >= len(FOLLOWS) - 2, f'跟进 {fc}>={len(FOLLOWS) - 2}')
|
|
oc = dbq(f'SELECT COUNT(*) c FROM opportunity_customer oc JOIN customer cu ON cu.id=oc.customer_id '
|
|
f"WHERE cu.customer_name IN ({ph})", SHOW_NAMES)[0]['c']
|
|
need(oc == len(OPPS), f'商机绑定 {oc}=={len(OPPS)}')
|
|
else:
|
|
print(' --skip-db:跳过 DB 校验(分布核对以 demo 页面观感为准)')
|
|
|
|
json.dump({'customers': {f[0]: cid for f, cid in zip(FIRMS + POOLS + [ARCHIVED], CIDS) if cid}},
|
|
open(IDS_FILE, 'w', encoding='utf-8'), ensure_ascii=False, indent=2)
|
|
|
|
mine = api('POST', '/api/customer/workspace/mine/page', form={'current': 1, 'size': 1}, step='mine total')
|
|
pool = api('POST', '/api/customer/workspace/pool/page', form={'current': 1, 'size': 1}, step='pool total')
|
|
print(f'\nmine total={(mine or {}).get("total")} pool total={(pool or {}).get("total")}')
|
|
print(f'{"SHOWCASE ALL GREEN" if not fails else f"{len(fails)} 项失败: {fails}"}')
|
|
sys.exit(0 if not fails else 1)
|
|
|