Files
IT_asset_management/app/services/dell_service.py

165 lines
5.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Dell asset lookup service.
Two modes (chosen automatically):
1. **No credentials** Returns a partial pre-fill (brand=Dell, OS default, service_tag).
Model and warranty must be filled in manually; a link to Dell's support page is provided.
Dell's public website is protected by Akamai and cannot be scraped reliably.
2. **TechDirect API** Full data including model, warranty dates, serial number.
Register free at https://tdm.dell.com → API Services → Create an API key pair.
Set DELL_CLIENT_ID and DELL_CLIENT_SECRET in your .env file.
"""
import logging
from datetime import datetime
import requests
from flask import current_app
log = logging.getLogger(__name__)
_TOKEN_URL = "https://apigtwb2c.us.dell.com/auth/oauth/v2/token"
_ASSET_URL = "https://apigtwb2c.us.dell.com/PROD/sbil/eapi/v5/asset-entitlements"
_SUPPORT_PAGE = "https://www.dell.com/support/home/en-us/product-support/servicetag/{tag}/overview"
_TYPE_MAP = [
(["latitude", "inspiron", "xps", "vostro", "precision 5", "precision 7"], "Laptop"),
(["optiplex", "precision tower", "precision 3", "precision 9",
"optiplex micro", "optiplex small"], "Desktop"),
(["poweredge", "server"], "Server"),
(["wyse", "thin client"], "Other"),
(["monitor", "display", "screen", "s24", "s27", "p24", "u27"], "Monitor"),
]
def _detect_type(description: str) -> str:
desc = description.lower()
for keywords, asset_type in _TYPE_MAP:
if any(kw in desc for kw in keywords):
return asset_type
return "Laptop" # sensible default for Dell business hardware
# ------------------------------------------------------------------
# Partial pre-fill (no credentials)
# ------------------------------------------------------------------
def _partial_prefill(tag: str) -> dict:
"""
Return a minimal pre-fill dict using only what we know without querying Dell.
Includes a link to Dell's support page so the user can look up the rest.
"""
return {
"service_tag": tag,
"serial_number": "",
"brand": "Dell",
"model": "",
"asset_type": "Laptop",
"operating_system": "Windows 11 Pro",
"warranty_expiry": "",
"purchase_date": "",
"source": "partial",
"support_url": _SUPPORT_PAGE.format(tag=tag),
}
# ------------------------------------------------------------------
# Official TechDirect API (requires credentials)
# ------------------------------------------------------------------
def _get_token(client_id: str, client_secret: str) -> str:
resp = requests.post(
_TOKEN_URL,
data={
"grant_type": "client_credentials",
"client_id": client_id,
"client_secret": client_secret,
},
timeout=10,
)
resp.raise_for_status()
return resp.json()["access_token"]
def _lookup_api(tag: str, client_id: str, client_secret: str) -> dict:
try:
token = _get_token(client_id, client_secret)
except Exception as exc:
raise RuntimeError(f"Failed to obtain Dell API token: {exc}") from exc
try:
resp = requests.get(
_ASSET_URL,
params={"servicetags": tag},
headers={"Authorization": f"Bearer {token}"},
timeout=15,
)
resp.raise_for_status()
data = resp.json()
except Exception as exc:
raise RuntimeError(f"Dell API request failed: {exc}") from exc
if not data:
raise RuntimeError(f"No data returned for service tag '{tag}'.")
item = data[0] if isinstance(data, list) else data
system_desc = item.get("productLineDescription") or item.get("systemDescription") or ""
model = system_desc.strip()
serial_number = (item.get("serviceTag") or tag).upper()
warranty_expiry = None
for ent in (item.get("entitlements") or []):
end_str = ent.get("endDate") or ""
if end_str:
try:
dt = datetime.fromisoformat(end_str[:10])
if warranty_expiry is None or dt > warranty_expiry:
warranty_expiry = dt
except ValueError:
pass
ship_date = item.get("shipDate") or ""
purchase_date = None
if ship_date:
try:
purchase_date = datetime.fromisoformat(ship_date[:10])
except ValueError:
pass
return {
"service_tag": tag,
"serial_number": serial_number,
"brand": "Dell",
"model": model,
"asset_type": _detect_type(f"{system_desc} {item.get('productFamily', '')}"),
"operating_system": "Windows 11 Pro",
"warranty_expiry": warranty_expiry.strftime("%Y-%m-%d") if warranty_expiry else "",
"purchase_date": purchase_date.strftime("%Y-%m-%d") if purchase_date else "",
"source": "techdirect_api",
"support_url": _SUPPORT_PAGE.format(tag=tag),
}
# ------------------------------------------------------------------
# Public entry point
# ------------------------------------------------------------------
def lookup_service_tag(service_tag: str) -> dict:
"""
Look up a Dell service tag.
Uses TechDirect API when DELL_CLIENT_ID + DELL_CLIENT_SECRET are configured;
otherwise returns a partial pre-fill with a link to Dell's support page.
"""
tag = service_tag.strip().upper()
client_id = current_app.config.get("DELL_CLIENT_ID", "")
client_secret = current_app.config.get("DELL_CLIENT_SECRET", "")
if client_id and client_secret:
log.debug("Dell lookup via TechDirect API for %s", tag)
return _lookup_api(tag, client_id, client_secret)
log.debug("Dell lookup returning partial pre-fill for %s (no API credentials)", tag)
return _partial_prefill(tag)