Email: a stdlib SMTP mailer (settings-first config, graceful no-op when unconfigured), a test-email endpoint wired to the Email settings page, forced first-login password change (users.mustchangepassword, migration 7d23, /change-password flow), new-user welcome mail, and on-demand report/alert delivery (POST /api/reports/email + Email Report buttons) with an external-cron-with-a-scoped-PAT path documented for automation. All tests patch smtplib - no network. Labels: a shared /print/asset-label/<type>/<id> view any asset detail page opens - card or plain style, QR or barcode, configurable encoding. Per-type qr_target_* templates plus label_default_style/codetype/encodes settings on the Printing page. Measuring-tool labels default to encoding their inspection-operation code (derived from the location name, e.g. 0615), so every tool in an area shares the area code - verified by decoding the rendered QR. Machine labels default to the machine number; blank-serial handled gracefully. 808 tests pass; both features verified live. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
100 lines
4.0 KiB
JavaScript
100 lines
4.0 KiB
JavaScript
// Shared wiring for the asset label pages (single AssetLabel.vue and batch
|
|
// AssetLabelBatch.vue): per-type api/route config plus the "what does the code
|
|
// encode" resolution so both surfaces behave identically.
|
|
import {
|
|
machinesApi, computersApi, printersApi, networkApi, measuringtoolsApi,
|
|
} from '../../api'
|
|
import { fillUrlTemplate } from '@/utils/qrTarget'
|
|
import { getSetting, getSiteBaseUrl } from '@/utils/siteSettings'
|
|
|
|
// Per asset-type: which api loads it, its detail/list routes, the extension key
|
|
// nested in the merged payload, and its qr_target_<type> settings key.
|
|
export const TYPE_CONFIG = {
|
|
machine: {
|
|
api: machinesApi, extkey: 'machine', targetKey: 'qr_target_machine',
|
|
label: 'Machine', detailPath: id => `/machines/${id}`, listPath: '/machines',
|
|
},
|
|
computer: {
|
|
api: computersApi, extkey: 'computer', targetKey: 'qr_target_computer',
|
|
label: 'Computer', detailPath: id => `/pcs/${id}`, listPath: '/pcs',
|
|
},
|
|
printer: {
|
|
api: printersApi, extkey: 'printer', targetKey: 'qr_target_printer',
|
|
label: 'Printer', detailPath: id => `/printers/${id}`, listPath: '/printers',
|
|
},
|
|
network_device: {
|
|
api: networkApi, extkey: 'network_device', targetKey: 'qr_target_network_device',
|
|
label: 'Network Device', detailPath: id => `/network/${id}`, listPath: '/network',
|
|
},
|
|
measuring_tool: {
|
|
api: measuringtoolsApi, extkey: 'measuringtool', targetKey: 'qr_target_measuring_tool',
|
|
label: 'Measuring Tool', detailPath: id => `/measuringtools/${id}`, listPath: '/measuringtools',
|
|
},
|
|
}
|
|
|
|
// Hardcoded fallback default per type when the site setting is unset. Machines
|
|
// encode their machine number, measuring tools their inspection location.
|
|
const DEFAULT_ENCODES = { machine: 'assetnumber', measuring_tool: 'location' }
|
|
|
|
export function hasLocationType(assettype) {
|
|
return assettype === 'measuring_tool'
|
|
}
|
|
|
|
// The default encode mode for a type: the label_default_encodes_<type> site
|
|
// setting when valid, else the hardcoded fallback.
|
|
export async function resolveDefaultEncodes(assettype) {
|
|
const valid = ['assetpage', 'assetnumber', 'serialnumber', 'custom']
|
|
if (hasLocationType(assettype)) valid.push('location')
|
|
const seeded = await getSetting(`label_default_encodes_${assettype}`, '')
|
|
return valid.includes(seeded) ? seeded : (DEFAULT_ENCODES[assettype] || 'assetpage')
|
|
}
|
|
|
|
// 'location' degrades to the asset page when the asset has no location code.
|
|
export function effectiveEncodes(encodes, asset) {
|
|
if (encodes === 'location' && !asset?.locationcode) return 'assetpage'
|
|
return encodes
|
|
}
|
|
|
|
function tokensFor(asset) {
|
|
return {
|
|
assetid: asset.assetid || '',
|
|
assetnumber: asset.assetnumber || '',
|
|
serialnumber: asset.serialnumber || '',
|
|
name: asset.name || '',
|
|
pluginid: asset.pluginid || '',
|
|
locationcode: asset.locationcode || '',
|
|
locationname: asset.locationname || '',
|
|
}
|
|
}
|
|
|
|
// The string a label's code encodes for one asset. Empty when the chosen field
|
|
// has no value (e.g. serial number on an asset with none).
|
|
export async function resolveCodeText(assettype, asset, encodes) {
|
|
const config = TYPE_CONFIG[assettype]
|
|
if (!config || !asset) return ''
|
|
const detailId = asset.pluginid
|
|
const assetPageUrl = `${await getSiteBaseUrl()}${config.detailPath(detailId)}`
|
|
switch (effectiveEncodes(encodes, asset)) {
|
|
case 'assetnumber': return asset.assetnumber || ''
|
|
case 'serialnumber': return asset.serialnumber || ''
|
|
case 'location': return asset.locationcode || ''
|
|
case 'custom': {
|
|
const template = (await getSetting(config.targetKey, '')).trim()
|
|
return template ? fillUrlTemplate(template, tokensFor(asset)) : assetPageUrl
|
|
}
|
|
case 'assetpage':
|
|
default: return assetPageUrl
|
|
}
|
|
}
|
|
|
|
// Human caption printed under the code.
|
|
export function captionFor(asset, encodes) {
|
|
if (!asset) return ''
|
|
switch (effectiveEncodes(encodes, asset)) {
|
|
case 'location': return asset.locationcode || ''
|
|
case 'serialnumber': return asset.serialnumber || ''
|
|
case 'assetnumber':
|
|
default: return asset.assetnumber || ''
|
|
}
|
|
}
|