The retire dialog says "History and relationships are kept, and it can be
restored." The API always kept that promise - ?active=false lists retired rows,
PUT {"isactive": true} restores one - and no screen in the product could reach
either. A retired asset simply vanished: the lists filtered it out, global
search missed it, and its detail page still rendered Edit and Retire with
nothing anywhere saying it was retired.
Five list pages gained a "Show retired" toggle that passes active=false, and a
retired row now carries a badge. Five detail pages gained a banner saying the
record is retired and what that means, a Restore button, and the suppression of
Edit and Retire while it is retired - offering Retire on an already-retired
record was the part that made the state invisible.
Verified end to end in a browser: retire a machine, the detail page says so and
offers Restore, the list hides it until the toggle is ticked and badges it when
shown, and Restore puts it back (isactive true again on the API).
One thing this does NOT do: global search still does not reach a retired asset.
That is a separate surface with its own filter, and worth deciding on
deliberately rather than sliding in here - a search that returns retired
records by default would change every result page.
Scope: product
279 lines
12 KiB
Vue
279 lines
12 KiB
Vue
<template>
|
|
<div class="detail-page">
|
|
<div class="page-header">
|
|
<h2>Measuring Tool Details</h2>
|
|
<div class="header-actions">
|
|
<router-link :to="`/print/asset-label/measuring_tool/${tool?.measuringtool?.measuringtoolid}`" class="btn btn-secondary" v-if="tool" target="_blank">
|
|
Print Label
|
|
</router-link>
|
|
<router-link :to="`/measuringtools/${tool?.measuringtool?.measuringtoolid}/edit`" class="btn btn-primary" v-if="tool">
|
|
Edit
|
|
</router-link>
|
|
<button v-if="tool && !isRetired" @click="confirmRetire" class="btn btn-danger">Retire</button>
|
|
<!-- A retired record offered Edit and Retire exactly like a live
|
|
one, and nothing said it was retired. Restoring it was possible
|
|
through the API and nowhere in the product. -->
|
|
<button v-if="tool && isRetired" @click="restore" class="btn btn-primary">Restore</button>
|
|
<router-link to="/measuringtools" class="btn btn-secondary">Back to List</router-link>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="tool && tool.isactive === false" class="retired-banner card">
|
|
<strong>This measuring tool 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="tool">
|
|
<!-- Hero Section -->
|
|
<div class="hero-card">
|
|
<div class="hero-content">
|
|
<div class="hero-title">
|
|
<h1>{{ tool.assetnumber }}</h1>
|
|
<span v-if="tool.name" class="hero-alias">{{ tool.name }}</span>
|
|
</div>
|
|
<div class="hero-meta">
|
|
<span class="badge badge-lg badge-primary">
|
|
{{ tool.assettypename || 'Measuring Tool' }}
|
|
</span>
|
|
<span class="badge badge-lg" :style="colorStyle(tool.measuringtool?.calibrationstatuscolor)">
|
|
{{ statusLabel(tool.measuringtool?.calibrationstatus) }}
|
|
</span>
|
|
</div>
|
|
<div class="hero-details">
|
|
<div class="hero-detail" v-if="tool.measuringtool?.measuringtooltypename">
|
|
<span class="hero-detail-label">Type</span>
|
|
<span class="hero-detail-value">{{ tool.measuringtool.measuringtooltypename }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="tool.measuringtool?.nextcalibrationdate">
|
|
<span class="hero-detail-label">Next Calibration</span>
|
|
<span class="hero-detail-value">{{ formatDate(tool.measuringtool.nextcalibrationdate) }}</span>
|
|
</div>
|
|
<div class="hero-detail" v-if="tool.locationname">
|
|
<span class="hero-detail-label">Location</span>
|
|
<span class="hero-detail-value">{{ tool.locationname }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- The 6am band: which PC drives this instrument.
|
|
A metrology tool is reached through the PC that runs it - the
|
|
import synthesizes one measuring-tool asset per metrology PC and
|
|
a 'controls' edge PC -> tool (stage_metrology), so the
|
|
counterpart here is a COMPUTER, exactly as on the machine page.
|
|
This was first written as counterparttype="machine" labelled
|
|
"Used on", which filtered for a counterpart that cannot exist
|
|
and so rendered nothing on every gage that had a PC. A hand-held
|
|
gage genuinely has no such edge, and the band correctly shows
|
|
nothing for it. -->
|
|
<AssetTriageBand
|
|
:assetid="tool.assetid"
|
|
counterparttype="computer"
|
|
counterpartlabel="Controlled by"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Canonical card order: Identity -> type-specific -> status -> Location & Organization -> domain -> Custom Fields -> Warranty -> Relationships -> Notes -> audit footer -->
|
|
<!-- Main Content Grid -->
|
|
<div class="content-grid">
|
|
<!-- Left Column -->
|
|
<div class="content-column">
|
|
<!-- Identity Section -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Identity</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Asset Number (Gage Tag)</span>
|
|
<span class="info-value">{{ tool.assetnumber }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.name">
|
|
<span class="info-label">Name</span>
|
|
<span class="info-value">{{ tool.name }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.gaugelabreference && isEnabled('gaugelabreference', 'measuring_tool')">
|
|
<span class="info-label">Gauge Lab Reference</span>
|
|
<span class="info-value mono">{{ tool.gaugelabreference }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.maintenancereference && isEnabled('maintenancereference', 'measuring_tool')">
|
|
<span class="info-label">Maintenance Reference</span>
|
|
<span class="info-value mono">{{ tool.maintenancereference }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.serialnumber">
|
|
<span class="info-label">Serial Number</span>
|
|
<span class="info-value mono">{{ tool.serialnumber }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Type</span>
|
|
<span class="info-value">{{ tool.measuringtool?.measuringtooltypename || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calibration Section -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Calibration</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Status</span>
|
|
<span class="info-value">
|
|
<span class="badge" :style="colorStyle(tool.measuringtool?.calibrationstatuscolor)">
|
|
{{ statusLabel(tool.measuringtool?.calibrationstatus) }}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Last Calibration</span>
|
|
<span class="info-value">{{ tool.measuringtool?.lastcalibrationdate ? formatDate(tool.measuringtool.lastcalibrationdate) : '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Next Calibration</span>
|
|
<span class="info-value">{{ tool.measuringtool?.nextcalibrationdate ? formatDate(tool.measuringtool.nextcalibrationdate) : '-' }}</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.measuringtool?.calibrationintervaldays">
|
|
<span class="info-label">Interval</span>
|
|
<span class="info-value">{{ tool.measuringtool.calibrationintervaldays }} days</span>
|
|
</div>
|
|
<div class="info-row" v-if="tool.measuringtool?.calibrationprovider">
|
|
<span class="info-label">Provider</span>
|
|
<span class="info-value">{{ tool.measuringtool.calibrationprovider }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column -->
|
|
<div class="content-column">
|
|
<!-- Location & Organization -->
|
|
<div class="section-card">
|
|
<h3 class="section-title">Location & Organization</h3>
|
|
<div class="info-list">
|
|
<div class="info-row">
|
|
<span class="info-label">Location</span>
|
|
<span class="info-value">{{ tool.locationname || '-' }}</span>
|
|
</div>
|
|
<div class="info-row">
|
|
<span class="info-label">Business Unit</span>
|
|
<span class="info-value">{{ tool.businessunitname || '-' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Custom Fields -->
|
|
<CustomFieldsSection :assetid="tool.assetid" />
|
|
|
|
<!-- Warranty -->
|
|
<PluginAssetPanels :assetid="tool.assetid" />
|
|
|
|
<!-- All relationships (partof, connectedto, ...) -->
|
|
<AssetRelationships v-if="tool.assetid" :assetid="tool.assetid" />
|
|
|
|
<!-- Notes -->
|
|
<div class="section-card" v-if="tool.notes">
|
|
<h3 class="section-title">Notes</h3>
|
|
<p class="notes-text">{{ tool.notes }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Audit Footer -->
|
|
<div class="audit-footer">
|
|
<span>Created {{ formatDateTime(tool.createddate) }}<template v-if="tool.createdby"> by {{ tool.createdby }}</template></span>
|
|
<span>Modified {{ formatDateTime(tool.modifieddate) }}<template v-if="tool.modifiedby"> by {{ tool.modifiedby }}</template></span>
|
|
</div>
|
|
</template>
|
|
|
|
<div v-else class="card">
|
|
<p style="text-align: center; color: var(--text-light);">Measuring tool not found</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onMounted } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { colorStyle } from '@/utils/colorStyle'
|
|
import { measuringtoolsApi } from '@/api'
|
|
import AssetTriageBand from '@/components/AssetTriageBand.vue'
|
|
import CustomFieldsSection from '@/components/CustomFieldsSection.vue'
|
|
import PluginAssetPanels from '@/components/PluginAssetPanels.vue'
|
|
import AssetRelationships from '@/components/AssetRelationships.vue'
|
|
import { useIdentifierFlags } from '@/composables/identifierSettings'
|
|
import { setRecordTitle } from '@/composables/pageTitle'
|
|
import { useToast } from '@/composables/toast'
|
|
import { apiError } from '@/utils/apiError'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const toast = useToast()
|
|
|
|
const { isEnabled } = useIdentifierFlags()
|
|
|
|
const loading = ref(true)
|
|
const tool = ref(null)
|
|
|
|
const STATUS_LABELS = { overdue: 'Overdue', duesoon: 'Due Soon', current: 'Current', unknown: 'Unknown' }
|
|
function statusLabel(status) { return STATUS_LABELS[status] || 'Unknown' }
|
|
function formatDate(d) { if (!d) return '-'; return new Date(d + 'T00:00:00').toLocaleDateString() }
|
|
function formatDateTime(d) { if (!d) return '-'; return new Date(d).toLocaleString() }
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
// by-asset route resolves the tool from a core asset id (search/cross-link
|
|
// rows carry assetid, not the extension id); detail route keys on the id.
|
|
const response = route.params.assetid
|
|
? await measuringtoolsApi.getByAsset(route.params.assetid)
|
|
: await measuringtoolsApi.get(route.params.id)
|
|
tool.value = response.data.data
|
|
setRecordTitle(tool.value?.assetnumber || tool.value?.name)
|
|
} catch (err) {
|
|
console.error('Error loading measuring tool:', err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
})
|
|
|
|
// Retire, not delete: the server deactivates the asset, so the measuring tool
|
|
// leaves every list and report while its history and relationships stay put.
|
|
// Reversible by an admin setting isactive back to true.
|
|
const isRetired = computed(() => tool.value?.isactive === false)
|
|
|
|
async function restore() {
|
|
const label = tool.value?.assetnumber || tool.value?.name || 'this tool'
|
|
try {
|
|
await measuringtoolsApi.update(route.params.id, { isactive: true })
|
|
toast.success(`Restored ${label}`)
|
|
const response = await measuringtoolsApi.get(route.params.id)
|
|
tool.value = response.data.data
|
|
} catch (error) {
|
|
console.error('Error restoring:', error)
|
|
toast.error(apiError(error, 'Failed to restore'))
|
|
}
|
|
}
|
|
|
|
async function confirmRetire() {
|
|
const label = tool.value?.assetnumber || tool.value?.name || 'this measuring tool'
|
|
if (!confirm(`Retire ${label}?\n\nIt will no longer appear in ShopDB. `
|
|
+ `History and relationships are kept, and it can be restored.`)) return
|
|
try {
|
|
await measuringtoolsApi.remove(tool.value?.measuringtool?.measuringtoolid)
|
|
toast.success(`Retired ${label}`)
|
|
router.push('/measuringtools')
|
|
} catch (error) {
|
|
console.error('Error retiring measuring tool:', error)
|
|
toast.error(apiError(error, 'Failed to retire measuring tool'))
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.retired-banner {
|
|
border-left: 3px solid var(--warning);
|
|
margin-bottom: 1rem;
|
|
}
|
|
.mono {
|
|
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
|
|
}
|
|
</style>
|