"""Unified Machine model - combines equipment and PCs.""" from shopdb.extensions import db from .base import BaseModel, SoftDeleteMixin, AuditMixin class MachineType(BaseModel): """ Machine type classification. Categories: Equipment, PC, Network, Printer """ __tablename__ = 'machinetypes' machinetypeid = db.Column(db.Integer, primary_key=True) machinetype = db.Column(db.String(100), unique=True, nullable=False) category = db.Column( db.String(50), nullable=False, default='Equipment', comment='Equipment, PC, Network, or Printer' ) description = db.Column(db.Text) icon = db.Column(db.String(50), comment='Icon name for UI') def __repr__(self): return f"" 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"" 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"" 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"" @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