feat(console): add admin CRUD screens for parts, manufacturers, crossref, import
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
277
console/screens/admin_fabricantes.py
Normal file
277
console/screens/admin_fabricantes.py
Normal file
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
Admin CRUD screen for Manufacturers in the AUTOPARTES console application.
|
||||
|
||||
Provides a list view with create (F3), edit (ENTER), and delete (F8/Del)
|
||||
operations for the manufacturers table.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Form field definitions for create/edit
|
||||
_FIELDS = [
|
||||
{'label': 'Nombre', 'key': 'name', 'width': 30},
|
||||
{'label': 'Tipo', 'key': 'type', 'width': 15, 'hint': 'oem/aftermarket/remanufactured'},
|
||||
{'label': 'Calidad', 'key': 'quality_tier', 'width': 10, 'hint': 'premium/standard/economy'},
|
||||
{'label': 'Pais', 'key': 'country', 'width': 20},
|
||||
{'label': 'Website', 'key': 'website', 'width': 40},
|
||||
]
|
||||
|
||||
# Footer labels per mode
|
||||
_FOOTER_LIST = [
|
||||
("F3", "Nuevo"),
|
||||
("ENTER", "Editar"),
|
||||
("F8", "Eliminar"),
|
||||
("ESC", "Atras"),
|
||||
]
|
||||
|
||||
_FOOTER_FORM = [
|
||||
("TAB/Down", "Siguiente"),
|
||||
("Up", "Anterior"),
|
||||
("F9", "Guardar"),
|
||||
("ESC", "Cancelar"),
|
||||
]
|
||||
|
||||
|
||||
class AdminFabricantesScreen(Screen):
|
||||
"""Admin CRUD screen for the manufacturers table."""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(name="admin_fabricantes", title="Administracion de Fabricantes")
|
||||
self._mode = 'list' # 'list' or 'form'
|
||||
self._selected = 0
|
||||
self._manufacturers = []
|
||||
self._editing_id = None # None = creating, int = editing
|
||||
self._focused_field = 0
|
||||
self._form_data = {}
|
||||
self._dirty = False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Data loading
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _load_manufacturers(self, db):
|
||||
"""Load all manufacturers."""
|
||||
self._manufacturers = db.get_manufacturers()
|
||||
|
||||
def _init_form(self, mfr=None):
|
||||
"""Initialise form_data from an existing manufacturer or blank."""
|
||||
self._form_data = {}
|
||||
if mfr:
|
||||
for f in _FIELDS:
|
||||
val = mfr.get(f['key'], '')
|
||||
self._form_data[f['key']] = str(val) if val is not None else ''
|
||||
else:
|
||||
for f in _FIELDS:
|
||||
self._form_data[f['key']] = ''
|
||||
self._focused_field = 0
|
||||
self._dirty = False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Render
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def render(self, context, db, renderer):
|
||||
renderer.draw_header(
|
||||
f" {APP_NAME} v{VERSION}",
|
||||
" ADMINISTRACION DE FABRICANTES ",
|
||||
)
|
||||
|
||||
if self._mode == 'list':
|
||||
self._render_list(db, renderer)
|
||||
else:
|
||||
self._render_form(renderer)
|
||||
|
||||
def _render_list(self, db, renderer):
|
||||
"""Render the manufacturers list."""
|
||||
self._load_manufacturers(db)
|
||||
|
||||
headers = ["NOMBRE", "TIPO", "CALIDAD", "PAIS", "WEBSITE"]
|
||||
widths = [20, 14, 10, 14, 20]
|
||||
rows = []
|
||||
for m in self._manufacturers:
|
||||
rows.append((
|
||||
truncate(m.get("name", ""), 20),
|
||||
truncate(m.get("type", "") or "", 14),
|
||||
truncate(m.get("quality_tier", "") or "", 10),
|
||||
truncate(m.get("country", "") or "", 14),
|
||||
truncate(m.get("website", "") or "", 20),
|
||||
))
|
||||
|
||||
renderer.draw_table(
|
||||
headers,
|
||||
rows,
|
||||
widths,
|
||||
page_info={
|
||||
"page": 1,
|
||||
"total_pages": 1,
|
||||
"total_rows": len(rows),
|
||||
},
|
||||
selected_row=self._selected,
|
||||
)
|
||||
renderer.draw_footer(_FOOTER_LIST)
|
||||
|
||||
def _render_form(self, renderer):
|
||||
"""Render the create/edit form."""
|
||||
title = "EDITAR FABRICANTE" if self._editing_id else "NUEVO FABRICANTE"
|
||||
fields = []
|
||||
for f in _FIELDS:
|
||||
fields.append({
|
||||
'label': f['label'],
|
||||
'value': self._form_data.get(f['key'], ''),
|
||||
'width': f['width'],
|
||||
'hint': f.get('hint', ''),
|
||||
})
|
||||
renderer.draw_form(fields, focused_index=self._focused_field, title=title)
|
||||
renderer.draw_footer(_FOOTER_FORM)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Key handling
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def on_key(self, key, context, db, renderer, nav):
|
||||
if self._mode == 'list':
|
||||
return self._handle_list_key(key, db, renderer)
|
||||
else:
|
||||
return self._handle_form_key(key, db, renderer)
|
||||
|
||||
def _handle_list_key(self, key, db, renderer):
|
||||
"""Handle keys in list mode."""
|
||||
# ESC: go back
|
||||
if key == Key.ESCAPE:
|
||||
return "back"
|
||||
|
||||
# Arrow navigation
|
||||
if key == Key.UP:
|
||||
if self._selected > 0:
|
||||
self._selected -= 1
|
||||
return None
|
||||
|
||||
if key == Key.DOWN:
|
||||
if self._manufacturers and self._selected < len(self._manufacturers) - 1:
|
||||
self._selected += 1
|
||||
return None
|
||||
|
||||
# F3: create new manufacturer
|
||||
if key == Key.F3:
|
||||
self._mode = 'form'
|
||||
self._editing_id = None
|
||||
self._init_form()
|
||||
return None
|
||||
|
||||
# ENTER: edit selected manufacturer
|
||||
if key == Key.ENTER:
|
||||
if self._manufacturers and 0 <= self._selected < len(self._manufacturers):
|
||||
mfr = self._manufacturers[self._selected]
|
||||
self._editing_id = mfr["id"]
|
||||
self._mode = 'form'
|
||||
self._init_form(mfr)
|
||||
return None
|
||||
|
||||
# Number keys 1-9: edit manufacturer at that row index
|
||||
if 49 <= key <= 57:
|
||||
idx = key - 49
|
||||
if 0 <= idx < len(self._manufacturers):
|
||||
mfr = self._manufacturers[idx]
|
||||
self._editing_id = mfr["id"]
|
||||
self._mode = 'form'
|
||||
self._init_form(mfr)
|
||||
return None
|
||||
|
||||
# F8 or DEL: delete selected manufacturer
|
||||
if key in (Key.F8, 330): # 330 = KEY_DC (Delete)
|
||||
if self._manufacturers and 0 <= self._selected < len(self._manufacturers):
|
||||
mfr = self._manufacturers[self._selected]
|
||||
name = mfr.get("name", "")
|
||||
confirmed = renderer.show_message(
|
||||
f"Eliminar fabricante?\n{name}",
|
||||
"confirm",
|
||||
)
|
||||
if confirmed:
|
||||
try:
|
||||
db.delete_manufacturer(mfr["id"])
|
||||
except Exception as exc:
|
||||
renderer.show_message(f"Error:\n{exc}", "error")
|
||||
return None
|
||||
if self._selected >= len(self._manufacturers) - 1:
|
||||
self._selected = max(0, self._selected - 1)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def _handle_form_key(self, key, db, renderer):
|
||||
"""Handle keys in form mode."""
|
||||
# ESC: cancel form (with dirty check)
|
||||
if key == Key.ESCAPE:
|
||||
if self._dirty:
|
||||
confirmed = renderer.show_message(
|
||||
"Descartar cambios?", "confirm"
|
||||
)
|
||||
if not confirmed:
|
||||
return None
|
||||
self._mode = 'list'
|
||||
return None
|
||||
|
||||
# TAB / Down: next field
|
||||
if key in (Key.TAB, Key.DOWN):
|
||||
if self._focused_field < len(_FIELDS) - 1:
|
||||
self._focused_field += 1
|
||||
return None
|
||||
|
||||
# Up: previous field
|
||||
if key == Key.UP:
|
||||
if self._focused_field > 0:
|
||||
self._focused_field -= 1
|
||||
return None
|
||||
|
||||
# F9: save
|
||||
if key == Key.F9:
|
||||
return self._save(db, renderer)
|
||||
|
||||
# Backspace: delete last char from current field value
|
||||
if key in (Key.BACKSPACE, 8):
|
||||
field_key = _FIELDS[self._focused_field]['key']
|
||||
val = self._form_data.get(field_key, '')
|
||||
if val:
|
||||
self._form_data[field_key] = val[:-1]
|
||||
self._dirty = True
|
||||
return None
|
||||
|
||||
# Printable characters: append to current field
|
||||
if 32 <= key <= 126:
|
||||
field_def = _FIELDS[self._focused_field]
|
||||
field_key = field_def['key']
|
||||
val = self._form_data.get(field_key, '')
|
||||
if len(val) < field_def['width']:
|
||||
self._form_data[field_key] = val + chr(key)
|
||||
self._dirty = True
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def _save(self, db, renderer):
|
||||
"""Validate and save the form data."""
|
||||
data = dict(self._form_data)
|
||||
|
||||
# Validate required fields
|
||||
if not data.get('name', '').strip():
|
||||
renderer.show_message("Nombre es requerido", "error")
|
||||
return None
|
||||
|
||||
try:
|
||||
if self._editing_id:
|
||||
db.update_manufacturer(self._editing_id, data)
|
||||
renderer.show_message("Fabricante actualizado correctamente", "info")
|
||||
else:
|
||||
db.create_manufacturer(data)
|
||||
renderer.show_message("Fabricante creado correctamente", "info")
|
||||
except Exception as exc:
|
||||
renderer.show_message(f"Error al guardar:\n{exc}", "error")
|
||||
return None
|
||||
|
||||
self._mode = 'list'
|
||||
self._dirty = False
|
||||
return None
|
||||
Reference in New Issue
Block a user