from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import func, select from sqlalchemy.orm import Session from app.api.dependencies import require_auth from app.db.session import get_db from app.models.rule import RuleMaster from app.schemas.rule import RuleCreate, RuleResponse, RuleStatusUpdate, RuleUpdate from app.services.audit import rule_to_dict, write_rule_audit from app.services.rule_repository import RuleRepository router = APIRouter(prefix="/rules", tags=["Rule Management"], dependencies=[Depends(require_auth)]) repo = RuleRepository() @router.get("", response_model=list[RuleResponse]) def list_rules( tenant_id: str | None = None, product_type: str | None = None, dealer_id: str | None = None, rule_type: str | None = None, is_active: bool | None = Query(default=None), db: Session = Depends(get_db), ) -> list[RuleMaster]: return repo.list_rules(db, tenant_id, product_type, dealer_id, rule_type, is_active) @router.post("", response_model=RuleResponse, status_code=status.HTTP_201_CREATED) def create_rule(payload: RuleCreate, db: Session = Depends(get_db)) -> RuleMaster: dealer_filter = ( RuleMaster.dealer_id.is_(None) if payload.dealer_id is None else RuleMaster.dealer_id == payload.dealer_id ) latest_version = db.scalar( select(func.max(RuleMaster.version)).where( RuleMaster.rule_code == payload.rule_code, RuleMaster.tenant_id == payload.tenant_id, RuleMaster.product_type == payload.product_type, dealer_filter, ) ) rule = RuleMaster(**payload.model_dump(), version=(latest_version or 0) + 1) db.add(rule) db.flush() write_rule_audit(db, rule.id, "CREATE", payload.created_by, None, rule_to_dict(rule)) db.commit() db.refresh(rule) repo.invalidate(rule.tenant_id, rule.product_type, rule.dealer_id) return rule @router.put("/{rule_id}", response_model=RuleResponse) def update_rule(rule_id: int, payload: RuleUpdate, db: Session = Depends(get_db)) -> RuleMaster: rule = db.get(RuleMaster, rule_id) if not rule: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Rule not found") before = rule_to_dict(rule) updates = payload.model_dump(exclude_unset=True) actor = updates.pop("actor", "system") for field, value in updates.items(): setattr(rule, field, value) write_rule_audit(db, rule.id, "UPDATE", actor, before, rule_to_dict(rule)) db.commit() db.refresh(rule) repo.invalidate(rule.tenant_id, rule.product_type, rule.dealer_id) return rule @router.patch("/{rule_id}/status", response_model=RuleResponse) def update_rule_status( rule_id: int, payload: RuleStatusUpdate, db: Session = Depends(get_db) ) -> RuleMaster: rule = db.get(RuleMaster, rule_id) if not rule: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Rule not found") before = rule_to_dict(rule) rule.is_active = payload.is_active write_rule_audit(db, rule.id, "STATUS_UPDATE", payload.actor, before, rule_to_dict(rule)) db.commit() db.refresh(rule) repo.invalidate(rule.tenant_id, rule.product_type, rule.dealer_id) return rule