feat(console): add part detail and comparator screens
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
282
console/screens/comparador.py
Normal file
282
console/screens/comparador.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
"""
|
||||||
|
Part comparator screen for the AUTOPARTES console application.
|
||||||
|
|
||||||
|
Displays a side-by-side comparison of an OEM part against its aftermarket
|
||||||
|
alternatives. The first column is always the OEM part; subsequent columns
|
||||||
|
are aftermarket options. Below the comparison table, cross-reference
|
||||||
|
numbers are shown grouped by type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from console.core.screens import Screen
|
||||||
|
from console.core.keybindings import Key
|
||||||
|
from console.config import APP_NAME, VERSION
|
||||||
|
from console.utils.formatting import format_currency, quality_bar
|
||||||
|
|
||||||
|
|
||||||
|
# Footer labels
|
||||||
|
_FOOTER = [
|
||||||
|
("\u2190\u2192", "Scroll"),
|
||||||
|
("#", "Ver detalle"),
|
||||||
|
("F3", "Otra parte"),
|
||||||
|
("ESC", "Atras"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ComparadorScreen(Screen):
|
||||||
|
"""Side-by-side OEM vs aftermarket comparison."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(name="comparador", title="Comparador")
|
||||||
|
self._part = None
|
||||||
|
self._alternatives = []
|
||||||
|
self._cross_refs = []
|
||||||
|
self._manufacturers = {} # id -> dict
|
||||||
|
self._col_offset = 0 # horizontal scroll offset
|
||||||
|
self._selected_alt = 0 # currently highlighted alternative
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Data loading
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _load(self, context, db):
|
||||||
|
"""Load OEM part, alternatives, cross-refs, and manufacturer info."""
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
if part_id is None:
|
||||||
|
self._part = None
|
||||||
|
self._alternatives = []
|
||||||
|
self._cross_refs = []
|
||||||
|
return
|
||||||
|
|
||||||
|
self._part = db.get_part(part_id)
|
||||||
|
self._alternatives = db.get_alternatives(part_id) if self._part else []
|
||||||
|
self._cross_refs = db.get_cross_references(part_id) if self._part else []
|
||||||
|
|
||||||
|
# Build manufacturer lookup for country info
|
||||||
|
try:
|
||||||
|
mfrs = db.get_manufacturers()
|
||||||
|
self._manufacturers = {m["id"]: m for m in mfrs}
|
||||||
|
except Exception:
|
||||||
|
self._manufacturers = {}
|
||||||
|
|
||||||
|
# Set initial column offset to show the selected alternative
|
||||||
|
selected = context.get("selected_alt_index", 0)
|
||||||
|
if 0 <= selected < len(self._alternatives):
|
||||||
|
self._selected_alt = selected
|
||||||
|
else:
|
||||||
|
self._selected_alt = 0
|
||||||
|
self._col_offset = 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_warranty(months):
|
||||||
|
"""Format warranty months as 'X meses' or '──' if missing."""
|
||||||
|
if months is None:
|
||||||
|
return "\u2500\u2500"
|
||||||
|
return f"{months} meses"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _calc_savings(oem_price, alt_price):
|
||||||
|
"""Calculate percentage savings of alt vs OEM.
|
||||||
|
|
||||||
|
Returns a formatted string like '-28%' or '──' when prices are
|
||||||
|
unavailable.
|
||||||
|
"""
|
||||||
|
if oem_price is None or alt_price is None or oem_price == 0:
|
||||||
|
return "\u2500\u2500"
|
||||||
|
pct = ((oem_price - alt_price) / oem_price) * 100
|
||||||
|
if pct > 0:
|
||||||
|
return f"-{pct:.0f}%"
|
||||||
|
elif pct < 0:
|
||||||
|
return f"+{abs(pct):.0f}%"
|
||||||
|
return "0%"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_stock(in_stock):
|
||||||
|
"""Format boolean in_stock as Si/No."""
|
||||||
|
if in_stock is None:
|
||||||
|
return "\u2500\u2500"
|
||||||
|
return "Si" if in_stock else "No"
|
||||||
|
|
||||||
|
def _build_columns(self):
|
||||||
|
"""Build the column list for draw_comparison.
|
||||||
|
|
||||||
|
First column is the OEM part, followed by each aftermarket
|
||||||
|
alternative. Returns a list of dicts with 'header' and 'rows'.
|
||||||
|
"""
|
||||||
|
if self._part is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
p = self._part
|
||||||
|
oem_price = None # OEM parts don't have price_usd in our schema
|
||||||
|
|
||||||
|
# ── OEM column ──
|
||||||
|
oem_col = {
|
||||||
|
"header": "OEM",
|
||||||
|
"rows": [
|
||||||
|
("Numero", p.get("oem_part_number", "")),
|
||||||
|
("Calidad", quality_bar("oem")),
|
||||||
|
("Tier", "OEM"),
|
||||||
|
("Precio USD", "\u2500\u2500"),
|
||||||
|
("Ahorro", "\u2500\u2500"),
|
||||||
|
("Garantia", "\u2500\u2500"),
|
||||||
|
("En stock", "\u2500\u2500"),
|
||||||
|
("Fabricante", p.get("category_name", "")),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = [oem_col]
|
||||||
|
|
||||||
|
# ── Aftermarket columns ──
|
||||||
|
for alt in self._alternatives:
|
||||||
|
tier = alt.get("quality_tier", "") or ""
|
||||||
|
price = alt.get("price_usd")
|
||||||
|
mfr_id = alt.get("manufacturer_id")
|
||||||
|
mfr_name = alt.get("manufacturer_name", "")
|
||||||
|
mfr_country = ""
|
||||||
|
if mfr_id and mfr_id in self._manufacturers:
|
||||||
|
mfr_country = self._manufacturers[mfr_id].get("country", "") or ""
|
||||||
|
|
||||||
|
alt_col = {
|
||||||
|
"header": mfr_name,
|
||||||
|
"rows": [
|
||||||
|
("Numero", alt.get("part_number", "")),
|
||||||
|
("Calidad", quality_bar(tier.lower()) if tier else "\u2500\u2500"),
|
||||||
|
("Tier", tier.capitalize()),
|
||||||
|
("Precio USD", format_currency(price)),
|
||||||
|
("Ahorro", self._calc_savings(oem_price, price)),
|
||||||
|
("Garantia", self._format_warranty(alt.get("warranty_months"))),
|
||||||
|
("En stock", self._format_stock(alt.get("in_stock"))),
|
||||||
|
("Fabricante", mfr_country if mfr_country else mfr_name),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
columns.append(alt_col)
|
||||||
|
|
||||||
|
return columns
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Render
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def render(self, context, db, renderer):
|
||||||
|
self._load(context, db)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
renderer.draw_header(
|
||||||
|
f" {APP_NAME} v{VERSION}",
|
||||||
|
" COMPARADOR OEM vs AFTERMARKET ",
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._part is None:
|
||||||
|
renderer.draw_text(5, 4, "Parte no encontrada", "error")
|
||||||
|
renderer.draw_footer([("ESC", "Atras")])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Build comparison columns
|
||||||
|
all_columns = self._build_columns()
|
||||||
|
|
||||||
|
# Apply horizontal scroll: always show OEM (col 0) + offset slice
|
||||||
|
if len(all_columns) <= 1:
|
||||||
|
visible_columns = all_columns
|
||||||
|
else:
|
||||||
|
# Determine how many alt columns we can show.
|
||||||
|
# The renderer will auto-size, but let's allow scrolling
|
||||||
|
# through alternatives.
|
||||||
|
h, w = renderer.get_size()
|
||||||
|
# Rough estimate: label_w ~12, each col ~15-20 chars
|
||||||
|
# We keep it simple: show OEM + up to 3 alternatives at a time
|
||||||
|
max_visible_alts = max((w - 20) // 18, 1)
|
||||||
|
alt_cols = all_columns[1:] # all aftermarket columns
|
||||||
|
end = min(self._col_offset + max_visible_alts, len(alt_cols))
|
||||||
|
visible_columns = [all_columns[0]] + alt_cols[self._col_offset:end]
|
||||||
|
|
||||||
|
part_name = self._part.get("name_es") or self._part.get("name", "")
|
||||||
|
oem_number = self._part.get("oem_part_number", "")
|
||||||
|
title = f"COMPARACION: {oem_number} - {part_name}"
|
||||||
|
|
||||||
|
renderer.draw_comparison(visible_columns, title=title)
|
||||||
|
|
||||||
|
# ── Cross-references below the comparison ──
|
||||||
|
h, w = renderer.get_size()
|
||||||
|
# Estimate row where comparison ends:
|
||||||
|
# title(3) + header(1) + sep(1) + 8 data rows + 1 gap = 14
|
||||||
|
xref_row = 3 + 3 + 1 + 8 + 2
|
||||||
|
|
||||||
|
if self._cross_refs and xref_row < h - 4:
|
||||||
|
section_title = (
|
||||||
|
"\u2500\u2500 CROSS-REFERENCES "
|
||||||
|
+ "\u2500" * max(w - 24, 4)
|
||||||
|
)
|
||||||
|
renderer.draw_text(xref_row, 2, section_title, "title")
|
||||||
|
xref_row += 1
|
||||||
|
|
||||||
|
# Group by reference type
|
||||||
|
by_type = {}
|
||||||
|
for xr in self._cross_refs:
|
||||||
|
rtype = xr.get("reference_type", "other") or "other"
|
||||||
|
by_type.setdefault(rtype, []).append(
|
||||||
|
xr.get("cross_reference_number", "")
|
||||||
|
)
|
||||||
|
for rtype, numbers in by_type.items():
|
||||||
|
if xref_row >= h - 3:
|
||||||
|
break
|
||||||
|
line = f"{rtype.capitalize()}: {', '.join(numbers)}"
|
||||||
|
renderer.draw_text(xref_row, 4, line, "normal")
|
||||||
|
xref_row += 1
|
||||||
|
|
||||||
|
# Scroll indicator
|
||||||
|
if len(all_columns) > 1:
|
||||||
|
alt_count = len(all_columns) - 1
|
||||||
|
indicator = (
|
||||||
|
f" Mostrando alternativas "
|
||||||
|
f"{self._col_offset + 1}-"
|
||||||
|
f"{min(self._col_offset + len(visible_columns) - 1, alt_count)}"
|
||||||
|
f" de {alt_count}"
|
||||||
|
)
|
||||||
|
indicator_row = min(xref_row + 1, h - 4)
|
||||||
|
if indicator_row > 0:
|
||||||
|
renderer.draw_text(indicator_row, 2, indicator, "info")
|
||||||
|
|
||||||
|
renderer.draw_footer(_FOOTER)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Key handling
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def on_key(self, key, context, db, renderer, nav):
|
||||||
|
# ESC: back to part detail
|
||||||
|
if key == Key.ESCAPE:
|
||||||
|
return "back"
|
||||||
|
|
||||||
|
# Left arrow: scroll columns left
|
||||||
|
if key == Key.LEFT:
|
||||||
|
if self._col_offset > 0:
|
||||||
|
self._col_offset -= 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Right arrow: scroll columns right
|
||||||
|
if key == Key.RIGHT:
|
||||||
|
max_offset = max(len(self._alternatives) - 1, 0)
|
||||||
|
if self._col_offset < max_offset:
|
||||||
|
self._col_offset += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Number keys (1-9): view alternative detail
|
||||||
|
if 49 <= key <= 57: # '1'..'9'
|
||||||
|
idx = key - 49 # 0-based
|
||||||
|
if self._alternatives and 0 <= idx < len(self._alternatives):
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
return (
|
||||||
|
"comparador",
|
||||||
|
{"part_id": part_id, "selected_alt_index": idx},
|
||||||
|
"Comparador",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# F3: search for another part (go to buscar_parte)
|
||||||
|
if key == Key.F3:
|
||||||
|
return ("buscar_parte", {}, "Buscar Parte")
|
||||||
|
|
||||||
|
return None
|
||||||
242
console/screens/parte_detalle.py
Normal file
242
console/screens/parte_detalle.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
"""
|
||||||
|
Part detail screen for the AUTOPARTES console application.
|
||||||
|
|
||||||
|
Shows full part information (OEM number, name, group, category, etc.)
|
||||||
|
with a table of aftermarket alternatives. Number keys navigate to
|
||||||
|
the comparator screen; F4 shows cross-references; F6 lists compatible
|
||||||
|
vehicles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from console.core.screens import Screen
|
||||||
|
from console.core.keybindings import Key
|
||||||
|
from console.config import APP_NAME, VERSION
|
||||||
|
from console.utils.formatting import format_currency, truncate, quality_bar
|
||||||
|
|
||||||
|
|
||||||
|
# Footer labels
|
||||||
|
_FOOTER = [
|
||||||
|
("#", "Comparar"),
|
||||||
|
("F4", "Cross-Ref"),
|
||||||
|
("F6", "Vehiculos"),
|
||||||
|
("ESC", "Atras"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class ParteDetalleScreen(Screen):
|
||||||
|
"""Detail view for a single OEM part with aftermarket alternatives."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(name="parte_detalle", title="Detalle de Parte")
|
||||||
|
self._part = None
|
||||||
|
self._alternatives = []
|
||||||
|
self._selected_alt = 0
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Data loading
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _load(self, context, db):
|
||||||
|
"""Load part and alternatives from context['part_id']."""
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
if part_id is None:
|
||||||
|
self._part = None
|
||||||
|
self._alternatives = []
|
||||||
|
return
|
||||||
|
self._part = db.get_part(part_id)
|
||||||
|
self._alternatives = db.get_alternatives(part_id) if self._part else []
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_warranty(months):
|
||||||
|
"""Format warranty months as 'X meses' or '──' if missing."""
|
||||||
|
if months is None:
|
||||||
|
return "──"
|
||||||
|
return f"{months} meses"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_weight(kg):
|
||||||
|
"""Format weight in kilograms or '──' if missing."""
|
||||||
|
if kg is None:
|
||||||
|
return "──"
|
||||||
|
return f"{kg} kg"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_discontinued(flag):
|
||||||
|
"""Format the is_discontinued flag as Si/No."""
|
||||||
|
if flag:
|
||||||
|
return "Si"
|
||||||
|
return "No"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Render
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def render(self, context, db, renderer):
|
||||||
|
self._load(context, db)
|
||||||
|
|
||||||
|
# Header
|
||||||
|
renderer.draw_header(
|
||||||
|
f" {APP_NAME} v{VERSION}",
|
||||||
|
" DETALLE DE PARTE ",
|
||||||
|
)
|
||||||
|
|
||||||
|
if self._part is None:
|
||||||
|
renderer.draw_text(5, 4, "Parte no encontrada", "error")
|
||||||
|
renderer.draw_footer([("ESC", "Atras")])
|
||||||
|
return
|
||||||
|
|
||||||
|
p = self._part
|
||||||
|
|
||||||
|
# ── Top section: part detail fields ──
|
||||||
|
fields = [
|
||||||
|
("Numero OEM", p.get("oem_part_number", "")),
|
||||||
|
("Nombre", p.get("name", "")),
|
||||||
|
("Nombre (ES)", p.get("name_es", "") or ""),
|
||||||
|
("Grupo", p.get("group_name_es") or p.get("group_name", "")),
|
||||||
|
("Categoria", p.get("category_name_es") or p.get("category_name", "")),
|
||||||
|
("Descripcion", p.get("description_es") or p.get("description", "") or ""),
|
||||||
|
("Material", p.get("material", "") or "──"),
|
||||||
|
("Peso", self._format_weight(p.get("weight_kg"))),
|
||||||
|
("Descontinuada", self._format_discontinued(p.get("is_discontinued"))),
|
||||||
|
]
|
||||||
|
renderer.draw_detail(fields, title="INFORMACION DE LA PARTE")
|
||||||
|
|
||||||
|
# ── Bottom section: alternatives table ──
|
||||||
|
h, w = renderer.get_size()
|
||||||
|
# Calculate where the detail section ends (title=3 rows + fields + 1 gap)
|
||||||
|
table_start_row = 3 + 3 + len(fields) + 1
|
||||||
|
|
||||||
|
if self._alternatives:
|
||||||
|
# Draw section title
|
||||||
|
section_title = "\u2500\u2500 ALTERNATIVAS AFTERMARKET " + "\u2500" * max(w - 32, 4)
|
||||||
|
renderer.draw_text(table_start_row, 2, section_title, "title")
|
||||||
|
table_start_row += 1
|
||||||
|
|
||||||
|
headers = ["FABRICANTE", "NUMERO", "CALIDAD", "PRECIO", "GARANTIA"]
|
||||||
|
widths = [14, 16, 10, 10, 10]
|
||||||
|
rows = []
|
||||||
|
for alt in self._alternatives:
|
||||||
|
rows.append((
|
||||||
|
truncate(alt.get("manufacturer_name", ""), 14),
|
||||||
|
truncate(alt.get("part_number", ""), 16),
|
||||||
|
(alt.get("quality_tier", "") or "").capitalize(),
|
||||||
|
format_currency(alt.get("price_usd")),
|
||||||
|
self._format_warranty(alt.get("warranty_months")),
|
||||||
|
))
|
||||||
|
|
||||||
|
renderer.draw_table(
|
||||||
|
headers,
|
||||||
|
rows,
|
||||||
|
widths,
|
||||||
|
selected_row=self._selected_alt,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
renderer.draw_text(
|
||||||
|
table_start_row, 4,
|
||||||
|
"No hay alternativas aftermarket registradas",
|
||||||
|
"info",
|
||||||
|
)
|
||||||
|
|
||||||
|
renderer.draw_footer(_FOOTER)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Key handling
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def on_key(self, key, context, db, renderer, nav):
|
||||||
|
# ESC: go back
|
||||||
|
if key == Key.ESCAPE:
|
||||||
|
return "back"
|
||||||
|
|
||||||
|
# Arrow navigation for alternatives
|
||||||
|
if key == Key.UP:
|
||||||
|
if self._selected_alt > 0:
|
||||||
|
self._selected_alt -= 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
if key == Key.DOWN:
|
||||||
|
if self._alternatives and self._selected_alt < len(self._alternatives) - 1:
|
||||||
|
self._selected_alt += 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Number keys (1-9): navigate to comparador for the selected alternative
|
||||||
|
if 49 <= key <= 57: # '1'..'9'
|
||||||
|
idx = key - 49 # 0-based
|
||||||
|
if self._alternatives and 0 <= idx < len(self._alternatives):
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
return (
|
||||||
|
"comparador",
|
||||||
|
{"part_id": part_id, "selected_alt_index": idx},
|
||||||
|
"Comparador",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# ENTER: navigate to comparador for the currently highlighted alternative
|
||||||
|
if key == Key.ENTER:
|
||||||
|
if self._alternatives and 0 <= self._selected_alt < len(self._alternatives):
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
return (
|
||||||
|
"comparador",
|
||||||
|
{"part_id": part_id, "selected_alt_index": self._selected_alt},
|
||||||
|
"Comparador",
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# F4: show cross-references
|
||||||
|
if key == Key.F4:
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
if part_id is None:
|
||||||
|
return None
|
||||||
|
xrefs = db.get_cross_references(part_id)
|
||||||
|
if not xrefs:
|
||||||
|
renderer.show_message("No hay cross-references para esta parte", "info")
|
||||||
|
return None
|
||||||
|
# Build message text grouped by reference type
|
||||||
|
lines = []
|
||||||
|
by_type = {}
|
||||||
|
for xr in xrefs:
|
||||||
|
rtype = xr.get("reference_type", "other") or "other"
|
||||||
|
by_type.setdefault(rtype, []).append(
|
||||||
|
xr.get("cross_reference_number", "")
|
||||||
|
)
|
||||||
|
for rtype, numbers in by_type.items():
|
||||||
|
lines.append(f"{rtype.capitalize()}: {', '.join(numbers)}")
|
||||||
|
msg = "CROSS-REFERENCES\n" + "\n".join(lines)
|
||||||
|
renderer.show_message(msg, "info")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# F6: show vehicles that use this part
|
||||||
|
if key == Key.F6:
|
||||||
|
part_id = context.get("part_id")
|
||||||
|
if part_id is None:
|
||||||
|
return None
|
||||||
|
vehicles = db.get_vehicles_for_part(part_id)
|
||||||
|
if not vehicles:
|
||||||
|
renderer.show_message(
|
||||||
|
"No hay vehiculos registrados para esta parte", "info"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
# Build message with vehicle list (limit to avoid overflow)
|
||||||
|
lines = []
|
||||||
|
for v in vehicles[:10]:
|
||||||
|
brand = v.get("brand", "")
|
||||||
|
model = v.get("model", "")
|
||||||
|
year = v.get("year", "")
|
||||||
|
engine = v.get("engine", "")
|
||||||
|
line = f"{brand} {model} {year}"
|
||||||
|
if engine:
|
||||||
|
line += f" ({engine})"
|
||||||
|
position = v.get("position", "")
|
||||||
|
if position:
|
||||||
|
line += f" - {position}"
|
||||||
|
lines.append(line)
|
||||||
|
if len(vehicles) > 10:
|
||||||
|
lines.append(f"... y {len(vehicles) - 10} mas")
|
||||||
|
msg = "VEHICULOS COMPATIBLES\n" + "\n".join(lines)
|
||||||
|
renderer.show_message(msg, "info")
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user