125 lines
4.4 KiB
Python
125 lines
4.4 KiB
Python
from datetime import UTC, datetime
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session, selectinload
|
|
|
|
from app.models.repair_estimate import RepairEstimate, RepairEstimateEvent, RepairEstimateItem
|
|
|
|
|
|
class RepairEstimateRepository:
|
|
@staticmethod
|
|
def list_by_repair(db: Session, repair_id: int) -> list[RepairEstimate]:
|
|
return list(
|
|
db.scalars(
|
|
select(RepairEstimate)
|
|
.options(selectinload(RepairEstimate.items), selectinload(RepairEstimate.events))
|
|
.where(RepairEstimate.repair_id == repair_id)
|
|
.order_by(RepairEstimate.created_at.desc(), RepairEstimate.id.desc())
|
|
)
|
|
)
|
|
|
|
@staticmethod
|
|
def get(db: Session, *, repair_id: int, estimate_id: int) -> RepairEstimate | None:
|
|
return db.scalar(
|
|
select(RepairEstimate)
|
|
.options(selectinload(RepairEstimate.items), selectinload(RepairEstimate.events))
|
|
.where(RepairEstimate.repair_id == repair_id)
|
|
.where(RepairEstimate.id == estimate_id)
|
|
)
|
|
|
|
@staticmethod
|
|
def get_latest_public(db: Session, repair_id: int) -> RepairEstimate | None:
|
|
return db.scalar(
|
|
select(RepairEstimate)
|
|
.options(selectinload(RepairEstimate.items))
|
|
.where(RepairEstimate.repair_id == repair_id)
|
|
.where(RepairEstimate.status.in_(["sent", "approved", "declined"]))
|
|
.order_by(RepairEstimate.sent_at.desc().nullslast(), RepairEstimate.created_at.desc(), RepairEstimate.id.desc())
|
|
.limit(1)
|
|
)
|
|
|
|
@staticmethod
|
|
def get_next_number(db: Session, year: int) -> str:
|
|
prefix = f"KV{year}-"
|
|
latest = db.scalar(
|
|
select(RepairEstimate.estimate_number)
|
|
.where(RepairEstimate.estimate_number.like(f"{prefix}%"))
|
|
.order_by(RepairEstimate.estimate_number.desc())
|
|
.limit(1)
|
|
)
|
|
next_number = 1
|
|
if latest:
|
|
next_number = int(latest.split("-")[-1]) + 1
|
|
return f"{prefix}{next_number:06d}"
|
|
|
|
@staticmethod
|
|
def save(db: Session, estimate: RepairEstimate) -> RepairEstimate:
|
|
db.add(estimate)
|
|
db.commit()
|
|
db.refresh(estimate)
|
|
return RepairEstimateRepository.get(db, repair_id=estimate.repair_id, estimate_id=estimate.id) or estimate
|
|
|
|
@staticmethod
|
|
def replace_items(db: Session, estimate: RepairEstimate, items: list[RepairEstimateItem]) -> None:
|
|
estimate.items.clear()
|
|
db.flush()
|
|
for item in items:
|
|
estimate.items.append(item)
|
|
|
|
@staticmethod
|
|
def delete(db: Session, estimate: RepairEstimate) -> None:
|
|
db.delete(estimate)
|
|
db.commit()
|
|
|
|
@staticmethod
|
|
def add_event(
|
|
db: Session,
|
|
*,
|
|
estimate_id: int,
|
|
event_type: str,
|
|
actor_type: str,
|
|
actor_user_id: int | None = None,
|
|
note: str | None = None,
|
|
commit: bool = True,
|
|
) -> RepairEstimateEvent:
|
|
event = RepairEstimateEvent(
|
|
estimate_id=estimate_id,
|
|
event_type=event_type,
|
|
actor_type=actor_type,
|
|
actor_user_id=actor_user_id,
|
|
note=note,
|
|
)
|
|
db.add(event)
|
|
if commit:
|
|
db.commit()
|
|
db.refresh(event)
|
|
return event
|
|
|
|
@staticmethod
|
|
def mark_sent(db: Session, estimate: RepairEstimate) -> RepairEstimate:
|
|
estimate.status = "sent"
|
|
estimate.sent_at = datetime.now(UTC)
|
|
db.commit()
|
|
db.refresh(estimate)
|
|
return RepairEstimateRepository.get(db, repair_id=estimate.repair_id, estimate_id=estimate.id) or estimate
|
|
|
|
@staticmethod
|
|
def count_open(db: Session) -> int:
|
|
return db.scalar(select(func.count(RepairEstimate.id)).where(RepairEstimate.status.in_(["draft", "sent"]))) or 0
|
|
|
|
@staticmethod
|
|
def count_waiting(db: Session) -> int:
|
|
return db.scalar(select(func.count(RepairEstimate.id)).where(RepairEstimate.status == "sent")) or 0
|
|
|
|
@staticmethod
|
|
def count_approved_today(db: Session) -> int:
|
|
today = datetime.now(UTC).date()
|
|
return db.scalar(
|
|
select(func.count(RepairEstimate.id))
|
|
.where(RepairEstimate.status == "approved")
|
|
.where(func.date(RepairEstimate.approved_at) == today)
|
|
) or 0
|
|
|
|
@staticmethod
|
|
def count_declined(db: Session) -> int:
|
|
return db.scalar(select(func.count(RepairEstimate.id)).where(RepairEstimate.status == "declined")) or 0
|