""" app/models/work_order.py ------------------------ Vendor work orders (phase36). A work order dispatches an existing Issue to an external contractor by email. The contractor opens a tokenized public link (no account required) to see the scoped issue details and to Acknowledge and then mark the work Completed. Staff see the work-order status on the issue and are notified on each vendor action. Builds on the existing free-text `vendor_*` fields on Issue (phase26) — a work order is the "send it and track it" layer on top of that. """ import secrets from app import db from app.utils.time_utils import now_eastern class IssueWorkOrder(db.Model): __tablename__ = 'issue_work_orders' id = db.Column(db.Integer, primary_key=True) issue_id = db.Column( db.Integer, db.ForeignKey('issues.id', ondelete='CASCADE'), nullable=False, index=True ) vendor_name = db.Column(db.String(150), nullable=False) vendor_email = db.Column(db.String(255), nullable=False) # Unguessable public access token (URL path segment). Never exposed to # anyone but the vendor who receives the emailed link. token = db.Column(db.String(64), nullable=False, unique=True, index=True) status = db.Column( db.Enum('sent', 'acknowledged', 'completed'), nullable=False, default='sent' ) message = db.Column(db.Text, nullable=True) # staff → vendor instructions vendor_note = db.Column(db.Text, nullable=True) # vendor → staff completion note sent_at = db.Column(db.DateTime, nullable=False, default=now_eastern) acknowledged_at = db.Column(db.DateTime, nullable=True) completed_at = db.Column(db.DateTime, nullable=True) created_by = db.Column( db.Integer, db.ForeignKey('users.id', ondelete='SET NULL'), nullable=True ) created_at = db.Column(db.DateTime, nullable=False, default=now_eastern) # Relationships issue = db.relationship('Issue', backref=db.backref( 'work_orders', lazy='dynamic', cascade='all, delete-orphan', order_by='IssueWorkOrder.created_at.desc()')) creator = db.relationship('User', foreign_keys=[created_by]) @staticmethod def new_token(): """Return a fresh unguessable token (43 url-safe chars ≈ 256 bits).""" return secrets.token_urlsafe(32) @property def status_label(self): return {'sent': 'Sent', 'acknowledged': 'Acknowledged', 'completed': 'Completed'}.get(self.status, self.status) def __repr__(self): return f''