Email procedure (beta), WorkFlow update
This commit is contained in:
@@ -25,8 +25,10 @@ class NormalizedOrder(TypedDict):
|
||||
line_items: List[dict]
|
||||
shipping_info: dict
|
||||
creator: Optional[str]
|
||||
assignee: Optional[str]
|
||||
description: Optional[str]
|
||||
tracking_numbers: List[dict]
|
||||
shipping_method: Optional[str]
|
||||
summary: str
|
||||
status: str
|
||||
source_created_at: Optional[dt.datetime]
|
||||
|
||||
@@ -165,7 +165,7 @@ class JiraService(OrderService):
|
||||
|
||||
# Always pull summary/status/created/creator, plus every configured
|
||||
# deliverable field and contact field.
|
||||
fields = "summary,status,created,creator,description"
|
||||
fields = "summary,status,created,creator,assignee,description"
|
||||
if self.all_sku_field_ids:
|
||||
fields += "," + ",".join(self.all_sku_field_ids)
|
||||
contact_field_ids = [v for v in self.contact_field_ids.values() if v]
|
||||
@@ -323,6 +323,11 @@ class JiraService(OrderService):
|
||||
creator = fields.get("creator") or {}
|
||||
return creator.get("displayName") or creator.get("emailAddress") or ""
|
||||
|
||||
@staticmethod
|
||||
def _extract_assignee(fields: dict) -> str:
|
||||
assignee = fields.get("assignee") or {}
|
||||
return assignee.get("displayName") or assignee.get("emailAddress") or ""
|
||||
|
||||
def _to_normalized_order(self, issue: dict) -> NormalizedOrder:
|
||||
fields = issue.get("fields", {})
|
||||
created_raw = fields.get("created")
|
||||
@@ -339,6 +344,7 @@ class JiraService(OrderService):
|
||||
company = resolve_company_for_skus(skus, self.sku_map)
|
||||
shipping_info = self._extract_shipping_info(fields)
|
||||
creator = self._extract_creator(fields)
|
||||
assignee = self._extract_assignee(fields)
|
||||
description = adf_to_text(fields.get("description"))
|
||||
|
||||
# These order tickets typically leave the JIRA Summary field
|
||||
@@ -355,8 +361,10 @@ class JiraService(OrderService):
|
||||
line_items=line_items,
|
||||
shipping_info=shipping_info,
|
||||
creator=creator,
|
||||
assignee=assignee,
|
||||
description=description,
|
||||
tracking_numbers=[],
|
||||
shipping_method=None, # comes from ShipStation, not JIRA - see shipstation_service.py
|
||||
summary=summary,
|
||||
status=(fields.get("status") or {}).get("name", ""),
|
||||
source_created_at=created_at,
|
||||
|
||||
@@ -45,6 +45,7 @@ import requests
|
||||
from PyQt6.QtCore import QRunnable, QThreadPool
|
||||
|
||||
from app import config
|
||||
from app.companies import parse_mapping
|
||||
from app.services.base import OrderService, NormalizedOrder
|
||||
from app.tracking import suggest_jira_status
|
||||
|
||||
@@ -64,6 +65,22 @@ SHIPMENT_LOOKBACK_DAYS = 7
|
||||
MAX_CONCURRENT_REQUESTS = 5
|
||||
|
||||
|
||||
def _resolve_shipping_method(service_code: str) -> str:
|
||||
"""Maps a ShipStation service_code (e.g. 'ups_ground') to your team's
|
||||
JIRA-facing term (e.g. 'Ground') via SHIPPING_METHOD_LABELS. Falls back
|
||||
to a prettified version of the raw code for anything not mapped, so an
|
||||
unmapped service still shows something readable rather than nothing."""
|
||||
if not service_code:
|
||||
return ""
|
||||
settings = config.load_settings()
|
||||
mapping = parse_mapping(
|
||||
settings.get("SHIPPING_METHOD_LABELS", "") or config.DEFAULT_SHIPPING_METHOD_LABELS
|
||||
)
|
||||
if service_code in mapping:
|
||||
return mapping[service_code]
|
||||
return service_code.replace("_", " ").title()
|
||||
|
||||
|
||||
class ShipStationServiceError(Exception):
|
||||
"""Raised for any ShipStation fetch failure, with a message safe to show in the UI."""
|
||||
|
||||
@@ -124,6 +141,7 @@ class ShipStationService(OrderService):
|
||||
# --- Sort/correlate now, entirely in memory, after both batches landed ---
|
||||
tracking_by_ticket: Dict[str, List[dict]] = {}
|
||||
raw_by_ticket: Dict[str, dict] = {}
|
||||
shipping_method_by_ticket: Dict[str, str] = {}
|
||||
self.unmatched_labels = []
|
||||
|
||||
for label in usable_labels:
|
||||
@@ -144,9 +162,20 @@ class ShipStationService(OrderService):
|
||||
raw_by_ticket.setdefault(ticket_number, {"labels": [], "shipment": shipment})
|
||||
raw_by_ticket[ticket_number]["labels"].append(label)
|
||||
|
||||
# Shipping Method reflects the OUTBOUND label specifically (matches
|
||||
# the Ship Sheet's usage) - only set from the first non-return label
|
||||
# seen per ticket, so a return label's service doesn't overwrite it.
|
||||
if not label.get("is_return_label") and ticket_number not in shipping_method_by_ticket:
|
||||
shipping_method_by_ticket[ticket_number] = _resolve_shipping_method(
|
||||
label.get("service_code", "")
|
||||
)
|
||||
|
||||
return [
|
||||
self._to_normalized_order(
|
||||
ticket_number, tracking_by_ticket[ticket_number], raw_by_ticket[ticket_number]
|
||||
ticket_number,
|
||||
tracking_by_ticket[ticket_number],
|
||||
raw_by_ticket[ticket_number],
|
||||
shipping_method_by_ticket.get(ticket_number, ""),
|
||||
)
|
||||
for ticket_number in tracking_by_ticket
|
||||
]
|
||||
@@ -280,7 +309,7 @@ class ShipStationService(OrderService):
|
||||
|
||||
@staticmethod
|
||||
def _to_normalized_order(
|
||||
ticket_number: str, tracking_numbers: List[dict], raw: dict
|
||||
ticket_number: str, tracking_numbers: List[dict], raw: dict, shipping_method: str
|
||||
) -> NormalizedOrder:
|
||||
suggested_status = suggest_jira_status(tracking_numbers) or "Tracking Pulled"
|
||||
numbers_display = ", ".join(
|
||||
@@ -294,7 +323,13 @@ class ShipStationService(OrderService):
|
||||
ticket_number=ticket_number,
|
||||
company="", # not used - the JIRA row this merges onto already has one
|
||||
skus=[],
|
||||
line_items=[],
|
||||
shipping_info={},
|
||||
creator=None,
|
||||
assignee=None,
|
||||
description=None,
|
||||
tracking_numbers=tracking_numbers,
|
||||
shipping_method=shipping_method,
|
||||
summary=numbers_display,
|
||||
status=suggested_status,
|
||||
source_created_at=None,
|
||||
|
||||
Reference in New Issue
Block a user