"""正式工资资料、FIFO核算和逐段明细入口。""" from typing import Annotated from fastapi import APIRouter, Path, Query from fastapi.responses import Response from pydantic import BaseModel, Field from app.api.deps import DbSession, LoginUser, PayrollWriter, RequestMetaDep from app.core.response import PageQuery, ok from app.schemas.payroll import AllocationAdjustmentIn, PayrollMonthIn, PayrollSetupIn from app.services import payroll_calculation, payroll_inputs, payroll_months, settlement_service router = APIRouter(prefix="/payroll", tags=["正式工资核算"]) class ProbationAdjustmentIn(BaseModel): personnel_id: int = Field(ge=1) year: str = Field(pattern=r"^20\d{2}$") fingerprint: str = Field(min_length=64, max_length=64) reason: str = Field(min_length=1, max_length=255) @router.post("/probation/adjustments") def probation_adjustment(payload: ProbationAdjustmentIn, session: DbSession, actor: PayrollWriter, meta: RequestMetaDep): from app.services.probation_service import adjustment as apply return ok(apply(session, payload, actor, meta)) @router.get("/probation") def probation_preview(session: DbSession, _: LoginUser, month: str, personnel_id: int | None = None): from app.services import probation_service return ok(probation_service.preview(session, month, personnel_id)) @router.get("/probation/export") def probation_export(session: DbSession, _: LoginUser, month: str): from app.services import probation_service return Response(probation_service.export(session, month), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="probation_balances.xlsx"'}) @router.get("/salary-export/{month}") def salary_export(month: str, session: DbSession, _: LoginUser): from app.core.month import validate_month from app.services.salary_completion import export_settlement validate_month(month) return Response(export_settlement(session, month), media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={"Content-Disposition": 'attachment; filename="monthly_salary.xlsx"'}) @router.get("/months/{month}") def month_state( month: Annotated[str, Path(pattern=r"^20[0-9]{2}-(0[1-9]|1[0-2])$")], session: DbSession, _: LoginUser, ): return ok(payroll_months.month_state(session, month)) @router.get("/businesses/{business_id}/input") def input_detail(business_id: int, session: DbSession, _: LoginUser): return ok(payroll_inputs.detail(session, business_id)) @router.post("/businesses/{business_id}/input") def save_input( business_id: int, payload: PayrollSetupIn, session: DbSession, actor: PayrollWriter, meta: RequestMetaDep, ): return ok(payroll_inputs.setup(session, business_id, payload, actor, meta)) @router.post("/businesses/{business_id}/calculate") def calculate( business_id: int, payload: PayrollMonthIn, session: DbSession, actor: PayrollWriter, meta: RequestMetaDep, ): return ok( payroll_calculation.calculate( session, business_id, payload.month, operator=actor, meta=meta, reason=payload.reason ) ) @router.get("/ledger/{ledger_id}/allocations") def allocations( ledger_id: int, query: Annotated[PageQuery, Query()], session: DbSession, _: LoginUser ): return ok(payroll_calculation.allocation_page(session, ledger_id, query)) @router.post("/allocation-adjustments") def adjustment( payload: AllocationAdjustmentIn, session: DbSession, actor: PayrollWriter, meta: RequestMetaDep ): return ok( settlement_service.create_allocation_adjustment(session, payload, operator=actor, meta=meta) )