More Shipstation integrations(warehouses)
This commit is contained in:
+15
-13
@@ -25,11 +25,10 @@ from PyQt6.QtWidgets import (
|
||||
QFileDialog,
|
||||
)
|
||||
|
||||
from app import config
|
||||
from app.services import SERVICE_REGISTRY
|
||||
from app.services.odoo_export import export_orders_to_csv
|
||||
from app.services.shipstation_send import export_order_to_shipstation_csv
|
||||
from app.status_rules import parse_status_list, find_new_cancellations
|
||||
from app.status_rules import get_cancelled_statuses, find_new_cancellations
|
||||
from app.ui.settings_dialog import SettingsDialog
|
||||
from app.ui.widgets.orders_table import OrdersTableView
|
||||
from app.ui.widgets.dashboard import DashboardWidget
|
||||
@@ -37,7 +36,7 @@ from app.ui.widgets.order_detail_dialog import OrderDetailDialog
|
||||
from app.workers import (
|
||||
FetchOrdersWorker,
|
||||
SendToShipStationWorker,
|
||||
load_active_and_done_orders,
|
||||
load_orders_by_view,
|
||||
get_dashboard_stats,
|
||||
)
|
||||
|
||||
@@ -65,10 +64,13 @@ class MainWindow(QMainWindow):
|
||||
self.dashboard = DashboardWidget()
|
||||
self.orders_table = OrdersTableView()
|
||||
self.orders_table.order_double_clicked.connect(self._on_order_double_clicked)
|
||||
self.cancelled_table = OrdersTableView()
|
||||
self.cancelled_table.order_double_clicked.connect(self._on_order_double_clicked)
|
||||
self.done_table = OrdersTableView()
|
||||
self.done_table.order_double_clicked.connect(self._on_order_double_clicked)
|
||||
self.tabs.addTab(self.dashboard, "Dashboard")
|
||||
self.tabs.addTab(self.orders_table, "Active Orders")
|
||||
self.tabs.addTab(self.cancelled_table, "Cancelled")
|
||||
self.tabs.addTab(self.done_table, "Done")
|
||||
layout.addWidget(self.tabs)
|
||||
|
||||
@@ -91,13 +93,13 @@ class MainWindow(QMainWindow):
|
||||
toolbar.addSeparator()
|
||||
|
||||
export_action = QAction("Export Visible Orders to Odoo CSV", self)
|
||||
export_action.setToolTip("Exports from whichever of Active Orders / Done is open")
|
||||
export_action.setToolTip("Exports from whichever tab is currently open")
|
||||
export_action.triggered.connect(self._on_export_clicked)
|
||||
toolbar.addAction(export_action)
|
||||
|
||||
toolbar.addSeparator()
|
||||
|
||||
emergency_action = QAction("Send to ShipStation (Emergency)", self)
|
||||
emergency_action = QAction("Send to ShipStation", self)
|
||||
emergency_action.setToolTip("Select a ticket in Active Orders first")
|
||||
emergency_action.triggered.connect(self._on_emergency_send_clicked)
|
||||
toolbar.addAction(emergency_action)
|
||||
@@ -186,10 +188,7 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
def _notify_new_cancellations(self, status_changes: list) -> None:
|
||||
cancelled_statuses = parse_status_list(
|
||||
config.get("CANCELLED_STATUSES", config.DEFAULT_CANCELLED_STATUSES)
|
||||
)
|
||||
newly_cancelled = find_new_cancellations(status_changes, cancelled_statuses)
|
||||
newly_cancelled = find_new_cancellations(status_changes, get_cancelled_statuses())
|
||||
if not newly_cancelled:
|
||||
return
|
||||
|
||||
@@ -217,7 +216,7 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Nothing to export",
|
||||
"Switch to the Active Orders or Done tab first, then export.",
|
||||
"Switch to Active Orders, Cancelled, or Done first, then export.",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -333,13 +332,15 @@ class MainWindow(QMainWindow):
|
||||
)
|
||||
|
||||
def _refresh_everything(self) -> None:
|
||||
active_orders, done_orders = load_active_and_done_orders()
|
||||
active_orders, cancelled_orders, done_orders = load_orders_by_view()
|
||||
self.orders_table.set_orders(active_orders)
|
||||
self.cancelled_table.set_orders(cancelled_orders)
|
||||
self.done_table.set_orders(done_orders)
|
||||
self.dashboard.update_stats(get_dashboard_stats())
|
||||
if not hasattr(self, "_suppress_ready_status"):
|
||||
self.status_label.setText(
|
||||
f"{len(active_orders)} active order(s), {len(done_orders)} done."
|
||||
f"{len(active_orders)} active, {len(cancelled_orders)} cancelled, "
|
||||
f"{len(done_orders)} done."
|
||||
)
|
||||
|
||||
|
||||
@@ -351,7 +352,8 @@ class MainWindow(QMainWindow):
|
||||
# _on_import_clicked, and it can eventually replace/augment odoo_export.py.
|
||||
# - Emailed-label SKU: these tickets won't get tracking numbers pulled via
|
||||
# the normal ShipStation flow - once its handling is defined, it likely
|
||||
# needs its own terminal status and its own "mark as sent" action here.
|
||||
# needs its own status/action here so it doesn't get stuck in Active
|
||||
# forever with nothing to trigger its move to Done.
|
||||
# - Pipeline actions ("mark fulfilled manually", "void a label"): add
|
||||
# toolbar actions gated on table selection.
|
||||
# - If the window grows too much, split each tab's toolbar into its own
|
||||
|
||||
@@ -4,6 +4,12 @@ Settings dialog.
|
||||
Built dynamically from app.config.SETTINGS_SCHEMA, grouped by section
|
||||
(Database, JIRA, ...). When a new service adds settings to that
|
||||
schema, they show up here automatically - no UI changes needed.
|
||||
|
||||
The number of settings has grown a lot, so the group content lives in
|
||||
a QScrollArea and the dialog's height is capped to the actual screen's
|
||||
available space - Save/Cancel stay outside the scroll area, pinned at
|
||||
the bottom, so they're always reachable no matter how tall the content
|
||||
gets or how small the screen is.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -19,6 +25,9 @@ from PyQt6.QtWidgets import (
|
||||
QGroupBox,
|
||||
QLabel,
|
||||
QMessageBox,
|
||||
QScrollArea,
|
||||
QWidget,
|
||||
QApplication,
|
||||
)
|
||||
|
||||
from app import config
|
||||
@@ -28,7 +37,6 @@ class SettingsDialog(QDialog):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Settings")
|
||||
self.setMinimumWidth(480)
|
||||
|
||||
self._fields: dict[str, QLineEdit] = {}
|
||||
current_values = config.load_settings()
|
||||
@@ -38,11 +46,15 @@ class SettingsDialog(QDialog):
|
||||
for key, (label, group, is_secret) in config.SETTINGS_SCHEMA.items():
|
||||
groups[group].append((key, label, is_secret))
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(
|
||||
outer_layout = QVBoxLayout(self)
|
||||
outer_layout.addWidget(
|
||||
QLabel("Changes are saved to your .env file and applied immediately.")
|
||||
)
|
||||
|
||||
# All the group boxes live inside a scrollable area, since the
|
||||
# full list no longer reliably fits on smaller screens.
|
||||
scroll_content = QWidget()
|
||||
content_layout = QVBoxLayout(scroll_content)
|
||||
for group_name, entries in groups.items():
|
||||
box = QGroupBox(group_name)
|
||||
form = QFormLayout(box)
|
||||
@@ -52,7 +64,13 @@ class SettingsDialog(QDialog):
|
||||
field.setEchoMode(QLineEdit.EchoMode.Password)
|
||||
form.addRow(label, field)
|
||||
self._fields[key] = field
|
||||
layout.addWidget(box)
|
||||
content_layout.addWidget(box)
|
||||
content_layout.addStretch()
|
||||
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidget(scroll_content)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
outer_layout.addWidget(scroll_area, stretch=1)
|
||||
|
||||
button_row = QHBoxLayout()
|
||||
save_button = QPushButton("Save")
|
||||
@@ -62,7 +80,24 @@ class SettingsDialog(QDialog):
|
||||
button_row.addStretch()
|
||||
button_row.addWidget(cancel_button)
|
||||
button_row.addWidget(save_button)
|
||||
layout.addLayout(button_row)
|
||||
outer_layout.addLayout(button_row)
|
||||
|
||||
self._size_to_fit_screen()
|
||||
|
||||
def _size_to_fit_screen(self) -> None:
|
||||
"""Start at a comfortable size, but never taller/wider than the
|
||||
actual screen has room for - whatever's left just scrolls."""
|
||||
width, height = 560, 720
|
||||
|
||||
screen = self.screen() or QApplication.primaryScreen()
|
||||
if screen is not None:
|
||||
available = screen.availableGeometry()
|
||||
# Leave a little breathing room so window chrome/taskbars don't
|
||||
# push Save off-screen either.
|
||||
height = min(height, available.height() - 80)
|
||||
width = min(width, available.width() - 80)
|
||||
|
||||
self.resize(max(width, 400), max(height, 300))
|
||||
|
||||
def _on_save(self) -> None:
|
||||
values = {key: field.text().strip() for key, field in self._fields.items()}
|
||||
|
||||
Reference in New Issue
Block a user