diff --git a/sps_integration/sps_integration/doctype/paths_to_skip/__init__.py b/sps_integration/sps_integration/doctype/paths_to_skip/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.js b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.js new file mode 100644 index 0000000..c9a9cb6 --- /dev/null +++ b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, UnifyXperts and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Paths to Skip", { +// refresh(frm) { + +// }, +// }); diff --git a/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.json b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.json new file mode 100644 index 0000000..53de8f1 --- /dev/null +++ b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.json @@ -0,0 +1,45 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:file_path", + "creation": "2025-04-09 03:39:31.669681", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "file_path" + ], + "fields": [ + { + "fieldname": "file_path", + "fieldtype": "Data", + "label": "File Path", + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-04-09 03:42:04.355255", + "modified_by": "Administrator", + "module": "SPS Integration", + "name": "Paths to Skip", + "naming_rule": "By fieldname", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.py b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.py new file mode 100644 index 0000000..af9ec4d --- /dev/null +++ b/sps_integration/sps_integration/doctype/paths_to_skip/paths_to_skip.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, UnifyXperts and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PathstoSkip(Document): + pass diff --git a/sps_integration/sps_integration/doctype/paths_to_skip/test_paths_to_skip.py b/sps_integration/sps_integration/doctype/paths_to_skip/test_paths_to_skip.py new file mode 100644 index 0000000..a3d7c4c --- /dev/null +++ b/sps_integration/sps_integration/doctype/paths_to_skip/test_paths_to_skip.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, UnifyXperts and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestPathstoSkip(FrappeTestCase): + pass diff --git a/sps_integration/sps_integration/order_acknowledgement.py b/sps_integration/sps_integration/order_acknowledgement.py index 8fbd2ba..ada084f 100644 --- a/sps_integration/sps_integration/order_acknowledgement.py +++ b/sps_integration/sps_integration/order_acknowledgement.py @@ -103,10 +103,9 @@ def sales_order_acknowledgement(data: dict): response = requests.post(url=doc.order_acknowledgement_url, headers=headers, data=json.dumps(formatted_data)) if response.status_code in [200, 201]: - response_json = response.json() frappe.log_error(title="SO Acknowledgment Success", message=f"{response.text}") else: frappe.log_error(title="SO Acknowledgment Failure", message=f"{response.text}") except Exception as e: - frappe.log_error(title="SO Acknowledgment Error", message=str(e)) + frappe.log_error(title="SO Acknowledgment Error", message=str(frappe.get_traceback())) diff --git a/sps_integration/sps_integration/order_sync.py b/sps_integration/sps_integration/order_sync.py index 1d2a1d7..9526ccf 100644 --- a/sps_integration/sps_integration/order_sync.py +++ b/sps_integration/sps_integration/order_sync.py @@ -5,7 +5,9 @@ from .orders_utills import (check_customer_if_exists, get_sps_market_place_order_ID, create_sps_sales_order_item_row, check_and_create_contact, - create_customer_if_not_exists) + create_customer_if_not_exists, + get_path_to_skip, + exclude_skipped_file_paths) # from .order_acknowledgement import get_sales_order_pyload from .validate_creds import validate_api_user # from .constants import SPS_SO_URL @@ -62,6 +64,83 @@ def enqueue_sps_sync_order(doc:str): frappe.log_error(title= "Enqueue failed",message =f"{frappe.get_traceback()}") +# @frappe.whitelist(allow_guest=True) +# def get_sps_sales_order(doc: str): +# """ +# Calls the SPS API to retrieve a list of all Sales Order URLs. +# Iterates through the URLs, fetches the corresponding payloads, +# syncs them into ERPNext, and stores the Sales Order URL paths. + +# Args: +# doc (str): The settings document. + +# Returns: +# None +# """ + +# try: +# data = retrieve_sps_order_urls(setting_doc_name=doc) +# frappe.log_error(title="Get SPS Sales Order Polling", message=f"{data}") +# sales_orders = frappe.get_all("Sales Order", fields = ["name", "custom_sales_order_path"]) +# filtered_sales_orders = {order["custom_sales_order_path"] for order in sales_orders if order["custom_sales_order_path"]} +# frappe.log_error( +# title = "Previously Synced Sales Order", +# message = f"{filtered_sales_orders}" +# ) +# results = data.get("results", []) +# failed_requests = [] +# skipped_sales_orders = [] + +# setting_doc = frappe.get_doc("SPS Integration Settings", doc) +# expires_on = get_datetime(setting_doc.expires_on) +# current_time = now_datetime() +# if expires_on <= current_time or (expires_on - current_time).total_seconds() <= 300: +# refresh_access_token(setting_doc_name=doc) +# access_token = setting_doc.get_password("access_token") +# else: +# access_token = setting_doc.get_password("access_token") + +# headers = { +# "Authorization": f"Bearer {access_token}", +# "Content-Type": "application/json" +# } + +# for record in results: +# so_url = record.get("url") +# path = record.get("path", "").split("/")[-1] + +# if path in filtered_sales_orders: +# skipped_sales_orders.append(path) +# continue +# if so_url: +# try: +# response = requests.get(url=so_url, headers=headers) + +# if response.status_code == 200: +# sales_order = create_sps_sales_order( +# data=response.json(), +# setting_doc=doc, +# path=path +# ) +# if frappe.db.exists("Sales Order", {"name": sales_order, "docstatus":1}): +# sales_order_acknowledgement(data=response.json()) +# else: +# failed_requests.append(so_url) +# except requests.RequestException as e: +# failed_requests.append(so_url) +# frappe.log_error(title="Request Error", message=str(e)) + +# api_info = { +# "Total Sales Order Payload": len(results), +# "Failed Requests": failed_requests +# } +# frappe.log_error(title="Skipped Synced Sales Order", message=f"{skipped_sales_orders}") +# frappe.log_error(title="API Detail Info", message=f"{api_info}") + +# except Exception as e: +# frappe.log_error(title="Enqueuing Error", message=f"{frappe.get_traceback()}") + + @frappe.whitelist(allow_guest=True) def get_sps_sales_order(doc: str): """ @@ -77,19 +156,10 @@ def get_sps_sales_order(doc: str): """ try: - data = retrieve_sps_order_urls(setting_doc_name=doc) - frappe.log_error(title="Get SPS Sales Order Polling", message=f"{data}") - sales_orders = frappe.get_all("Sales Order", fields = ["name", "custom_sales_order_path"]) - filtered_sales_orders = {order["custom_sales_order_path"] for order in sales_orders if order["custom_sales_order_path"]} - frappe.log_error( - title = "Previously Synced Sales Order", - message = f"{filtered_sales_orders}" - ) - results = data.get("results", []) + access_token = "" failed_requests = [] - skipped_sales_orders = [] - setting_doc = frappe.get_doc("SPS Integration Settings", doc) + SPS_URL = setting_doc.get_sales_order_url expires_on = get_datetime(setting_doc.expires_on) current_time = now_datetime() if expires_on <= current_time or (expires_on - current_time).total_seconds() <= 300: @@ -97,23 +167,19 @@ def get_sps_sales_order(doc: str): access_token = setting_doc.get_password("access_token") else: access_token = setting_doc.get_password("access_token") - + order_url_path = retrieve_sps_order_urls(setting_doc_name=doc).get("results", []) headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } - - for record in results: - so_url = record.get("url") - path = record.get("path", "").split("/")[-1] - - if path in filtered_sales_orders: - skipped_sales_orders.append(path) - continue - if so_url: + # Filtered path url for sales order to avoid duplicates + filtered_url_path = exclude_skipped_file_paths(paths=[d.get("path").split("/")[-1] for d in order_url_path]) + for path in filtered_url_path: + # path = record.get("path", "").split("/")[-1] + url = f"{SPS_URL}{path}" + if url: try: - response = requests.get(url=so_url, headers=headers) - + response = requests.get(url=url, headers=headers) if response.status_code == 200: sales_order = create_sps_sales_order( data=response.json(), @@ -121,22 +187,25 @@ def get_sps_sales_order(doc: str): path=path ) if frappe.db.exists("Sales Order", {"name": sales_order, "docstatus":1}): - sales_order_acknowledgement(data=response.json()) + # Once the sales order sync in ERPNext will ack the sales order + # sales_order_acknowledgement(data=response.json()) + path_to_skip = frappe.new_doc("Paths to Skip") + path_to_skip.file_path = path + path_to_skip.save() + frappe.db.commit() else: - failed_requests.append(so_url) + failed_requests.append(url) except requests.RequestException as e: - failed_requests.append(so_url) frappe.log_error(title="Request Error", message=str(e)) - api_info = { - "Total Sales Order Payload": len(results), - "Failed Requests": failed_requests + "Total sales order payload": len(filtered_url_path), + "Failed sales order": failed_requests } - frappe.log_error(title="Skipped Synced Sales Order", message=f"{skipped_sales_orders}") frappe.log_error(title="API Detail Info", message=f"{api_info}") - except Exception as e: - frappe.log_error(title="Enqueuing Error", message=f"{frappe.get_traceback()}") + frappe.log_error(title="Error while sales order sycing", message=f"{frappe.get_traceback()}") + + # start_date: str will add later def retrieve_sps_order_urls(setting_doc_name: str) -> None: @@ -241,7 +310,8 @@ def create_sps_sales_order(data: dict, setting_doc: str, path: str) -> None: # Adding items to the sales order for item in data.get("LineItem", []): line_item = item.get("OrderLine", {}) - sku = line_item.get("VendorPartNumber") or line_item.get("BuyerPartNumber") + # sku = line_item.get("VendorPartNumber") or line_item.get("BuyerPartNumber") + sku = line_item.get("VendorPartNumber") quantity = line_item.get("OrderQty", 0) uom = line_item.get("OrderQtyUOM") amount = line_item.get("PurchasePrice", 0.0) diff --git a/sps_integration/sps_integration/orders_utills.py b/sps_integration/sps_integration/orders_utills.py index f612cc3..6ebcd59 100644 --- a/sps_integration/sps_integration/orders_utills.py +++ b/sps_integration/sps_integration/orders_utills.py @@ -263,3 +263,31 @@ def create_customer_if_not_exists(data: dict, setting_doc: str) -> str: return doc.name +def get_path_to_skip(): + """Function to get all paths to skip""" + return frappe.get_all("Paths to Skip", pluck="file_path") + +def exclude_skipped_file_paths(paths: List[str]) -> List[str]: + """ + Excludes file paths that are marked as skipped in the 'File Skip List' doctype. + + Args: + paths (List[str]): List of file paths to filter. + + Returns: + List[str]: Filtered list with skipped paths removed. + """ + try: + skipped_set = set(get_path_to_skip()) + filtered_paths = [path for path in paths if path not in skipped_set] + frappe.log_error( + title="Exclude skipped file paths", + message=f"Skipped Paths: {skipped_set}\nFiltered Paths: {filtered_paths}" + ) + return filtered_paths + except Exception as e: + frappe.log_error( + title="Error while excluding skipped file paths", + message=frappe.get_traceback() + ) + return [] \ No newline at end of file