Initial commit: Shop Database Flask Application
Flask backend with Vue 3 frontend for shop floor machine management. Includes database schema export for MySQL shopdb_flask database. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
252
shopdb/core/models/machine.py
Normal file
252
shopdb/core/models/machine.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""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"<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