from decimal import Decimal from fastapi import APIRouter, Depends, HTTPException, Query, status from app.core.config import settings from app.repositories.audit import create_audit_event from app.repositories.pricing import ( create_price_rule, create_pricing_category, delete_price_rule, delete_pricing_category, list_price_rules, list_pricing_categories, update_price_rule, update_pricing_category, ) from app.repositories.scheme_version_pricing import replace_scheme_version_pricing_snapshot from app.repositories.schemes import get_scheme_record_by_scheme_id from app.schemas.pricing import ( PriceRuleCreateRequest, PriceRuleItem, PriceRuleUpdateRequest, PricingBundleResponse, PricingCategoryCreateRequest, PricingCategoryItem, PricingCategoryUpdateRequest, ) from app.security.auth import require_api_key from app.services.draft_guard import get_current_draft_context router = APIRouter() async def _refresh_current_draft_snapshot_if_possible( *, scheme_id: str, expected_scheme_version_id: str | None = None, ) -> dict | None: scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) return await replace_scheme_version_pricing_snapshot( scheme_id=scheme.scheme_id, scheme_version_id=version.scheme_version_id, ) @router.get(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing", response_model=PricingBundleResponse) async def get_pricing_bundle(scheme_id: str, role: str = Depends(require_api_key)): categories = await list_pricing_categories(scheme_id) rules = await list_price_rules(scheme_id) return PricingBundleResponse( categories=[ PricingCategoryItem( pricing_category_id=row.pricing_category_id, scheme_id=row.scheme_id, name=row.name, code=row.code, created_at=row.created_at.isoformat(), ) for row in categories ], rules=[ PriceRuleItem( price_rule_id=row.price_rule_id, scheme_id=row.scheme_id, pricing_category_id=row.pricing_category_id, target_type=row.target_type, target_ref=row.target_ref, amount=str(row.amount), currency=row.currency, created_at=row.created_at.isoformat(), ) for row in rules ], ) @router.post(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/categories") async def create_pricing_category_endpoint( scheme_id: str, payload: PricingCategoryCreateRequest, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) pricing_category_id = await create_pricing_category( scheme_id=scheme.scheme_id, name=payload.name, code=payload.code, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.category.created", object_type="pricing_category", object_ref=pricing_category_id, details={ "name": payload.name, "code": payload.code, "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "pricing_category_id": pricing_category_id, "scheme_id": scheme.scheme_id, "scheme_version_id": version.scheme_version_id, "name": payload.name, "code": payload.code, } @router.put(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/categories/{{pricing_category_id}}") async def update_pricing_category_endpoint( scheme_id: str, pricing_category_id: str, payload: PricingCategoryUpdateRequest, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) row = await update_pricing_category( scheme_id=scheme.scheme_id, pricing_category_id=pricing_category_id, name=payload.name, code=payload.code, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.category.updated", object_type="pricing_category", object_ref=pricing_category_id, details={ "name": payload.name, "code": payload.code, "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "pricing_category_id": row.pricing_category_id, "scheme_id": row.scheme_id, "scheme_version_id": version.scheme_version_id, "name": row.name, "code": row.code, } @router.delete(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/categories/{{pricing_category_id}}") async def delete_pricing_category_endpoint( scheme_id: str, pricing_category_id: str, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) await delete_pricing_category( scheme_id=scheme.scheme_id, pricing_category_id=pricing_category_id, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.category.deleted", object_type="pricing_category", object_ref=pricing_category_id, details={ "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "deleted": True, "pricing_category_id": pricing_category_id, "scheme_version_id": version.scheme_version_id, } @router.post(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/rules") async def create_price_rule_endpoint( scheme_id: str, payload: PriceRuleCreateRequest, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) try: amount = Decimal(payload.amount) except Exception: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Некорректная сумма", ) price_rule_id = await create_price_rule( scheme_id=scheme.scheme_id, pricing_category_id=payload.pricing_category_id, target_type=payload.target_type, target_ref=payload.target_ref, amount=amount, currency=payload.currency, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.rule.created", object_type="price_rule", object_ref=price_rule_id, details={ "pricing_category_id": payload.pricing_category_id, "target_type": payload.target_type, "target_ref": payload.target_ref, "amount": payload.amount, "currency": payload.currency, "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "price_rule_id": price_rule_id, "scheme_id": scheme.scheme_id, "scheme_version_id": version.scheme_version_id, "pricing_category_id": payload.pricing_category_id, "target_type": payload.target_type, "target_ref": payload.target_ref, "amount": payload.amount, "currency": payload.currency, } @router.put(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/rules/{{price_rule_id}}") async def update_price_rule_endpoint( scheme_id: str, price_rule_id: str, payload: PriceRuleUpdateRequest, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) try: amount = Decimal(payload.amount) except Exception: raise HTTPException( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Некорректная сумма", ) row = await update_price_rule( scheme_id=scheme.scheme_id, price_rule_id=price_rule_id, pricing_category_id=payload.pricing_category_id, target_type=payload.target_type, target_ref=payload.target_ref, amount=amount, currency=payload.currency, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.rule.updated", object_type="price_rule", object_ref=price_rule_id, details={ "pricing_category_id": payload.pricing_category_id, "target_type": payload.target_type, "target_ref": payload.target_ref, "amount": payload.amount, "currency": payload.currency, "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "price_rule_id": row.price_rule_id, "scheme_id": row.scheme_id, "scheme_version_id": version.scheme_version_id, "pricing_category_id": row.pricing_category_id, "target_type": row.target_type, "target_ref": row.target_ref, "amount": str(row.amount), "currency": row.currency, } @router.delete(f"{settings.api_v1_prefix}/schemes/{{scheme_id}}/pricing/rules/{{price_rule_id}}") async def delete_price_rule_endpoint( scheme_id: str, price_rule_id: str, expected_scheme_version_id: str | None = Query(default=None), role: str = Depends(require_api_key), ): scheme, version = await get_current_draft_context( scheme_id=scheme_id, expected_scheme_version_id=expected_scheme_version_id, ) await delete_price_rule( scheme_id=scheme.scheme_id, price_rule_id=price_rule_id, ) snapshot = await _refresh_current_draft_snapshot_if_possible( scheme_id=scheme.scheme_id, expected_scheme_version_id=version.scheme_version_id, ) await create_audit_event( scheme_id=scheme.scheme_id, event_type="pricing.rule.deleted", object_type="price_rule", object_ref=price_rule_id, details={ "scheme_version_id": version.scheme_version_id, "snapshot": snapshot, }, ) return { "deleted": True, "price_rule_id": price_rule_id, "scheme_version_id": version.scheme_version_id, }