- 平台费用新增状态字段(计入费用/暂不计入),默认计入费用 - 费用表格月份列置首,所有表头点击正/倒序排序 - 首页新增回款提醒条(金色渐变,含确收/回款/未回款/回款率) - 业务表格回款率着色:<30%红,30-80%黄,>=100%绿 - 版本号v1.0.0.5
286 lines
15 KiB
Python
286 lines
15 KiB
Python
# seed_data.py — 丰富示例数据填充(仅在空库时执行一次)
|
||
# 为每个工作台生成 > 1 亿的总规模,覆盖万/十万/百万/千万量级
|
||
|
||
import json
|
||
import random
|
||
random.seed(42)
|
||
|
||
from datetime import date
|
||
from db import db, _exec, one
|
||
|
||
TENANTS = ["科普·无界", "科研·无界", "医患·无界", "MCN·无界", "学会·无界"]
|
||
|
||
# 每个工作台的差异化客户和项目模板
|
||
TENANT_CUSTOMERS = {
|
||
"科普·无界": [
|
||
("信达生物", ["肿瘤免疫科普年度框架", "信迪利单抗医生教育", "CSCO卫星会项目", "患者随访管理系统"]),
|
||
("百利天恒", ["BL-B01D1上市前教育", "双抗药物科普专区", "数字人患教视频"]),
|
||
("齐鲁制药", ["多产品线医生教育", "安可达学术推广", "基层医疗科普巡讲"]),
|
||
("天广实生物", ["血液肿瘤科普项目", "CD20单抗学术推广"]),
|
||
("复星医药", ["复宏汉霖学术年会", "CAR-T患者教育"]),
|
||
],
|
||
"科研·无界": [
|
||
("北京协和医院", ["临床研究数据采集系统", "罕见病队列研究", "AI辅助诊断验证"]),
|
||
("中国人民解放军总医院", ["战场创伤数据平台", "远程会诊系统"]),
|
||
("复旦大学附属华山医院", ["脑卒中AI预警系统", "神经内科临床研究"]),
|
||
("中山大学肿瘤防治中心", ["鼻咽癌早筛项目", "真实世界研究平台"]),
|
||
("四川大学华西医院", ["精准医学大数据平台", "基层科研能力提升"]),
|
||
],
|
||
"医患·无界": [
|
||
("平安好医生", ["在线问诊科普内容", "慢病管理专区", "随访SOP标准化"]),
|
||
("微医集团", ["互联网医院患教系统", "云药房科普推送"]),
|
||
("丁香园", ["医生社区运营升级", "用药助手科普专区"]),
|
||
("圆心科技", ["药房患者教育", "DTP药房随访管理", "罕见病用药指导"]),
|
||
("国药控股", ["国药患者管理平台", "肿瘤药房随访系统"]),
|
||
],
|
||
"MCN·无界": [
|
||
("抖音健康", ["医学IP孵化矩阵", "短视频科普内容制作", "健康直播运营"]),
|
||
("快手健康", ["医生个人IP打造", "快手健康科普专区"]),
|
||
("小红书医美", ["皮肤健康科普矩阵", "医美科普合规审核"]),
|
||
("B站知识区", ["深度科普纪录片", "医学动画制作"]),
|
||
("微信视频号", ["医生科普直播运营", "企业微信科普推送"]),
|
||
],
|
||
"学会·无界": [
|
||
("中华医学会", ["学术年会数字化平台", "继续教育学分系统", "基层医生培训"]),
|
||
("中国医师协会", ["医师定考管理系统", "专科医师培训平台"]),
|
||
("中国抗癌协会", ["肿瘤科普万里行", "CACA指南推广"]),
|
||
("中国药学会", ["合理用药科普平台", "药师继续教育"]),
|
||
("中华护理学会", ["护理科研能力提升", "专科护士培训系统"]),
|
||
],
|
||
}
|
||
|
||
# 签约金额模板:每个工作台的千万/百万/十万/万级别项目的签约金额
|
||
def generate_sign_amounts():
|
||
amounts = []
|
||
# 千万级:2个(4000-7000万)
|
||
amounts.append(random.randint(4500, 7000) * 10000)
|
||
amounts.append(random.randint(4000, 6500) * 10000)
|
||
# 百万级:4个(400-980万)
|
||
for _ in range(4):
|
||
amounts.append(random.randint(400, 980) * 10000)
|
||
# 十万级:4个(30-98万)
|
||
for _ in range(4):
|
||
amounts.append(random.randint(30, 98) * 10000)
|
||
# 万级:3个(3-10万)
|
||
for _ in range(3):
|
||
amounts.append(random.randint(3, 10) * 10000)
|
||
return sorted(amounts, reverse=True)
|
||
|
||
|
||
# 生成 budget_data JSON(每月确收/毛利/回款)
|
||
def make_budget_data(sign_amount, signed_month, status):
|
||
"""生成12个月的预算数据"""
|
||
months = []
|
||
start_m = int(signed_month.split("-")[1]) if signed_month else 1
|
||
rev_total = sign_amount if status == "已签约" else int(sign_amount * random.uniform(0.3, 0.7))
|
||
gross_rate = random.uniform(0.25, 0.55)
|
||
payment_rate = random.uniform(0.7, 0.95)
|
||
|
||
for i in range(12):
|
||
m = (start_m + i - 1) % 12 + 1
|
||
yr = 2026 + (start_m + i - 1) // 12
|
||
month_key = f"{yr}-{m:02d}"
|
||
|
||
# 越往后确收越多
|
||
progress = min(1.0, (i + 1) / max(1, random.randint(3, 8)))
|
||
rev = round(rev_total * progress * random.uniform(0.85, 1.15))
|
||
gross = round(rev * gross_rate * random.uniform(0.9, 1.1))
|
||
payment = round(rev * payment_rate * random.uniform(0.85, 1.15))
|
||
cost = round(rev * random.uniform(0.3, 0.5))
|
||
paid = round(cost * random.uniform(0.8, 1.0))
|
||
|
||
months.append({
|
||
"month": month_key,
|
||
"rev": rev,
|
||
"gross": gross,
|
||
"payment": payment,
|
||
"cost": cost,
|
||
"paid": paid,
|
||
})
|
||
return json.dumps(months)
|
||
|
||
|
||
# 生成 expense_data(平台费用)
|
||
def make_expense_data():
|
||
"""生成若干条费用记录"""
|
||
items = []
|
||
expense_types = ["平台采购", "人力成本", "研发费用", "通用费用", "奖金"]
|
||
for q in range(1, 7):
|
||
for et in random.sample(expense_types, random.randint(2, 4)):
|
||
month_str = f"2026-{q:02d}"
|
||
amount = random.randint(5000, 150000)
|
||
incurred = int(amount * random.uniform(0.6, 1.0))
|
||
items.append({"month": month_str, "expense_type": et, "amount": amount, "incurred": incurred})
|
||
return json.dumps(items)
|
||
|
||
|
||
def seed_db():
|
||
"""填充丰富示例数据"""
|
||
conn = db()
|
||
try:
|
||
if one(conn, "SELECT id FROM sales_leads LIMIT 1"):
|
||
return
|
||
|
||
today = date.today().isoformat()
|
||
pf_ids = {} # tenant -> [project_finance_ids]
|
||
|
||
for tenant in TENANTS:
|
||
customers = TENANT_CUSTOMERS[tenant]
|
||
amounts = generate_sign_amounts()
|
||
cust_idx = 0
|
||
|
||
for i, sign_amount in enumerate(amounts):
|
||
cust_name, projects = customers[cust_idx % len(customers)]
|
||
proj_name = projects[i % len(projects)] if i < len(projects) else f"{cust_name} 项目{i+1}"
|
||
cust_idx += i % 3
|
||
|
||
# 70% 已签约, 30% 待签约
|
||
status = "已签约" if random.random() < 0.7 else "待签约"
|
||
sign_month = f"2026-{random.randint(1, 6):02d}" if status == "已签约" else ""
|
||
project_code = f"{tenant[:2]}-2026-{i+1:03d}"
|
||
business_type = random.choice(["学术推广", "医生教育", "患者管理", "科研合作", "科普内容"])
|
||
|
||
# 如果签约,总额 = 签约额;否则为0
|
||
total_rev = int(sign_amount * random.uniform(0.8, 1.0)) if status == "已签约" else 0
|
||
total_gross = int(total_rev * random.uniform(0.25, 0.5)) if status == "已签约" else 0
|
||
total_payment = int(total_rev * random.uniform(0.7, 0.9)) if status == "已签约" else 0
|
||
total_cost = int(total_rev * random.uniform(0.3, 0.5)) if status == "已签约" else 0
|
||
total_paid = int(total_cost * random.uniform(0.7, 0.95)) if status == "已签约" else 0
|
||
|
||
budget_data = make_budget_data(sign_amount, sign_month, status)
|
||
expense_data = make_expense_data() if status == "已签约" else "[]"
|
||
task_data = json.dumps([
|
||
{"task": f"{proj_name} 项目启动", "due": f"2026-{min(12, 1+i//2):02d}-01", "status": "已完成" if i < 8 else "进行中"},
|
||
{"task": f"方案评审与确认", "due": f"2026-{min(12, 2+i//2):02d}-15", "status": "已完成" if i < 6 else "进行中"},
|
||
{"task": f"内容生产阶段", "due": f"2026-{min(12, 3+i//2):02d}-30", "status": "进行中"},
|
||
])
|
||
|
||
cur = _exec(conn, """
|
||
INSERT INTO project_finances
|
||
(tenant, project_id, business_type, customer_name, sign_amount, sign_month, status,
|
||
sales_person, total_rev, total_gross, total_payment, total_cost, total_paid,
|
||
budget_data, expense_data, task_data,
|
||
project_code, start_date, end_date, task_type, task_count,
|
||
service_fee_standard, project_manager, contact_name, contact_phone, other_info,
|
||
created_at, updated_at)
|
||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""", (
|
||
tenant, f"PF-{tenant[:2]}-{i+1:04d}", business_type, cust_name,
|
||
sign_amount, sign_month, status,
|
||
f"销售{random.choice(['A','B','C','D'])}", total_rev, total_gross,
|
||
total_payment, total_cost, total_paid,
|
||
budget_data, expense_data, task_data,
|
||
project_code, f"2026-{random.randint(1,3):02d}-01", f"2026-{random.randint(9,12):02d}-30",
|
||
random.choice(["内容生产", "视频制作", "专家访谈", "学术会议", "患者教育"]),
|
||
random.randint(10, 100), random.choice([800, 1000, 1200, 1500, 2000]),
|
||
f"经理{random.choice(['A','B','C'])}", f"联系人{random.randint(1,20)}",
|
||
f"1380000{random.randint(1000,9999)}", f"备注信息 {i+1}",
|
||
today, today,
|
||
))
|
||
# Store for later use
|
||
pf_ids.setdefault(tenant, []).append((cur.lastrowid, proj_name, cust_name, status))
|
||
|
||
# operation_projects: 每个工作台 3-5 个
|
||
op_count = random.randint(3, 5)
|
||
for j in range(op_count):
|
||
pf_row = random.choice(pf_ids[tenant])
|
||
cur2 = _exec(conn, """
|
||
INSERT INTO operation_projects
|
||
(tenant, project_name, project_version, project_type, project_status,
|
||
current_stage, owner, start_date, end_date, target_customer, customer_need,
|
||
expected_contract_amount, expected_sign_date, sign_probability, next_action,
|
||
sop_stage, execution_progress, current_deliverable, notes)
|
||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||
""", (
|
||
tenant, f"{pf_row[1]} 运营方案", "v1.0",
|
||
"execution" if random.random() < 0.6 else "opportunity",
|
||
"SOP 执行中" if random.random() < 0.6 else "方案已提交",
|
||
random.choice(["商务推进", "内容生产", "渠道分发"]),
|
||
f"OPC{random.randint(1,5)}", "2026-06-01", "2026-12-31",
|
||
pf_row[2], "科普内容项目执行与管理",
|
||
pf_row[0] if pf_row[3] == "已签约" else random.randint(50, 300) * 10000,
|
||
"2026-06", 70 + random.randint(0, 30),
|
||
"补齐版本要求文件并更新下一节点",
|
||
"SOP 执行", random.randint(30, 80), "当前交付物 v1.0",
|
||
"运营备注",
|
||
))
|
||
_exec(conn, """
|
||
INSERT INTO follow_up_records
|
||
(target_type, target_id, followed_at, content, next_action, follower)
|
||
VALUES (?,?,?,?,?,?)
|
||
""", (
|
||
"operation", cur2.lastrowid, today,
|
||
f"运营项目跟进:{pf_row[1]},当前进度正常", "继续推进下一里程碑",
|
||
f"销售{random.choice(['A','B'])}",
|
||
))
|
||
|
||
# expense_records: 每个工作台 15-20 条
|
||
expense_count = random.randint(15, 20)
|
||
expense_types = ["通用费用", "平台采购", "人力成本", "研发费用", "奖金", "其他"]
|
||
for j in range(expense_count):
|
||
et = random.choice(expense_types)
|
||
month_str = f"2026-{random.randint(1, 6):02d}"
|
||
amount = random.randint(5000, 500000)
|
||
incurred = int(amount * random.uniform(0.5, 1.0))
|
||
_exec(conn, """
|
||
INSERT INTO expense_records
|
||
(tenant, expense_type, expense_month, amount, incurred_amount, notes, status)
|
||
VALUES (?,?,?,?,?,?,?)
|
||
""", (
|
||
tenant, et, month_str, amount, incurred,
|
||
f"{et} - {random.choice(['季度预算', '紧急支出', '常规费用', '项目费用'])}",
|
||
"计入费用",
|
||
))
|
||
|
||
print(f"[seed] {tenant}: {len(amounts)} 项目, {op_count} 运营项目, {expense_count} 费用记录")
|
||
|
||
conn.commit() # 提交所有工作台的核心数据
|
||
|
||
# 全域数据:sales_leads 对所有 tenant 可见
|
||
global_sales = [
|
||
("齐鲁制药", "P0", "跟进中", "多产品线科普年度框架"),
|
||
("百利天恒", "P0", "方案中", "BL-B01D1 上市前医生教育"),
|
||
("信达生物", "P0", "已签约", "现有科普项目升级/续约"),
|
||
("三生制药", "P1", "待跟进", "多科室医生教育+患者科普"),
|
||
("天广实生物", "P1", "待跟进", "血液肿瘤医生教育机会"),
|
||
]
|
||
for customer, priority, status, note in global_sales:
|
||
cur = _exec(conn,
|
||
"INSERT INTO sales_leads (target_customer, priority, status, created_at) VALUES (?,?,?,?)",
|
||
(customer, priority, status, today),
|
||
)
|
||
_exec(conn,
|
||
"INSERT INTO follow_up_records (target_type,target_id,followed_at,content,next_action,follower) VALUES (?,?,?,?,?,?)",
|
||
("sales", cur.lastrowid, today, note, "明确下一次沟通", "销售A"),
|
||
)
|
||
|
||
# 全域 business_proposals
|
||
cur = _exec(conn,
|
||
"INSERT INTO business_proposals (customer_or_project_name,version,description,status,created_date, tenant) VALUES (?,?,?,?,?,?)",
|
||
("信达生物", "v1.5", "信达科普项目续约与报价方案", "已提交客户", "2026-05-28", "科普·无界"),
|
||
)
|
||
proposal_id = cur.lastrowid
|
||
|
||
# 全域 product_versions
|
||
products = [
|
||
("妙手医生服务小程序", "v1.1", "视频任务增强 + 积分商城", "2026-Q3", "规划中", "P1"),
|
||
("数字化营销后台管理系统", "v1.2", "运营数据看板 + 智能审核", "2026-Q3", "设计中", "P0"),
|
||
("妙手患者服务", "v0.5", "科普浏览 + 医生主页 MVP", "2026-Q3", "规划中", "P2"),
|
||
("数字人内容平台", "v0.1", "基础数字人视频生成 MVP", "2026-Q3", "规划中", "P2"),
|
||
("渠道分发引擎", "v1.0", "六渠道统一分发", "2027-Q1", "规划中", "P1"),
|
||
]
|
||
for p in products:
|
||
cur = _exec(conn,
|
||
"INSERT INTO product_versions (product_name,version,version_goal,launch_date,status,priority,tenant) VALUES (?,?,?,?,?,?,?)",
|
||
(*p, "科普·无界"),
|
||
)
|
||
_exec(conn,
|
||
"INSERT INTO follow_up_records (target_type,target_id,followed_at,content,next_action,follower) VALUES (?,?,?,?,?,?)",
|
||
("product", cur.lastrowid, today, f"{p[0]} {p[1]}:{p[2]}", "按路线图推进", "产品经理"),
|
||
)
|
||
|
||
conn.commit()
|
||
print("[seed] 丰富示例数据已填充")
|
||
finally:
|
||
conn.close()
|