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:
2026-04-09 11:00:37 +03:00
parent 3604a46421
commit 704e01669f
10 changed files with 243 additions and 10615 deletions

53
main.py
View File

@@ -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)