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:
cproudlock
2026-06-26 09:59:17 -04:00
parent cb47b9087e
commit b0a0670fcd
23 changed files with 344 additions and 2047 deletions

View File

@@ -66,10 +66,10 @@
<option value="">Select type...</option>
<option
v-for="pt in pcTypes"
:key="pt.machinetypeid"
:value="pt.machinetypeid"
:key="pt.computertypeid"
:value="pt.computertypeid"
>
{{ pt.machinetype }}
{{ pt.computertype }}
</option>
</select>
</div>
@@ -268,7 +268,7 @@
<script setup>
import { ref, onMounted, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { machinesApi, machinetypesApi, assetsApi, vendorsApi, locationsApi, modelsApi, operatingsystemsApi } from '../../api'
import { computersApi, assetsApi, vendorsApi, locationsApi, modelsApi, operatingsystemsApi } from '../../api'
import ShopFloorMap from '../../components/ShopFloorMap.vue'
import Modal from '../../components/Modal.vue'
import { currentTheme } from '../../stores/theme'
@@ -316,17 +316,10 @@ const operatingsystems = ref([])
// Filter models by selected vendor and PC type
const filteredModels = computed(() => {
return models.value.filter(m => {
if (form.value.vendorid && m.vendorid !== form.value.vendorid) {
return false
}
// only exclude models that have a type set and it differs; most models
// have no machinetypeid, so a strict check hides the PC's own model
if (form.value.machinetypeid && m.machinetypeid && m.machinetypeid !== form.value.machinetypeid) {
return false
}
return true
})
// filter by vendor only (PC type now maps to computertypeid, a different id
// space than a model's machinetypeid)
if (!form.value.vendorid) return models.value
return models.value.filter(m => m.vendorid === form.value.vendorid)
})
onMounted(async () => {
@@ -334,7 +327,7 @@ onMounted(async () => {
// Load reference data
// perpage 100 so dropdowns aren't truncated to the default 20-row page
const [ptRes, statusRes, vendorRes, allModels, locRes, osRes] = await Promise.all([
machinetypesApi.list({ category: 'PC', perpage: 100 }),
computersApi.types.list({ perpage: 100 }),
assetsApi.statuses.list(),
vendorsApi.list({ perpage: 100 }),
modelsApi.listAll(), // backend caps perpage at 100; page through all
@@ -349,28 +342,28 @@ onMounted(async () => {
locations.value = locRes.data.data || []
operatingsystems.value = osRes.data.data || []
// Load PC if editing
// Load PC if editing (asset-based shape: extension under pc.computer)
if (isEdit.value) {
const response = await machinesApi.get(route.params.id)
const response = await computersApi.get(route.params.id)
const pc = response.data.data
const ext = pc.computer || {}
// Get IP from communications
const primaryComm = pc.communications?.find(c => c.isprimary) || pc.communications?.[0]
form.value = {
machinenumber: pc.machinenumber || '',
alias: pc.alias || '',
hostname: pc.hostname || '',
machinenumber: pc.assetnumber || '',
alias: pc.name && pc.name.toUpperCase() !== 'NONE' ? pc.name : '',
hostname: ext.hostname || '',
serialnumber: pc.serialnumber || '',
machinetypeid: pc.machinetype?.machinetypeid || '',
statusid: pc.status?.statusid || '',
vendorid: pc.vendor?.vendorid || '',
modelnumberid: pc.model?.modelnumberid || '',
locationid: pc.location?.locationid || '',
osid: pc.operatingsystem?.osid || '',
loggedinuser: pc.loggedinuser || '',
isvnc: pc.isvnc || false,
iswinrm: pc.iswinrm || false,
machinetypeid: ext.computertypeid || '',
statusid: pc.statusid || '',
vendorid: ext.vendorid || '',
modelnumberid: ext.modelnumberid || '',
locationid: pc.locationid || '',
osid: ext.osid || '',
loggedinuser: ext.loggedinuser || '',
isvnc: ext.isvnc || false,
iswinrm: ext.iswinrm || false,
notes: pc.notes || '',
mapx: pc.mapx ?? null,
mapy: pc.mapy ?? null,
@@ -408,40 +401,35 @@ async function savePC() {
saving.value = true
try {
const machineData = {
machinenumber: form.value.machinenumber,
alias: form.value.alias,
hostname: form.value.hostname,
serialnumber: form.value.serialnumber,
machinetypeid: form.value.machinetypeid || null,
// One payload for the computers plugin (asset core + computer extension +
// primary IP). "PC Number" is the business identifier (assetnumber).
const payload = {
assetnumber: form.value.machinenumber,
hostname: form.value.hostname || null,
serialnumber: form.value.serialnumber || null,
computertypeid: form.value.machinetypeid || null,
statusid: form.value.statusid || null,
vendorid: form.value.vendorid || null,
modelnumberid: form.value.modelnumberid || null,
locationid: form.value.locationid || null,
osid: form.value.osid || null,
loggedinuser: form.value.loggedinuser,
loggedinuser: form.value.loggedinuser || null,
isvnc: form.value.isvnc,
iswinrm: form.value.iswinrm,
notes: form.value.notes,
notes: form.value.notes || null,
ipaddress: form.value.ipaddress || null,
mapx: form.value.mapx,
mapy: form.value.mapy
}
let machineId
if (isEdit.value) {
await machinesApi.update(route.params.id, machineData)
machineId = route.params.id
} else {
const response = await machinesApi.create(machineData)
machineId = response.data.data.machineid
// only set display name when an alias is given, so we don't clobber it
if (form.value.alias) {
payload.name = form.value.alias
}
// Handle IP address - update communication record
if (form.value.ipaddress) {
await machinesApi.updateCommunication(machineId, {
ipaddress: form.value.ipaddress,
isprimary: true
})
if (isEdit.value) {
await computersApi.update(route.params.id, payload)
} else {
await computersApi.create(payload)
}
router.push('/pcs')