July 4 - Update Vendor Work-Order Workflow
This commit is contained in:
@@ -6,4 +6,5 @@ from app.models.issue import Issue
|
||||
from app.models.project import Project, CustomerAssignment
|
||||
from app.models.api_token import RefreshToken, DeviceToken
|
||||
from app.models.notification_matrix import NotificationMatrix
|
||||
from app.models.inspection_schedule import InspectionSchedule
|
||||
from app.models.inspection_schedule import InspectionSchedule
|
||||
from app.models.work_order import IssueWorkOrder
|
||||
@@ -33,6 +33,10 @@ EVENT_ADMIN_BROADCAST = 'admin_broadcast' # bulk messages sent by admin to all
|
||||
# notifies the assigned inspector (phase34).
|
||||
EVENT_INSPECTION_SCHEDULED = 'inspection_scheduled'
|
||||
|
||||
# Fired when an external contractor acknowledges or completes a vendor work
|
||||
# order via the tokenized public link (phase36).
|
||||
EVENT_WORK_ORDER = 'work_order_update'
|
||||
|
||||
ALL_EVENT_TYPES = {
|
||||
EVENT_ISSUE_ASSIGNED: 'Issue assigned to me',
|
||||
EVENT_ISSUE_STATUS: 'Issue status changed',
|
||||
@@ -43,6 +47,7 @@ ALL_EVENT_TYPES = {
|
||||
EVENT_SLA_ALERT: 'SLA at-risk / breached alerts',
|
||||
EVENT_ADMIN_BROADCAST: 'Admin broadcast (system announcements)',
|
||||
EVENT_INSPECTION_SCHEDULED: 'Scheduled inspection due (assigned to me)',
|
||||
EVENT_WORK_ORDER: 'Contractor updated a work order',
|
||||
# Customer-facing — only relevant for customer role accounts
|
||||
EVENT_CUSTOMER_INSPECTION_DONE: 'Inspection completed at my facility (portal)',
|
||||
EVENT_CUSTOMER_ISSUE_UPDATED: 'Issue created or updated at my facility (portal)',
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
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'<IssueWorkOrder {self.id} issue={self.issue_id} {self.status}>'
|
||||
Reference in New Issue
Block a user