diff --git a/scripts/site_imports/wjf/run.py b/scripts/site_imports/wjf/run.py index 4d0c2a5..0dcac3e 100644 --- a/scripts/site_imports/wjf/run.py +++ b/scripts/site_imports/wjf/run.py @@ -319,6 +319,36 @@ def stage_assets(h): return counts +def stage_printers(h): + """Printers come from the printers TABLE (not the machines hub). assetnumber + is synthesized PRN-{printerid} (no machinenumber). The printer's IP folds + into a Communication via the create route. Location resolves from the host + machineid when it is one of the LocationOnly rows. Skips inactive.""" + made = 0 + for p in h.source.rows('shopdb_src', + 'SELECT printerid, modelid, printerwindowsname, printercsfname, ' + 'serialnumber, fqdn, ipaddress, machineid, maptop, mapleft, iscsf, ' + 'installpath, printernotes, printerpin FROM printers WHERE isactive=1'): + payload = { + 'assetnumber': f"PRN-{p['printerid']}", + 'name': (p['printerwindowsname'] or '').strip() or None, + 'serialnumber': (p['serialnumber'] or '').strip() or None, + 'hostname': (p['fqdn'] or '').strip() or None, + 'windowsname': (p['printerwindowsname'] or '').strip() or None, + 'sharename': (p['printercsfname'] or '').strip() or None, + 'ipaddress': (p['ipaddress'] or '').strip() or None, + 'iscsf': _truthy_bit(p['iscsf']), + 'installpath': p['installpath'], 'pin': p['printerpin'], + 'notes': p['printernotes'], 'mapx': p['mapleft'], 'mapy': p['maptop'], + 'modelnumberid': h.ids.get('model', p['modelid']), + 'locationid': h.ids.get('location', p['machineid']), + } + status, _ = h.post('/api/printers', payload) + if status in (200, 201): + made += 1 + return {'printers': made} + + def stage_communications(h): """Fold each asset's primary IP from the source communications table. There is no bulk-communications endpoint, so this is one of the plan's documented @@ -518,16 +548,26 @@ def stage_relationships(h): Edges whose endpoints did not become assets (locations/skipped/dups) drop. Dedups on the (source, target, type) triple.""" # Seed relationship types (case-insensitive collation folds Controls onto - # the seeded 'controls', etc), capturing the crosswalk. + # the seeded 'controls', etc), capturing the crosswalk. A "Controlled By" + # type is the reverse of "Controls" - flag it so edges get flipped to the + # forward direction instead of importing a redundant inverse type. + reverse_typeids = set() + controls_newid = None for t in h.source.rows('shopdb_src', 'SELECT relationshiptypeid, relationshiptype FROM relationshiptypes WHERE isactive=1'): name = (t['relationshiptype'] or '').strip() if not name: continue + lname = name.lower() + if 'controlled by' in lname: + reverse_typeids.add(t['relationshiptypeid']) + continue # do not create an inverse type; edges map to Controls newid = _upsert(h, '/api/assets/relationshiptypes', {'relationshiptype': name}, 'relationshiptype', 'relationshiptypeid', '/api/assets/relationshiptypes') if newid: h.ids.put('relationshiptype', t['relationshiptypeid'], newid) + if lname == 'controls': + controls_newid = newid seen = set() made = dropped = 0 @@ -536,7 +576,12 @@ def stage_relationships(h): 'FROM machinerelationships WHERE isactive=1'): source = h.ids.get('asset', r['machineid']) target = h.ids.get('asset', r['related_machineid']) - typeid = h.ids.get('relationshiptype', r['relationshiptypeid']) + # "Controlled By" edges flip to the forward Controls direction. + if r['relationshiptypeid'] in reverse_typeids: + source, target = target, source + typeid = controls_newid + else: + typeid = h.ids.get('relationshiptype', r['relationshiptypeid']) if not (source and target and typeid) or source == target: dropped += 1 continue @@ -656,6 +701,7 @@ STAGES = { 'catalog': stage_catalog, 'assets': stage_assets, 'locations': stage_locations, + 'printers': stage_printers, 'communications': stage_communications, 'applications': stage_applications, 'warranties': stage_warranties, @@ -672,9 +718,9 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument( '--stages', - default='reference,employees,catalog,assets,locations,communications,' - 'applications,warranties,notifications,knowledgebase,' - 'relationships,subnets,usb,verify', + default='reference,employees,catalog,assets,locations,printers,' + 'communications,applications,warranties,notifications,' + 'knowledgebase,relationships,subnets,usb,verify', help='comma list of stages to run') args = parser.parse_args()