|
|
|
@@ -0,0 +1,190 @@ |
|
|
|
import os |
|
|
|
import sqlite3 |
|
|
|
import argparse |
|
|
|
from typing import List, Tuple |
|
|
|
|
|
|
|
DB_FILE = "finance_demo.db" |
|
|
|
|
|
|
|
DEPARTMENTS = ["市场部", "研发部", "行政部", "财务部", "销售部"] |
|
|
|
CATEGORIES = ["广告", "人力", "办公", "差旅", "培训"] |
|
|
|
VENDORS = { |
|
|
|
"广告": ["XX传媒", "广告联盟", "新媒体投放"], |
|
|
|
"人力": ["外包A", "外包B", "人才中介"], |
|
|
|
"办公": ["办公城", "文具行", "设备商"], |
|
|
|
"差旅": ["航空公司", "铁路公司", "酒店集团"], |
|
|
|
"培训": ["培训机构", "认证中心"], |
|
|
|
} |
|
|
|
|
|
|
|
EXPENSES_ROWS: List[Tuple[str, str, str, str, float]] = [ |
|
|
|
# 2024-07 |
|
|
|
("市场部", "2024-07-05", "广告", "XX传媒", 15000), |
|
|
|
("研发部", "2024-07-15", "人力", "外包A", 25000), |
|
|
|
("行政部", "2024-07-08", "办公", "办公城", 5000), |
|
|
|
("财务部", "2024-07-22", "差旅", "航空公司", 12000), |
|
|
|
("销售部", "2024-07-18", "培训", "培训机构", 6000), |
|
|
|
|
|
|
|
# 2024-08 |
|
|
|
("市场部", "2024-08-10", "广告", "XX传媒", 18000), |
|
|
|
("研发部", "2024-08-20", "人力", "外包B", 30000), |
|
|
|
("行政部", "2024-08-12", "办公", "办公城", 8000), |
|
|
|
("财务部", "2024-08-25", "差旅", "铁路公司", 9000), |
|
|
|
("销售部", "2024-08-03", "培训", "培训机构", 7000), |
|
|
|
("销售部", "2024-08-18", "广告", "广告联盟", 11000), |
|
|
|
("市场部", "2024-08-22", "广告", "新媒体投放", 12000), # 便于供应商聚合/TopN |
|
|
|
|
|
|
|
# 2024-09 |
|
|
|
("市场部", "2024-09-06", "广告", "XX传媒", 12000), |
|
|
|
("研发部", "2024-09-10", "人力", "外包A", 28000), |
|
|
|
("行政部", "2024-09-13", "办公", "文具行", 3000), |
|
|
|
("财务部", "2024-09-21", "差旅", "酒店集团", 6000), |
|
|
|
("销售部", "2024-09-02", "培训", "认证中心", 5000), |
|
|
|
("销售部", "2024-09-27", "广告", "广告联盟", 9000), |
|
|
|
|
|
|
|
# 假期段 2024-10-01 ~ 2024-10-07(用于节假日区间查询示例) |
|
|
|
("市场部", "2024-10-01", "广告", "XX传媒", 5000), |
|
|
|
("研发部", "2024-10-03", "人力", "外包B", 7000), |
|
|
|
("行政部", "2024-10-05", "办公", "设备商", 4000), |
|
|
|
] |
|
|
|
|
|
|
|
# 月度预算 |
|
|
|
BUDGETS_ROWS: List[Tuple[str, str, float]] = [ |
|
|
|
# 2024-07 |
|
|
|
("市场部", "2024-07", 20000), |
|
|
|
("研发部", "2024-07", 26000), |
|
|
|
("行政部", "2024-07", 6000), |
|
|
|
("财务部", "2024-07", 15000), |
|
|
|
("销售部", "2024-07", 8000), |
|
|
|
# 2024-08 |
|
|
|
("市场部", "2024-08", 22000), |
|
|
|
("研发部", "2024-08", 31000), |
|
|
|
("行政部", "2024-08", 9000), |
|
|
|
("财务部", "2024-08", 10000), |
|
|
|
("销售部", "2024-08", 12000), |
|
|
|
# 2024-09 |
|
|
|
("市场部", "2024-09", 16000), |
|
|
|
("研发部", "2024-09", 29000), |
|
|
|
("行政部", "2024-09", 7000), |
|
|
|
("财务部", "2024-09", 8000), |
|
|
|
("销售部", "2024-09", 11000), |
|
|
|
] |
|
|
|
|
|
|
|
CREATE_EXPENSES_SQL = """ |
|
|
|
CREATE TABLE IF NOT EXISTS expenses ( |
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
|
|
department TEXT NOT NULL, |
|
|
|
date TEXT NOT NULL, -- YYYY-MM-DD |
|
|
|
category TEXT NOT NULL, -- 差旅/广告/人力/办公/培训 等 |
|
|
|
vendor TEXT NOT NULL, |
|
|
|
amount REAL NOT NULL |
|
|
|
); |
|
|
|
""" |
|
|
|
|
|
|
|
CREATE_BUDGETS_SQL = """ |
|
|
|
CREATE TABLE IF NOT EXISTS budgets ( |
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, |
|
|
|
department TEXT NOT NULL, |
|
|
|
month TEXT NOT NULL, -- YYYY-MM |
|
|
|
budget_amount REAL NOT NULL |
|
|
|
); |
|
|
|
""" |
|
|
|
|
|
|
|
def ensure_db(reset: bool) -> sqlite3.Connection: |
|
|
|
"""创建/重置数据库文件并建表。""" |
|
|
|
if reset and os.path.exists(DB_FILE): |
|
|
|
os.remove(DB_FILE) |
|
|
|
print(f"已删除旧数据库: {DB_FILE}") |
|
|
|
|
|
|
|
need_create = not os.path.exists(DB_FILE) |
|
|
|
conn = sqlite3.connect(DB_FILE) |
|
|
|
cur = conn.cursor() |
|
|
|
|
|
|
|
if need_create: |
|
|
|
print(f"创建新数据库: {DB_FILE}") |
|
|
|
|
|
|
|
cur.execute(CREATE_EXPENSES_SQL) |
|
|
|
cur.execute(CREATE_BUDGETS_SQL) |
|
|
|
conn.commit() |
|
|
|
return conn |
|
|
|
|
|
|
|
def table_count(cur: sqlite3.Cursor, table: str) -> int: |
|
|
|
cur.execute(f"SELECT COUNT(*) FROM {table}") |
|
|
|
return cur.fetchone()[0] |
|
|
|
|
|
|
|
def insert_if_empty(conn: sqlite3.Connection): |
|
|
|
"""仅在表为空时插入初始化数据;若已有数据则跳过。""" |
|
|
|
cur = conn.cursor() |
|
|
|
exp_cnt = table_count(cur, "expenses") |
|
|
|
bud_cnt = table_count(cur, "budgets") |
|
|
|
|
|
|
|
if exp_cnt == 0: |
|
|
|
cur.executemany( |
|
|
|
"INSERT INTO expenses(department,date,category,vendor,amount) VALUES(?,?,?,?,?)", |
|
|
|
EXPENSES_ROWS |
|
|
|
) |
|
|
|
print(f"插入 expenses: {len(EXPENSES_ROWS)} 条") |
|
|
|
else: |
|
|
|
print(f"expenses 已有 {exp_cnt} 条,跳过插入。") |
|
|
|
|
|
|
|
if bud_cnt == 0: |
|
|
|
cur.executemany( |
|
|
|
"INSERT INTO budgets(department,month,budget_amount) VALUES(?,?,?)", |
|
|
|
BUDGETS_ROWS |
|
|
|
) |
|
|
|
print(f"插入 budgets: {len(BUDGETS_ROWS)} 条") |
|
|
|
else: |
|
|
|
print(f"budgets 已有 {bud_cnt} 条,跳过插入。") |
|
|
|
|
|
|
|
conn.commit() |
|
|
|
|
|
|
|
def quick_selfcheck(conn: sqlite3.Connection): |
|
|
|
"""跑几条典型 SQL 做快速自检,与你的 Demo 问题一致。""" |
|
|
|
cur = conn.cursor() |
|
|
|
|
|
|
|
checks = [ |
|
|
|
("检查 2024-08 市场部费用总额", |
|
|
|
"SELECT IFNULL(SUM(amount),0) FROM expenses WHERE department='市场部' AND date LIKE '2024-08%';"), |
|
|
|
("检查 2024-08 各部门费用从高到低", |
|
|
|
"SELECT department, IFNULL(SUM(amount),0) AS total FROM expenses WHERE date LIKE '2024-08%' GROUP BY department ORDER BY total DESC;"), |
|
|
|
("检查 2024-08 预算与实际对比", |
|
|
|
"SELECT b.department, b.budget_amount, IFNULL(e.actual,0) AS actual, (IFNULL(e.actual,0)-b.budget_amount) AS diff " |
|
|
|
"FROM budgets b LEFT JOIN (SELECT department, SUM(amount) AS actual FROM expenses WHERE date LIKE '2024-08%' GROUP BY department) e " |
|
|
|
"ON b.department=e.department WHERE b.month='2024-08' ORDER BY diff DESC;"), |
|
|
|
("检查 2024-10-01 到 2024-10-07 区间记录", |
|
|
|
"SELECT date, department, category, vendor, amount FROM expenses WHERE date BETWEEN '2024-10-01' AND '2024-10-07' ORDER BY date;"), |
|
|
|
] |
|
|
|
|
|
|
|
print("\n==== 快速自检(部分查询结果预览) ====") |
|
|
|
for title, sql in checks: |
|
|
|
cur.execute(sql) |
|
|
|
rows = cur.fetchall() |
|
|
|
print(f"- {title}: {len(rows)} 行") |
|
|
|
# 打印最多前 3 行 |
|
|
|
for r in rows[:3]: |
|
|
|
print(" ", tuple(r)) |
|
|
|
print("==== 自检结束 ====\n") |
|
|
|
|
|
|
|
def parse_args(): |
|
|
|
ap = argparse.ArgumentParser(description="初始化 finance_demo.db") |
|
|
|
g = ap.add_mutually_exclusive_group() |
|
|
|
g.add_argument("--reset", action="store_true", help="若存在旧数据库则删除重建(默认)") |
|
|
|
g.add_argument("--keep", action="store_true", help="若存在旧数据库则保留,仅在表为空时插入") |
|
|
|
return ap.parse_args() |
|
|
|
|
|
|
|
def main(): |
|
|
|
args = parse_args() |
|
|
|
reset = True |
|
|
|
if args.keep: |
|
|
|
reset = False |
|
|
|
|
|
|
|
conn = ensure_db(reset=reset) |
|
|
|
insert_if_empty(conn) |
|
|
|
quick_selfcheck(conn) |
|
|
|
conn.close() |
|
|
|
|
|
|
|
print(f"✅ 初始化完成,数据库文件: {DB_FILE}") |
|
|
|
print("下一步:") |
|
|
|
print(" 1) 运行 uvicorn middleware:app --reload") |
|
|
|
print(" 2) 打开 http://127.0.0.1:8000/ui/ 进行演示") |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
main() |