Initial commit: add compliance_checks table, per-check metadata on assets, and compliance audit trail
This commit is contained in:
164
app/services/dell_service.py
Normal file
164
app/services/dell_service.py
Normal file
@@ -0,0 +1,164 @@
|
||||
"""
|
||||
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)
|
||||
Reference in New Issue
Block a user