Rename the equipment domain to machines; retype the models catalog (ADR-011)
The equipment plugin is now the machines plugin, ending the UI-vs-code vocabulary split while the contract is pre-1.0 and nothing external depends on the old names. - plugins/equipment -> plugins/machines: manifest, class, /api/machines, machines.* permissions, registry key (with an auto-migrating load shim for existing installs). - Tables: equipment -> machines (equipmentid -> machineid) and equipmenttypes -> machinetypes, renamed in the plugin's own migration chain (machines0002rename), idempotent for both upgrading and fresh installs. - The legacy core machinetypes lookup actually types the vendor MODELS catalog, so it is renamed losslessly to modeltypes (models.modeltypeid, /api/modeltypes, Model Types settings page) rather than collapsed, freeing the machinetypes name. Core migration 7d17_machines_rename also flips data in place: assettypes row equipment -> machine, auditlog entitytype, identifier_/search_ settings keys, permission rows, and renames alembic_version_equipment. - Frontend: machinesApi/modeltypesApi, item.machine response shape, assettype value compares 'equipment' -> 'machine' (map, search, custom fields, relationships), routes machines.js with plugin gating retagged, /print/machine-badge, Machine Types (subtypes) and Model Types (catalog) settings pages, machines-by-type report id. - Docs swept; ADRs left as history per the authoring rule. Upgrade: flask db upgrade then flask plugin upgrade-all. Verified: dev DB flipped live (262 machines, 35 modeltypes, 95 models retyped, zero equipment tables remain); fresh scratch-MySQL install produces the new names; 341 tests green; naming/style green; frontend builds; live E2E on machines list/detail, PC relationships, map, reports, and both settings pages. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
205
migrations/versions/7d17_machines_rename.py
Normal file
205
migrations/versions/7d17_machines_rename.py
Normal file
@@ -0,0 +1,205 @@
|
||||
"""Equipment -> machines rename, core half
|
||||
|
||||
Part 1 of the approved equipment -> machines rename (the plugin half lives in
|
||||
plugins/machines/migrations/versions/0002_rename_from_equipment.py):
|
||||
|
||||
1. machinetypes -> modeltypes (+ machinetypeid -> modeltypeid,
|
||||
machinetype -> modeltype, models.machinetypeid -> models.modeltypeid).
|
||||
The table types the vendor MODELS catalog, not machine instances, so
|
||||
the new name is role-accurate and frees "machinetypes" for the plugin.
|
||||
2. Data flips: assettypes 'equipment' -> 'machine' (incl pluginname and
|
||||
tablename), auditlogs.entitytype 'Equipment' -> 'Machine', settings keys
|
||||
identifier_*_equipment_enabled -> identifier_*_machine_enabled,
|
||||
search_equipment_enabled -> search_machine_enabled, and permission
|
||||
rows equipment.* -> machines.* (role links survive).
|
||||
3. alembic_version_equipment -> alembic_version_machines (if present) so
|
||||
the renamed plugin's migration chain resumes seamlessly.
|
||||
|
||||
Idempotent via inspector guards, following the 7c01 pattern. MySQL is the
|
||||
only dialect the core chain runs against in practice.
|
||||
|
||||
Revision ID: 7d17_machines_rename
|
||||
Revises: 7d16_directoryemployees
|
||||
Create Date: 2026-07-11
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = '7d17_machines_rename'
|
||||
down_revision = '7d16_directoryemployees'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _fk_names(insp, table, referred_table):
|
||||
# find FK constraint names on table pointing at referred_table
|
||||
return [fk['name'] for fk in insp.get_foreign_keys(table)
|
||||
if fk.get('referred_table') == referred_table and fk.get('name')]
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
insp = sa.inspect(bind)
|
||||
tables = set(insp.get_table_names())
|
||||
|
||||
# -- 1. machinetypes -> modeltypes ------------------------------------
|
||||
if 'machinetypes' in tables and 'modeltypes' not in tables:
|
||||
bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")
|
||||
|
||||
# drop the models -> machinetypes FK and its index before renaming
|
||||
for name in _fk_names(insp, 'models', 'machinetypes'):
|
||||
bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}")
|
||||
model_indexes = {ix['name'] for ix in insp.get_indexes('models')}
|
||||
if 'machinetypeid' in model_indexes:
|
||||
bind.exec_driver_sql("ALTER TABLE models DROP KEY machinetypeid")
|
||||
|
||||
bind.exec_driver_sql("RENAME TABLE machinetypes TO modeltypes")
|
||||
bind.exec_driver_sql(
|
||||
"ALTER TABLE modeltypes "
|
||||
"CHANGE machinetypeid modeltypeid INT NOT NULL AUTO_INCREMENT, "
|
||||
"CHANGE machinetype modeltype VARCHAR(100) NOT NULL, "
|
||||
"DROP KEY machinetype, "
|
||||
"ADD UNIQUE KEY modeltype (modeltype)"
|
||||
)
|
||||
bind.exec_driver_sql(
|
||||
"ALTER TABLE models "
|
||||
"CHANGE machinetypeid modeltypeid INT NULL, "
|
||||
"ADD KEY modeltypeid (modeltypeid), "
|
||||
"ADD CONSTRAINT fk_models_modeltypeid "
|
||||
"FOREIGN KEY (modeltypeid) REFERENCES modeltypes (modeltypeid)"
|
||||
)
|
||||
bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")
|
||||
|
||||
# -- 2. data flips ------------------------------------------------------
|
||||
if 'assettypes' in tables:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE assettypes SET assettype='machine', pluginname='machines', "
|
||||
"tablename='machines' WHERE assettype='equipment'"
|
||||
)
|
||||
if 'auditlogs' in tables:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE auditlogs SET entitytype='Machine' "
|
||||
"WHERE entitytype='Equipment'"
|
||||
)
|
||||
if 'settings' in tables:
|
||||
_rename_setting_keys(bind, old_marker='_equipment_',
|
||||
new_marker='_machine_')
|
||||
if 'permissions' in tables:
|
||||
_rename_permissions(bind, old_prefix='equipment.',
|
||||
new_prefix='machines.',
|
||||
old_category='equipment', new_category='machines')
|
||||
|
||||
# -- 3. plugin version table --------------------------------------------
|
||||
if ('alembic_version_equipment' in tables
|
||||
and 'alembic_version_machines' not in tables):
|
||||
bind.exec_driver_sql(
|
||||
"RENAME TABLE alembic_version_equipment TO alembic_version_machines"
|
||||
)
|
||||
|
||||
|
||||
def _rename_setting_keys(bind, old_marker, new_marker):
|
||||
"""Rename settings keys embedding the assettype value, collision-safe.
|
||||
|
||||
Covers identifier_<name>_equipment_enabled and search_equipment_enabled.
|
||||
If a row with the new key already exists (fresh seed ran on new code),
|
||||
the old row is dropped instead of renamed.
|
||||
"""
|
||||
# broad fetch; the marker replace below filters non-matches
|
||||
rows = bind.exec_driver_sql(
|
||||
"SELECT settingid, `key` FROM settings"
|
||||
).fetchall()
|
||||
for settingid, key in rows:
|
||||
newkey = key.replace(old_marker, new_marker)
|
||||
if newkey == key:
|
||||
continue
|
||||
exists = bind.exec_driver_sql(
|
||||
"SELECT 1 FROM settings WHERE `key` = %s", (newkey,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
bind.exec_driver_sql(
|
||||
"DELETE FROM settings WHERE settingid = %s", (settingid,))
|
||||
else:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE settings SET `key` = %s WHERE settingid = %s",
|
||||
(newkey, settingid))
|
||||
|
||||
|
||||
def _rename_permissions(bind, old_prefix, new_prefix, old_category,
|
||||
new_category):
|
||||
"""Rename permission rows in place so role links survive, collision-safe."""
|
||||
rows = bind.exec_driver_sql(
|
||||
"SELECT permissionid, name FROM permissions"
|
||||
).fetchall()
|
||||
for permissionid, name in rows:
|
||||
if not name.startswith(old_prefix):
|
||||
continue
|
||||
newname = new_prefix + name[len(old_prefix):]
|
||||
exists = bind.exec_driver_sql(
|
||||
"SELECT 1 FROM permissions WHERE name = %s", (newname,)
|
||||
).fetchone()
|
||||
if exists:
|
||||
bind.exec_driver_sql(
|
||||
"DELETE FROM permissions WHERE permissionid = %s",
|
||||
(permissionid,))
|
||||
else:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE permissions SET name = %s, category = %s "
|
||||
"WHERE permissionid = %s",
|
||||
(newname, new_category, permissionid))
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
insp = sa.inspect(bind)
|
||||
tables = set(insp.get_table_names())
|
||||
|
||||
if ('alembic_version_machines' in tables
|
||||
and 'alembic_version_equipment' not in tables):
|
||||
bind.exec_driver_sql(
|
||||
"RENAME TABLE alembic_version_machines TO alembic_version_equipment"
|
||||
)
|
||||
|
||||
if 'settings' in tables:
|
||||
_rename_setting_keys(bind, old_marker='_machine_',
|
||||
new_marker='_equipment_')
|
||||
if 'permissions' in tables:
|
||||
_rename_permissions(bind, old_prefix='machines.',
|
||||
new_prefix='equipment.',
|
||||
old_category='machines', new_category='equipment')
|
||||
if 'auditlogs' in tables:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE auditlogs SET entitytype='Equipment' "
|
||||
"WHERE entitytype='Machine'"
|
||||
)
|
||||
if 'assettypes' in tables:
|
||||
bind.exec_driver_sql(
|
||||
"UPDATE assettypes SET assettype='equipment', "
|
||||
"pluginname='equipment', tablename='equipment' "
|
||||
"WHERE assettype='machine'"
|
||||
)
|
||||
|
||||
if 'modeltypes' in tables and 'machinetypes' not in tables:
|
||||
bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=0")
|
||||
for name in _fk_names(insp, 'models', 'modeltypes'):
|
||||
bind.exec_driver_sql(f"ALTER TABLE models DROP FOREIGN KEY {name}")
|
||||
model_indexes = {ix['name'] for ix in insp.get_indexes('models')}
|
||||
if 'modeltypeid' in model_indexes:
|
||||
bind.exec_driver_sql("ALTER TABLE models DROP KEY modeltypeid")
|
||||
bind.exec_driver_sql("RENAME TABLE modeltypes TO machinetypes")
|
||||
bind.exec_driver_sql(
|
||||
"ALTER TABLE machinetypes "
|
||||
"CHANGE modeltypeid machinetypeid INT NOT NULL AUTO_INCREMENT, "
|
||||
"CHANGE modeltype machinetype VARCHAR(100) NOT NULL, "
|
||||
"DROP KEY modeltype, "
|
||||
"ADD UNIQUE KEY machinetype (machinetype)"
|
||||
)
|
||||
bind.exec_driver_sql(
|
||||
"ALTER TABLE models "
|
||||
"CHANGE modeltypeid machinetypeid INT NULL, "
|
||||
"ADD KEY machinetypeid (machinetypeid), "
|
||||
"ADD CONSTRAINT models_ibfk_1 "
|
||||
"FOREIGN KEY (machinetypeid) REFERENCES machinetypes (machinetypeid)"
|
||||
)
|
||||
bind.exec_driver_sql("SET FOREIGN_KEY_CHECKS=1")
|
||||
Reference in New Issue
Block a user