Files
Autoparts-DB/console/screens/buscar_parte.py
consultoria-as 7b2a904498 feat: migrate to PostgreSQL + SQLAlchemy ORM, rebrand to Nexus Autoparts
- Migrate from SQLite to PostgreSQL with normalized schema
- Add 11 lookup tables (fuel_type, body_type, drivetrain, transmission,
  materials, position_part, manufacture_type, quality_tier, countries,
  reference_type, shapes)
- Rewrite dashboard/server.py (76 routes) using SQLAlchemy text() queries
- Rewrite console/db.py (27 methods) using SQLAlchemy ORM
- Add models.py with 27 SQLAlchemy model definitions
- Add config.py for centralized DB_URL configuration
- Add migrate_to_postgres.py migration script
- Add docs/METABASE_GUIDE.md with complete data entry guide
- Rebrand from "AUTOPARTS DB" to "NEXUS AUTOPARTS"
- Fill vehicle data gaps via NHTSA API + heuristics:
  engines (cylinders, power, torque), brands (country, founded_year),
  models (body_type, production years), MYE (drivetrain, transmission, trim)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 05:24:47 +00:00

154 lines
4.7 KiB
Python

"""
Part number search screen for the NEXUS AUTOPARTS console application.
Prompts the user for a part number (OEM, aftermarket, or cross-reference)
and displays matching results in a table. Selecting a result navigates
to the part detail screen.
"""
from console.core.screens import Screen
from console.core.keybindings import Key
from console.config import APP_NAME, VERSION
from console.utils.formatting import truncate
# Match type labels in Spanish
_TYPE_LABELS = {
"oem": "OEM",
"aftermarket": "Aftermarket",
"cross_reference": "X-Ref",
}
# Footer labels
_FOOTER_INPUT = [
("ENTER", "Buscar"),
("ESC", "Atras"),
]
_FOOTER_RESULTS = [
("1-9", "Ver parte"),
("ENTER", "Ver parte"),
("F3", "Nueva busqueda"),
("ESC", "Atras"),
]
class BuscarParteScreen(Screen):
"""Search by part number (OEM, aftermarket, cross-reference)."""
def __init__(self):
super().__init__(name="buscar_parte", title="Buscar por Numero de Parte")
self._results = None
self._search_term = None
self._selected = 0
self._needs_input = True
# ------------------------------------------------------------------
# Render
# ------------------------------------------------------------------
def render(self, context, db, renderer):
# Header
renderer.draw_header(
f" {APP_NAME} v{VERSION}",
" BUSCAR POR NUMERO DE PARTE ",
)
if self._needs_input:
# Show the input dialog (handled in on_key via on_enter-like flow)
# Just draw footer; the input dialog will overlay
renderer.draw_footer(_FOOTER_INPUT)
return
if self._results is None:
renderer.draw_text(5, 4, "Presione F3 para buscar", "info")
renderer.draw_footer(_FOOTER_RESULTS)
return
if not self._results:
renderer.draw_text(
5, 4,
f'No se encontraron resultados para "{self._search_term}"',
"info",
)
renderer.draw_footer(_FOOTER_RESULTS)
return
# Display results table
headers = ["TIPO", "NUMERO", "DESCRIPCION", "FUENTE"]
widths = [12, 20, 30, 20]
rows = []
for r in self._results:
rows.append((
_TYPE_LABELS.get(r.get("match_type", ""), r.get("match_type", "")),
truncate(r.get("matched_number", ""), 20),
truncate(r.get("name_es") or r.get("name", ""), 30),
truncate(r.get("oem_part_number", ""), 20),
))
renderer.draw_table(
headers,
rows,
widths,
selected_row=self._selected,
)
renderer.draw_footer(_FOOTER_RESULTS)
# ------------------------------------------------------------------
# Key handling
# ------------------------------------------------------------------
def on_key(self, key, context, db, renderer, nav):
# If we need input, show the input dialog
if self._needs_input:
self._needs_input = False
value = renderer.show_input("Numero de parte", max_len=30)
if value is None:
# User pressed ESC in input dialog
if self._results is not None:
# Go back to results view
return None
return "back"
if value.strip():
self._search_term = value.strip()
self._results = db.search_part_number(self._search_term)
self._selected = 0
return None
# ESC: go back
if key == Key.ESCAPE:
return "back"
# F3: new search
if key == Key.F3:
self._needs_input = True
return None
# Arrow navigation
if key == Key.UP:
if self._selected > 0:
self._selected -= 1
return None
if key == Key.DOWN:
if self._results and self._selected < len(self._results) - 1:
self._selected += 1
return None
# ENTER: view selected part
if key == Key.ENTER:
if self._results and 0 <= self._selected < len(self._results):
part = self._results[self._selected]
return ("parte_detalle", {"part_id": part["id"]}, "Parte")
return None
# Number keys: direct selection (1-9)
if 49 <= key <= 57: # '1'..'9'
idx = key - 49 # 0-based
if self._results and 0 <= idx < len(self._results):
part = self._results[idx]
return ("parte_detalle", {"part_id": part["id"]}, "Parte")
return None
return None