100 lines
4.0 KiB
Python
100 lines
4.0 KiB
Python
"""Item receipts — 1:1 with item_decisions. Quote-level arrival uses separate endpoints."""
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.auth import require_role
|
|
from app.db.engine import get_db
|
|
from app.models.procurement import ItemDecision, ItemReceipt, LineItem
|
|
from app.schemas.procurement import ReceiptCreate, ReceiptUpdate
|
|
from app.services.expand_service import receipt_full
|
|
from app.services.quote_service import _fallback_confirmer_id
|
|
from app.utils.errors import ConflictError, NotFoundError
|
|
|
|
router = APIRouter(
|
|
prefix="/quotes/{quote_id}/line-items/{line_item_id}/receipt",
|
|
tags=["item-receipts"],
|
|
)
|
|
_procurement_writer = require_role("admin", "procurement_manager")
|
|
|
|
|
|
async def _line_item_or_404(db: AsyncSession, quote_id, line_item_id) -> LineItem:
|
|
li = await db.scalar(
|
|
select(LineItem).where(LineItem.id == line_item_id, LineItem.quote_id == quote_id)
|
|
)
|
|
if li is None:
|
|
raise NotFoundError("خط آیتم یافت نشد")
|
|
return li
|
|
|
|
|
|
async def _decision_or_404(db: AsyncSession, line_item_id) -> ItemDecision:
|
|
decision = await db.scalar(select(ItemDecision).where(ItemDecision.line_item_id == line_item_id))
|
|
if decision is None:
|
|
raise NotFoundError("تصمیمی برای این خط آیتم ثبت نشده است")
|
|
return decision
|
|
|
|
|
|
@router.get("")
|
|
async def get_receipt(quote_id, line_item_id, db: AsyncSession = Depends(get_db)):
|
|
await _line_item_or_404(db, quote_id, line_item_id)
|
|
decision = await db.scalar(select(ItemDecision).where(ItemDecision.line_item_id == line_item_id))
|
|
if decision is None:
|
|
raise NotFoundError("رسیدی برای این خط آیتم ثبت نشده است")
|
|
receipt = await db.scalar(select(ItemReceipt).where(ItemReceipt.item_decision_id == decision.id))
|
|
if receipt is None:
|
|
raise NotFoundError("رسیدی برای این خط آیتم ثبت نشده است")
|
|
return {"data": receipt_full(receipt)}
|
|
|
|
|
|
@router.post("", status_code=201)
|
|
async def create_receipt(
|
|
quote_id,
|
|
line_item_id,
|
|
payload: ReceiptCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
_auth=Depends(_procurement_writer),
|
|
):
|
|
await _line_item_or_404(db, quote_id, line_item_id)
|
|
decision = await _decision_or_404(db, line_item_id)
|
|
existing = await db.scalar(select(ItemReceipt).where(ItemReceipt.item_decision_id == decision.id))
|
|
if existing is not None:
|
|
raise ConflictError("رسید این خط آیتم قبلاً ثبت شده است")
|
|
receipt = ItemReceipt(
|
|
item_decision_id=decision.id,
|
|
confirmed_by=await _fallback_confirmer_id(db),
|
|
received_at=payload.received_at,
|
|
received_qty=payload.received_qty,
|
|
on_time=payload.on_time,
|
|
quality_note=payload.quality_note,
|
|
)
|
|
db.add(receipt)
|
|
await db.flush()
|
|
return {"data": receipt_full(receipt)}
|
|
|
|
|
|
@router.patch("")
|
|
async def update_receipt(
|
|
quote_id,
|
|
line_item_id,
|
|
payload: ReceiptUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
_auth=Depends(_procurement_writer),
|
|
):
|
|
await _line_item_or_404(db, quote_id, line_item_id)
|
|
decision = await db.scalar(select(ItemDecision).where(ItemDecision.line_item_id == line_item_id))
|
|
if decision is None:
|
|
raise NotFoundError("تصمیمی برای این خط آیتم ثبت نشده است")
|
|
receipt = await db.scalar(select(ItemReceipt).where(ItemReceipt.item_decision_id == decision.id))
|
|
if receipt is None:
|
|
raise NotFoundError("رسیدی برای این خط آیتم ثبت نشده است")
|
|
if payload.received_at is not None:
|
|
receipt.received_at = payload.received_at
|
|
if payload.received_qty is not None:
|
|
receipt.received_qty = payload.received_qty
|
|
if payload.on_time is not None:
|
|
receipt.on_time = payload.on_time
|
|
if payload.quality_note is not None:
|
|
receipt.quality_note = payload.quality_note
|
|
await db.flush()
|
|
return {"data": receipt_full(receipt)}
|