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:
cproudlock
2026-01-13 16:07:34 -05:00
commit 1196de6e88
188 changed files with 19921 additions and 0 deletions

View 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