fix(businessdate): 修 dayend_balance/ledger 错误 import accounting.businessdate(实际在 appbase)——导入即 ModuleNotFoundError,切日/日结链路从未可用;dayend_balance 支持注入 sor 复用调用方事务上下文+返回快照日;新增 wwwroot/api/dayend.dspy 切日端点(localhost 门禁+当日幂等守卫,三步:日结余额快照→总账→business_date+1)供生产 cron 调用(2026-09-09 生产增量部署前置)
This commit is contained in:
parent
8cc85098a9
commit
c40f7f665a
@ -53,6 +53,14 @@ account_config + accounting_config 记账配置 + currency + exchange_rate 初
|
||||
- `currency_create/update/delete.dspy` — 币种维护
|
||||
- `exchange_rate_create/update/delete.dspy` — 汇率维护
|
||||
- `fetch_forex_rates.dspy` — 拉取外部外汇牌价
|
||||
- `dayend.dspy` — 日终切日(localhost 门禁 + 当日幂等守卫)。三步一个事务:
|
||||
`dayend_balance`(上一营业日余额快照进 acc_balance)→ `accounting_ledger`(总账)
|
||||
→ `new_business_date`(params.business_date +1 天)。供宿主应用 crontab 每日调用
|
||||
(pipeline-app 生产:`5 0 * * * curl -s http://127.0.0.1:9090/accounting/api/dayend.dspy`)。
|
||||
⚠️ 依赖 params 表已有 business_date 行,缺失会抛 BusinessDateParamsError——生产播种
|
||||
见 pipeline-app 迁移 m0017。历史 bug(2026-09-09 修复):`dayend_balance.py`/`ledger.py`
|
||||
曾写 `from accounting.businessdate import ...`(实际模块在 appbase),导入即
|
||||
ModuleNotFoundError,日结/总账从未可运行。
|
||||
|
||||
**credit_limit/(信用额度管理)**:
|
||||
|
||||
|
||||
@ -1,18 +1,30 @@
|
||||
from datetime import datetime
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.uniqueID import getID
|
||||
from accounting.businessdate import previous_business_date
|
||||
from appbase.businessdate import previous_business_date
|
||||
from accounting.const import *
|
||||
|
||||
async def dayend_balance():
|
||||
dat = await previous_business_date()
|
||||
ts = datetime.now()
|
||||
sql = """select a.* from (select accountid, max(acc_date) as acc_date, balance from acc_balance where accountid is not null group by accountid) a where acc_date < ${acc_date}$"""
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(DBNAME()) as sor:
|
||||
async def dayend_balance(sor=None):
|
||||
"""日结:把「上一营业日」之前没有余额行的账户快照成 acc_balance 行。
|
||||
|
||||
返回快照的营业日(dat)。可注入 sor(切日端点在同一事务上下文里串
|
||||
new_business_date → dayend_balance → accounting_ledger 三步)。
|
||||
历史 bug 修复(2026-09-09):原 import accounting.businessdate 是错的
|
||||
(businessdate 在 appbase 模块),导入即 ModuleNotFoundError——本函数
|
||||
从未被任何入口调用过,切日链路整体缺位。
|
||||
"""
|
||||
async def _f(sor):
|
||||
dat = await previous_business_date(sor=sor)
|
||||
sql = """select a.* from (select accountid, max(acc_date) as acc_date, balance from acc_balance where accountid is not null group by accountid) a where acc_date < ${acc_date}$"""
|
||||
recs = await sor.sqlExe(sql, {'acc_date':dat})
|
||||
for r in recs:
|
||||
r['id'] = getID()
|
||||
r['acc_date'] = dat
|
||||
await sor.C('acc_balance', r)
|
||||
return dat
|
||||
|
||||
if sor:
|
||||
return await _f(sor)
|
||||
db = DBPools()
|
||||
async with db.sqlorContext(DBNAME()) as sor:
|
||||
return await _f(sor)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
from datetime import datetime
|
||||
from appPublic.uniqueID import getID
|
||||
from appPublic.timeUtils import strdate_add
|
||||
from accounting.businessdate import get_business_date
|
||||
from appbase.businessdate import get_business_date
|
||||
|
||||
async def accounting_ledger(sor):
|
||||
rd = await get_business_date(sor)
|
||||
|
||||
48
wwwroot/api/dayend.dspy
Normal file
48
wwwroot/api/dayend.dspy
Normal file
@ -0,0 +1,48 @@
|
||||
# 日终切日任务端点:日结余额快照 + 日结总账 + 营业日期推进
|
||||
# GET /accounting/api/dayend.dspy
|
||||
#
|
||||
# 安全:本端点是定时任务入口(写库),只允许本机调用——与 fetch_forex_rates.dspy 同模式。
|
||||
# 判定依据 client_ip:nginx 用 $proxy_add_x_forwarded_for 追加模式、中间件取链尾值,
|
||||
# 外部伪造 X-Forwarded-For 会被追加真实 IP 到链尾,伪造不成立;且应用端口不对外开放。
|
||||
_ip = request.get('client_ip') or ''
|
||||
if _ip not in ('127.0.0.1', '::1', 'localhost'):
|
||||
return json.dumps({'success': False, 'message': '仅允许本机调用(定时任务入口)'},
|
||||
ensure_ascii=False)
|
||||
|
||||
from sqlor.dbpools import get_sor_context
|
||||
|
||||
# 切日三步(同一 sor 上下文,全成才提交):
|
||||
# 1. dayend_balance —— 上一营业日余额快照(acc_balance 补齐无当日行的账户)
|
||||
# 2. accounting_ledger —— 上一营业日总账(delete+insert 幂等重建 ledger 当日行)
|
||||
# 3. new_business_date —— params.business_date 推进 +1 天
|
||||
# 顺序不可颠倒:前两步都读「上一营业日」,推进日期必须在最后。
|
||||
async with get_sor_context(request._run_ns, 'accounting') as sor:
|
||||
try:
|
||||
from datetime import date
|
||||
from accounting.dayend_balance import dayend_balance
|
||||
from accounting.ledger import accounting_ledger
|
||||
from appbase.businessdate import get_business_date, new_business_date
|
||||
|
||||
old_bd = await get_business_date(sor)
|
||||
# 幂等守卫:business_date 已到今天则跳过(cron 重跑/手工重放不会多跳日期)
|
||||
today = date.today().isoformat()
|
||||
if old_bd >= today:
|
||||
return json.dumps({
|
||||
'success': True, 'skipped': True,
|
||||
'business_date': old_bd,
|
||||
'message': '已是当日营业日期,跳过切日(幂等)',
|
||||
}, ensure_ascii=False)
|
||||
|
||||
snap_date = await dayend_balance(sor)
|
||||
await accounting_ledger(sor)
|
||||
await new_business_date(sor)
|
||||
new_bd = await get_business_date(sor)
|
||||
await sor.sqlExe("COMMIT", {})
|
||||
return json.dumps({
|
||||
'success': True,
|
||||
'business_date': '%s -> %s' % (old_bd, new_bd),
|
||||
'snapshot_date': snap_date,
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return json.dumps({'success': False, 'message': 'dayend failed: %s' % str(e)[:200]},
|
||||
ensure_ascii=False)
|
||||
Loading…
x
Reference in New Issue
Block a user