Files
shopdb-flask/plugins/warranty/models/warranty.py
cproudlock 2fce81f33f Attach proof of cover to a warranty
A provider lookup answers whether a unit is covered. It does not produce the
invoice or the extended-warranty certificate, and a manually entered warranty
had nowhere to keep one - so the proof stayed in somebody's mailbox until they
left.

Two columns rather than one: the served URL of the stored document, and the
name the vendor sent it under, because "Dell invoice 4471.pdf" is what a person
recognises a year later and "warranty-12.pdf" is not. The download route sends
the original name back.

Authenticated in both directions, unlike an asset photo: an invoice carries
pricing and a service tag. One document per warranty, replacing any prior
extension so a re-upload as .pdf does not leave the old .png behind claiming to
be current. Capped at 25MB - a certificate is a document, not a disk image.

Office formats are allowed because purchase records genuinely arrive as .msg
and .xlsx, not only as PDFs.
2026-08-12 11:45:40 -04:00

107 lines
3.7 KiB
Python

"""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'),
)