From 0adde63128c53e077a3e5d90c10b373d40e2e0a9 Mon Sep 17 00:00:00 2001 From: Rohan Date: Wed, 26 Oct 2022 18:34:41 +0530 Subject: [PATCH] fix: update order with shipstation store information (#63) --- shipstation_integration/customer.py | 29 ++++-- .../public/js/delivery_note.js | 19 ++++ shipstation_integration/shipments.py | 7 +- shipstation_integration/shipping.py | 91 +++++++++++++++++-- 4 files changed, 130 insertions(+), 16 deletions(-) diff --git a/shipstation_integration/customer.py b/shipstation_integration/customer.py index 8ea81df..838203b 100644 --- a/shipstation_integration/customer.py +++ b/shipstation_integration/customer.py @@ -1,16 +1,21 @@ from typing import TYPE_CHECKING import frappe -from frappe.utils import parse_addr +from frappe.utils import getdate, parse_addr if TYPE_CHECKING: from erpnext.selling.doctype.sales_order.sales_order import SalesOrder from frappe.contacts.doctype.address.address import Address from frappe.contacts.doctype.contact.contact import Contact from shipstation.models import ShipStationAddress, ShipStationOrder + from shipstation_integration.shipstation_integration.doctype.shipstation_store.shipstation_store import ( + ShipstationStore, + ) -def update_customer_details(existing_so: str, order: "ShipStationOrder"): +def update_customer_details( + existing_so: str, order: "ShipStationOrder", store: "ShipstationStore" +): existing_so_doc: "SalesOrder" = frappe.get_doc("Sales Order", existing_so) email_id, user_name = parse_addr(existing_so_doc.amazon_customer) @@ -18,8 +23,19 @@ def update_customer_details(existing_so: str, order: "ShipStationOrder"): contact = create_contact(order, email_id) existing_so_doc.contact_person = contact.name - existing_so_doc.shipstation_order_id = order.order_id - existing_so_doc.has_pii = True + existing_so_doc.update( + { + "shipstation_order_id": order.order_id, + "shipstation_store_name": store.store_name, + "shipstation_customer_notes": getattr(order, "customer_notes", None), + "shipstation_internal_notes": getattr(order, "internal_notes", None), + "marketplace_order_id": order.order_number, + "delivery_date": getdate(order.ship_date), + "has_pii": True, + "integration_doctype": "Shipstation Settings", + "integration_doc": store.parent, + } + ) if order.bill_to and order.bill_to.street1: if existing_so_doc.customer_address: @@ -106,10 +122,7 @@ def create_customer(order: "ShipStationOrder"): ) customer_name = ( - order.customer_email - or order.customer_id - or order.ship_to.name - or customer_id + order.customer_email or order.customer_id or order.ship_to.name or customer_id ) if frappe.db.exists("Customer", customer_name): diff --git a/shipstation_integration/public/js/delivery_note.js b/shipstation_integration/public/js/delivery_note.js index f6681c4..d77c4ad 100644 --- a/shipstation_integration/public/js/delivery_note.js +++ b/shipstation_integration/public/js/delivery_note.js @@ -1,5 +1,24 @@ frappe.ui.form.on("Delivery Note", { refresh: frm => { shipping.shipstation(frm); + + if (frm.doc.docstatus === 1 && frm.doc.shipstation_order_id) { + frm.add_custom_button(__('Fetch Shipment'), () => { + frappe.call({ + method: "shipstation_integration.shipping.fetch_shipment", + args: { + "delivery_note": frm.doc.name + }, + freeze: true, + callback: function(r) { + if (r.message) { + frappe.msgprint(`A shipment was fetched from Shipstation and created at ${r.message}`) + } else { + frappe.msgprint(`No new shipment(s) were found against Shipstation ID: ${frm.doc.shipstation_order_id.bold()}`) + } + } + }); + }).removeClass("btn-default").addClass("btn-primary"); + } } }) diff --git a/shipstation_integration/shipments.py b/shipstation_integration/shipments.py index 93f5986..7c8b740 100644 --- a/shipstation_integration/shipments.py +++ b/shipstation_integration/shipments.py @@ -135,8 +135,11 @@ def create_erpnext_shipment(shipment: "ShipStationOrder", store: "ShipstationSto if store.create_delivery_note: delivery_note = create_delivery_note(shipment, sales_invoice) + shipment_doc = None if store.create_shipment: - shipment = create_shipment(shipment, store, delivery_note) + shipment_doc = create_shipment(shipment, store, delivery_note) + + return shipment_doc def cancel_voided_shipments(shipment: "ShipStationOrder"): @@ -281,4 +284,4 @@ def create_shipment( shipment_doc.submit() frappe.db.commit() - return shipment + return shipment_doc diff --git a/shipstation_integration/shipping.py b/shipstation_integration/shipping.py index 969c978..da5fa32 100644 --- a/shipstation_integration/shipping.py +++ b/shipstation_integration/shipping.py @@ -9,13 +9,21 @@ import frappe from frappe import _ from frappe.contacts.doctype.address.address import Address -from frappe.utils.data import get_datetime, today +from frappe.utils import get_datetime, get_link_to_form, today from frappe.utils.file_manager import save_file +from shipstation_integration.shipments import ( + cancel_voided_shipments, + create_erpnext_shipment, +) + if TYPE_CHECKING: from frappe.core.doctype.file.file import File from shipstation_integration.shipstation_integration.doctype.shipstation_settings.shipstation_settings import ( - ShipstationSettings + ShipstationSettings, + ) + from shipstation_integration.shipstation_integration.doctype.shipstation_store.shipstation_store import ( + ShipstationStore, ) @@ -105,7 +113,7 @@ def _create_shipping_label(doc: str, values: str, user: str = str()): if isinstance(shipment, dict) and shipment.get("ExceptionMessage"): process_error( shipment, - message="There was an error generating the label. Please contact your administrator." + message="There was an error generating the label. Please contact your administrator.", ) pdf = BytesIO(base64.b64decode(shipment.label_data)) @@ -125,7 +133,7 @@ def attach_shipping_label(pdf: BytesIO, doctype: str, name: str): if not isinstance(pdf, BytesIO): process_error( pdf, - message="There was an error attaching the label. Please contact your administrator." + message="There was an error attaching the label. Please contact your administrator.", ) file: "File" = save_file( @@ -134,7 +142,7 @@ def attach_shipping_label(pdf: BytesIO, doctype: str, name: str): dt=doctype, dn=name, folder="Home/Shipstation Labels", - is_private=True + is_private=True, ) return file @@ -179,7 +187,7 @@ def get_shipstation_address(address: Address, person_name: str = str()): state=address.state, postal_code=address.pincode, phone=address.phone, - country=country_code + country=country_code, ) @@ -229,3 +237,74 @@ def get_shipstation_settings(doc: str) -> Optional[str]: def push_attachment_update(attachment: "File", user: str): js = f"if (cur_frm.doc.name =='{attachment.attached_to_name}') {{cur_frm.refresh();}}" frappe.publish_realtime("eval_js", js, user=user or frappe.session.user) + + +@frappe.whitelist() +def fetch_shipment(delivery_note: str): + delivery_note = frappe.get_doc("Delivery Note", delivery_note) + + if ( + delivery_note.integration_doctype == "Shipstation Settings" + and delivery_note.integration_doc + ): + settings = [delivery_note.integration_doc] + else: + settings = frappe.get_all("Shipstation Settings", pluck="name") + + for setting in settings: + sss_doc = frappe.get_doc("Shipstation Settings", setting) + client = sss_doc.client() + client.timeout = 60 + + parameters = { + "order_id": delivery_note.shipstation_order_id, + "include_shipment_items": True, + } + + try: + shipments: List["ShipStationOrder"] = client.list_shipments( + parameters=parameters + ) + except Exception as e: + frappe.log_error( + title="Error while fetching Shipstation shipment", message=e + ) + return + + created_shipments = [] + for shipment in shipments: + # sometimes Shipstation will return `None` in the response + if not shipment: + continue + + existing_shipments = frappe.get_all( + "Shipment", filters={"shipment_id": shipment.shipment_id} + ) + + if existing_shipments: + continue + + if shipment.voided: + cancel_voided_shipments(shipment) + else: + store_id = shipment.advanced_options.store_id + store: "ShipstationStore" = next( + ( + store + for store in sss_doc.shipstation_stores + if store.store_id == store_id + ), + None, + ) + + delivery_note.db_set("shipstation_shipment_id", shipment.shipment_id) + shipment = create_erpnext_shipment(shipment, store) + if shipment: + created_shipments.append( + get_link_to_form(shipment.doctype, shipment.name) + ) + + if created_shipments: + frappe.msgprint( + _("Created Shipment(s): {0}").format(", ".join(created_shipments)) + )