More Shipstation integrations
This commit is contained in:
@@ -60,6 +60,21 @@ class JiraService(OrderService):
|
||||
settings["COMPANY_SKU_MAP"] or config.DEFAULT_COMPANY_SKU_MAP
|
||||
)
|
||||
|
||||
# Recipient/shipping contact fields - shared across both companies'
|
||||
# tickets (unlike the deliverable fields, these aren't split by
|
||||
# company). Only used by the emergency "Send to ShipStation" action.
|
||||
self.contact_field_ids = {
|
||||
"name": settings["JIRA_FIELD_NAME"].strip(),
|
||||
"phone": settings["JIRA_FIELD_PHONE"].strip(),
|
||||
"email": settings["JIRA_FIELD_EMAIL"].strip(),
|
||||
"address1": settings["JIRA_FIELD_ADDRESS1"].strip(),
|
||||
"address2": settings["JIRA_FIELD_ADDRESS2"].strip(),
|
||||
"city": settings["JIRA_FIELD_CITY"].strip(),
|
||||
"state": settings["JIRA_FIELD_STATE"].strip(),
|
||||
"zip": settings["JIRA_FIELD_ZIP"].strip(),
|
||||
"npi": settings["JIRA_FIELD_NPI"].strip(),
|
||||
}
|
||||
|
||||
# Statuses at which we stop re-checking a ticket for changes -
|
||||
# see fetch_orders() for why we re-check at all.
|
||||
self.terminal_statuses = parse_status_list(
|
||||
@@ -129,10 +144,14 @@ class JiraService(OrderService):
|
||||
auth = (self.email, self.api_token)
|
||||
headers = {"Accept": "application/json"}
|
||||
|
||||
# Always pull summary/status/created, plus every configured deliverable field.
|
||||
fields = "summary,status,created"
|
||||
# Always pull summary/status/created/creator, plus every configured
|
||||
# deliverable field and contact field.
|
||||
fields = "summary,status,created,creator"
|
||||
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]
|
||||
if contact_field_ids:
|
||||
fields += "," + ",".join(contact_field_ids)
|
||||
|
||||
all_issues: List[dict] = []
|
||||
start_at = 0
|
||||
@@ -221,26 +240,53 @@ class JiraService(OrderService):
|
||||
|
||||
return []
|
||||
|
||||
def _extract_skus_and_texts(self, fields: dict) -> Tuple[List[str], List[str]]:
|
||||
def _extract_skus_and_texts(self, fields: dict) -> Tuple[List[str], List[str], List[dict]]:
|
||||
"""
|
||||
Scan every configured deliverable field and return:
|
||||
- skus: just the code part (e.g. "SH011"), deduped, order preserved
|
||||
- texts: the full "CODE: description" strings, for use as a
|
||||
fallback summary when the ticket's actual Summary is blank
|
||||
(which is the normal case for these order tickets)
|
||||
- line_items: [{"sku": "SH011", "item_name": "Shipping - Return
|
||||
Label..."}], one per deliverable - this is what the ShipStation
|
||||
CSV template's SKU/Item Name columns come from
|
||||
"""
|
||||
skus: List[str] = []
|
||||
texts: List[str] = []
|
||||
line_items: List[dict] = []
|
||||
|
||||
for field_id in self.all_sku_field_ids:
|
||||
for raw_value in self._raw_values_from_field(fields, field_id):
|
||||
texts.append(raw_value)
|
||||
match = DELIVERABLE_CODE_PATTERN.match(raw_value)
|
||||
code = match.group(1) if match else raw_value
|
||||
item_name = match.group(2).strip() if match else ""
|
||||
if code not in skus:
|
||||
skus.append(code)
|
||||
line_items.append({"sku": code, "item_name": item_name})
|
||||
|
||||
return skus, texts
|
||||
return skus, texts, line_items
|
||||
|
||||
def _extract_single_value(self, fields: dict, field_id: str) -> str:
|
||||
"""One value from a contact field (name/phone/address/etc.), which -
|
||||
same as the deliverable fields - can come back as plain text, a
|
||||
single-select dict, or (rarely) a list. Reuses the same flexible
|
||||
extraction and just takes the first value."""
|
||||
if not field_id:
|
||||
return ""
|
||||
values = self._raw_values_from_field(fields, field_id)
|
||||
return values[0] if values else ""
|
||||
|
||||
def _extract_shipping_info(self, fields: dict) -> dict:
|
||||
return {
|
||||
key: self._extract_single_value(fields, field_id)
|
||||
for key, field_id in self.contact_field_ids.items()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _extract_creator(fields: dict) -> str:
|
||||
creator = fields.get("creator") or {}
|
||||
return creator.get("displayName") or creator.get("emailAddress") or ""
|
||||
|
||||
def _to_normalized_order(self, issue: dict) -> NormalizedOrder:
|
||||
fields = issue.get("fields", {})
|
||||
@@ -254,8 +300,10 @@ class JiraService(OrderService):
|
||||
created_at = None
|
||||
|
||||
ticket_number = issue.get("key", "")
|
||||
skus, deliverable_texts = self._extract_skus_and_texts(fields)
|
||||
skus, deliverable_texts, line_items = self._extract_skus_and_texts(fields)
|
||||
company = resolve_company_for_skus(skus, self.sku_map)
|
||||
shipping_info = self._extract_shipping_info(fields)
|
||||
creator = self._extract_creator(fields)
|
||||
|
||||
# These order tickets typically leave the JIRA Summary field
|
||||
# blank - fall back to the deliverables so there's still
|
||||
@@ -268,6 +316,9 @@ class JiraService(OrderService):
|
||||
ticket_number=ticket_number,
|
||||
company=company,
|
||||
skus=skus,
|
||||
line_items=line_items,
|
||||
shipping_info=shipping_info,
|
||||
creator=creator,
|
||||
tracking_numbers=[],
|
||||
summary=summary,
|
||||
status=(fields.get("status") or {}).get("name", ""),
|
||||
|
||||
Reference in New Issue
Block a user