from datetime import datetime from app import db from app.utils.timezone import now_in_app_timezone def local_now(): """Get current time in local timezone as naive datetime (for database storage)""" return now_in_app_timezone().replace(tzinfo=None) class InvoiceEmail(db.Model): """Model for tracking invoice emails sent to clients""" __tablename__ = 'invoice_emails' id = db.Column(db.Integer, primary_key=True) invoice_id = db.Column(db.Integer, db.ForeignKey('invoices.id'), nullable=False, index=True) # Email details recipient_email = db.Column(db.String(200), nullable=False) subject = db.Column(db.String(500), nullable=False) sent_at = db.Column(db.DateTime, nullable=False, default=local_now) sent_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) # Tracking opened_at = db.Column(db.DateTime, nullable=True) # When email was opened (if tracked) opened_count = db.Column(db.Integer, nullable=False, default=0) # Number of times opened last_opened_at = db.Column(db.DateTime, nullable=True) # Last time email was opened # Payment tracking paid_at = db.Column(db.DateTime, nullable=True) # When invoice was marked as paid (if after email) # Status status = db.Column(db.String(20), nullable=False, default='sent') # 'sent', 'opened', 'paid', 'bounced', 'failed' # Error tracking error_message = db.Column(db.Text, nullable=True) # Error message if send failed # Metadata created_at = db.Column(db.DateTime, default=local_now, nullable=False) updated_at = db.Column(db.DateTime, default=local_now, onupdate=local_now, nullable=False) # Relationships invoice = db.relationship('Invoice', backref='email_records') sender = db.relationship('User', backref='sent_invoice_emails') def __init__(self, invoice_id, recipient_email, subject, sent_by, **kwargs): self.invoice_id = invoice_id self.recipient_email = recipient_email self.subject = subject self.sent_by = sent_by self.status = kwargs.get('status', 'sent') self.error_message = kwargs.get('error_message') def __repr__(self): return f' {self.recipient_email} ({self.status})>' def mark_opened(self): """Mark email as opened""" if not self.opened_at: self.opened_at = local_now() self.last_opened_at = local_now() self.opened_count += 1 if self.status == 'sent': self.status = 'opened' def mark_paid(self): """Mark invoice as paid (after email was sent)""" if not self.paid_at: self.paid_at = local_now() self.status = 'paid' def mark_failed(self, error_message): """Mark email send as failed""" self.status = 'failed' self.error_message = error_message def mark_bounced(self): """Mark email as bounced""" self.status = 'bounced' def to_dict(self): """Convert invoice email to dictionary""" return { 'id': self.id, 'invoice_id': self.invoice_id, 'recipient_email': self.recipient_email, 'subject': self.subject, 'sent_at': self.sent_at.isoformat() if self.sent_at else None, 'sent_by': self.sent_by, 'opened_at': self.opened_at.isoformat() if self.opened_at else None, 'opened_count': self.opened_count, 'last_opened_at': self.last_opened_at.isoformat() if self.last_opened_at else None, 'paid_at': self.paid_at.isoformat() if self.paid_at else None, 'status': self.status, 'error_message': self.error_message, 'created_at': self.created_at.isoformat() if self.created_at else None, 'updated_at': self.updated_at.isoformat() if self.updated_at else None }