"""Operator issue reports (screenshot + free-text notes from the tray). An operator on a shop-floor PC opens the tray "Report an issue" dialog, writes a free-text description, and optionally attaches a screenshot of their screen. The agent uploads that to ShopDB, where it becomes one OperatorReport row linked to the reporting PC's asset. The screenshot bytes live on disk under the instance uploads dir (like employee photos); this row holds only metadata plus the relative file path so the image can be served back and purged on retention. Reports are transient by design: a retention window (default 7 days) purges old rows and their image files, so this is a rolling operator-signal feed, not a permanent record. """ from datetime import datetime from shopdb.api import db def _utciso(value): """Naive-UTC datetime -> ISO string marked UTC, or None.""" return value.isoformat() + 'Z' if value else None class OperatorReport(db.Model): """One operator-submitted issue report for a PC asset.""" __tablename__ = 'operatorreports' operatorreportid = db.Column(db.Integer, primary_key=True) # The PC this report is about. ON DELETE CASCADE: when the asset goes, its # operator reports go with it. assetid = db.Column( db.Integer, db.ForeignKey('assets.assetid', ondelete='CASCADE'), nullable=False, ) # The dropdown reason. Only 'Other (free text)' is functional today, but the # column is kept so the richer reason routing can return without a migration. reason = db.Column(db.String(128), nullable=False, default='Other (free text)') # The operator's free-text description. Nullable: a screenshot alone is a # valid report. details = db.Column(db.Text, nullable=True) # Screenshot metadata. imagefilename is the stored file name under the # operator-reports uploads dir; null when the operator declined a screenshot. imagefilename = db.Column(db.String(255), nullable=True) imagemimetype = db.Column(db.String(64), nullable=True) imagebytesize = db.Column(db.Integer, nullable=True) # The hostname the agent reported at upload time, kept for traceability even # if the asset link is later re-pointed. reportedhostname = db.Column(db.String(255), nullable=True) createdat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow) __table_args__ = ( db.Index('idx_operatorreports_asset', 'assetid'), db.Index('idx_operatorreports_createdat', 'createdat'), ) def __repr__(self): return f"" def has_image(self): return bool(self.imagefilename) def to_dict(self): return { 'operatorreportid': self.operatorreportid, 'assetid': self.assetid, 'reason': self.reason, 'details': self.details, 'hasimage': self.has_image(), 'imagemimetype': self.imagemimetype, 'imagebytesize': self.imagebytesize, 'reportedhostname': self.reportedhostname, 'createdat': _utciso(self.createdat), }