import ulid from datetime import datetime, timedelta from sqlalchemy.orm import Session from sqlalchemy import select from typing import Optional, List from fastapi import HTTPException, status from app.models.ServiceModel import ServiceJob, ServiceJobEvent from app.models.ServiceQuoteModel import ServiceJobQuote from app.schemas.ServiceQuoteSchema import ServiceJobQuoteCreate class QuoteService: def create_or_revise_quote( self, db: Session, job_id: str, staff_id: str, data: ServiceJobQuoteCreate ) -> ServiceJobQuote: job = db.get(ServiceJob, job_id) if not job: raise HTTPException(status_code=404, detail="Service job not found") # Find existing quotes to determine version stmt = select(ServiceJobQuote).where(ServiceJobQuote.service_job_id == job_id).order_by(ServiceJobQuote.version.desc()) existing_quotes = db.execute(stmt).scalars().all() version = 1 if existing_quotes: version = existing_quotes[0].version + 1 # Supersede all older quotes for old_q in existing_quotes: if old_q.status in ["PENDING_CUSTOMER", "DRAFT"]: old_q.status = "SUPERSEDED" # Create new quote version expiry_time = datetime.utcnow() + timedelta(days=4) quote = ServiceJobQuote( quote_id=str(ulid.ULID()), service_job_id=job_id, version=version, subtotal=data.subtotal, tax=data.tax, additional_damage_amount=data.additional_damage_amount, total=data.total, status="PENDING_CUSTOMER", reason=data.reason, expires_at=data.expires_at or expiry_time, created_by=staff_id, ) db.add(quote) # Log Event & Update Job status job.status = "QUOTE_SENT" event = ServiceJobEvent( event_id=str(ulid.ULID()), job_id=job_id, event_type="QUOTE_SENT", performed_by=staff_id, notes=f"Repair estimate quote V{version} for {data.total} INR sent to customer." ) db.add(event) db.commit() db.refresh(quote) return quote def respond_to_quote(self, db: Session, quote_id: str, customer_id: str, action: str) -> ServiceJobQuote: quote = db.get(ServiceJobQuote, quote_id) if not quote: raise HTTPException(status_code=404, detail="Quote estimate not found") job = db.get(ServiceJob, quote.service_job_id) if not job or job.customer_id != customer_id: raise HTTPException(status_code=403, detail="Not authorized to respond to this quote") if quote.status != "PENDING_CUSTOMER": raise HTTPException(status_code=400, detail=f"Cannot respond to quote in status {quote.status}") if action.upper() == "ACCEPT": quote.status = "ACCEPTED" quote.accepted_at = datetime.utcnow() job.status = "QUOTE_ACCEPTED" note = "Customer accepted the quote estimate." elif action.upper() == "REJECT": quote.status = "REJECTED" quote.rejected_at = datetime.utcnow() job.status = "CUSTOMER_REJECTED" note = "Customer rejected the quote estimate." else: raise HTTPException(status_code=400, detail="Invalid quote response action") event = ServiceJobEvent( event_id=str(ulid.ULID()), job_id=job.job_id, event_type=f"QUOTE_{quote.status}", performed_by=customer_id, notes=note ) db.add(event) db.commit() db.refresh(quote) return quote