IT Consulting · AI Automation · Free IT Courses · Real-World Tech Insights

Articles
Back to Blog
PythonFlaskIT Asset ManagementSQLiteChart.jsEnterprise IT

From Excel to Enterprise Web App: How I Built an IT Asset Manager in Python

580 enterprise IT assets — laptops, iPhones, iPads, monitors, printers — tracked in a shared Excel file. No audit trail. No validation. Duplicate serial numbers everywhere. Here's how I replaced it with a proper web application in 6 weeks.

Waqas Syed
July 13, 20268 min read
Syed Waqas Tayyab — IT Expert & AI Engineer

Syed Waqas Tayyab · IT Expert & AI Engineer · HiTecH Technology HUB

From Excel to Enterprise Web App: How I Built an IT Asset Manager in Python

"Can you tell me how many unassigned laptops we have?" Simple question. Before I built this app, answering it required opening a shared Excel file, scrolling through hundreds of rows, applying filters, hoping the data hadn't been corrupted by whoever last edited it, and spending 10 minutes to surface a number that should take 10 seconds.

That question — asked by my IT manager on a Monday morning — is why I built the IT Asset Manager.


Syed Waqas Tayyab

Syed Waqas Tayyab

Senior IT System Engineer · SAP Saudi Arabia · 15+ Years

Azure Security Certified · SAP AI Hub · MBA · MENA IT Expert

The Excel Problem

The shared Excel file had been the system of record for hardware assets since anyone could remember. On paper, it worked. In practice:

  • No audit trail. If a serial number changed, no one knew when or why.
  • Duplicate serial numbers. Two people adding the same device at the same time created corrupted records.
  • No validation. Every field was free text — typos, inconsistent formatting, missing mandatory data.
  • Terrible search. Ctrl+F in a 580-row spreadsheet across 12 columns is not a workflow.
  • Broken exports. Sending the Excel file to management meant formatting it manually every time.
  • No dashboard. Low stock? End-of-life devices? Warranty expiry? All invisible.

The Excel file wasn't a system — it was a shared text document pretending to be a database.


Building the Replacement

I chose Python + Flask for the backend because I needed something I could build and deploy quickly on my local Mac without any server infrastructure. SQLite for the database — appropriate for a single-location asset register with ~1,500 records and no concurrent write conflicts.

The Data Model

The core assets table captures everything relevant to an enterprise IT asset:

CREATE TABLE assets (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    asset_type    TEXT NOT NULL,        -- Laptop, iPhone, iPad, Monitor, Printer
    status        TEXT NOT NULL,        -- Assigned, Unassigned, In Repair, Retired
    serial_no     TEXT UNIQUE NOT NULL, -- Enforced uniqueness at DB level
    sap_eq        TEXT UNIQUE,          -- SAP equipment number (ERP reference)
    model         TEXT NOT NULL,
    assigned_to   TEXT,
    location      TEXT,
    purchase_date TEXT,
    warranty_exp  TEXT,
    notes         TEXT,
    created_at    TEXT,
    updated_at    TEXT
);

CREATE TABLE asset_history (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    asset_id   INTEGER,
    field      TEXT,
    old_value  TEXT,
    new_value  TEXT,
    changed_at TEXT
);

The UNIQUE constraint on serial_no does what Excel never could — it makes duplicate serial numbers a database error, not a user error.

Smart Excel Import

The most-used feature after the dashboard. The import logic handles two modes:

Add Only — safe for regular use. New records from the Excel file are inserted; existing serial numbers are skipped. Zero risk of overwriting live data.

Add + Update — for bulk refreshes. New records are inserted; existing records are updated if any field has changed. A history entry is written for every change.

Both modes filter garbage headers — Excel files from vendors often have metadata rows at the top. The import skips any row where the first cell doesn't look like an asset type.

def smart_import(file, mode="add_only"):
    wb = load_workbook(file)
    ws = wb.active
    imported, skipped, updated = 0, 0, 0

    for row in ws.iter_rows(min_row=2, values_only=True):
        if not is_valid_asset_row(row):
            skipped += 1
            continue
        
        existing = db.execute(
            "SELECT * FROM assets WHERE serial_no = ?", [row[2]]
        ).fetchone()

        if existing and mode == "add_only":
            skipped += 1
        elif existing and mode == "add_update":
            update_asset_with_history(existing, row)
            updated += 1
        else:
            insert_asset(row)
            imported += 1

    return imported, skipped, updated

The Dashboard

The dashboard answers the questions an IT manager actually asks:

  • KPI cards: Total assets, Assigned, Unassigned, In Repair — live counts from SQLite
  • 6-month trend chart: Using Chart.js, showing asset additions per month
  • Low stock alerts: Categories where available stock drops below threshold
  • Oldest stock: Assets with the earliest purchase dates — warranty risk visibility
  • Last import: Timestamp of the most recent Excel import

All rendered server-side by Flask + Jinja2, no external API calls, sub-100ms page loads.

The AI Chat Widget

The most impressive feature to demo. A floating chat widget in the bottom-right corner accepts natural language queries and returns card-formatted results:

"Show me all unassigned laptops in Riyadh" "How many iPhones are in repair?" "List assets assigned to employees who left"

Under the hood, the query is sent to the Claude API with a system prompt that includes the database schema. Claude generates a SQLite query. The app executes it safely (read-only, parameterised) and formats the results as cards.

That 10-minute Monday morning question now takes 10 seconds — typed in plain English.


The Colour-Coded Excel Export

Every management request for an asset report used to mean an hour of formatting. Now it's one click.

The export generates a multi-sheet Excel workbook using OpenPyXL:

  • Sheet 1: All assets, colour-coded by status (green = assigned, yellow = unassigned, orange = in repair, red = retired)
  • Sheet 2: Summary pivot — counts by category and status
  • Sheet 3: Assets flagged for warranty review
from openpyxl.styles import PatternFill, Font

STATUS_COLORS = {
    'Assigned':   PatternFill(fgColor='90EE90', fill_type='solid'),  # green
    'Unassigned': PatternFill(fgColor='FFD700', fill_type='solid'),  # yellow
    'In Repair':  PatternFill(fgColor='FFA500', fill_type='solid'),  # orange
    'Retired':    PatternFill(fgColor='FF6B6B', fill_type='solid'),  # red
}

Management gets a formatted report. IT gets 60 minutes back every month.


Results After Deployment

Metric Before (Excel) After (Web App)
Duplicate serial numbers Regular occurrence Zero — DB constraint
Asset query time 10 min manual search 10 sec via AI chat
Audit trail None Every change logged
Export time 60 min formatting One click
Data validation None 5 mandatory fields enforced
Concurrent editing Conflicts and corruption Single-user Flask, no conflicts

1,500+ assets now tracked with 100% accuracy. The Monday morning question takes 10 seconds.


What This Taught Me

Building this app from scratch — no tutorial, no starter kit, just the Flask docs and a clear problem to solve — taught me more about software engineering than any course I've taken.

The key insight: the database design is the most important decision you make. Getting the schema right (unique constraints, history table, proper field types) took half a day. It saved months of data integrity issues.

The second insight: audit logs are not optional. The first time someone asked "when did this asset's status change?" — three weeks after launch — I was glad I'd built the history table from day one.


Waqas Syed is a Senior IT System Engineer at SAP Saudi Arabia. The IT Asset Manager is one of several tools he has built to replace manual IT workflows with Python-powered web applications.

All Articles
PythonFlaskIT Asset ManagementSQLiteChart.jsEnterprise IT
Waqas AI ChatBot ◆
Home
Loading weather…