2026-10-16 08:47:29 -04:00
|
|
|
|
"""绩效核算接口(M3 ④):台账查询与单笔核算触发。
|
|
|
|
|
|
|
|
|
|
|
|
核算动作 = 把回款固化为台账行(每笔回款仅一次;重算走独立入口并授权)。
|
|
|
|
|
|
准入:读=登录即可;写(核算/重算/批量)=管理员与营销后勤
|
|
|
|
|
|
(权限清单待澄清 #6,答复后细化)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from typing import Annotated
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, Path, Query
|
2026-10-16 09:39:40 -04:00
|
|
|
|
from pydantic import BaseModel, Field
|
2026-10-16 08:47:29 -04:00
|
|
|
|
|
|
|
|
|
|
from app.api.deps import DbSession, RequestMetaDep, require_roles
|
2026-10-16 09:39:40 -04:00
|
|
|
|
from app.core.constants import ROLE_ADMIN, ROLE_FINANCE, ROLE_MARKETING_OPS
|
2026-10-16 08:47:29 -04:00
|
|
|
|
from app.core.response import ApiResponse, PageResult, ok
|
|
|
|
|
|
from app.models import User
|
|
|
|
|
|
from app.schemas.calc import CalcReceiptOut, LedgerOut, LedgerQuery
|
|
|
|
|
|
from app.services import calc_service
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/calc", tags=["绩效工资 · 绩效核算"])
|
|
|
|
|
|
|
|
|
|
|
|
_CalcWriter = Annotated[User, Depends(require_roles(ROLE_ADMIN, ROLE_MARKETING_OPS))]
|
2026-10-16 09:39:40 -04:00
|
|
|
|
# 重算是授权动作:管理员/财务(营销后勤不可重算)
|
|
|
|
|
|
_RecalcUser = Annotated[User, Depends(require_roles(ROLE_ADMIN, ROLE_FINANCE))]
|
2026-10-16 08:47:29 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/ledger", response_model=ApiResponse[PageResult[LedgerOut]], summary="绩效台账")
|
|
|
|
|
|
def list_ledger(
|
|
|
|
|
|
session: DbSession,
|
|
|
|
|
|
query: Annotated[LedgerQuery, Query()],
|
|
|
|
|
|
) -> ApiResponse[PageResult[LedgerOut]]:
|
|
|
|
|
|
"""分页查询绩效核算台账(默认仅生效行,可筛选历史版本)。"""
|
|
|
|
|
|
return ok(calc_service.list_ledger_page(session, query))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/ledger/{ledger_id}", response_model=ApiResponse[LedgerOut], summary="台账详情")
|
|
|
|
|
|
def get_ledger(
|
|
|
|
|
|
session: DbSession,
|
|
|
|
|
|
ledger_id: Annotated[int, Path(ge=1)],
|
|
|
|
|
|
) -> ApiResponse[LedgerOut]:
|
|
|
|
|
|
"""台账行详情(含计算快照)。"""
|
|
|
|
|
|
return ok(calc_service.get_ledger(session, ledger_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post(
|
|
|
|
|
|
"/receipts/{receipt_id}/calc",
|
|
|
|
|
|
response_model=ApiResponse[CalcReceiptOut],
|
|
|
|
|
|
summary="核算回款",
|
|
|
|
|
|
)
|
|
|
|
|
|
def calc_receipt(
|
|
|
|
|
|
receipt_id: Annotated[int, Path(ge=1)],
|
|
|
|
|
|
session: DbSession,
|
|
|
|
|
|
current_user: _CalcWriter,
|
|
|
|
|
|
meta: RequestMetaDep,
|
|
|
|
|
|
) -> ApiResponse[CalcReceiptOut]:
|
|
|
|
|
|
"""把回款固化为台账行(每笔回款仅一次;业务级一次性计提,重复回款仅登记进度)。"""
|
|
|
|
|
|
return ok(
|
|
|
|
|
|
calc_service.calc_receipt(session, receipt_id, operator=current_user, meta=meta),
|
|
|
|
|
|
message="核算完成",
|
|
|
|
|
|
)
|
2026-10-16 09:39:40 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RecalcIn(BaseModel):
|
|
|
|
|
|
"""重算入参:原因必填(重算授权留痕)。"""
|
|
|
|
|
|
|
|
|
|
|
|
reason: str = Field(min_length=1, max_length=255, description="重算原因")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class BatchIn(BaseModel):
|
|
|
|
|
|
"""批量核算入参。"""
|
|
|
|
|
|
|
|
|
|
|
|
receipt_ids: list[int] = Field(min_length=1, description="回款 ID 列表")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post(
|
|
|
|
|
|
"/receipts/{receipt_id}/recalc",
|
|
|
|
|
|
response_model=ApiResponse[CalcReceiptOut],
|
|
|
|
|
|
summary="重算回款",
|
|
|
|
|
|
)
|
|
|
|
|
|
def recalc_receipt(
|
|
|
|
|
|
receipt_id: Annotated[int, Path(ge=1)],
|
|
|
|
|
|
payload: RecalcIn,
|
|
|
|
|
|
session: DbSession,
|
|
|
|
|
|
current_user: _RecalcUser,
|
|
|
|
|
|
meta: RequestMetaDep,
|
|
|
|
|
|
) -> ApiResponse[CalcReceiptOut]:
|
|
|
|
|
|
"""重算(管理员/财务):作废旧版本、按当前参数落新版本并留痕。"""
|
|
|
|
|
|
return ok(
|
|
|
|
|
|
calc_service.recalc_receipt(
|
|
|
|
|
|
session, receipt_id, payload.reason, operator=current_user, meta=meta
|
|
|
|
|
|
),
|
|
|
|
|
|
message="重算完成",
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/batch", response_model=ApiResponse[list[dict]], summary="批量核算")
|
|
|
|
|
|
def batch_calc(
|
|
|
|
|
|
payload: BatchIn,
|
|
|
|
|
|
session: DbSession,
|
|
|
|
|
|
current_user: _CalcWriter,
|
|
|
|
|
|
meta: RequestMetaDep,
|
|
|
|
|
|
) -> ApiResponse[list[dict]]:
|
|
|
|
|
|
"""批量核算:逐笔独立事务,返回逐笔成败清单(失败不中断其它笔)。"""
|
|
|
|
|
|
return ok(
|
|
|
|
|
|
calc_service.batch_calc(session, payload.receipt_ids, operator=current_user, meta=meta),
|
|
|
|
|
|
message="批量核算完成",
|
|
|
|
|
|
)
|