179 lines
6.8 KiB
Python
179 lines
6.8 KiB
Python
from datetime import date
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.db.session import get_db
|
|
from app.models.operations import AccountingPeriod, OverheadEntry, StatementSnapshot
|
|
from app.schemas.operations import (
|
|
AccountingPeriodCreate,
|
|
AccountingPeriodRead,
|
|
CostAllocationGenerateResult,
|
|
CostAllocationRead,
|
|
OverheadEntryCreate,
|
|
OverheadEntryRead,
|
|
ProductMaterialCostBoardRead,
|
|
StatementSnapshotCreate,
|
|
StatementSnapshotRead,
|
|
)
|
|
from app.services.auth import AuthContext, require_authenticated_user
|
|
from app.services.operations import (
|
|
build_product_material_cost_board,
|
|
build_statement_summary,
|
|
generate_cost_allocations,
|
|
get_cost_allocation_rows,
|
|
get_overheads_query,
|
|
get_periods_query,
|
|
get_statement_snapshots_query,
|
|
to_decimal,
|
|
)
|
|
from app.services.sales_planning import code_token, next_doc_no
|
|
|
|
router = APIRouter(dependencies=[Depends(require_authenticated_user)])
|
|
|
|
|
|
def build_statement_snapshot_no(db: Session, period: AccountingPeriod, statement_type: str, report_date: date) -> str:
|
|
date_part = report_date.strftime("%Y%m%d")
|
|
period_part = code_token(period.period_code, f"PER{period.id:04d}", 8)
|
|
type_part = code_token(statement_type, "STM", 8)
|
|
return next_doc_no(db, StatementSnapshot, "snapshot_no", f"FIN-NB-{date_part}-{period_part}-{type_part}")
|
|
|
|
|
|
@router.get("/product-material-cost-board", response_model=ProductMaterialCostBoardRead)
|
|
def get_product_material_cost_board(
|
|
date_from: date | None = Query(default=None),
|
|
date_to: date | None = Query(default=None),
|
|
db: Session = Depends(get_db),
|
|
) -> ProductMaterialCostBoardRead:
|
|
end_date = date_to or date.today()
|
|
start_date = date_from or end_date.replace(day=1)
|
|
return build_product_material_cost_board(db, date_from=start_date, date_to=end_date)
|
|
|
|
|
|
@router.get("/periods", response_model=list[AccountingPeriodRead])
|
|
def list_accounting_periods(
|
|
limit: int = Query(default=50, ge=1, le=200),
|
|
db: Session = Depends(get_db),
|
|
) -> list[AccountingPeriodRead]:
|
|
rows = db.execute(get_periods_query(limit=limit)).mappings().all()
|
|
return [AccountingPeriodRead.model_validate(dict(row)) for row in rows]
|
|
|
|
|
|
@router.post("/periods", response_model=AccountingPeriodRead)
|
|
def create_accounting_period(payload: AccountingPeriodCreate, db: Session = Depends(get_db)) -> AccountingPeriodRead:
|
|
period_code = f"{payload.year_no}{payload.month_no:02d}"
|
|
period = AccountingPeriod(
|
|
period_code=period_code,
|
|
year_no=payload.year_no,
|
|
month_no=payload.month_no,
|
|
start_date=payload.start_date,
|
|
end_date=payload.end_date,
|
|
close_status=payload.close_status,
|
|
closed_at=None,
|
|
)
|
|
db.add(period)
|
|
db.commit()
|
|
|
|
row = db.execute(get_periods_query(limit=1).where(AccountingPeriod.id == period.id)).mappings().first()
|
|
if not row:
|
|
raise HTTPException(status_code=500, detail="财务期间创建后读取失败")
|
|
return AccountingPeriodRead.model_validate(dict(row))
|
|
|
|
|
|
@router.get("/overheads", response_model=list[OverheadEntryRead])
|
|
def list_overhead_entries(
|
|
limit: int = Query(default=100, ge=1, le=300),
|
|
db: Session = Depends(get_db),
|
|
) -> list[OverheadEntryRead]:
|
|
rows = db.execute(get_overheads_query(limit=limit)).mappings().all()
|
|
return [OverheadEntryRead.model_validate(dict(row)) for row in rows]
|
|
|
|
|
|
@router.post("/overheads", response_model=OverheadEntryRead)
|
|
def create_overhead_entry(
|
|
payload: OverheadEntryCreate,
|
|
context: AuthContext = Depends(require_authenticated_user),
|
|
db: Session = Depends(get_db),
|
|
) -> OverheadEntryRead:
|
|
period = db.get(AccountingPeriod, payload.period_id)
|
|
if not period:
|
|
raise HTTPException(status_code=404, detail="财务期间不存在")
|
|
|
|
entry = OverheadEntry(
|
|
period_id=payload.period_id,
|
|
overhead_type=payload.overhead_type,
|
|
dept_id=payload.dept_id,
|
|
amount=to_decimal(payload.amount, "0.01"),
|
|
allocation_basis=payload.allocation_basis,
|
|
description=payload.description,
|
|
created_by=context.user.id,
|
|
)
|
|
db.add(entry)
|
|
db.commit()
|
|
|
|
row = db.execute(get_overheads_query(limit=1).where(OverheadEntry.id == entry.id)).mappings().first()
|
|
if not row:
|
|
raise HTTPException(status_code=500, detail="制造费用录入后读取失败")
|
|
return OverheadEntryRead.model_validate(dict(row))
|
|
|
|
|
|
@router.get("/cost-allocations", response_model=list[CostAllocationRead])
|
|
def list_cost_allocations(
|
|
period_id: int | None = Query(default=None),
|
|
limit: int = Query(default=100, ge=1, le=500),
|
|
db: Session = Depends(get_db),
|
|
) -> list[CostAllocationRead]:
|
|
return get_cost_allocation_rows(db, period_id=period_id, limit=limit)
|
|
|
|
|
|
@router.post("/generate-cost-allocations/{period_id}", response_model=CostAllocationGenerateResult)
|
|
def generate_period_cost_allocations(period_id: int, db: Session = Depends(get_db)) -> CostAllocationGenerateResult:
|
|
period = db.get(AccountingPeriod, period_id)
|
|
if not period:
|
|
raise HTTPException(status_code=404, detail="财务期间不存在")
|
|
rows = generate_cost_allocations(db, period_id)
|
|
db.commit()
|
|
return CostAllocationGenerateResult(
|
|
period_id=period.id,
|
|
period_code=period.period_code,
|
|
generated_count=len(rows),
|
|
rows=rows,
|
|
)
|
|
|
|
|
|
@router.get("/statements", response_model=list[StatementSnapshotRead])
|
|
def list_statement_snapshots(
|
|
limit: int = Query(default=100, ge=1, le=300),
|
|
db: Session = Depends(get_db),
|
|
) -> list[StatementSnapshotRead]:
|
|
rows = db.execute(get_statement_snapshots_query(limit=limit)).mappings().all()
|
|
return [StatementSnapshotRead.model_validate(dict(row)) for row in rows]
|
|
|
|
|
|
@router.post("/statements", response_model=StatementSnapshotRead)
|
|
def create_statement_snapshot(
|
|
payload: StatementSnapshotCreate,
|
|
context: AuthContext = Depends(require_authenticated_user),
|
|
db: Session = Depends(get_db),
|
|
) -> StatementSnapshotRead:
|
|
period = db.get(AccountingPeriod, payload.period_id)
|
|
if not period:
|
|
raise HTTPException(status_code=404, detail="财务期间不存在")
|
|
|
|
summary_json = build_statement_summary(db, payload.period_id, payload.report_date)
|
|
snapshot = StatementSnapshot(
|
|
snapshot_no=build_statement_snapshot_no(db, period, payload.statement_type, payload.report_date),
|
|
period_id=payload.period_id,
|
|
statement_type=payload.statement_type,
|
|
report_date=payload.report_date,
|
|
summary_json=summary_json,
|
|
created_by=context.user.id,
|
|
)
|
|
db.add(snapshot)
|
|
db.commit()
|
|
|
|
row = db.execute(get_statement_snapshots_query(limit=1).where(StatementSnapshot.id == snapshot.id)).mappings().first()
|
|
if not row:
|
|
raise HTTPException(status_code=500, detail="财务报表快照创建后读取失败")
|
|
return StatementSnapshotRead.model_validate(dict(row))
|