computers: declare subordinate devices instead of coding each one
A PC that drives a device which is its own asset had been implemented twice. METROLOGY_TOOL_MAP covered CMM, Keyence, Genspect and wax-trace, minting a measuring_tool. A separate path keyed on one hardcoded pc-type minted a Part Marker machine and filed it under its operation. Both create a device, link the PC with controls, and archive that link when the PC is re-imaged: one mechanism with different nouns, written out twice because the second case arrived later. That is the same trap as the site literals in ADR-015 - a pattern implemented per instance rather than declared - and it has a known next occurrence. Part markers already share operation numbers, and any site with two marking lasers or two wax-trace units on one number needs identical treatment. One SUBORDINATE_DEVICE_MAP now declares asset type, type name, naming suffix, whether the device files partof the operation, and the relationship label. The labels are unchanged per case on purpose: those values are in the production database and only rows carrying them are archived by a collector push. A site overrides or adds an entry through subordinatedevice_<pctype> settings, per ADR-015, so the next case needs no code. A malformed override falls back to the default rather than failing the push, because a bad setting must not stop a bay reporting its inventory. metrology_tool_for stays as a shim over the same map: filters.py and the older tests read it, and unifying must not change what it returns. A test pins that. Also adds flask relationships check-shared-machines, which finds the next 0615 rather than waiting for someone to notice duplicate backups. Several devices legitimately sharing a number and two PCs mis-numbered at imaging look the same from outside; the difference is whether child assets exist, so that is what it reports. Read-only.
This commit is contained in:
@@ -728,9 +728,13 @@ class ComputersPlugin(BasePlugin):
|
||||
ordinary machine link to run.
|
||||
"""
|
||||
from shopdb.api import AssetRelationship, RelationshipType, Asset
|
||||
from .pctypemap import drives_partmarker, PARTMARKER_TYPENAME
|
||||
from .pctypemap import subordinate_device_for
|
||||
|
||||
if not drives_partmarker(pctype) or not comp or not comp.asset:
|
||||
spec = subordinate_device_for(pctype)
|
||||
if not spec or not spec.get('partof') or not comp or not comp.asset:
|
||||
# Only a device that FILES UNDER an operation goes through here.
|
||||
# A measuring tool is a subordinate device too, but it does not
|
||||
# share a machine number, so it keeps the simpler path.
|
||||
return []
|
||||
|
||||
pcasset = comp.asset
|
||||
@@ -744,15 +748,18 @@ class ComputersPlugin(BasePlugin):
|
||||
try:
|
||||
from plugins.machines.models import Machine, MachineType
|
||||
except ImportError:
|
||||
warnings.append('machines plugin unavailable; part marker skipped')
|
||||
warnings.append('machines plugin unavailable; {} device skipped'
|
||||
.format(spec['typename']))
|
||||
return []
|
||||
|
||||
# Reuse this PC's existing marker before minting one, so a re-image
|
||||
# never leaves a second marker behind for the same physical device.
|
||||
label = spec['label']
|
||||
|
||||
# Reuse this PC's existing device before minting one, so a re-image
|
||||
# never leaves a second device behind for the same physical unit.
|
||||
existing = AssetRelationship.query.filter(
|
||||
AssetRelationship.sourceassetid == pcasset.assetid,
|
||||
AssetRelationship.relationshiptypeid == controls.relationshiptypeid,
|
||||
AssetRelationship.label == PARTMARKER_LINK_ORIGIN,
|
||||
AssetRelationship.label == label,
|
||||
).all()
|
||||
reuse = next((rel for rel in existing if rel.isactive), None) \
|
||||
or (existing[0] if existing else None)
|
||||
@@ -761,35 +768,36 @@ class ComputersPlugin(BasePlugin):
|
||||
reuse.isactive = True
|
||||
markerasset = db.session.get(Asset, reuse.targetassetid)
|
||||
else:
|
||||
machinetype = AssetType.query.filter_by(assettype='machine').first()
|
||||
if not machinetype:
|
||||
warnings.append('machine asset type missing; part marker '
|
||||
'skipped')
|
||||
coretype = AssetType.query.filter_by(
|
||||
assettype=spec['assettype']).first()
|
||||
if not coretype:
|
||||
warnings.append('{} asset type missing; {} skipped'.format(
|
||||
spec['assettype'], spec['typename']))
|
||||
return []
|
||||
markertype = MachineType.query.filter_by(
|
||||
machinetype=PARTMARKER_TYPENAME).first()
|
||||
if not markertype:
|
||||
markertype = MachineType(machinetype=PARTMARKER_TYPENAME,
|
||||
description='Telesis part marker')
|
||||
db.session.add(markertype)
|
||||
devicetype = MachineType.query.filter_by(
|
||||
machinetype=spec['typename']).first()
|
||||
if not devicetype:
|
||||
devicetype = MachineType(machinetype=spec['typename'],
|
||||
description=spec.get('description'))
|
||||
db.session.add(devicetype)
|
||||
db.session.flush()
|
||||
|
||||
hostname = comp.hostname
|
||||
markerasset = Asset(
|
||||
assetnumber='{}-PARTMARKER'.format(
|
||||
pcasset.assetnumber or hostname),
|
||||
name='Part Marker ({})'.format(hostname),
|
||||
assettypeid=machinetype.assettypeid,
|
||||
assetnumber='{}-{}'.format(
|
||||
pcasset.assetnumber or hostname, spec['suffix']),
|
||||
name='{} ({})'.format(spec['typename'], hostname),
|
||||
assettypeid=coretype.assettypeid,
|
||||
statusid=1)
|
||||
db.session.add(markerasset)
|
||||
db.session.flush()
|
||||
db.session.add(Machine(assetid=markerasset.assetid,
|
||||
machinetypeid=markertype.machinetypeid))
|
||||
machinetypeid=devicetype.machinetypeid))
|
||||
db.session.add(AssetRelationship(
|
||||
sourceassetid=pcasset.assetid,
|
||||
targetassetid=markerasset.assetid,
|
||||
relationshiptypeid=controls.relationshiptypeid,
|
||||
label=PARTMARKER_LINK_ORIGIN,
|
||||
label=label,
|
||||
isactive=True))
|
||||
|
||||
# One marker per PC: archive any other collector marker link.
|
||||
@@ -798,14 +806,14 @@ class ComputersPlugin(BasePlugin):
|
||||
rel.isactive = False
|
||||
|
||||
operation = self._link_marker_to_operation(
|
||||
markerasset, machinenumber, pcasset, warnings)
|
||||
markerasset, machinenumber, pcasset, label, warnings)
|
||||
|
||||
return [{'assetid': markerasset.assetid,
|
||||
'assetnumber': markerasset.assetnumber,
|
||||
'operationassetid': operation}]
|
||||
|
||||
def _link_marker_to_operation(self, markerasset, machinenumber, pcasset,
|
||||
warnings):
|
||||
label, warnings):
|
||||
"""Make a marker `partof` the operation whose number its PC reports.
|
||||
|
||||
Unlike the PC-to-machine link this does NOT contest: an operation can
|
||||
@@ -852,7 +860,7 @@ class ComputersPlugin(BasePlugin):
|
||||
links = AssetRelationship.query.filter(
|
||||
AssetRelationship.sourceassetid == markerasset.assetid,
|
||||
AssetRelationship.relationshiptypeid == partof.relationshiptypeid,
|
||||
AssetRelationship.label == PARTMARKER_LINK_ORIGIN,
|
||||
AssetRelationship.label == label,
|
||||
).all()
|
||||
found = None
|
||||
for rel in links:
|
||||
@@ -866,7 +874,7 @@ class ComputersPlugin(BasePlugin):
|
||||
sourceassetid=markerasset.assetid,
|
||||
targetassetid=operation.assetid,
|
||||
relationshiptypeid=partof.relationshiptypeid,
|
||||
label=PARTMARKER_LINK_ORIGIN,
|
||||
label=label,
|
||||
isactive=True))
|
||||
return operation.assetid
|
||||
|
||||
|
||||
Reference in New Issue
Block a user