e8a918fc7b
- Added is_disney INTEGER DEFAULT 0 column to assets table - Backfilled 996 Disney assets based on D- customer prefix - Server: is_disney in AssetCreate/AssetUpdate models, INSERT/UPDATE - Server: disney_filter query param now uses is_disney column (no JOIN needed) - import_cantaloupe.py: sets is_disney=1 when customer starts with D- - import_machines.py: sets is_disney=1 when customer starts with D- - disney_classify.py: sets is_disney=1 when classifying - Frontend: new Location Type dropdown with Disney/Non-Disney toggle (replaces the __disney__/__non_disney__ park dropdown pseudo-options)
176 lines
6.4 KiB
Python
176 lines
6.4 KiB
Python
"""Import Cantaloupe machine list XLSX into canteen asset tracker DB.
|
|
|
|
Mapping (corrected per user feedback):
|
|
Asset ID → machine_id (MID — primary search key, e.g. "60006")
|
|
Device → description (CC reader telemetry ID, secondary search)
|
|
Location → name (human-readable asset name)
|
|
Place → room (breakroom name)
|
|
Type → stored in description alongside Device
|
|
Address+City+State+ZIP → address
|
|
Serial Number → serial_number
|
|
Make → make
|
|
Model → model
|
|
Status → all set to "active" (Excel RPC status stored in description only)
|
|
Customer → customers table (auto-created)
|
|
"""
|
|
|
|
import sqlite3
|
|
import openpyxl
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
DB_PATH = str(Path(__file__).parent / "assets.db")
|
|
XLSX_PATH = "/home/oplabs/.hermes/profiles/kanban/cache/documents/doc_5c60b6e791ea_Machine List(4).xlsx"
|
|
|
|
|
|
def connect():
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.execute("PRAGMA foreign_keys=ON")
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
def ensure_makes(conn, make_names):
|
|
"""Add missing makes to lookup table."""
|
|
existing = {r["name"] for r in conn.execute("SELECT name FROM makes").fetchall()}
|
|
for name in make_names:
|
|
if name and name not in existing:
|
|
conn.execute("INSERT INTO makes (name) VALUES (?)", (name,))
|
|
print(f" Added make: {name}")
|
|
|
|
def ensure_customers(conn, customer_names):
|
|
"""Create customer records, return {name: id} map."""
|
|
existing = {r["name"]: r["id"] for r in conn.execute("SELECT id, name FROM customers").fetchall()}
|
|
for name in customer_names:
|
|
if name and name not in existing:
|
|
conn.execute("INSERT INTO customers (name) VALUES (?)", (name,))
|
|
existing[name] = conn.execute(
|
|
"SELECT id FROM customers WHERE name = ?", (name,)
|
|
).fetchone()["id"]
|
|
return existing
|
|
|
|
def import_assets():
|
|
conn = connect()
|
|
|
|
# Step 1: Read Excel
|
|
print("Reading Excel...")
|
|
wb = openpyxl.load_workbook(XLSX_PATH, read_only=True, data_only=True)
|
|
ws = wb['Machine List']
|
|
|
|
# Column index (0-based from header row)
|
|
COL = {
|
|
"Device": 0, "Location": 1, "Asset ID": 2, "Place": 3, "Type": 4,
|
|
"City": 5, "Address": 6,
|
|
"Make": 39, "Model": 40, "Serial Number": 37,
|
|
"Status": 56, "Customer": 21,
|
|
"State": 33, "Postal Code": 34,
|
|
}
|
|
|
|
rows = []
|
|
for i, row in enumerate(ws.iter_rows(min_row=2, values_only=True)):
|
|
if row[0] is None:
|
|
continue
|
|
rows.append(row)
|
|
|
|
print(f"Loaded {len(rows)} rows from Excel")
|
|
|
|
# Step 2: Collect unique makes and customers
|
|
all_makes = set()
|
|
all_customers = set()
|
|
for r in rows:
|
|
make = str(r[COL["Make"]]).strip() if r[COL["Make"]] else ""
|
|
if make:
|
|
all_makes.add(make)
|
|
cust = str(r[COL["Customer"]]).strip() if r[COL["Customer"]] else ""
|
|
if cust:
|
|
all_customers.add(cust)
|
|
|
|
print(f"\nUnique makes: {len(all_makes)} — {sorted(all_makes)}")
|
|
print(f"Unique customers: {len(all_customers)}")
|
|
|
|
# Step 3: Seed lookup tables
|
|
ensure_makes(conn, all_makes)
|
|
customer_map = ensure_customers(conn, all_customers)
|
|
conn.commit()
|
|
|
|
# Step 4: Insert assets
|
|
inserted = 0
|
|
skipped = 0
|
|
errors = 0
|
|
|
|
for r in rows:
|
|
device = str(r[COL["Device"]]).strip() if r[COL["Device"]] else ""
|
|
location = str(r[COL["Location"]]).strip() if r[COL["Location"]] else ""
|
|
place = str(r[COL["Place"]]).strip() if r[COL["Place"]] else ""
|
|
machine_type = str(r[COL["Type"]]).strip() if r[COL["Type"]] else ""
|
|
address = str(r[COL["Address"]]).strip() if r[COL["Address"]] else ""
|
|
city = str(r[COL["City"]]).strip() if r[COL["City"]] else ""
|
|
state = str(r[COL["State"]]).strip() if r[COL["State"]] else ""
|
|
zip_code = str(r[COL["Postal Code"]]).strip() if r[COL["Postal Code"]] else ""
|
|
serial = str(r[COL["Serial Number"]]).strip() if r[COL["Serial Number"]] else ""
|
|
make = str(r[COL["Make"]]).strip() if r[COL["Make"]] else ""
|
|
model = str(r[COL["Model"]]).strip() if r[COL["Model"]] else ""
|
|
raw_status = str(r[COL["Status"]]).strip() if r[COL["Status"]] else ""
|
|
customer_name = str(r[COL["Customer"]]).strip() if r[COL["Customer"]] else ""
|
|
asset_id_str = str(r[COL["Asset ID"]]).strip() if r[COL["Asset ID"]] else ""
|
|
|
|
if not asset_id_str or not location:
|
|
errors += 1
|
|
continue
|
|
|
|
machine_id = asset_id_str
|
|
name = location
|
|
|
|
# Build full address
|
|
parts = [p for p in [address, city, state, zip_code] if p]
|
|
full_address = ", ".join(parts)
|
|
|
|
# Status — all set to active (RPC "On/Action Required/Incompatible" is
|
|
# pricing telemetry status, not machine condition. Stored in description.)
|
|
status = "active"
|
|
|
|
# Build description — Device (telemetry ID) + RPC status + Type
|
|
desc_parts = []
|
|
if device:
|
|
desc_parts.append(f"Device: {device}")
|
|
if raw_status:
|
|
desc_parts.append(f"RPC: {raw_status}")
|
|
if machine_type:
|
|
desc_parts.append(machine_type)
|
|
description = " — ".join(desc_parts)
|
|
|
|
# Customer lookup
|
|
customer_id = customer_map.get(customer_name)
|
|
|
|
is_disney = 1 if customer_name.startswith('D-') else 0
|
|
try:
|
|
cursor = conn.execute("""
|
|
INSERT INTO assets
|
|
(machine_id, name, description, category, status,
|
|
make, model, serial_number, address, room,
|
|
customer_id, is_disney)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
""", (
|
|
machine_id, name, description, "Equipment", status,
|
|
make, model, serial, full_address, place,
|
|
customer_id, is_disney,
|
|
))
|
|
inserted += 1
|
|
except sqlite3.IntegrityError as e:
|
|
errors += 1
|
|
if errors <= 3:
|
|
print(f" SKIP (dupe?): {machine_id} — {e}")
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
wb.close()
|
|
|
|
print(f"\n=== Import Summary ===")
|
|
print(f" Inserted: {inserted}")
|
|
print(f" Skipped: {skipped}")
|
|
print(f" Errors: {errors}")
|
|
return inserted
|
|
|
|
if __name__ == "__main__":
|
|
count = import_assets()
|
|
print(f"\nDone. {count} assets imported.")
|