247 lines
8.5 KiB
Python
247 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
成交/未成交记录 - 用于日报与数据分析
|
||
"""
|
||
import sqlite3
|
||
import os
|
||
from datetime import datetime
|
||
from typing import List, Dict, Optional
|
||
|
||
_DB_PATH = os.path.join(os.path.dirname(__file__), "deal_outcome_db", "outcomes.db")
|
||
_DB_TYPE = os.getenv("DB_TYPE", "sqlite").lower()
|
||
_MYSQL_HOST = os.getenv("MYSQL_HOST", "127.0.0.1")
|
||
_MYSQL_PORT = int(os.getenv("MYSQL_PORT", "3306"))
|
||
_MYSQL_USER = os.getenv("MYSQL_USER", "root")
|
||
_MYSQL_PASSWORD = os.getenv("MYSQL_PASSWORD", "")
|
||
_MYSQL_DATABASE = os.getenv("MYSQL_DATABASE", "ai_cs")
|
||
|
||
|
||
class _CompatResult:
|
||
def __init__(self, rows=None, rowcount: int = 0, lastrowid: int = 0):
|
||
self._rows = rows or []
|
||
self.rowcount = rowcount
|
||
self.lastrowid = lastrowid
|
||
|
||
def fetchall(self):
|
||
return self._rows
|
||
|
||
def fetchone(self):
|
||
return self._rows[0] if self._rows else None
|
||
|
||
|
||
class _PyMySQLCompatConn:
|
||
def __init__(self, conn):
|
||
self._conn = conn
|
||
|
||
def __enter__(self):
|
||
return self
|
||
|
||
def __exit__(self, exc_type, exc, tb):
|
||
if exc_type:
|
||
try:
|
||
self._conn.rollback()
|
||
except Exception:
|
||
pass
|
||
self._conn.close()
|
||
|
||
def execute(self, query: str, args=None):
|
||
cur = self._conn.cursor()
|
||
cur.execute(query, args or ())
|
||
rows = cur.fetchall() if cur.description else []
|
||
res = _CompatResult(rows=rows, rowcount=cur.rowcount, lastrowid=getattr(cur, "lastrowid", 0))
|
||
cur.close()
|
||
return res
|
||
|
||
def commit(self):
|
||
self._conn.commit()
|
||
|
||
def _is_mysql() -> bool:
|
||
return _DB_TYPE in ("mysql", "mariadb")
|
||
|
||
def _sql(query: str) -> str:
|
||
return query.replace("?", "%s") if _is_mysql() else query
|
||
|
||
|
||
def _get_conn() -> sqlite3.Connection:
|
||
if _is_mysql():
|
||
import pymysql
|
||
conn = pymysql.connect(
|
||
host=_MYSQL_HOST,
|
||
port=_MYSQL_PORT,
|
||
user=_MYSQL_USER,
|
||
password=_MYSQL_PASSWORD,
|
||
database=_MYSQL_DATABASE,
|
||
charset="utf8mb4",
|
||
cursorclass=pymysql.cursors.DictCursor,
|
||
autocommit=False,
|
||
)
|
||
return _PyMySQLCompatConn(conn)
|
||
os.makedirs(os.path.dirname(_DB_PATH), exist_ok=True)
|
||
conn = sqlite3.connect(_DB_PATH)
|
||
conn.row_factory = sqlite3.Row
|
||
return conn
|
||
|
||
|
||
def _init_db():
|
||
with _get_conn() as conn:
|
||
if _is_mysql():
|
||
conn.execute("""
|
||
CREATE TABLE IF NOT EXISTS deal_outcomes (
|
||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||
customer_id VARCHAR(128) NOT NULL,
|
||
customer_name VARCHAR(255) DEFAULT '',
|
||
acc_id VARCHAR(128) DEFAULT '',
|
||
platform VARCHAR(64) DEFAULT '',
|
||
date DATE NOT NULL,
|
||
outcome VARCHAR(16) NOT NULL,
|
||
reason TEXT,
|
||
order_id VARCHAR(128) DEFAULT '',
|
||
amount REAL DEFAULT 0,
|
||
discount_given INTEGER DEFAULT 0,
|
||
timestamp DATETIME NOT NULL
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||
""")
|
||
idx_rows = conn.execute("SHOW INDEX FROM deal_outcomes").fetchall()
|
||
exists = {str(r.get("Key_name", "")) for r in idx_rows}
|
||
if "idx_deal_date" not in exists:
|
||
conn.execute("CREATE INDEX idx_deal_date ON deal_outcomes(date)")
|
||
if "idx_deal_customer" not in exists:
|
||
conn.execute("CREATE INDEX idx_deal_customer ON deal_outcomes(customer_id)")
|
||
if "idx_deal_acc" not in exists:
|
||
conn.execute("CREATE INDEX idx_deal_acc ON deal_outcomes(acc_id)")
|
||
if "idx_deal_outcome" not in exists:
|
||
conn.execute("CREATE INDEX idx_deal_outcome ON deal_outcomes(outcome)")
|
||
else:
|
||
conn.execute("""
|
||
CREATE TABLE IF NOT EXISTS deal_outcomes (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
customer_id TEXT NOT NULL,
|
||
customer_name TEXT DEFAULT '',
|
||
acc_id TEXT DEFAULT '',
|
||
platform TEXT DEFAULT '',
|
||
date TEXT NOT NULL,
|
||
outcome TEXT NOT NULL CHECK(outcome IN ('成交','未成交')),
|
||
reason TEXT DEFAULT '',
|
||
order_id TEXT DEFAULT '',
|
||
amount REAL DEFAULT 0,
|
||
discount_given INTEGER DEFAULT 0,
|
||
timestamp TEXT NOT NULL
|
||
)
|
||
""")
|
||
conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_date ON deal_outcomes(date)")
|
||
conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_customer ON deal_outcomes(customer_id)")
|
||
conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_acc ON deal_outcomes(acc_id)")
|
||
conn.execute("CREATE INDEX IF NOT EXISTS idx_deal_outcome ON deal_outcomes(outcome)")
|
||
conn.commit()
|
||
|
||
|
||
_init_db()
|
||
|
||
|
||
def record_deal(
|
||
customer_id: str,
|
||
outcome: str,
|
||
reason: str = "",
|
||
customer_name: str = "",
|
||
acc_id: str = "",
|
||
platform: str = "",
|
||
order_id: str = "",
|
||
amount: float = 0,
|
||
discount_given: bool = False,
|
||
):
|
||
"""记录一笔成交或未成交"""
|
||
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||
date = datetime.now().strftime("%Y-%m-%d")
|
||
with _get_conn() as conn:
|
||
conn.execute(
|
||
_sql("""INSERT INTO deal_outcomes
|
||
(customer_id, customer_name, acc_id, platform, date, outcome, reason,
|
||
order_id, amount, discount_given, timestamp)
|
||
VALUES (?,?,?,?,?,?,?,?,?,?,?)"""),
|
||
(
|
||
customer_id,
|
||
customer_name or "",
|
||
acc_id or "",
|
||
platform or "",
|
||
date,
|
||
outcome,
|
||
reason or "",
|
||
order_id or "",
|
||
amount,
|
||
1 if discount_given else 0,
|
||
ts,
|
||
),
|
||
)
|
||
conn.commit()
|
||
|
||
|
||
def get_daily_outcomes(date: str = "") -> List[Dict]:
|
||
"""获取指定日期的成交/未成交记录,用于日报"""
|
||
if not date:
|
||
date = datetime.now().strftime("%Y-%m-%d")
|
||
with _get_conn() as conn:
|
||
rows = conn.execute(
|
||
_sql("""
|
||
SELECT customer_id, customer_name, acc_id, outcome, reason,
|
||
order_id, amount, discount_given, timestamp
|
||
FROM deal_outcomes
|
||
WHERE date = ?
|
||
ORDER BY timestamp ASC
|
||
"""),
|
||
(date,),
|
||
).fetchall()
|
||
return [dict(r) for r in rows]
|
||
|
||
|
||
def get_daily_summary(date: str = "") -> Dict:
|
||
"""获取指定日期的成交/未成交汇总统计"""
|
||
outcomes = get_daily_outcomes(date)
|
||
success = [o for o in outcomes if o["outcome"] == "成交"]
|
||
fail = [o for o in outcomes if o["outcome"] == "未成交"]
|
||
|
||
# 按原因分组
|
||
fail_by_reason: Dict[str, int] = {}
|
||
for o in fail:
|
||
r = o.get("reason") or "其他"
|
||
fail_by_reason[r] = fail_by_reason.get(r, 0) + 1
|
||
|
||
return {
|
||
"date": date or datetime.now().strftime("%Y-%m-%d"),
|
||
"成交数": len(success),
|
||
"未成交数": len(fail),
|
||
"成交金额": sum(o.get("amount") or 0 for o in success),
|
||
"成交明细": success,
|
||
"未成交明细": fail,
|
||
"未成交原因分布": fail_by_reason,
|
||
}
|
||
|
||
|
||
def export_for_analysis(start_date: str = "", end_date: str = "") -> List[Dict]:
|
||
"""
|
||
导出成交/未成交记录,供数据库分析。
|
||
日期格式 YYYY-MM-DD,留空则查全部。
|
||
"""
|
||
with _get_conn() as conn:
|
||
if start_date and end_date:
|
||
rows = conn.execute(
|
||
_sql("""SELECT * FROM deal_outcomes
|
||
WHERE date BETWEEN ? AND ?
|
||
ORDER BY date, timestamp"""),
|
||
(start_date, end_date),
|
||
).fetchall()
|
||
elif start_date:
|
||
rows = conn.execute(
|
||
_sql("""SELECT * FROM deal_outcomes WHERE date >= ? ORDER BY date, timestamp"""),
|
||
(start_date,),
|
||
).fetchall()
|
||
elif end_date:
|
||
rows = conn.execute(
|
||
_sql("""SELECT * FROM deal_outcomes WHERE date <= ? ORDER BY date, timestamp"""),
|
||
(end_date,),
|
||
).fetchall()
|
||
else:
|
||
rows = conn.execute(
|
||
"""SELECT * FROM deal_outcomes ORDER BY date, timestamp"""
|
||
).fetchall()
|
||
return [dict(r) for r in rows]
|