"""Warranty models. A Warranty is provider-agnostic (manual entry, or looked up from Dell/Lenovo/HP later). It links to one or more assets via warrantyassets. Coverage status is DERIVED from enddate at read time, never stored, so it is always current. """ from datetime import date, timedelta from shopdb.api import db # Window before enddate where a warranty counts as "expiring soon". EXPIRING_WINDOW_DAYS = 180 # Derived status -> display color (hex). Reused by the frontend status badge. STATUS_COLORS = { 'active': '#4CAF50', 'expiring': '#FF9800', 'expired': '#F44336', 'unknown': '#9E9E9E', } def derive_status(enddate, today=None): """Coverage status from an end date. Never stored - always computed.""" if not enddate: return 'unknown' today = today or date.today() if enddate < today: return 'expired' if enddate <= today + timedelta(days=EXPIRING_WINDOW_DAYS): return 'expiring' return 'active' class Warranty(db.Model): __tablename__ = 'warranties' warrantyid = db.Column(db.Integer, primary_key=True) vendor = db.Column(db.String(100), nullable=False) # Service tag / serial the provider identifies the unit by. servicetag = db.Column(db.String(100)) # Where the record came from: manual, dell, lenovo, hp. provider = db.Column(db.String(20), nullable=False, server_default='manual') servicelevel = db.Column(db.String(150)) startdate = db.Column(db.Date) enddate = db.Column(db.Date) # When a provider lookup last refreshed this record. lastcheckeddate = db.Column(db.DateTime) notes = db.Column(db.Text) # Proof of cover: the purchase invoice, the extended-warranty certificate, # whatever the vendor sent. A provider lookup answers "is it covered"; this # answers "prove it" months later, when the email it arrived in is gone. # Stores the served URL, not the original filename - see the upload route. proofurl = db.Column(db.String(500)) prooffilename = db.Column(db.String(255)) isactive = db.Column(db.Boolean, nullable=False, server_default='1') links = db.relationship('WarrantyAsset', back_populates='warranty', cascade='all, delete-orphan') def status(self, today=None): return derive_status(self.enddate, today) def to_dict(self, today=None): status = self.status(today) return { 'warrantyid': self.warrantyid, 'vendor': self.vendor, 'servicetag': self.servicetag, 'provider': self.provider, 'servicelevel': self.servicelevel, 'startdate': self.startdate.isoformat() if self.startdate else None, 'enddate': self.enddate.isoformat() if self.enddate else None, 'lastcheckeddate': self.lastcheckeddate.isoformat() + 'Z' if self.lastcheckeddate else None, 'notes': self.notes, 'proofurl': self.proofurl, 'prooffilename': self.prooffilename, 'isactive': bool(self.isactive), 'status': status, 'statuscolor': STATUS_COLORS.get(status, STATUS_COLORS['unknown']), } class WarrantyAsset(db.Model): __tablename__ = 'warrantyassets' id = db.Column(db.Integer, primary_key=True) warrantyid = db.Column( db.Integer, db.ForeignKey('warranties.warrantyid', ondelete='CASCADE'), nullable=False ) assetid = db.Column( db.Integer, db.ForeignKey('assets.assetid', ondelete='CASCADE'), nullable=False ) warranty = db.relationship('Warranty', back_populates='links') __table_args__ = ( db.UniqueConstraint('warrantyid', 'assetid', name='uq_warrantyasset_warranty_asset'), )