updates
This commit is contained in:
Binary file not shown.
@@ -166,4 +166,38 @@ async def get_invoices_by_booking(booking_id: int, current_user: User=Depends(ge
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f'Error fetching invoices by booking: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/{id}/send-email')
|
||||
async def send_invoice_email(request: Request, id: int, current_user: User=Depends(authorize_roles('admin', 'staff', 'accountant')), db: Session=Depends(get_db)):
|
||||
try:
|
||||
invoice = db.query(Invoice).filter(Invoice.id == id).first()
|
||||
if not invoice:
|
||||
raise HTTPException(status_code=404, detail='Invoice not found')
|
||||
|
||||
from ..routes.booking_routes import _generate_invoice_email_html
|
||||
from ..models.user import User as UserModel
|
||||
from ..utils.mailer import send_email
|
||||
|
||||
invoice_dict = InvoiceService.invoice_to_dict(invoice)
|
||||
invoice_html = _generate_invoice_email_html(invoice_dict, is_proforma=invoice.is_proforma)
|
||||
invoice_type = 'Proforma Invoice' if invoice.is_proforma else 'Invoice'
|
||||
|
||||
user = db.query(UserModel).filter(UserModel.id == invoice.user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail='User not found')
|
||||
|
||||
await send_email(
|
||||
to=user.email,
|
||||
subject=f'{invoice_type} {invoice.invoice_number}',
|
||||
html=invoice_html
|
||||
)
|
||||
|
||||
logger.info(f'{invoice_type} {invoice.invoice_number} sent to {user.email}')
|
||||
return success_response(message=f'{invoice_type} sent successfully to {user.email}')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f'Error sending invoice email: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Binary file not shown.
@@ -6,6 +6,7 @@ from ..models.invoice import Invoice, InvoiceItem, InvoiceStatus
|
||||
from ..models.booking import Booking
|
||||
from ..models.payment import Payment, PaymentStatus
|
||||
from ..models.user import User
|
||||
from ..models.system_settings import SystemSettings
|
||||
from ..config.logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
@@ -29,14 +30,33 @@ class InvoiceService:
|
||||
@staticmethod
|
||||
def create_invoice_from_booking(booking_id: int, db: Session, created_by_id: Optional[int]=None, tax_rate: float=0.0, discount_amount: float=0.0, due_days: int=30, is_proforma: bool=False, invoice_amount: Optional[float]=None, request_id: Optional[str]=None, **kwargs) -> Dict[str, Any]:
|
||||
from sqlalchemy.orm import selectinload
|
||||
from ..models.service_usage import ServiceUsage
|
||||
from ..models.room import Room
|
||||
from ..models.room_type import RoomType
|
||||
from ..models.service import Service
|
||||
|
||||
logger.info(f'Creating invoice from booking {booking_id}', extra={'booking_id': booking_id, 'request_id': request_id})
|
||||
booking = db.query(Booking).options(selectinload(Booking.service_usages).selectinload('service'), selectinload(Booking.room).selectinload('room_type'), selectinload(Booking.payments)).filter(Booking.id == booking_id).first()
|
||||
booking = db.query(Booking).options(
|
||||
selectinload(Booking.service_usages).selectinload(ServiceUsage.service),
|
||||
selectinload(Booking.room).selectinload(Room.room_type),
|
||||
selectinload(Booking.payments)
|
||||
).filter(Booking.id == booking_id).first()
|
||||
if not booking:
|
||||
logger.error(f'Booking {booking_id} not found', extra={'booking_id': booking_id, 'request_id': request_id})
|
||||
raise ValueError('Booking not found')
|
||||
user = db.query(User).filter(User.id == booking.user_id).first()
|
||||
if not user:
|
||||
raise ValueError('User not found')
|
||||
|
||||
# Get tax_rate from system settings if not provided or is 0
|
||||
if tax_rate == 0.0:
|
||||
tax_rate_setting = db.query(SystemSettings).filter(SystemSettings.key == 'tax_rate').first()
|
||||
if tax_rate_setting and tax_rate_setting.value:
|
||||
try:
|
||||
tax_rate = float(tax_rate_setting.value)
|
||||
except (ValueError, TypeError):
|
||||
tax_rate = 0.0
|
||||
|
||||
invoice_number = generate_invoice_number(db, is_proforma=is_proforma)
|
||||
booking_total = float(booking.total_price)
|
||||
if invoice_amount is not None:
|
||||
@@ -61,7 +81,34 @@ class InvoiceService:
|
||||
else:
|
||||
status = InvoiceStatus.draft
|
||||
paid_date = None
|
||||
invoice = Invoice(invoice_number=invoice_number, booking_id=booking_id, user_id=booking.user_id, issue_date=datetime.utcnow(), due_date=datetime.utcnow() + timedelta(days=due_days), paid_date=paid_date, subtotal=subtotal, tax_rate=tax_rate, tax_amount=tax_amount, discount_amount=discount_amount, total_amount=total_amount, amount_paid=amount_paid, balance_due=balance_due, status=status, is_proforma=is_proforma, company_name=kwargs.get('company_name'), company_address=kwargs.get('company_address'), company_phone=kwargs.get('company_phone'), company_email=kwargs.get('company_email'), company_tax_id=kwargs.get('company_tax_id'), company_logo_url=kwargs.get('company_logo_url'), customer_name=user.full_name or f'{user.email}', customer_email=user.email, customer_address=user.address, customer_phone=user.phone, customer_tax_id=kwargs.get('customer_tax_id'), notes=kwargs.get('notes'), terms_and_conditions=kwargs.get('terms_and_conditions'), payment_instructions=kwargs.get('payment_instructions'), created_by_id=created_by_id)
|
||||
# Get company information from system settings if not provided
|
||||
company_name = kwargs.get('company_name')
|
||||
company_address = kwargs.get('company_address')
|
||||
company_phone = kwargs.get('company_phone')
|
||||
company_email = kwargs.get('company_email')
|
||||
company_tax_id = kwargs.get('company_tax_id')
|
||||
company_logo_url = kwargs.get('company_logo_url')
|
||||
|
||||
# If company info not provided, fetch from system settings
|
||||
if not company_name or not company_address or not company_phone or not company_email:
|
||||
company_settings = db.query(SystemSettings).filter(
|
||||
SystemSettings.key.in_(['company_name', 'company_address', 'company_phone', 'company_email', 'company_logo_url'])
|
||||
).all()
|
||||
|
||||
settings_dict = {setting.key: setting.value for setting in company_settings if setting.value}
|
||||
|
||||
if not company_name and settings_dict.get('company_name'):
|
||||
company_name = settings_dict['company_name']
|
||||
if not company_address and settings_dict.get('company_address'):
|
||||
company_address = settings_dict['company_address']
|
||||
if not company_phone and settings_dict.get('company_phone'):
|
||||
company_phone = settings_dict['company_phone']
|
||||
if not company_email and settings_dict.get('company_email'):
|
||||
company_email = settings_dict['company_email']
|
||||
if not company_logo_url and settings_dict.get('company_logo_url'):
|
||||
company_logo_url = settings_dict['company_logo_url']
|
||||
|
||||
invoice = Invoice(invoice_number=invoice_number, booking_id=booking_id, user_id=booking.user_id, issue_date=datetime.utcnow(), due_date=datetime.utcnow() + timedelta(days=due_days), paid_date=paid_date, subtotal=subtotal, tax_rate=tax_rate, tax_amount=tax_amount, discount_amount=discount_amount, total_amount=total_amount, amount_paid=amount_paid, balance_due=balance_due, status=status, is_proforma=is_proforma, company_name=company_name, company_address=company_address, company_phone=company_phone, company_email=company_email, company_tax_id=company_tax_id, company_logo_url=company_logo_url, customer_name=user.full_name or f'{user.email}', customer_email=user.email, customer_address=user.address, customer_phone=user.phone, customer_tax_id=kwargs.get('customer_tax_id'), notes=kwargs.get('notes'), terms_and_conditions=kwargs.get('terms_and_conditions'), payment_instructions=kwargs.get('payment_instructions'), created_by_id=created_by_id)
|
||||
db.add(invoice)
|
||||
db.flush()
|
||||
services_total = sum((float(su.total_price) for su in booking.service_usages))
|
||||
@@ -110,9 +157,15 @@ class InvoiceService:
|
||||
if 'tax_rate' in kwargs or 'discount_amount' in kwargs:
|
||||
tax_rate = kwargs.get('tax_rate', invoice.tax_rate)
|
||||
discount_amount = kwargs.get('discount_amount', invoice.discount_amount)
|
||||
invoice.tax_amount = (invoice.subtotal - discount_amount) * (float(tax_rate) / 100)
|
||||
invoice.total_amount = invoice.subtotal + invoice.tax_amount - discount_amount
|
||||
invoice.balance_due = invoice.total_amount - invoice.amount_paid
|
||||
# Convert decimal types to float for arithmetic operations
|
||||
subtotal = float(invoice.subtotal) if invoice.subtotal else 0.0
|
||||
discount_amount = float(discount_amount) if discount_amount else 0.0
|
||||
tax_rate = float(tax_rate) if tax_rate else 0.0
|
||||
amount_paid = float(invoice.amount_paid) if invoice.amount_paid else 0.0
|
||||
|
||||
invoice.tax_amount = (subtotal - discount_amount) * (tax_rate / 100)
|
||||
invoice.total_amount = subtotal + invoice.tax_amount - discount_amount
|
||||
invoice.balance_due = invoice.total_amount - amount_paid
|
||||
if invoice.balance_due <= 0 and invoice.status != InvoiceStatus.paid:
|
||||
invoice.status = InvoiceStatus.paid
|
||||
invoice.paid_date = datetime.utcnow()
|
||||
|
||||
Reference in New Issue
Block a user