More Shipstation integrations
This commit is contained in:
+105
-1
@@ -28,12 +28,18 @@ from PyQt6.QtWidgets import (
|
||||
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.ui.settings_dialog import SettingsDialog
|
||||
from app.ui.widgets.orders_table import OrdersTableView
|
||||
from app.ui.widgets.dashboard import DashboardWidget
|
||||
from app.ui.widgets.order_detail_dialog import OrderDetailDialog
|
||||
from app.workers import FetchOrdersWorker, load_active_and_done_orders, get_dashboard_stats
|
||||
from app.workers import (
|
||||
FetchOrdersWorker,
|
||||
SendToShipStationWorker,
|
||||
load_active_and_done_orders,
|
||||
get_dashboard_stats,
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
@@ -44,6 +50,7 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self._workers: dict[str, FetchOrdersWorker] = {}
|
||||
self._import_actions: dict[str, QAction] = {}
|
||||
self._send_worker: SendToShipStationWorker | None = None
|
||||
|
||||
self._build_ui()
|
||||
self._refresh_everything()
|
||||
@@ -90,6 +97,13 @@ class MainWindow(QMainWindow):
|
||||
|
||||
toolbar.addSeparator()
|
||||
|
||||
emergency_action = QAction("Send to ShipStation (Emergency)", self)
|
||||
emergency_action.setToolTip("Select a ticket in Active Orders first")
|
||||
emergency_action.triggered.connect(self._on_emergency_send_clicked)
|
||||
toolbar.addAction(emergency_action)
|
||||
|
||||
toolbar.addSeparator()
|
||||
|
||||
settings_action = QAction("Settings", self)
|
||||
settings_action.triggered.connect(self._on_settings_clicked)
|
||||
toolbar.addAction(settings_action)
|
||||
@@ -228,6 +242,96 @@ class MainWindow(QMainWindow):
|
||||
|
||||
self.status_label.setText(f"Exported {count} order(s) to {filepath}")
|
||||
|
||||
def _on_emergency_send_clicked(self) -> None:
|
||||
order = self.orders_table.selected_order()
|
||||
if order is None:
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"No ticket selected",
|
||||
"Select a ticket in Active Orders first, then click Send to ShipStation.",
|
||||
)
|
||||
return
|
||||
|
||||
info = order.shipping_info or {}
|
||||
line_items = order.line_items or []
|
||||
skus_display = ", ".join(item.get("sku", "") for item in line_items) or (
|
||||
", ".join(order.skus or []) or "(none)"
|
||||
)
|
||||
address_line2 = f" {info.get('address2')}" if info.get("address2") else ""
|
||||
|
||||
summary_lines = [
|
||||
f"Ticket: {order.ticket_number or order.external_id}",
|
||||
f"Company: {order.company}",
|
||||
f"Name: {info.get('name') or '(missing)'}",
|
||||
f"Address: {info.get('address1') or '(missing)'}{address_line2}",
|
||||
f" {info.get('city', '')}, {info.get('state', '')} {info.get('zip', '')}",
|
||||
f"SKUs: {skus_display}",
|
||||
]
|
||||
|
||||
box = QMessageBox(self)
|
||||
box.setIcon(QMessageBox.Icon.Warning)
|
||||
box.setWindowTitle("Send to ShipStation")
|
||||
box.setText("Review before sending - this creates a real order in ShipStation.")
|
||||
box.setInformativeText("\n".join(summary_lines))
|
||||
api_button = box.addButton("Send via API Now", QMessageBox.ButtonRole.AcceptRole)
|
||||
csv_button = box.addButton("Export CSV Row...", QMessageBox.ButtonRole.ActionRole)
|
||||
box.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
|
||||
box.exec()
|
||||
|
||||
clicked = box.clickedButton()
|
||||
if clicked is api_button:
|
||||
self._send_via_api(order)
|
||||
elif clicked is csv_button:
|
||||
self._send_via_csv(order)
|
||||
|
||||
def _send_via_api(self, order) -> None:
|
||||
self.status_label.setText(
|
||||
f"Sending {order.ticket_number or order.external_id} to ShipStation..."
|
||||
)
|
||||
worker = SendToShipStationWorker(order)
|
||||
worker.finished_ok.connect(self._on_emergency_send_ok)
|
||||
worker.failed.connect(self._on_emergency_send_failed)
|
||||
self._send_worker = worker # keep a reference so it isn't garbage collected
|
||||
worker.start()
|
||||
|
||||
def _on_emergency_send_ok(self, result: dict) -> None:
|
||||
shipment_id = result.get("shipment_id", "?")
|
||||
self.status_label.setText(f"Sent to ShipStation - shipment {shipment_id} created.")
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Sent to ShipStation",
|
||||
f"Order created in ShipStation (shipment {shipment_id}).\n\n"
|
||||
"This is a live order - please double-check it in ShipStation to confirm "
|
||||
"automation packed and priced it correctly, especially the first few times "
|
||||
"you use this.",
|
||||
)
|
||||
|
||||
def _on_emergency_send_failed(self, message: str) -> None:
|
||||
self.status_label.setText("Send to ShipStation failed.")
|
||||
QMessageBox.critical(self, "Send to ShipStation failed", message)
|
||||
|
||||
def _send_via_csv(self, order) -> None:
|
||||
default_name = f"shipstation_{order.ticket_number or order.external_id}.csv"
|
||||
filepath, _ = QFileDialog.getSaveFileName(
|
||||
self, "Export Ticket to ShipStation CSV", default_name, "CSV Files (*.csv)"
|
||||
)
|
||||
if not filepath:
|
||||
return
|
||||
|
||||
try:
|
||||
count = export_order_to_shipstation_csv([order], filepath)
|
||||
except OSError as exc:
|
||||
QMessageBox.critical(self, "Export failed", str(exc))
|
||||
return
|
||||
|
||||
self.status_label.setText(f"Exported {count} row(s) to {filepath}")
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Exported",
|
||||
f"Wrote {count} row(s) to:\n{filepath}\n\n"
|
||||
"Upload this into ShipStation's Import Orders wizard.",
|
||||
)
|
||||
|
||||
def _refresh_everything(self) -> None:
|
||||
active_orders, done_orders = load_active_and_done_orders()
|
||||
self.orders_table.set_orders(active_orders)
|
||||
|
||||
Reference in New Issue
Block a user