- settlement_service:generate(确认回款口径 #8 策略点=有效台账行按回款日 归月、经理聚合 #12 暂口径基数不含经理个人户、月结 uk 一行一月、草稿/ 驳回可重生成、已审定/待审 400)→ submit(审批开关分流,关=自动通过即 锁定、开=待财务复核)→ approval_service.act 扩 order_type=2/3 钩子 (通过=mark_locked/调整单生效;驳回=回草稿/作废,延迟导入防循环) - 锁定拦截:回款新增写路径校验回款月已审定 400(比需求「提示」取硬, 一期安全侧;M2 已核算禁改防线先于其生效) - 调整单:新建(TZ 单号,开关分流自动通过留痕/走审批)+ 列表 - schemas settlement:金额 Decimal 声明(str 声明遇 Decimal 入参拒绝) - 测试 6 例:生成回填/无记录 400/已审定重生成 400/关自动锁定/锁定后回款 禁改/开两节点流转至锁定(finance→leader) Co-Authored-By: Claude Code <noreply@anthropic.com>
95 lines
2.9 KiB
Python
95 lines
2.9 KiB
Python
"""月度结算仓储:只读写数据并 flush;commit/rollback 在服务层。"""
|
||
|
||
from sqlalchemy import func, select
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models import MonthlySettlement, SettlementItem
|
||
from app.schemas.settlement import SettlementQuery
|
||
|
||
|
||
def get_by_id(session: Session, settlement_id: int) -> MonthlySettlement | None:
|
||
return session.get(MonthlySettlement, settlement_id)
|
||
|
||
|
||
def get_by_month(session: Session, month: str) -> MonthlySettlement | None:
|
||
"""按月份取月结行(settlement_month 全表唯一)。"""
|
||
return session.execute(
|
||
select(MonthlySettlement).where(MonthlySettlement.settlement_month == month)
|
||
).scalar_one_or_none()
|
||
|
||
|
||
def add(session: Session, settlement: MonthlySettlement) -> MonthlySettlement:
|
||
session.add(settlement)
|
||
session.flush()
|
||
return settlement
|
||
|
||
|
||
def list_settlements(session: Session, query: SettlementQuery) -> list[MonthlySettlement]:
|
||
return list(
|
||
session.execute(
|
||
select(MonthlySettlement)
|
||
.where(*_filters(query))
|
||
.order_by(MonthlySettlement.settlement_month.desc())
|
||
.offset(query.offset)
|
||
.limit(query.page_size)
|
||
).scalars()
|
||
)
|
||
|
||
|
||
def count_settlements(session: Session, query: SettlementQuery) -> int:
|
||
return int(
|
||
session.execute(
|
||
select(func.count()).select_from(MonthlySettlement).where(*_filters(query))
|
||
).scalar_one()
|
||
)
|
||
|
||
|
||
def _filters(query: SettlementQuery) -> tuple:
|
||
filters = []
|
||
if query.status is not None:
|
||
filters.append(MonthlySettlement.status == query.status)
|
||
return tuple(filters)
|
||
|
||
|
||
def list_items(session: Session, settlement_id: int) -> list[SettlementItem]:
|
||
return list(
|
||
session.execute(
|
||
select(SettlementItem)
|
||
.where(SettlementItem.settlement_id == settlement_id)
|
||
.order_by(SettlementItem.id)
|
||
).scalars()
|
||
)
|
||
|
||
|
||
def replace_items(session: Session, settlement_id: int, rows: list[SettlementItem]) -> None:
|
||
"""全量替换明细(先删后 flush 再插,防御写法;不 commit)。"""
|
||
session.execute(delete_items_stmt(settlement_id))
|
||
session.flush()
|
||
for row in rows:
|
||
row.settlement_id = settlement_id
|
||
session.add(row)
|
||
session.flush()
|
||
|
||
|
||
def delete_items_stmt(settlement_id: int):
|
||
from sqlalchemy import delete
|
||
|
||
return delete(SettlementItem).where(SettlementItem.settlement_id == settlement_id)
|
||
|
||
|
||
def update_ledger_settlement_id(
|
||
session: Session, ledger_ids: list[int], settlement_id: int
|
||
) -> None:
|
||
"""月结生成时把纳入的台账行回填 settlement_id(append-only 的受控回填)。"""
|
||
from sqlalchemy import update
|
||
|
||
from app.models import PerformanceLedger
|
||
|
||
if ledger_ids:
|
||
session.execute(
|
||
update(PerformanceLedger)
|
||
.where(PerformanceLedger.id.in_(ledger_ids))
|
||
.values(settlement_id=settlement_id)
|
||
)
|
||
session.flush()
|