diff --git a/backend/airlabs.db b/backend/airlabs.db index 2d56695..7ac369e 100644 Binary files a/backend/airlabs.db and b/backend/airlabs.db differ diff --git a/backend/seed_demo.py b/backend/seed_demo.py new file mode 100644 index 0000000..b40c6fe --- /dev/null +++ b/backend/seed_demo.py @@ -0,0 +1,362 @@ +"""补充演示数据 - 只添加项目/提交/成本,不动用户和角色""" +from datetime import date, timedelta +from database import SessionLocal, engine +from models import * + +db = SessionLocal() + + +def get_user(username): + u = db.query(User).filter(User.username == username).first() + if not u: + print(f" [WARN] user '{username}' not found") + return u + + +def seed_demo(): + # 清除旧的项目相关数据(不动 users 和 roles) + db.query(SubmissionHistory).delete() + db.query(Submission).delete() + db.query(OutsourceCost).delete() + db.query(CostOverride).delete() + db.query(AIToolCost).delete() + db.query(OverheadCost).delete() + db.query(Project).delete() + db.commit() + print("[1] Cleared old project data") + + # 获取真实用户 + huhaonan = get_user("huhaonan") # 主管/总导演 + dengqingrui = get_user("dengqingrui") # 主管/AI导演 + qiushaohui = get_user("qiushaohui") # 主管/制片 + chenbaodan = get_user("chenbaodan") # 组长/动画制作 + maruoqing = get_user("maruoqing") # 组长/AI导演 + weichunli = get_user("weichunli") # 组长/AI导演 + panziyan = get_user("panziyan") # 组长/剪辑 + daixiaoqian = get_user("daixiaoqian") # 组员/动画制作 + tanruping = get_user("tanruping") # 组员/动画制作 + zhengyiqing = get_user("zhengyiqing") # 组员/动画制作 + huangxuewen = get_user("huangxuewen") # 组员/动画制作 + liushiqi = get_user("liushiqi") # 组员/动画制作 + daiwei = get_user("daiwei") # 组员/动画制作 + huangrongying = get_user("huangrongying") # 组员/编剧 + jiahaozheng = get_user("jiahaozheng") # 组员/剪辑 + wangyansen = get_user("wangyansen") # 组员/剪辑 + huangqiuxia = get_user("huangqiuxia") # 组员/动画制作 + lijing = get_user("lijing") # 组员/动画制作 + yemeilian = get_user("yemeilian") # 组员/动画制作 + chenxuanying = get_user("chenxuanying") # 组员/动画制作 + + # ── 项目 ── + proj_a = Project( + name="星际漫游 第一季", project_type=ProjectType.CLIENT_FORMAL, + leader_id=huhaonan.id, current_phase=PhaseGroup.PRODUCTION, + episode_duration_minutes=5, episode_count=13, + estimated_completion_date=date.today() + timedelta(days=60), + contract_amount=100000, + ) + proj_b = Project( + name="品牌方 TVC 宣传片", project_type=ProjectType.CLIENT_FORMAL, + leader_id=dengqingrui.id, current_phase=PhaseGroup.PRODUCTION, + episode_duration_minutes=1, episode_count=3, + estimated_completion_date=date.today() + timedelta(days=20), + contract_amount=50000, + ) + proj_c = Project( + name="甲方风格测试", project_type=ProjectType.CLIENT_TEST, + leader_id=maruoqing.id, current_phase=PhaseGroup.PRE, + episode_duration_minutes=1, episode_count=1, + ) + proj_d = Project( + name="AI 短剧原创 S1", project_type=ProjectType.INTERNAL_ORIGINAL, + leader_id=weichunli.id, current_phase=PhaseGroup.PRE, + episode_duration_minutes=8, episode_count=6, + estimated_completion_date=date.today() + timedelta(days=90), + ) + db.add_all([proj_a, proj_b, proj_c, proj_d]) + db.flush() + print("[2] Created 4 projects") + + # ── 内容提交(模拟近 20 天的数据) ── + base = date.today() - timedelta(days=20) + subs = [] + + # --- 项目A:星际漫游 --- + # 黄溶莹 - 编剧 - 前期方案 + for i in range(6): + d = base + timedelta(days=i) + subs.append(Submission( + user_id=huangrongying.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRE, work_type=WorkType.PLAN, + content_type=ContentType.DESIGN, total_seconds=0, + submit_to=SubmitTo.INTERNAL, description=f"第{i+1}集剧本初稿", + submit_date=d, + )) + + # 陈保丹 - 组长 - 动画制作 + for i in range(12): + d = base + timedelta(days=i + 3) + secs = 55 + (i % 3) * 20 + wt = WorkType.TEST if i < 2 else WorkType.PRODUCTION + subs.append(Submission( + user_id=chenbaodan.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRODUCTION, work_type=wt, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.INTERNAL, description=f"第1集场景{i+1}动画", + submit_date=d, + )) + + # 代晓倩 - 动画制作 + for i in range(10): + d = base + timedelta(days=i + 2) + secs = 40 + (i % 4) * 15 + subs.append(Submission( + user_id=daixiaoqian.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"第2集片段{i+1}", + submit_date=d, + )) + + # 谭如平 - 动画制作 + for i in range(8): + d = base + timedelta(days=i + 4) + secs = 35 + (i % 3) * 25 + wt = WorkType.TEST if i == 0 else WorkType.PRODUCTION + subs.append(Submission( + user_id=tanruping.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRODUCTION, work_type=wt, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"第3集镜头{i+1}", + submit_date=d, + )) + + # 郑奕晴 - 动画制作 + for i in range(9): + d = base + timedelta(days=i + 3) + secs = 45 + (i % 2) * 30 + subs.append(Submission( + user_id=zhengyiqing.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"第4集场景动画{i+1}", + submit_date=d, + )) + + # 黄雪雯 - 动画制作 + for i in range(7): + d = base + timedelta(days=i + 5) + secs = 30 + (i % 3) * 20 + subs.append(Submission( + user_id=huangxuewen.id, project_id=proj_a.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"第5集片段{i+1}", + submit_date=d, + )) + + # 潘梓彦 - 剪辑 - 后期 + for i in range(4): + d = base + timedelta(days=i + 14) + subs.append(Submission( + user_id=panziyan.id, project_id=proj_a.id, + project_phase=PhaseGroup.POST, work_type=WorkType.PRODUCTION, + content_type=ContentType.EDITING, total_seconds=0, + submit_to=SubmitTo.PRODUCER, description=f"第{i+1}集粗剪", + submit_date=d, + )) + + # 贾浩正 - 剪辑 + for i in range(3): + d = base + timedelta(days=i + 15) + subs.append(Submission( + user_id=jiahaozheng.id, project_id=proj_a.id, + project_phase=PhaseGroup.POST, work_type=WorkType.PRODUCTION, + content_type=ContentType.EDITING, total_seconds=0, + submit_to=SubmitTo.PRODUCER, description=f"第{i+5}集粗剪", + submit_date=d, + )) + + # --- 项目B:品牌方 TVC --- + # 马若情 - AI导演 + for i in range(6): + d = base + timedelta(days=i + 5) + secs = 20 + i * 10 + subs.append(Submission( + user_id=maruoqing.id, project_id=proj_b.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.INTERNAL, description=f"TVC 片段{i+1}", + submit_date=d, + )) + + # 刘诗琪 - 动画制作 + for i in range(5): + d = base + timedelta(days=i + 7) + secs = 15 + (i % 3) * 10 + subs.append(Submission( + user_id=liushiqi.id, project_id=proj_b.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"TVC 补充镜头{i+1}", + submit_date=d, + )) + + # 王炎森 - 剪辑 + for i in range(3): + d = base + timedelta(days=i + 13) + subs.append(Submission( + user_id=wangyansen.id, project_id=proj_b.id, + project_phase=PhaseGroup.POST, work_type=WorkType.PRODUCTION, + content_type=ContentType.EDITING, total_seconds=0, + submit_to=SubmitTo.PRODUCER, description=f"TVC 第{i+1}版剪辑", + submit_date=d, + )) + + # --- 项目C:甲方风格测试 --- + for i in range(3): + d = base + timedelta(days=i + 1) + subs.append(Submission( + user_id=huangrongying.id, project_id=proj_c.id, + project_phase=PhaseGroup.PRE, work_type=WorkType.PLAN, + content_type=ContentType.DESIGN, total_seconds=0, + submit_to=SubmitTo.INTERNAL, description=f"风格方案{i+1}", + submit_date=d, + )) + for i in range(4): + d = base + timedelta(days=i + 4) + secs = 10 + i * 5 + subs.append(Submission( + user_id=daiwei.id, project_id=proj_c.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.TEST, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.INTERNAL, description=f"风格测试片段{i+1}", + submit_date=d, + )) + + # --- 项目D:AI 短剧原创 --- + for i in range(5): + d = base + timedelta(days=i) + subs.append(Submission( + user_id=huangrongying.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRE, work_type=WorkType.PLAN, + content_type=ContentType.DESIGN, total_seconds=0, + submit_to=SubmitTo.INTERNAL, description=f"原创剧本第{i+1}集大纲", + submit_date=d, + )) + for i in range(6): + d = base + timedelta(days=i + 8) + secs = 60 + (i % 3) * 25 + subs.append(Submission( + user_id=weichunli.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.INTERNAL, description=f"原创第1集片段{i+1}", + submit_date=d, + )) + for i in range(5): + d = base + timedelta(days=i + 10) + secs = 50 + (i % 2) * 35 + subs.append(Submission( + user_id=huangqiuxia.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"原创第2集动画{i+1}", + submit_date=d, + )) + for i in range(4): + d = base + timedelta(days=i + 12) + secs = 45 + i * 15 + subs.append(Submission( + user_id=lijing.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"原创第3集片段{i+1}", + submit_date=d, + )) + for i in range(3): + d = base + timedelta(days=i + 14) + secs = 40 + i * 20 + subs.append(Submission( + user_id=yemeilian.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"原创第4集动画{i+1}", + submit_date=d, + )) + for i in range(3): + d = base + timedelta(days=i + 15) + secs = 55 + (i % 2) * 20 + subs.append(Submission( + user_id=chenxuanying.id, project_id=proj_d.id, + project_phase=PhaseGroup.PRODUCTION, work_type=WorkType.PRODUCTION, + content_type=ContentType.ANIMATION, total_seconds=secs, + duration_minutes=secs // 60, duration_seconds=secs % 60, + submit_to=SubmitTo.LEADER, description=f"原创第5集场景{i+1}", + submit_date=d, + )) + + db.add_all(subs) + print(f"[3] Created {len(subs)} submissions") + + # ── AI 工具成本 ── + db.add(AIToolCost( + tool_name="Midjourney", subscription_period=SubscriptionPeriod.MONTHLY, + amount=200, allocation_type=CostAllocationType.TEAM, + recorded_by=qiushaohui.id, record_date=date.today().replace(day=1), + )) + db.add(AIToolCost( + tool_name="Runway", subscription_period=SubscriptionPeriod.MONTHLY, + amount=600, allocation_type=CostAllocationType.PROJECT, + project_id=proj_a.id, + recorded_by=qiushaohui.id, record_date=date.today().replace(day=1), + )) + db.add(AIToolCost( + tool_name="ChatGPT Plus", subscription_period=SubscriptionPeriod.MONTHLY, + amount=150, allocation_type=CostAllocationType.TEAM, + recorded_by=qiushaohui.id, record_date=date.today().replace(day=1), + )) + print("[4] Created 3 AI tool costs") + + # ── 外包成本 ── + db.add(OutsourceCost( + project_id=proj_a.id, outsource_type=OutsourceType.ANIMATION, + episode_start=10, episode_end=13, amount=20000, + recorded_by=qiushaohui.id, record_date=date.today() - timedelta(days=5), + )) + print("[5] Created 1 outsource cost") + + # ── 固定开支 ── + db.add(OverheadCost( + cost_type=OverheadCostType.OFFICE_RENT, + amount=8000, record_month=date.today().strftime("%Y-%m"), + recorded_by=qiushaohui.id, note="办公室月租", + )) + db.add(OverheadCost( + cost_type=OverheadCostType.UTILITIES, + amount=500, record_month=date.today().strftime("%Y-%m"), + recorded_by=qiushaohui.id, note="水电费", + )) + print("[6] Created 2 overhead costs") + + db.commit() + print("\n[DONE] Demo data seeded successfully!") + print(f" Projects: 4") + print(f" Submissions: {len(subs)}") + print(f" AI tools: 3, Outsource: 1, Overhead: 2") + + +if __name__ == "__main__": + seed_demo()