ifixkart-backend/app/services/QuoteService.py

103 lines
3.9 KiB
Python

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_quote(
self, db: Session, job_id: str, data: ServiceJobQuoteCreate, staff_id: str = "SYSTEM"
) -> ServiceJobQuote:
return self.create_or_revise_quote(db, job_id, staff_id, data)
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