fix: db update bug, add action log with 30-day purge, rebuild exe
- main.py: _pending_record_id locks resolved DB key at Add/Update time; show original barcode in update frame; auto-focus mass field on open; clear all fields and return focus to ID input after confirm/reset - database_manager.py: buffered=True cursors on all SELECTs; no fetchall() after DML; replace ON DUPLICATE KEY UPDATE VALUES() with explicit UPDATE then INSERT fallback; add app_actions.log with structured per-action entries; purge_old_action_logs(30) on startup - dist/DatabaseApp.exe: rebuilt single-file Windows binary (30.9 MB) - remove unused files: README, WINDOWS_README, run_app.sh, setup_database.sh, setup_user.sql, test_database.py, sept.csv"
This commit is contained in:
53
main.py
53
main.py
@@ -19,6 +19,7 @@ class DatabaseApp(App):
|
||||
super().__init__(**kwargs)
|
||||
self.db_manager = DatabaseManager()
|
||||
self.active_numpad_input = None
|
||||
self._pending_record_id = None # resolved (trimmed) ID locked at show_update_frame time
|
||||
|
||||
def build(self):
|
||||
# Set window to fullscreen first so Window.height reflects the screen
|
||||
@@ -112,12 +113,23 @@ class DatabaseApp(App):
|
||||
self.id_input.bind(focus=self.on_id_input_focus)
|
||||
search_layout.add_widget(self.id_input)
|
||||
search_layout.add_widget(Label(text='Mass:', size_hint_x=0.25, font_size=f_normal, bold=True))
|
||||
# Mass input + last-update label share the 0.75 right side equally
|
||||
mass_row = BoxLayout(orientation='horizontal', size_hint_x=0.75, spacing=sp)
|
||||
self.mass_input = TextInput(
|
||||
multiline=False, size_hint_x=0.75,
|
||||
multiline=False, size_hint_x=0.5,
|
||||
hint_text='Mass (read-only)',
|
||||
readonly=True, font_size=f_normal, padding=[7, 7]
|
||||
)
|
||||
search_layout.add_widget(self.mass_input)
|
||||
mass_row.add_widget(self.mass_input)
|
||||
self.last_update_label = Label(
|
||||
text='Last update: never',
|
||||
size_hint_x=0.5, font_size=max(9, int(13 * s)),
|
||||
bold=False, color=(0.7, 0.7, 0.7, 1),
|
||||
halign='left', valign='middle'
|
||||
)
|
||||
self.last_update_label.bind(size=self.last_update_label.setter('text_size'))
|
||||
mass_row.add_widget(self.last_update_label)
|
||||
search_layout.add_widget(mass_row)
|
||||
content_layout.add_widget(search_layout)
|
||||
|
||||
# Mode indicator row
|
||||
@@ -271,7 +283,7 @@ class DatabaseApp(App):
|
||||
self._refocus_active()
|
||||
|
||||
def set_update_frame_enabled(self, enabled):
|
||||
self.update_id_input.readonly = not enabled
|
||||
self.update_id_input.readonly = True # ID is always readonly – always set from search
|
||||
self.update_mass_input.readonly = not enabled
|
||||
self.update_confirm_btn.disabled = not enabled
|
||||
self.delete_btn.disabled = not enabled
|
||||
@@ -313,17 +325,21 @@ class DatabaseApp(App):
|
||||
self.override_btn.background_color = (0.6, 0.2, 0.6, 1) if is_override else (0.3, 0.3, 0.3, 1)
|
||||
|
||||
def show_update_frame(self, instance):
|
||||
# If no value in search, copy from search fields
|
||||
record_id = self.id_input.text.strip()
|
||||
mass_text = self.mass_input.text.strip()
|
||||
self.set_update_frame_enabled(True)
|
||||
# If mass field is empty, just clear update frame
|
||||
if not record_id:
|
||||
self.update_id_input.text = ''
|
||||
self.update_mass_input.text = ''
|
||||
self._pending_record_id = None
|
||||
return
|
||||
self.update_id_input.text = record_id
|
||||
# Lock in the resolved (trimmed) DB id now; display original scan for the operator
|
||||
self._pending_record_id = self._resolve_id(record_id)
|
||||
self.update_id_input.text = record_id # show original barcode value
|
||||
self.update_mass_input.text = mass_text
|
||||
# Direct numpad and keyboard focus to mass field so operator can immediately enter new mass
|
||||
self.active_numpad_input = self.update_mass_input
|
||||
Clock.schedule_once(lambda dt: setattr(self.update_mass_input, 'focus', True), 0.05)
|
||||
|
||||
def search_record(self, instance):
|
||||
record_id = self.id_input.text.strip()
|
||||
@@ -344,11 +360,17 @@ class DatabaseApp(App):
|
||||
def _update(dt):
|
||||
if record:
|
||||
self.mass_input.text = str(record[1])
|
||||
t_update = record[2] if len(record) > 2 else None
|
||||
if t_update:
|
||||
self.last_update_label.text = f'Last update: {t_update.strftime("%d/%m/%Y %H:%M")}'
|
||||
else:
|
||||
self.last_update_label.text = 'Last update: never'
|
||||
self.show_status(f"Found: {record[0]} = {record[1]}")
|
||||
self.highlight_record(resolved_id)
|
||||
else:
|
||||
self.show_status(f"ID '{record_id}' not found in database", error=True)
|
||||
self.mass_input.text = ""
|
||||
self.last_update_label.text = 'Last update: never'
|
||||
self.show_status(f"ID '{record_id}' not found in database", error=True)
|
||||
Clock.schedule_once(_update)
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
@@ -357,7 +379,7 @@ class DatabaseApp(App):
|
||||
|
||||
def add_update_record(self, instance):
|
||||
"""Add or update a record from the update frame."""
|
||||
record_id = self.update_id_input.text.strip()
|
||||
record_id = self._pending_record_id
|
||||
mass_text = self.update_mass_input.text.strip()
|
||||
if not record_id or not mass_text:
|
||||
self.show_status("Please enter both ID and mass in update frame", error=True)
|
||||
@@ -378,9 +400,16 @@ class DatabaseApp(App):
|
||||
def _update(dt):
|
||||
if success:
|
||||
self.show_status(f"Successfully added/updated: {record_id} = {mass}")
|
||||
# Clear all fields and return focus to ID input
|
||||
self.update_id_input.text = ""
|
||||
self.update_mass_input.text = ""
|
||||
self.id_input.text = ""
|
||||
self.mass_input.text = ""
|
||||
self.last_update_label.text = 'Last update: never'
|
||||
self._pending_record_id = None
|
||||
self.set_update_frame_enabled(False)
|
||||
self.active_numpad_input = self.id_input
|
||||
Clock.schedule_once(lambda dt2: setattr(self.id_input, 'focus', True), 0.05)
|
||||
self.refresh_data(None)
|
||||
else:
|
||||
self.show_status("Failed to add/update record", error=True)
|
||||
@@ -392,11 +421,10 @@ class DatabaseApp(App):
|
||||
|
||||
def delete_record(self, instance):
|
||||
"""Delete a record using the update frame fields."""
|
||||
record_id = self.update_id_input.text.strip()
|
||||
record_id = self._pending_record_id
|
||||
if not record_id:
|
||||
self.show_status("Please enter an ID in the update fields to delete", error=True)
|
||||
return
|
||||
|
||||
# Confirm deletion
|
||||
self.show_confirmation_popup(
|
||||
f"Are you sure you want to delete ID '{record_id}'?",
|
||||
@@ -435,6 +463,11 @@ class DatabaseApp(App):
|
||||
"""Reset/clear the first ID and mass fields and set focus on ID field."""
|
||||
self.id_input.text = ""
|
||||
self.mass_input.text = ""
|
||||
self.last_update_label.text = 'Last update: never'
|
||||
self.update_id_input.text = ""
|
||||
self.update_mass_input.text = ""
|
||||
self._pending_record_id = None
|
||||
self.set_update_frame_enabled(False)
|
||||
self.active_numpad_input = self.id_input
|
||||
self.id_input.focus = True
|
||||
self.show_status("Fields cleared", error=False)
|
||||
|
||||
Reference in New Issue
Block a user