Remediate the 2026-09-03 feature-test findings
This commit is contained in:
@@ -9,14 +9,24 @@
|
||||
<router-link :to="`/print/asset-label/machine/${machine?.machine?.machineid}`" class="btn btn-secondary" v-if="machine" target="_blank">
|
||||
Print Label
|
||||
</router-link>
|
||||
<router-link :to="`/machines/${machine?.machine?.machineid}/edit`" class="btn btn-primary" v-if="machine">
|
||||
<router-link :to="`/machines/${machine?.machine?.machineid}/edit`" class="btn btn-primary" v-if="machine && !isRetired">
|
||||
Edit
|
||||
</router-link>
|
||||
<button @click="confirmRetire" class="btn btn-danger" v-if="machine">Retire</button>
|
||||
<button @click="confirmRetire" class="btn btn-danger" v-if="machine && !isRetired">Retire</button>
|
||||
<!-- A retired record offered Edit and Retire exactly like a live one,
|
||||
with nothing on the page saying it was retired. Restoring it was
|
||||
possible through the API and nowhere in the product. -->
|
||||
<button @click="restore" class="btn btn-primary" v-if="machine && isRetired">Restore</button>
|
||||
<router-link to="/machines" class="btn btn-secondary">Back to List</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="machine && isRetired" class="retired-banner card">
|
||||
<strong>This machine is retired.</strong>
|
||||
It stays out of every list and report, and its history and relationships
|
||||
are intact. Restore puts it back.
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading">Loading...</div>
|
||||
|
||||
<template v-else-if="machine">
|
||||
@@ -295,7 +305,7 @@ const { heroWarranty, warrantyDate } = useWarrantyBadge(() => machine.value?.ass
|
||||
|
||||
// relationships render via the shared AssetRelationships card
|
||||
|
||||
onMounted(async () => {
|
||||
async function loadMachine() {
|
||||
try {
|
||||
const response = await machinesApi.get(route.params.id)
|
||||
machine.value = response.data.data
|
||||
@@ -305,7 +315,9 @@ onMounted(async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(loadMachine)
|
||||
|
||||
function getStatusClass(status) {
|
||||
if (!status) return 'badge-info'
|
||||
@@ -324,6 +336,20 @@ function formatDate(dateStr) {
|
||||
// Retire, not delete: the server deactivates the asset, so the machine
|
||||
// leaves every list and report while its history and relationships stay put.
|
||||
// Reversible by an admin setting isactive back to true.
|
||||
const isRetired = computed(() => machine.value?.isactive === false)
|
||||
|
||||
async function restore() {
|
||||
const label = machine.value?.assetnumber || machine.value?.name || 'this machine'
|
||||
try {
|
||||
await machinesApi.update(machine.value?.machine?.machineid, { isactive: true })
|
||||
toast.success(`Restored ${label}`)
|
||||
await loadMachine()
|
||||
} catch (error) {
|
||||
console.error('Error restoring machine:', error)
|
||||
toast.error(apiError(error, 'Failed to restore machine'))
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRetire() {
|
||||
const label = machine.value?.assetnumber || machine.value?.name || 'this machine'
|
||||
if (!confirm(`Retire ${label}?\n\nIt will no longer appear in ShopDB. `
|
||||
@@ -340,6 +366,10 @@ async function confirmRetire() {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.retired-banner {
|
||||
border-left: 3px solid var(--warning);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.mono {
|
||||
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
||||
}
|
||||
|
||||
@@ -416,6 +416,7 @@ import CustomFieldsInputs from '@/components/CustomFieldsInputs.vue'
|
||||
import PrinterAssignmentPicker from '@/components/PrinterAssignmentPicker.vue'
|
||||
import { currentTheme } from '@/stores/theme'
|
||||
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
||||
import { loadAssetTypeIds, assetTypeId } from '@/composables/assetTypeIds'
|
||||
import { apiError } from '@/utils/apiError'
|
||||
import { planControllingPcChange } from './controllingPc'
|
||||
|
||||
@@ -426,8 +427,11 @@ const router = useRouter()
|
||||
|
||||
const isEdit = computed(() => !!route.params.id)
|
||||
|
||||
// Seeded asset-type id for machines (see /api/assets/types).
|
||||
const MACHINE_ASSETTYPEID = 1
|
||||
// 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 MACHINE_ASSETTYPEID = computed(() => assetTypeId('machine'))
|
||||
const customFieldsRef = ref(null)
|
||||
const printerPickerRef = ref(null)
|
||||
const currentAssetId = ref(null)
|
||||
@@ -571,15 +575,16 @@ watch(() => form.value.controllervendorid, (newVal, oldVal) => {
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAssetTypeIds()
|
||||
try {
|
||||
// Load reference data in parallel
|
||||
const [typesRes, statusRes, vendorRes, locRes, allModels, buRes, pcsRes, relTypesRes] = await Promise.all([
|
||||
machinesApi.types.list(),
|
||||
assetsApi.statuses.list(),
|
||||
vendorsApi.list({ perpage: 500 }),
|
||||
vendorsApi.listAll(),
|
||||
locationsApi.listAll(), // backend caps perpage at 100; page through all
|
||||
modelsApi.listAll({ category: 'Equipment' }), // backend caps perpage at 100; page through all
|
||||
businessunitsApi.list({ perpage: 500 }),
|
||||
businessunitsApi.listAll(),
|
||||
computersApi.listAll(), // backend caps perpage at 100; page through all
|
||||
assetsApi.types.list() // Used for relationship types, will fix below
|
||||
])
|
||||
@@ -763,7 +768,13 @@ async function saveMachine() {
|
||||
maintenanceintervaldays: form.value.maintenanceintervaldays || null,
|
||||
notes: form.value.notes || null,
|
||||
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
|
||||
}
|
||||
|
||||
let savedMachine
|
||||
|
||||
@@ -95,7 +95,7 @@ onMounted(loadData)
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await machinesApi.types.list({ perpage: 200, active: false })
|
||||
const response = await machinesApi.types.list({ perpage: 100, active: false })
|
||||
items.value = response.data.data || []
|
||||
} catch (err) {
|
||||
console.error('Error loading machine types:', err)
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
placeholder="Search machines..."
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
<!-- Retire is a soft delete and the confirm dialog promises the record
|
||||
"can be restored". The API kept that promise all along (?active=false
|
||||
lists them, PUT isactive restores) but nothing in the UI could reach
|
||||
it, so a retired machine was simply gone. -->
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="showRetired" @change="loadMachines" />
|
||||
Show retired
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
@@ -41,6 +49,7 @@
|
||||
@click="item.machine?.machineid && $router.push(`/machines/${item.machine.machineid}`)">
|
||||
<td>
|
||||
{{ item.assetnumber }}<template v-if="item.dualpathpartner"> / {{ item.dualpathpartner.assetnumber }}</template>
|
||||
<span v-if="item.isactive === false" class="badge badge-secondary">retired</span>
|
||||
</td>
|
||||
<td>{{ item.name || '-' }}</td>
|
||||
<td class="mono">{{ item.serialnumber || '-' }}</td>
|
||||
@@ -122,6 +131,7 @@ function applySuggestion(word) {
|
||||
// search page documents. Load directly.
|
||||
loadMachines()
|
||||
}
|
||||
const showRetired = ref(false)
|
||||
const totalPages = ref(1)
|
||||
const perPage = ref(20)
|
||||
|
||||
@@ -139,6 +149,8 @@ async function loadMachines() {
|
||||
perpage: perPage.value
|
||||
}
|
||||
if (search.value) params.search = search.value
|
||||
// active=false means "include retired", matching the API.
|
||||
if (showRetired.value) params.active = 'false'
|
||||
|
||||
const response = await machinesApi.list(params)
|
||||
machines.value = response.data.data || []
|
||||
|
||||
Reference in New Issue
Block a user