Resolve an asset type by name, and send a map position with its level

Wave 5, the asset forms. Both defects are the same mistake in different
clothes: a form deciding something it should have been told.

Four forms carried a hardcoded asset-type id - const COMPUTER_ASSETTYPEID = 2,
and the same for machines, printers and network devices - to tell
CustomFieldsInputs which type's fields to render. Those ids come from seed
order. On the instance they were written against, computer was 2; on a freshly
seeded database computer is 1 and 2 is network_device. So the PC form asked for
another type's custom fields, rendered them, and dropped everything typed into
them behind a success toast, because the values were keyed to a type the record
does not have. Verified in a browser against a correctly built dist: the form
now requests assettypeid=1 on an instance where computer is 1.

The name is the stable thing - assettype is a fixed vocabulary and the API
already publishes the mapping - so composables/assetTypeIds.js resolves it once
per page load. A failed load leaves it undefined, which renders no custom-field
panel: a missing panel is honest, a guessed id is what this replaces.

Three of the five forms that emit a map position sent mapx and mapy without
levelid. ADR-017 is explicit that a position without its level cannot be drawn
on the right floor plan, so a picked pin saved a null level and rendered
nowhere - while the form displayed "Position: x, y" back, because it kept the
value it had never sent.

The ADR-017 gate should have caught that and did not: it globbed '*.py' only,
so it policed what the server emits and never what the browser sends. It now
covers .vue and .js too, and accepts both quoting styles. Verified by planting:
drop levelid from PCForm again and the gate names that exact line.

Five composable tests, including a fixture with a DIFFERENT seed order - the
case the constants got wrong.

Still open from this area, and deliberately not in this commit: the PC form
writes an INHERITED location back as the record's own. Fixing it needs the API
to distinguish an owned locationid from a derived one, which is a contract
change rather than a form fix.

Scope: product
This commit is contained in:
cproudlock
2026-09-03 14:42:24 -04:00
parent ffced2f822
commit 45acaf0b8f
7 changed files with 208 additions and 13 deletions

View File

@@ -336,6 +336,7 @@ import Modal from '@/components/Modal.vue'
import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
import { currentTheme } from '@/stores/theme'
import { useIdentifierFlags } from '@/composables/identifierSettings'
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
import { apiError } from '@/utils/apiError'
import { getPrinterHostnameTemplate } from '@/utils/siteSettings'
@@ -346,8 +347,11 @@ const router = useRouter()
const isEdit = computed(() => !!route.params.id)
// Seeded asset-type id for printers (see /api/assets/types).
const PRINTER_ASSETTYPEID = 4
// Resolved by NAME at load, never hardcoded: the id comes from seed
// order, so the constant that used to live here pointed at a different
// asset type on a differently-seeded site - the form then rendered THAT
// type's custom fields and dropped whatever was typed into them.
const PRINTER_ASSETTYPEID = computed(() => assetTypeId('printer'))
const customFieldsRef = ref(null)
const currentAssetId = ref(null)
const manualHostname = ref(false)
@@ -542,6 +546,7 @@ function onWindowsNameInput() {
}
onMounted(async () => {
await loadAssetTypeIds()
try {
// Load reference data (models paged in full via listAll, see api/index.js)
// perpage 100 so dropdowns aren't truncated to the default 20-row page
@@ -686,7 +691,13 @@ async function savePrinter() {
isduplex: !!form.value.isduplex,
isnetwork: !!form.value.isnetwork,
mapx: form.value.mapx,
mapy: form.value.mapy
mapy: form.value.mapy,
// ADR-017: a position without its level cannot be drawn on the right
// floor plan. The picker sets all three and this payload sent two, so a
// picked pin saved a levelid of null and then rendered nowhere - the
// form even showed "Position: x, y" back, because it kept the value it
// had never sent.
levelid: form.value.levelid
}
// only set the display name when an alias is given, so we don't clobber it
if (form.value.alias) {