Retire the legacy Machine model (ADR-001 cutover)
The asset/computer model is now the single source of truth. Remove the Machine instance layer end to end: - Delete models Machine, MachineStatus, PCType, MachineRelationship, InstalledApp, PrinterData; keep MachineType (models.machinetypeid still references it). - Delete the /api/machines, /api/statuses, /api/pctypes blueprints and the legacy /api/printers/legacy (PrinterData) blueprint. - Drop the deprecated communications.machineid column and its FK. - Migration 7c01 drops tables machines, machinestatuses, pctypes, machinerelationships, installedapps, printerdata (idempotent). - Fix remaining readers (applications install counts) to ComputerInstalledApp. - Frontend: remove dead machinesApi/statusesApi/pctypesApi wrappers; repoint the PC Types settings page at computer types. 143 tests pass; all asset/computer/dashboard/report/collector endpoints 200. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
"""Unified Machine model - combines equipment and PCs."""
|
||||
"""Legacy machine type lookup.
|
||||
|
||||
The Machine instance model and its PC/status lookups were retired (ADR-001);
|
||||
assets are the platform contract. MachineType is kept only because the shared
|
||||
`models` table still references it via models.machinetypeid.
|
||||
"""
|
||||
|
||||
from shopdb.extensions import db
|
||||
from .base import BaseModel, SoftDeleteMixin, AuditMixin
|
||||
from .base import BaseModel
|
||||
|
||||
|
||||
class MachineType(BaseModel):
|
||||
@@ -24,229 +29,3 @@ class MachineType(BaseModel):
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MachineType {self.machinetype}>"
|
||||
|
||||
|
||||
class MachineStatus(BaseModel):
|
||||
"""Machine status options."""
|
||||
__tablename__ = 'machinestatuses'
|
||||
|
||||
statusid = db.Column(db.Integer, primary_key=True)
|
||||
status = db.Column(db.String(50), unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
color = db.Column(db.String(20), comment='CSS color for UI')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<MachineStatus {self.status}>"
|
||||
|
||||
|
||||
class PCType(BaseModel):
|
||||
"""
|
||||
PC type classification for more specific PC categorization.
|
||||
Examples: Shopfloor PC, Engineer Workstation, CMM PC, etc.
|
||||
"""
|
||||
__tablename__ = 'pctypes'
|
||||
|
||||
pctypeid = db.Column(db.Integer, primary_key=True)
|
||||
pctype = db.Column(db.String(100), unique=True, nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PCType {self.pctype}>"
|
||||
|
||||
|
||||
class Machine(BaseModel, SoftDeleteMixin, AuditMixin):
|
||||
"""
|
||||
Unified machine model for all asset types.
|
||||
|
||||
Machine types can be:
|
||||
- CNC machines, CMMs, EDMs, etc. (manufacturing equipment)
|
||||
- PCs (shopfloor PCs, engineer workstations, etc.)
|
||||
- Network devices (servers, switches, etc.) - if network_devices plugin not used
|
||||
|
||||
The machinetype.category field distinguishes between types.
|
||||
"""
|
||||
__tablename__ = 'machines'
|
||||
|
||||
machineid = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# Identification
|
||||
machinenumber = db.Column(
|
||||
db.String(50),
|
||||
unique=True,
|
||||
nullable=False,
|
||||
index=True,
|
||||
comment='Business identifier (e.g., CMM01, G5QX1GT3ESF)'
|
||||
)
|
||||
alias = db.Column(
|
||||
db.String(100),
|
||||
comment='Friendly name'
|
||||
)
|
||||
hostname = db.Column(
|
||||
db.String(100),
|
||||
index=True,
|
||||
comment='Network hostname (for PCs)'
|
||||
)
|
||||
serialnumber = db.Column(
|
||||
db.String(100),
|
||||
index=True,
|
||||
comment='Hardware serial number'
|
||||
)
|
||||
|
||||
# Classification
|
||||
machinetypeid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('machinetypes.machinetypeid'),
|
||||
nullable=False
|
||||
)
|
||||
pctypeid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('pctypes.pctypeid'),
|
||||
nullable=True,
|
||||
comment='Set for PCs, NULL for equipment'
|
||||
)
|
||||
businessunitid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('businessunits.businessunitid'),
|
||||
nullable=True
|
||||
)
|
||||
modelnumberid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('models.modelnumberid'),
|
||||
nullable=True
|
||||
)
|
||||
vendorid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('vendors.vendorid'),
|
||||
nullable=True
|
||||
)
|
||||
|
||||
# Status
|
||||
statusid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('machinestatuses.statusid'),
|
||||
default=1,
|
||||
comment='In Use, Spare, Retired, etc.'
|
||||
)
|
||||
|
||||
# Location and mapping
|
||||
locationid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('locations.locationid'),
|
||||
nullable=True
|
||||
)
|
||||
mapleft = db.Column(db.Integer, comment='X coordinate on floor map')
|
||||
maptop = db.Column(db.Integer, comment='Y coordinate on floor map')
|
||||
islocationonly = db.Column(
|
||||
db.Boolean,
|
||||
default=False,
|
||||
comment='Virtual location marker (not actual machine)'
|
||||
)
|
||||
|
||||
# PC-specific fields (nullable for non-PC machines)
|
||||
osid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('operatingsystems.osid'),
|
||||
nullable=True
|
||||
)
|
||||
loggedinuser = db.Column(db.String(100), nullable=True)
|
||||
lastreporteddate = db.Column(db.DateTime, nullable=True)
|
||||
lastboottime = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# Features/flags
|
||||
isvnc = db.Column(db.Boolean, default=False, comment='VNC remote access enabled')
|
||||
iswinrm = db.Column(db.Boolean, default=False, comment='WinRM enabled')
|
||||
isshopfloor = db.Column(db.Boolean, default=False, comment='Shopfloor PC')
|
||||
requiresmanualconfig = db.Column(
|
||||
db.Boolean,
|
||||
default=False,
|
||||
comment='Multi-PC machine needs manual configuration'
|
||||
)
|
||||
|
||||
# Notes
|
||||
notes = db.Column(db.Text, nullable=True)
|
||||
|
||||
# Relationships
|
||||
machinetype = db.relationship('MachineType', backref='machines')
|
||||
pctype = db.relationship('PCType', backref='machines')
|
||||
businessunit = db.relationship('BusinessUnit', backref='machines')
|
||||
model = db.relationship('Model', backref='machines')
|
||||
vendor = db.relationship('Vendor', backref='machines')
|
||||
status = db.relationship('MachineStatus', backref='machines')
|
||||
location = db.relationship('Location', backref='machines')
|
||||
operatingsystem = db.relationship('OperatingSystem', backref='machines')
|
||||
|
||||
# Communications (one-to-many)
|
||||
communications = db.relationship(
|
||||
'Communication',
|
||||
backref='machine',
|
||||
cascade='all, delete-orphan',
|
||||
lazy='dynamic'
|
||||
)
|
||||
|
||||
# Installed applications (for PCs)
|
||||
installedapps = db.relationship(
|
||||
'InstalledApp',
|
||||
back_populates='machine',
|
||||
cascade='all, delete-orphan',
|
||||
lazy='dynamic'
|
||||
)
|
||||
|
||||
# Indexes
|
||||
__table_args__ = (
|
||||
db.Index('idx_machine_type_bu', 'machinetypeid', 'businessunitid'),
|
||||
db.Index('idx_machine_location', 'locationid'),
|
||||
db.Index('idx_machine_active', 'isactive'),
|
||||
db.Index('idx_machine_hostname', 'hostname'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Machine {self.machinenumber}>"
|
||||
|
||||
@property
|
||||
def display_name(self):
|
||||
"""Get display name (alias if set, otherwise machinenumber)."""
|
||||
return self.alias or self.machinenumber
|
||||
|
||||
@property
|
||||
def derived_machinetype(self):
|
||||
"""Get machinetype from model (single source of truth)."""
|
||||
if self.model and self.model.machinetype:
|
||||
return self.model.machinetype
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_pc(self):
|
||||
"""Check if this machine is a PC type."""
|
||||
mt = self.derived_machinetype
|
||||
return mt.category == 'PC' if mt else False
|
||||
|
||||
@property
|
||||
def is_equipment(self):
|
||||
"""Check if this machine is equipment."""
|
||||
mt = self.derived_machinetype
|
||||
return mt.category == 'Equipment' if mt else False
|
||||
|
||||
@property
|
||||
def is_network_device(self):
|
||||
"""Check if this machine is a network device."""
|
||||
mt = self.derived_machinetype
|
||||
return mt.category == 'Network' if mt else False
|
||||
|
||||
@property
|
||||
def is_printer(self):
|
||||
"""Check if this machine is a printer."""
|
||||
mt = self.derived_machinetype
|
||||
return mt.category == 'Printer' if mt else False
|
||||
|
||||
@property
|
||||
def primary_ip(self):
|
||||
"""Get primary IP address from communications."""
|
||||
comm = self.communications.filter_by(
|
||||
isprimary=True,
|
||||
comtypeid=1 # IP type
|
||||
).first()
|
||||
if comm:
|
||||
return comm.ipaddress
|
||||
# Fall back to any IP
|
||||
comm = self.communications.filter_by(comtypeid=1).first()
|
||||
return comm.ipaddress if comm else None
|
||||
|
||||
Reference in New Issue
Block a user