feat(T+): 完成数据贯通与自动接入闭环
打通 T+ 快照、业务、发货、回款和核算准备链路,保留来源差异并提供待补状态。 新增业务自动接入任务、跨平台读取服务托管、权限复核、来源关联和前端核对页面。 补充迁移、隔离集成测试、浏览器模拟验收及角色授权边界修复。
This commit is contained in:
1 parent
3efe72c344
commit
1e53792ad6
151 files changed
+8842
-401
No files matched your search
@@ -24,7 +24,7 @@
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -38,7 +38,7 @@ from app.core.constants import (
|
||||
NO_PREFIX_RECEIPT,
|
||||
)
|
||||
from app.core.db_errors import is_duplicate_entry
|
||||
from app.core.exceptions import BadRequestError, NotFoundError
|
||||
from app.core.exceptions import BadRequestError, ConflictError, NotFoundError
|
||||
from app.core.response import PageResult
|
||||
from app.models import Business, Receipt
|
||||
from app.models.business import BUSINESS_STATUS_ACTIVE
|
||||
@@ -50,7 +50,7 @@ from app.schemas.receipt import (
|
||||
ReceiptQuery,
|
||||
ReceiptUpdateIn,
|
||||
)
|
||||
from app.services import audit_service
|
||||
from app.services import audit_service, receipt_confirmation
|
||||
from app.services.audit_service import AuditRecord, RequestMeta
|
||||
from app.services.business_service import _recalc_totals
|
||||
from app.services.formula import payment_days
|
||||
@@ -81,6 +81,10 @@ def _to_out(row: tuple) -> ReceiptOut:
|
||||
amount=str(receipt.amount),
|
||||
is_barter=receipt.is_barter,
|
||||
settlement_status=receipt.settlement_status,
|
||||
confirmation_status=1 if receipt_confirmation.is_confirmed(receipt) else 0,
|
||||
confirmed_by=receipt.confirmed_by,
|
||||
confirmed_at=receipt.confirmed_at,
|
||||
confirmation_revision=receipt_confirmation.revision(receipt),
|
||||
payment_days=(
|
||||
payment_days(ship_date, receipt.receipt_date) if ship_date is not None else None
|
||||
),
|
||||
@@ -151,6 +155,33 @@ def get_detail(session: Session, receipt_id: int) -> ReceiptOut:
|
||||
return _to_out(row)
|
||||
|
||||
|
||||
def confirm_receipt(session, receipt_id, payload, *, operator, meta=None) -> ReceiptOut:
|
||||
"""独立确认已核对内容;锁内复核指纹,重复确认不改首次确认人/时间。"""
|
||||
from app.services.authorization_service import require_permission
|
||||
|
||||
require_permission(session, operator, "business:receipts:confirm")
|
||||
with audit_service.audit(
|
||||
session, operator=operator,
|
||||
record=AuditRecord(module=MODULE_RECEIPT, action="人工确认", summary=f"人工确认回款 #{receipt_id}"),
|
||||
meta=meta,
|
||||
):
|
||||
receipt = receipt_repository.get_by_id(session, receipt_id)
|
||||
if receipt is None:
|
||||
raise NotFoundError("回款不存在")
|
||||
_ensure_business_writable(session, receipt.business_id, "确认")
|
||||
session.refresh(receipt, with_for_update=True)
|
||||
if payload.expected_revision != receipt_confirmation.revision(receipt):
|
||||
raise ConflictError("回款内容已变化,请重新查看并核对后确认")
|
||||
if not receipt_confirmation.is_confirmed(receipt):
|
||||
receipt.confirmation_status = 1
|
||||
receipt.confirmed_by = operator.username
|
||||
receipt.confirmed_at = datetime.now()
|
||||
receipt.confirmed_revision = payload.expected_revision
|
||||
out = get_detail(session, receipt_id)
|
||||
session.commit()
|
||||
return out
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CreatedReceipt:
|
||||
"""建行结果(出参 + 固化的 ID/批次号,commit 后不再触碰 ORM 属性)。"""
|
||||
@@ -296,9 +327,13 @@ def update_receipt(
|
||||
):
|
||||
if receipt is None:
|
||||
raise NotFoundError("回款不存在")
|
||||
from app.services.business_source import ensure_source_editable
|
||||
|
||||
ensure_source_editable(session, "receipts", receipt_id)
|
||||
business = _ensure_business_writable(session, receipt.business_id, "修改")
|
||||
session.refresh(receipt, with_for_update=True)
|
||||
if receipt.settlement_status == SETTLEMENT_DONE:
|
||||
raise BadRequestError("已核算回款禁止修改")
|
||||
business = _ensure_business_writable(session, receipt.business_id, "修改")
|
||||
# 月结锁定拦截:原月与新月都不得落入已锁定月份(改到账日=跨月搬数,
|
||||
# 只查新月会让「从锁定月改到未锁定月」绕过拦截)
|
||||
from app.services import settlement_service
|
||||
@@ -311,11 +346,14 @@ def update_receipt(
|
||||
session, payload.shipment_id, receipt.business_id
|
||||
)
|
||||
|
||||
old_revision = receipt_confirmation.revision(receipt)
|
||||
receipt.receipt_date = payload.receipt_date
|
||||
receipt.shipment_id = payload.shipment_id
|
||||
receipt.amount = Decimal(payload.amount)
|
||||
receipt.is_barter = payload.is_barter
|
||||
receipt.remark = payload.remark
|
||||
if receipt_confirmation.revision(receipt) != old_revision:
|
||||
receipt_confirmation.invalidate(receipt)
|
||||
_recalc_totals(session, business)
|
||||
out = _to_out((receipt, business.business_no, shipment_no, ship_date))
|
||||
batch_no = receipt.receipt_batch_no
|
||||
@@ -353,6 +391,9 @@ def delete_receipt(
|
||||
):
|
||||
if receipt is None:
|
||||
raise NotFoundError("回款不存在")
|
||||
from app.services.business_source import ensure_source_editable
|
||||
|
||||
ensure_source_editable(session, "receipts", receipt_id)
|
||||
if receipt.settlement_status == SETTLEMENT_DONE:
|
||||
raise BadRequestError("已核算回款禁止删除")
|
||||
business = _ensure_business_writable(session, receipt.business_id, "删除")
|
||||
|
||||
Reference in new issue
Block a user