feat: 计划模块 + 待签约迁移 + 表格排序 + 已签约→项目

- 新增计划模块(plan_finances/plan_expense_records, 侧边栏计划tab)
- 待签约项目自动迁移到计划模块(data_fixes 幂等)
- 已签约tab改名为项目(业务+计划两个模块)
- 业务模块表格点击列头排序(月度/季度/总视图)
- 计划与业务侧边栏位置对调
- plan模块CSS修复(使用finance-tab类)
- 版本号 v1.0.0.17
This commit is contained in:
mac
2026-07-08 12:13:06 +08:00
parent e233c294b0
commit d2ea5d0d5d
9 changed files with 1442 additions and 44 deletions

View File

@@ -17,7 +17,7 @@ def run_migrations():
"""
from migrations.tables import migrate_create_tables
from migrations.columns import migrate_add_columns
from migrations.data_fixes import migrate_fix_task_status, migrate_rename_tenant, migrate_drop_product_fields, migrate_fix_service_fee_standard, migrate_fix_expense_status
from migrations.data_fixes import migrate_fix_task_status, migrate_rename_tenant, migrate_drop_product_fields, migrate_fix_service_fee_standard, migrate_fix_expense_status, migrate_move_pending_to_plan
from migrations.data_split import migrate_data_split
from migrations.seed import migrate_seed_users, migrate_seed_demo_data
@@ -28,6 +28,7 @@ def run_migrations():
migrate_drop_product_fields()
migrate_fix_service_fee_standard()
migrate_fix_expense_status()
migrate_move_pending_to_plan()
migrate_data_split()
migrate_seed_users()
migrate_seed_demo_data()

View File

@@ -112,3 +112,31 @@ def migrate_fix_expense_status():
print(f"[migrate] expense_records: {affected} 条记录 status 修正为 '计入费用'")
finally:
conn.close()
def migrate_move_pending_to_plan():
"""迁移业务待签约项目到计划模块(幂等)"""
from db import db
conn = db()
try:
cur = conn.cursor()
# 仅迁移 id 不存在于计划表的待签约项目
cur.execute(
"INSERT INTO plan_finances "
"SELECT * FROM project_finances WHERE status='待签约' "
"AND id NOT IN (SELECT id FROM plan_finances)"
)
moved = cur.rowcount
if moved:
# 删除已迁移的原记录
cur.execute(
"DELETE FROM project_finances WHERE status='待签约' "
"AND id IN (SELECT id FROM plan_finances)"
)
deleted = cur.rowcount
conn.commit()
print(f"[migrate] 待签约项目迁移到计划: {moved} 条 (已删除 {deleted} 条)")
cur.close()
finally:
conn.close()

View File

@@ -158,6 +158,8 @@ def migrate_create_tables():
created_at VARCHAR(30) NOT NULL DEFAULT '',
updated_at VARCHAR(30) NOT NULL DEFAULT ''
)""",
"""CREATE TABLE IF NOT EXISTS plan_finances LIKE project_finances""",
"""CREATE TABLE IF NOT EXISTS plan_expense_records LIKE expense_records""",
]
for ddl in tables:

View File

@@ -25,6 +25,8 @@ TABLES = {
"tasks": ("project_tasks", ["project_id", "phase", "milestone", "task", "owner", "due_date", "blockers", "notes", "status", "sort_order", "priority", "tenant"]),
"projectFinances": ("project_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "total_payment", "total_cost", "total_paid", "budget_data", "expense_data", "start_date", "end_date", "task_type", "task_count", "service_fee_standard", "project_manager", "task_data", "project_code", "contact_name", "contact_phone", "other_info"]),
"expense": ("expense_records", ["expense_type", "expense_month", "amount", "incurred_amount", "notes", "status", "tenant"]),
"planFinances": ("plan_finances", ["project_id", "tenant", "business_type", "customer_name", "sign_amount", "sign_month", "status", "sales_person", "owner", "total_rev", "total_gross", "total_payment", "total_cost", "total_paid", "budget_data", "expense_data", "start_date", "end_date", "task_type", "task_count", "service_fee_standard", "project_manager", "task_data", "project_code", "contact_name", "contact_phone", "other_info"]),
"planExpense": ("plan_expense_records", ["expense_type", "expense_month", "amount", "incurred_amount", "notes", "status", "tenant"]),
}
# ---------- 鉴权装饰器 ----------
@@ -426,7 +428,7 @@ def bootstrap():
"recent": sorted(all_recent, key=lambda x: x.get("id", 0), reverse=True)[:8],
"risks": [],
}
return jsonify({"summary": summary, "sales": [], "proposals": [], "operations": [], "products": [], "finance": [], "projectFinances": [], "financeMonthly": merged_monthly, "tasks": [], "expense": [], "tenant": tenant, "tenants": allowed})
return jsonify({"summary": summary, "sales": [], "proposals": [], "operations": [], "products": [], "finance": [], "projectFinances": [], "financeMonthly": merged_monthly, "tasks": [], "expense": [], "planFinances": [], "planExpense": [], "tenant": tenant, "tenants": allowed})
def q(sql, *args):
return rows(conn, sql, args)
@@ -438,6 +440,8 @@ def bootstrap():
tasks = q("SELECT * FROM project_tasks WHERE tenant=? ORDER BY phase, sort_order, id", tenant)
pfs = q("SELECT * FROM project_finances WHERE tenant=? ORDER BY id DESC", tenant)
expense = q("SELECT * FROM expense_records WHERE tenant=? ORDER BY id DESC", tenant)
planPfs = q("SELECT * FROM plan_finances WHERE tenant=? ORDER BY id DESC", tenant)
planExpense = q("SELECT * FROM plan_expense_records WHERE tenant=? ORDER BY id DESC", tenant)
signed_pfs = [x for x in pfs if x["status"] == "已签约"]
def parse_budget(pf):
@@ -650,7 +654,7 @@ def bootstrap():
"recent": q("SELECT * FROM follow_up_records WHERE tenant=? ORDER BY id DESC LIMIT 8", tenant),
"risks": [{"title": "执行提醒", "content": x["next_action"]} for x in operations if x["next_action"]][:5],
}
return jsonify({"summary": summary, "sales": sales, "proposals": proposals, "operations": operations, "products": products, "finance": finance, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "expense": expense, "tenant": tenant, "tenants": allowed})
return jsonify({"summary": summary, "sales": sales, "proposals": proposals, "operations": operations, "products": products, "finance": finance, "projectFinances": pfs, "financeMonthly": monthly_finance(conn, tenant), "tasks": tasks, "expense": expense, "planFinances": planPfs, "planExpense": planExpense, "tenant": tenant, "tenants": allowed})
finally:
conn.close()