first commit
This commit is contained in:
commit
efa5080897
127 changed files with 18610 additions and 0 deletions
133
backend/app/routes_kyc.py
Normal file
133
backend/app/routes_kyc.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from .auth import current_user
|
||||
from .config import settings
|
||||
from .database import get_db
|
||||
from .kyc_constants import ALLOWED_UPLOAD_EXTENSIONS, KYC_DOCUMENT_TYPES
|
||||
from .kyc_service import ensure_kyc_application, refresh_application_status, serialize_kyc, upload_root
|
||||
from .kyc_validators import validate_gst, validate_pan
|
||||
from .models import KycApplication, KycDocument, UserAccount
|
||||
|
||||
router = APIRouter(prefix="/api/v1/kyc", tags=["kyc"])
|
||||
|
||||
|
||||
class KycDetailsRequest(BaseModel):
|
||||
pan_number: str = Field(min_length=10, max_length=10)
|
||||
gst_number: str = Field(min_length=15, max_length=15)
|
||||
|
||||
|
||||
@router.get("/template")
|
||||
def download_template(_: UserAccount = Depends(current_user)) -> FileResponse:
|
||||
template = Path(settings.kyc_template_path)
|
||||
if not template.is_file():
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="KYC template not found")
|
||||
return FileResponse(
|
||||
template,
|
||||
media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
filename="KYC Form - Pvt Ltd Client.docx",
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
def get_my_kyc(db: Session = Depends(get_db), user: UserAccount = Depends(current_user)) -> dict:
|
||||
application = ensure_kyc_application(db, user)
|
||||
return serialize_kyc(application)
|
||||
|
||||
|
||||
@router.put("/me")
|
||||
def update_my_kyc(
|
||||
payload: KycDetailsRequest,
|
||||
db: Session = Depends(get_db),
|
||||
user: UserAccount = Depends(current_user),
|
||||
) -> dict:
|
||||
application = ensure_kyc_application(db, user)
|
||||
if application.status == "approved":
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Approved KYC cannot be edited")
|
||||
try:
|
||||
application.pan_number = validate_pan(payload.pan_number)
|
||||
application.gst_number = validate_gst(payload.gst_number)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(exc)) from exc
|
||||
if application.status == "not_started":
|
||||
application.status = "in_progress"
|
||||
db.commit()
|
||||
db.refresh(application)
|
||||
return serialize_kyc(application)
|
||||
|
||||
|
||||
@router.post("/documents/{document_type}")
|
||||
async def upload_document(
|
||||
document_type: str,
|
||||
file: UploadFile = File(...),
|
||||
db: Session = Depends(get_db),
|
||||
user: UserAccount = Depends(current_user),
|
||||
) -> dict:
|
||||
if document_type not in KYC_DOCUMENT_TYPES:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid document type")
|
||||
application = ensure_kyc_application(db, user)
|
||||
if application.status == "approved":
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Approved KYC cannot be edited")
|
||||
if not application.pan_number or not application.gst_number:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Enter PAN and GST before uploading documents",
|
||||
)
|
||||
|
||||
filename = file.filename or ""
|
||||
extension = Path(filename).suffix.lower()
|
||||
if extension not in ALLOWED_UPLOAD_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Allowed file types: PDF, PNG, JPG, DOC, DOCX",
|
||||
)
|
||||
|
||||
content = await file.read()
|
||||
if not content:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="File is empty")
|
||||
if len(content) > settings.max_upload_bytes:
|
||||
raise HTTPException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="File exceeds 10 MB limit")
|
||||
|
||||
document = next(doc for doc in application.documents if doc.document_type == document_type)
|
||||
root = upload_root(settings.upload_dir)
|
||||
stored_name = f"{application.id}_{document_type}{extension}"
|
||||
stored_path = root / stored_name
|
||||
stored_path.write_bytes(content)
|
||||
|
||||
document.original_filename = filename
|
||||
document.stored_path = str(stored_path)
|
||||
document.status = "pending"
|
||||
document.review_note = None
|
||||
document.uploaded_at = datetime.now(timezone.utc)
|
||||
document.reviewed_at = None
|
||||
if application.status in {"not_started", "in_progress", "rejected"}:
|
||||
application.status = "in_progress"
|
||||
db.commit()
|
||||
db.refresh(application)
|
||||
return serialize_kyc(application)
|
||||
|
||||
|
||||
@router.post("/submit")
|
||||
def submit_kyc(db: Session = Depends(get_db), user: UserAccount = Depends(current_user)) -> dict:
|
||||
application = ensure_kyc_application(db, user)
|
||||
if application.status == "approved":
|
||||
return serialize_kyc(application)
|
||||
if not application.pan_number or not application.gst_number:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="PAN and GST are required")
|
||||
missing = [doc.document_type for doc in application.documents if doc.status == "missing"]
|
||||
if missing:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail="Upload all required documents before submitting",
|
||||
)
|
||||
application.status = "submitted"
|
||||
application.submitted_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
db.refresh(application)
|
||||
return serialize_kyc(application)
|
||||
Loading…
Add table
Add a link
Reference in a new issue