Bulk Dell warranty sync + add-warranty from asset pages
- POST /warranty/sync/dell: auto-detect Dell hardware by service tag (asset serial) and create warranties for the ones Dell recognizes, in batches of 100. Only looks up assets that lack a dated warranty, so re-runs are cheap and non-Dell serials are filtered out by Dell. DellProvider.bulk_lookup batches tags over the cached token. - "Sync Dell" button on the Warranties page with a toast summary. - WarrantyPanel "Add one / Add-manage" links deep-link to the warranties add modal pre-linked to that asset (?addfor=); WarrantiesList opens it prefilled. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -210,6 +210,95 @@ def refresh_warranty(warrantyid):
|
||||
return success_response(_warranty_payload(warranty), message='Warranty refreshed')
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Bulk Dell sync - auto-detect Dell hardware by service tag and pull coverage
|
||||
# =============================================================================
|
||||
|
||||
@warranty_bp.route('/sync/dell', methods=['POST'])
|
||||
@jwt_required()
|
||||
@require_permission('warranty.edit')
|
||||
def sync_dell():
|
||||
"""Look up every asset serial (= Dell service tag) that has no warranty yet
|
||||
and create warranties for the ones Dell recognizes.
|
||||
|
||||
Only assets that lack a dated warranty are looked up, so re-running is cheap
|
||||
and non-Dell serials are filtered out by Dell (they return no coverage).
|
||||
"""
|
||||
provider = get_provider('dell')
|
||||
|
||||
# Assets already covered by a dated warranty - skip these.
|
||||
covered = set()
|
||||
for link in WarrantyAsset.query.all():
|
||||
w = Warranty.query.get(link.warrantyid)
|
||||
if w and w.isactive and w.enddate:
|
||||
covered.add(link.assetid)
|
||||
|
||||
# Candidate assets: active, have a serial, not already covered.
|
||||
candidates = (Asset.query
|
||||
.filter(Asset.isactive.is_(True),
|
||||
Asset.serialnumber.isnot(None),
|
||||
Asset.serialnumber != '')
|
||||
.all())
|
||||
by_tag = {}
|
||||
for asset in candidates:
|
||||
if asset.assetid in covered:
|
||||
continue
|
||||
by_tag.setdefault(asset.serialnumber.strip().upper(), []).append(asset.assetid)
|
||||
|
||||
if not by_tag:
|
||||
return success_response({
|
||||
'candidates': 0, 'matched': 0, 'created': 0, 'updated': 0,
|
||||
}, message='No PCs need a warranty lookup.')
|
||||
|
||||
try:
|
||||
results = provider.bulk_lookup(list(by_tag.keys()))
|
||||
except (ProviderNotConfigured, WarrantyLookupError) as exc:
|
||||
return error_response(ErrorCodes.VALIDATION_ERROR, str(exc), http_code=400)
|
||||
|
||||
created = updated = matched = 0
|
||||
now = datetime.utcnow()
|
||||
for tag, assetids in by_tag.items():
|
||||
found = results.get(tag)
|
||||
if not found:
|
||||
continue
|
||||
matched += 1
|
||||
for assetid in assetids:
|
||||
# Reuse an existing Dell warranty for this asset if there is one.
|
||||
existing = None
|
||||
for link in WarrantyAsset.query.filter_by(assetid=assetid).all():
|
||||
candidate = Warranty.query.get(link.warrantyid)
|
||||
if candidate and candidate.provider == 'dell':
|
||||
existing = candidate
|
||||
break
|
||||
if existing:
|
||||
existing.servicelevel = found.get('servicelevel')
|
||||
existing.startdate = _parse_date(found.get('startdate'))
|
||||
existing.enddate = _parse_date(found.get('enddate'))
|
||||
existing.servicetag = tag
|
||||
existing.lastcheckeddate = now
|
||||
updated += 1
|
||||
else:
|
||||
warranty = Warranty(
|
||||
vendor='Dell', provider='dell', servicetag=tag,
|
||||
servicelevel=found.get('servicelevel'),
|
||||
startdate=_parse_date(found.get('startdate')),
|
||||
enddate=_parse_date(found.get('enddate')),
|
||||
lastcheckeddate=now,
|
||||
)
|
||||
warranty.links.append(WarrantyAsset(assetid=assetid))
|
||||
db.session.add(warranty)
|
||||
created += 1
|
||||
|
||||
db.session.commit()
|
||||
return success_response({
|
||||
'candidates': sum(len(v) for v in by_tag.values()),
|
||||
'tags': len(by_tag),
|
||||
'matched': matched,
|
||||
'created': created,
|
||||
'updated': updated,
|
||||
}, message=f'Dell sync: {created} added, {updated} updated, {matched} tags matched.')
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Report buckets (for the Reports hub)
|
||||
# =============================================================================
|
||||
|
||||
@@ -203,6 +203,34 @@ class DellProvider(WarrantyProvider):
|
||||
token = self._get_token(config)
|
||||
return self._map(self._fetch(config, token, servicetag))
|
||||
|
||||
def bulk_lookup(self, servicetags):
|
||||
"""Look up many tags at once. Dell takes up to 100 tags per call.
|
||||
|
||||
Returns {SERVICETAG_UPPER: {servicelevel, startdate, enddate}} for tags
|
||||
Dell recognizes; unknown/non-Dell tags are simply absent.
|
||||
"""
|
||||
config = self._config()
|
||||
if not (config['enabled'] and config['clientid'] and config['clientsecret']):
|
||||
raise ProviderNotConfigured(
|
||||
'Dell warranty lookup is not configured. Set warranty_dell_enabled, '
|
||||
'clientid and clientsecret in Settings > Integrations.'
|
||||
)
|
||||
tags = [t.strip() for t in servicetags if t and t.strip()]
|
||||
if not tags:
|
||||
return {}
|
||||
token = self._get_token(config)
|
||||
results = {}
|
||||
for start in range(0, len(tags), 100):
|
||||
chunk = tags[start:start + 100]
|
||||
payload = self._fetch(config, token, ','.join(chunk))
|
||||
assets = payload if isinstance(payload, list) else [payload]
|
||||
for asset in assets:
|
||||
tag = (asset.get('serviceTag') or '').upper()
|
||||
mapped = self._map([asset])
|
||||
if tag and mapped:
|
||||
results[tag] = mapped
|
||||
return results
|
||||
|
||||
|
||||
class ApiProvider(WarrantyProvider):
|
||||
"""Generic config-shaped stub for vendors not yet wired (Lenovo, HP)."""
|
||||
|
||||
Reference in New Issue
Block a user