优化更新

This commit is contained in:
侯欢 2025-12-09 13:28:22 +08:00
parent 1b1daa581e
commit 5e68d97127
4 changed files with 108 additions and 31 deletions

View File

@ -2,3 +2,4 @@
准备发送消息: 【益选便利店】2025-12-08的营业额3402.6
准备发送消息: 【益选便利店】2025-12-08的营业额3629.76
准备发送消息: 【益选便利店】2025-12-06的营业额1803.09
准备发送消息: 【益选便利店】2025-12-09的营业额3462.53

View File

@ -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:
p = ct.split(':')
ch = int(p[0]); cm = int(p[1])
except Exception:
pass
try:
cutoff = int(cutoff)
ch = int(ch)
except Exception:
cutoff = 23
# 只有当前时间 >= cutoff 才尝试结算今天
if datetime.now().hour >= cutoff:
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:
p = ct.split(':')
ch = int(p[0]); cm = int(p[1])
except Exception:
pass
try:
cutoff = int(load_config().get("cutoff_hour", 23))
ch = int(ch)
except Exception:
cutoff = 23
scheduler.add_job(daily_job, "cron", hour=cutoff, minute=0)
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):

View File

@ -11,5 +11,6 @@
1600,
2000
],
"cutoff_hour": 11
"cutoff_hour": 13,
"cutoff_time": "13:18"
}

Binary file not shown.