from sqlalchemy.orm import Session from app.models.invoice import Invoice, InvoiceItem, InvoiceStatus from app.schemas.invoice import InvoiceCreate, InvoiceUpdate from typing import List, Optional from decimal import Decimal import uuid class InvoiceService: def __init__(self, db: Session): self.db = db def get_invoices( self, user_id: int, skip: int = 0, limit: int = 100 ) -> List[Invoice]: return ( self.db.query(Invoice) .filter(Invoice.user_id == user_id) .offset(skip) .limit(limit) .all() ) def get_invoice(self, invoice_id: int, user_id: int) -> Optional[Invoice]: return ( self.db.query(Invoice) .filter(Invoice.id == invoice_id, Invoice.user_id == user_id) .first() ) def create_invoice(self, invoice_data: InvoiceCreate, user_id: int) -> Invoice: # Generate unique invoice number invoice_number = f"INV-{uuid.uuid4().hex[:8].upper()}" # Calculate totals subtotal = sum(item.quantity * item.unit_price for item in invoice_data.items) tax_amount = ( subtotal * (invoice_data.tax_rate / 100) if invoice_data.tax_rate else Decimal("0") ) total = subtotal + tax_amount # Create invoice invoice = Invoice( invoice_number=invoice_number, customer_id=invoice_data.customer_id, user_id=user_id, due_date=invoice_data.due_date, tax_rate=invoice_data.tax_rate or Decimal("0"), subtotal=subtotal, tax_amount=tax_amount, total=total, notes=invoice_data.notes, ) self.db.add(invoice) self.db.flush() # Get the invoice ID # Create invoice items for item_data in invoice_data.items: item_total = item_data.quantity * item_data.unit_price item = InvoiceItem( invoice_id=invoice.id, description=item_data.description, quantity=item_data.quantity, unit_price=item_data.unit_price, total_price=item_total, ) self.db.add(item) self.db.commit() self.db.refresh(invoice) return invoice def update_invoice( self, invoice_id: int, invoice_data: InvoiceUpdate, user_id: int ) -> Optional[Invoice]: invoice = self.get_invoice(invoice_id, user_id) if not invoice: return None # Update basic fields update_data = invoice_data.dict(exclude_unset=True, exclude={"items"}) for field, value in update_data.items(): setattr(invoice, field, value) # Update items if provided if invoice_data.items is not None: # Delete existing items self.db.query(InvoiceItem).filter( InvoiceItem.invoice_id == invoice_id ).delete() # Add new items subtotal = Decimal("0") for item_data in invoice_data.items: item_total = item_data.quantity * item_data.unit_price subtotal += item_total item = InvoiceItem( invoice_id=invoice.id, description=item_data.description, quantity=item_data.quantity, unit_price=item_data.unit_price, total_price=item_total, ) self.db.add(item) # Recalculate totals tax_amount = ( subtotal * (invoice.tax_rate / 100) if invoice.tax_rate else Decimal("0") ) invoice.subtotal = subtotal invoice.tax_amount = tax_amount invoice.total = subtotal + tax_amount self.db.commit() self.db.refresh(invoice) return invoice def delete_invoice(self, invoice_id: int, user_id: int) -> bool: invoice = self.get_invoice(invoice_id, user_id) if not invoice: return False self.db.delete(invoice) self.db.commit() return True def update_invoice_status( self, invoice_id: int, status: InvoiceStatus, user_id: int ) -> Optional[Invoice]: invoice = self.get_invoice(invoice_id, user_id) if not invoice: return None invoice.status = status self.db.commit() self.db.refresh(invoice) return invoice