Major crashing bug fixed, program returned to normal operations

This commit is contained in:
2026-09-03 10:03:50 -05:00
parent edd5f2a886
commit a1b438a61f
5 changed files with 432 additions and 94 deletions
+61 -21
View File
@@ -12,9 +12,26 @@ wrong and it defeats the "minimal interactions" requirement entirely.
Suggested fields come from app.serial_suggestions, based on keywords in
the ticket's line items - a starting point, not a fixed schema. Staff
can add any custom field the suggestions miss.
WORKAROUND NOTE: this dialog (along with Return Label) crashed the
whole process on close, confirmed via two full crash dumps (identical
fault offset both times) to be caused by Bitdefender Endpoint
Security's Advanced Threat Control corrupting a stack frame inside
Qt6Core.dll - not a bug in this code. Reusing a persistent instance
instead of destroying/recreating it per ticket did NOT resolve it -
the crash recurred at the same offset regardless, ruling out object
destruction timing as the cause. The current mitigation is in
main_window.py: this dialog is shown via show() (non-modal) instead of
exec() (modal), since exec() runs a nested event loop that disables
and re-enables the parent window - a different, more involved Windows
API sequence than a plain show/hide. This dialog still supports being
reused via set_order() regardless, since avoiding unnecessary
construction/destruction is sound practice either way.
"""
from __future__ import annotations
from typing import Optional
from PyQt6.QtWidgets import (
QDialog,
QVBoxLayout,
@@ -36,33 +53,19 @@ from app.serial_suggestions import suggest_serial_fields
class PackTicketDialog(QDialog):
def __init__(self, order: Order, parent=None):
super().__init__(parent)
self.order = order
self.setWindowTitle(f"Pack Ticket - {order.ticket_number or order.external_id}")
self.resize(520, 600)
self.order: Optional[Order] = None
self._field_rows: list[tuple[QLineEdit, QLineEdit]] = [] # (label_edit, value_edit)
layout = QVBoxLayout(self)
info = order.shipping_info or {}
kit_text = ", ".join(
f"{item.get('sku', '')}: {item.get('item_name', '')}" for item in (order.line_items or [])
) or ", ".join(order.skus or [])
summary_lines = [
f"Ticket: {order.ticket_number or order.external_id} Company: {order.company}",
f"Customer: {info.get('name') or '(missing)'}",
f"Kit: {kit_text or '(none)'}",
]
layout.addWidget(QLabel("\n".join(summary_lines)))
self.summary_label = QLabel()
layout.addWidget(self.summary_label)
layout.addWidget(QLabel("Serial numbers (scan or type; Enter moves to the next field):"))
scroll_area = QScrollArea()
scroll_area.setWidgetResizable(True)
self._fields_widget = QWidget()
self._fields_layout = QFormLayout(self._fields_widget)
scroll_area.setWidget(self._fields_widget)
layout.addWidget(scroll_area, stretch=1)
self._scroll_area = QScrollArea()
self._scroll_area.setWidgetResizable(True)
layout.addWidget(self._scroll_area, stretch=1)
add_field_row = QHBoxLayout()
self.new_field_label_input = QLineEdit()
@@ -75,7 +78,6 @@ class PackTicketDialog(QDialog):
layout.addLayout(add_field_row)
self.packed_checkbox = QCheckBox("Packed / ready to ship")
self.packed_checkbox.setChecked(bool(order.packed))
layout.addWidget(self.packed_checkbox)
button_box = QDialogButtonBox()
@@ -85,6 +87,44 @@ class PackTicketDialog(QDialog):
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
self.set_order(order)
def set_order(self, order: Order) -> None:
"""
Re-initializes this dialog for a different ticket, in place -
this is what lets main_window.py reuse a single persistent
instance instead of constructing (and eventually destroying) a
new one per ticket. See the module docstring for why that
matters here specifically.
"""
self.order = order
self.setWindowTitle(f"Pack Ticket - {order.ticket_number or order.external_id}")
self.resize(520, 600)
info = order.shipping_info or {}
kit_text = ", ".join(
f"{item.get('sku', '')}: {item.get('item_name', '')}" for item in (order.line_items or [])
) or ", ".join(order.skus or [])
summary_lines = [
f"Ticket: {order.ticket_number or order.external_id} Company: {order.company}",
f"Customer: {info.get('name') or '(missing)'}",
f"Kit: {kit_text or '(none)'}",
]
self.summary_label.setText("\n".join(summary_lines))
# Swap in a fresh fields widget rather than trying to clear rows
# out of the existing QFormLayout - simpler, and the old one is
# only deleteLater()'d, not force-destroyed immediately.
old_fields_widget = self._scroll_area.takeWidget()
if old_fields_widget is not None:
old_fields_widget.deleteLater()
self._fields_widget = QWidget()
self._fields_layout = QFormLayout(self._fields_widget)
self._scroll_area.setWidget(self._fields_widget)
self._field_rows = []
self.packed_checkbox.setChecked(bool(order.packed))
self._populate_initial_fields()
def _populate_initial_fields(self) -> None: