One printer picker for machines and PCs, and one default per asset
The assignment belongs to the MACHINE, and until now there was no way to set it except the generic relationships card or the API - the form for the thing the feature is about did not exist. MachineForm now carries the picker, and PCForm uses the SAME component rather than its own copy: the PC's set overrides the machine's, and two implementations of that would drift, with the two ends of an override disagreeing being exactly the bug nobody would spot. The shared picker also fixes what PCForm did on save. It wrote row at a time through the generic relationship endpoints, which is a non-atomic reconcile: an HTTP failure part way left a PC half-assigned with nothing recording what was meant. It now calls the reconcile endpoint, which validates the default before writing anything. A relationship type can now say it allows one active row per asset (relationshiptypes.issingular, migration 7d34), and defaultprinter says it. Cardinality belongs to the type rather than the printers plugin: core's create path is where every hand-made link passes, and the next type meaning "exactly one" gets the rule for free. Setting a second default REPLACES the first instead of refusing, because "make this the default" means that - and a card answering 409 would leave the user hunting for the old row. Without it the schema was happy to hold two defaults: the unique constraint is (source, target, type), so two different targets are two valid rows, and the resolver takes the OLDEST - the new default silently lost. Proven by disabling the new rule and watching the tests fail. FOUND WHILE TESTING IN A BROWSER, and it was not mine: MachineForm read .data.data off computersApi.listAll(), which resolves to the ARRAY - fetchAllPages has already unwrapped every page. The whole parallel load threw into the catch, so every dropdown on the machine edit form came up empty and the machine's own values never loaded. A build cannot see this; only opening the page can. GET /api/printers/assignments/for-asset/<id> returns an asset's OWN assignment, without inheritance, because the editor must show what this asset's rows say - otherwise a machine's printers appear ticked on the PC that inherits them and unticking one silently creates an override.
This commit is contained in:
@@ -344,6 +344,10 @@
|
||||
<!-- Site-defined custom fields for machines -->
|
||||
<CustomFieldsInputs ref="customFieldsRef" :assettypeid="MACHINE_ASSETTYPEID" :assetid="currentAssetId" />
|
||||
|
||||
<!-- Printers belong to the MACHINE, so whichever PC controls it installs
|
||||
them - including a replacement after a reimage. -->
|
||||
<PrinterAssignmentPicker ref="printerPickerRef" :assetid="currentAssetId" scope="machine" />
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
|
||||
<div style="display: flex; gap: 0.5rem; margin-top: 1.5rem;">
|
||||
@@ -366,6 +370,7 @@ import { loadMapConfig, levelOptions, levelName, state as mapConfig }
|
||||
from '@/composables/mapConfig'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
||||
import PrinterAssignmentPicker from '@/components/PrinterAssignmentPicker.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
@@ -380,6 +385,7 @@ const isEdit = computed(() => !!route.params.id)
|
||||
// Seeded asset-type id for machines (see /api/assets/types).
|
||||
const MACHINE_ASSETTYPEID = 1
|
||||
const customFieldsRef = ref(null)
|
||||
const printerPickerRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
|
||||
const loading = ref(true)
|
||||
@@ -479,7 +485,11 @@ onMounted(async () => {
|
||||
locations.value = locRes.data.data || []
|
||||
models.value = allModels
|
||||
businessunits.value = buRes.data.data || []
|
||||
pcs.value = pcsRes.data.data || []
|
||||
// listAll resolves to the ARRAY, not a response: fetchAllPages already
|
||||
// unwrapped every page. Reading .data.data off it threw, and the whole
|
||||
// parallel load went to the catch - so every dropdown on this form came up
|
||||
// empty and the machine's own values never loaded.
|
||||
pcs.value = pcsRes || []
|
||||
|
||||
// Load relationship types separately
|
||||
try {
|
||||
@@ -633,6 +643,17 @@ async function saveMachine() {
|
||||
}
|
||||
}
|
||||
|
||||
// Printer assignment, after the asset exists so a new machine can be
|
||||
// assigned in the same save. A failure here must not lose the machine the
|
||||
// user just entered, so it is reported and not thrown.
|
||||
if (assetId && printerPickerRef.value) {
|
||||
try {
|
||||
await printerPickerRef.value.save(assetId)
|
||||
} catch (printerErr) {
|
||||
console.error('Error saving printer assignment:', printerErr)
|
||||
}
|
||||
}
|
||||
|
||||
router.push(`/machines/${savedMachine.machine?.machineid || route.params.id}`)
|
||||
} catch (err) {
|
||||
console.error('Error saving machine:', err)
|
||||
|
||||
Reference in New Issue
Block a user