优化更新
This commit is contained in:
parent
1b1daa581e
commit
5e68d97127
1
app.log
1
app.log
@ -2,3 +2,4 @@
|
||||
准备发送消息: 【益选便利店】2025-12-08的营业额:3402.6
|
||||
准备发送消息: 【益选便利店】2025-12-08的营业额:3629.76
|
||||
准备发送消息: 【益选便利店】2025-12-06的营业额:1803.09
|
||||
准备发送消息: 【益选便利店】2025-12-09的营业额:3462.53
|
||||
|
||||
135
backend/app.py
135
backend/app.py
@ -23,23 +23,24 @@ load_dotenv()
|
||||
app = Flask(__name__, static_folder="../frontend", static_url_path="/static")
|
||||
CORS(app)
|
||||
|
||||
# 强制使用绝对路径解决 Windows 路径问题
|
||||
base_dir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
|
||||
db_path = os.path.join(base_dir, "data", "data.db")
|
||||
default_db_path = os.path.join(base_dir, "data", "data.db")
|
||||
if os.name == 'nt':
|
||||
# Windows 需要转义反斜杠,或者使用 4 个斜杠 + 驱动器号
|
||||
# SQLAlchemy 在 Windows 上通常接受 sqlite:///C:\path\to\file
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{db_path}"
|
||||
default_db_url = f"sqlite:///{default_db_path}"
|
||||
else:
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:////{db_path}"
|
||||
default_db_url = f"sqlite:////{default_db_path}"
|
||||
db_url = os.getenv('DATABASE_URL') or default_db_url
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = db_url
|
||||
|
||||
def _ensure_sqlite_dir(url):
|
||||
# 已通过绝对路径计算,直接检查 data 目录
|
||||
d = os.path.dirname(db_path)
|
||||
if not os.path.exists(d):
|
||||
def _ensure_sqlite_dir(url: str):
|
||||
if not isinstance(url, str) or not url.startswith('sqlite'):
|
||||
return
|
||||
p = url.replace('sqlite:////', '/').replace('sqlite:///', '')
|
||||
d = os.path.dirname(p)
|
||||
if d and not os.path.exists(d):
|
||||
os.makedirs(d, exist_ok=True)
|
||||
|
||||
_ensure_sqlite_dir(app.config['SQLALCHEMY_DATABASE_URI'])
|
||||
_ensure_sqlite_dir(db_url)
|
||||
db = SQLAlchemy(app)
|
||||
|
||||
class DailyRevenue(db.Model):
|
||||
@ -125,19 +126,33 @@ def daily_job(target_date=None):
|
||||
|
||||
def settle_today_if_due():
|
||||
cfg = load_config()
|
||||
cutoff = cfg.get("cutoff_hour", 23)
|
||||
ct = cfg.get("cutoff_time")
|
||||
ch = cfg.get("cutoff_hour", 23)
|
||||
cm = 0
|
||||
if isinstance(ct, str) and re.match(r'^\d{1,2}:\d{2}$', ct):
|
||||
try:
|
||||
cutoff = int(cutoff)
|
||||
p = ct.split(':')
|
||||
ch = int(p[0]); cm = int(p[1])
|
||||
except Exception:
|
||||
cutoff = 23
|
||||
# 只有当前时间 >= cutoff 才尝试结算今天
|
||||
if datetime.now().hour >= cutoff:
|
||||
pass
|
||||
try:
|
||||
ch = int(ch)
|
||||
except Exception:
|
||||
ch = 23
|
||||
if ch < 0 or ch > 23:
|
||||
ch = 23
|
||||
if cm < 0 or cm > 59:
|
||||
cm = 0
|
||||
local_tz = get_localzone()
|
||||
now = datetime.now(local_tz)
|
||||
if (now.hour > ch) or (now.hour == ch and now.minute >= cm):
|
||||
daily_job()
|
||||
|
||||
def settle_past_days():
|
||||
"""启动检查:补录过去3天未定版的数据(防止服务器宕机漏单)"""
|
||||
with app.app_context():
|
||||
today = datetime.now().date()
|
||||
local_tz = get_localzone()
|
||||
today = datetime.now(local_tz).date()
|
||||
for i in range(1, 4):
|
||||
d = today - timedelta(days=i)
|
||||
existing = DailyRevenue.query.filter_by(date=d).first()
|
||||
@ -209,7 +224,9 @@ def get_periods(today: date):
|
||||
def api_metrics():
|
||||
cfg = load_config()
|
||||
shop_name = cfg.get("shop_name", "益选便利店")
|
||||
today_local = datetime.now().date()
|
||||
local_tz = get_localzone()
|
||||
now_local = datetime.now(local_tz)
|
||||
today_local = now_local.date()
|
||||
periods = get_periods(today_local)
|
||||
yday = periods["yesterday"]
|
||||
day_before = periods["day_before"]
|
||||
@ -230,11 +247,33 @@ def api_metrics():
|
||||
cur += timedelta(days=1)
|
||||
return round(total, 2)
|
||||
weekdays = ['周一','周二','周三','周四','周五','周六','周日']
|
||||
def get_cutoff_hm():
|
||||
ct = cfg.get("cutoff_time")
|
||||
h = cfg.get("cutoff_hour", 23)
|
||||
m = 0
|
||||
if isinstance(ct, str) and re.match(r'^\d{1,2}:\d{2}$', ct):
|
||||
try:
|
||||
p = ct.split(':')
|
||||
h = int(p[0]); m = int(p[1])
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
h = int(h)
|
||||
except Exception:
|
||||
h = 23
|
||||
if h < 0 or h > 23:
|
||||
h = 23
|
||||
if m < 0 or m > 59:
|
||||
m = 0
|
||||
return h, m
|
||||
ch, cm = get_cutoff_hm()
|
||||
show_today = (now_local.hour > ch) or (now_local.hour == ch and now_local.minute >= cm)
|
||||
out = {
|
||||
"shop_name": shop_name,
|
||||
"server_now": datetime.now().isoformat(timespec='seconds'),
|
||||
"cutoff_hour": 23,
|
||||
"today": {"date": today_local.isoformat(), "weekday": weekdays[today_local.weekday()], "amount": amt_for(today_local)},
|
||||
"server_now": now_local.isoformat(timespec='seconds'),
|
||||
"cutoff_hour": ch,
|
||||
"cutoff_time": f"{ch:02d}:{cm:02d}",
|
||||
"today": {"date": today_local.isoformat(), "weekday": weekdays[today_local.weekday()], "amount": (amt_for(today_local) if show_today else None)},
|
||||
"yesterday": {"date": yday.isoformat(), "amount": amt_for(yday)},
|
||||
"day_before": {"date": day_before.isoformat(), "amount": amt_for(day_before)},
|
||||
"this_week": {"start": tw_s.isoformat(), "end": tw_e.isoformat(), "total": sum_final(tw_s, tw_e)},
|
||||
@ -245,16 +284,36 @@ def api_metrics():
|
||||
|
||||
@app.route("/api/series7")
|
||||
def api_series7():
|
||||
today_local = datetime.now().date()
|
||||
local_tz = get_localzone()
|
||||
now_local = datetime.now(local_tz)
|
||||
today_local = now_local.date()
|
||||
days = int(request.args.get('days', 7))
|
||||
days = max(7, min(days, 90))
|
||||
# 结束日期:若今日已定版则含今日,否则到昨日
|
||||
# 结束日期:若到达截止时间且今日已定版则含今日,否则到昨日
|
||||
end = today_local
|
||||
r_today = DailyRevenue.query.filter_by(date=today_local).first()
|
||||
if not (r_today and r_today.is_final):
|
||||
cfg = load_config()
|
||||
ct = cfg.get("cutoff_time")
|
||||
ch = cfg.get("cutoff_hour", 23)
|
||||
cm = 0
|
||||
if isinstance(ct, str) and re.match(r'^\d{1,2}:\d{2}$', ct):
|
||||
try:
|
||||
p = ct.split(':')
|
||||
ch = int(p[0]); cm = int(p[1])
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ch = int(ch)
|
||||
except Exception:
|
||||
ch = 23
|
||||
if ch < 0 or ch > 23:
|
||||
ch = 23
|
||||
if cm < 0 or cm > 59:
|
||||
cm = 0
|
||||
show_today = (now_local.hour > ch) or (now_local.hour == ch and now_local.minute >= cm)
|
||||
if not (show_today and r_today and r_today.is_final):
|
||||
end = today_local - timedelta(days=1)
|
||||
start = end - timedelta(days=days-1)
|
||||
cfg = load_config()
|
||||
series = []
|
||||
cur = start
|
||||
while cur <= end:
|
||||
@ -416,7 +475,8 @@ def auto_import_csv_on_start():
|
||||
def sync_log_to_db():
|
||||
"""启动时将 app.log 中缺失的数据同步到 DB(只同步今天之前)"""
|
||||
log_map = parse_app_log()
|
||||
today_local = datetime.now().date()
|
||||
local_tz = get_localzone()
|
||||
today_local = datetime.now(local_tz).date()
|
||||
for ds, amt in log_map.items():
|
||||
d = datetime.strptime(ds, '%Y-%m-%d').date()
|
||||
if d >= today_local:
|
||||
@ -430,11 +490,25 @@ def sync_log_to_db():
|
||||
if __name__ == "__main__":
|
||||
local_tz = get_localzone()
|
||||
scheduler = BackgroundScheduler(timezone=local_tz)
|
||||
cfg = load_config()
|
||||
ct = cfg.get("cutoff_time")
|
||||
ch = cfg.get("cutoff_hour", 23)
|
||||
cm = 0
|
||||
if isinstance(ct, str) and re.match(r'^\d{1,2}:\d{2}$', ct):
|
||||
try:
|
||||
cutoff = int(load_config().get("cutoff_hour", 23))
|
||||
p = ct.split(':')
|
||||
ch = int(p[0]); cm = int(p[1])
|
||||
except Exception:
|
||||
cutoff = 23
|
||||
scheduler.add_job(daily_job, "cron", hour=cutoff, minute=0)
|
||||
pass
|
||||
try:
|
||||
ch = int(ch)
|
||||
except Exception:
|
||||
ch = 23
|
||||
if ch < 0 or ch > 23:
|
||||
ch = 23
|
||||
if cm < 0 or cm > 59:
|
||||
cm = 0
|
||||
scheduler.add_job(daily_job, "cron", hour=ch, minute=cm)
|
||||
scheduler.start()
|
||||
with app.app_context():
|
||||
sync_log_to_db()
|
||||
@ -447,7 +521,8 @@ if __name__ == "__main__":
|
||||
def sse_events():
|
||||
def event_stream():
|
||||
while True:
|
||||
now = datetime.now()
|
||||
local_tz = get_localzone()
|
||||
now = datetime.now(local_tz)
|
||||
payload = {"type": "tick", "server_now": now.isoformat(timespec='seconds')}
|
||||
yield f"data: {json.dumps(payload)}\n\n"
|
||||
if now.minute in (0, 1):
|
||||
|
||||
@ -11,5 +11,6 @@
|
||||
1600,
|
||||
2000
|
||||
],
|
||||
"cutoff_hour": 11
|
||||
"cutoff_hour": 13,
|
||||
"cutoff_time": "13:18"
|
||||
}
|
||||
|
||||
BIN
data/data.db
BIN
data/data.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user