You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
273 lines
9.4 KiB
Python
273 lines
9.4 KiB
Python
import frappe
|
|
import datetime
|
|
from .orders_utills import (check_customer_if_exists,
|
|
create_address_contact,
|
|
get_sps_market_place_order_ID,
|
|
create_sps_sales_order_item_row,
|
|
check_and_create_contact,
|
|
create_customer_if_not_exists)
|
|
# from .order_acknowledgement import get_sales_order_pyload
|
|
from .validate_creds import validate_api_user
|
|
# from .constants import SPS_SO_URL
|
|
import requests
|
|
from frappe.utils import get_datetime, now_datetime
|
|
from .oauth import refresh_access_token
|
|
from .order_acknowledgement import sales_order_acknowledgement
|
|
|
|
@frappe.whitelist(allow_guest=True)
|
|
def web_hook():
|
|
api_key = frappe.get_request_header("API-Key")
|
|
api_secret = frappe.get_request_header("API-Secret")
|
|
|
|
auth_response = validate_api_user(api_key, api_secret)
|
|
|
|
if not auth_response:
|
|
return {"status": "failed", "message": "Unauthorized access"}
|
|
|
|
frappe.log_error(
|
|
title="SPS Web Hook",
|
|
message=f"Authenticated request from {frappe.session.user}: {frappe.request.get_data()}",
|
|
)
|
|
|
|
return {"message": "Webhook received successfully"}
|
|
|
|
def sps_order_sync_job():
|
|
"""
|
|
Function to sync the job in background.
|
|
Args:
|
|
None
|
|
Returns:
|
|
None
|
|
"""
|
|
setting_doc_list = frappe.get_list("SPS Integration Settings",{"enabled":1})
|
|
for doc in setting_doc_list:
|
|
enqueue_sps_sync_order(doc['name'])
|
|
|
|
def enqueue_sps_sync_order(doc:str):
|
|
"""
|
|
Enqueues the SPS order synchronization functionality as a background job.
|
|
The job executes every 30 minutes to sync orders.
|
|
Args:
|
|
doc : str
|
|
Returns:
|
|
None
|
|
"""
|
|
try:
|
|
frappe.enqueue(
|
|
"sps_integration.sps_integration.order_sync.get_sps_sales_order",
|
|
doc = doc,
|
|
timeout = 1800
|
|
)
|
|
except:
|
|
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()}")
|
|
|
|
# start_date: str will add later
|
|
def retrieve_sps_order_urls(setting_doc_name: str) -> None:
|
|
"""
|
|
Calls the SPS API to retrieve a list of all URLs.
|
|
Args:
|
|
setting_doc_name (str): The name of the settings document.
|
|
Returns:
|
|
None
|
|
"""
|
|
try:
|
|
setting_doc = frappe.get_doc("SPS Integration Settings", setting_doc_name)
|
|
access_token = setting_doc.get_password("access_token")
|
|
headers = {
|
|
"Authorization": f"Bearer {access_token}",
|
|
"Content-Type": "application/json"
|
|
}
|
|
response = requests.get(url=setting_doc.get_sales_order_url,
|
|
headers=headers
|
|
)
|
|
if response.status_code == 200:
|
|
frappe.log_error(
|
|
title="SPS Order Sync Success",
|
|
message=f"Response:\n{response.json()}"
|
|
)
|
|
return response.json()
|
|
frappe.log_error(
|
|
title="SPS Order API Call Failed",
|
|
message=f"Error: {response.status_code}\nResponse: {response.json()}"
|
|
)
|
|
|
|
except Exception as e:
|
|
frappe.log_error(
|
|
title="SPS API error",
|
|
message=f"Traceback{frappe.get_traceback()}\n\nError:SPS Order API Call Error",
|
|
)
|
|
|
|
|
|
def create_sps_sales_order(data: dict, setting_doc: str, path: str) -> None:
|
|
"""
|
|
Processes an SPS sales order and syncs it to ERPNext.
|
|
|
|
Args:
|
|
data (dict): Sales order payload.
|
|
setting_doc (str): SPS Integration Settings document.
|
|
"""
|
|
|
|
|
|
# Extract order details
|
|
order_header = data.get("Header", {}).get("OrderHeader", {})
|
|
po_no = order_header.get("PurchaseOrderNumber")
|
|
consumer_order_number = order_header.get("CustomerOrderNumber")
|
|
po_date = order_header.get("PurchaseOrderDate")
|
|
doc = frappe.get_doc("SPS Integration Settings", setting_doc)
|
|
|
|
# Get delivery date
|
|
delivery_date = next(
|
|
(date.get("Date") for date in data.get("Header", {}).get("Dates", [])
|
|
if date.get("DateTimeQualifier") == "001"), None
|
|
)
|
|
|
|
|
|
# Check if customer exists, otherwise create
|
|
_, customer_name = check_customer_if_exists(data)
|
|
|
|
contact_name = check_and_create_contact(data, setting_doc)
|
|
|
|
|
|
# Create addresses if not exists
|
|
billing_address, shipping_address = create_address_contact(data, customer_name)
|
|
|
|
# Get marketplace details
|
|
marketplace_name = doc.marketplace
|
|
|
|
if not marketplace_name:
|
|
frappe.log_error("SPS Order Error", "No wholesale marketplace found.")
|
|
return
|
|
|
|
|
|
series_name = frappe.get_value(
|
|
"Marketplace",
|
|
marketplace_name,
|
|
"naming_series"
|
|
)
|
|
|
|
# Create Sales Order
|
|
sales_order = frappe.new_doc("Sales Order")
|
|
sales_order.naming_series = series_name
|
|
sales_order.customer = customer_name
|
|
sales_order.custom_spss_purchase_order = po_no
|
|
sales_order.po_no = consumer_order_number
|
|
sales_order.transaction_date = datetime.datetime.strptime(po_date, "%Y-%m-%d").date()
|
|
# sales_order.marketplace_order_id = get_sps_market_place_order_ID(mo_id, setting_doc)
|
|
sales_order.marketplace = marketplace_name
|
|
sales_order.delivery_date = frappe.utils.add_to_date(po_date, days=doc.days)
|
|
sales_order.customer_address = billing_address
|
|
sales_order.shipping_address_name = shipping_address
|
|
sales_order.custom_sales_order_path = path
|
|
if contact_name:
|
|
sales_order.contact_person = contact_name
|
|
|
|
# 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")
|
|
quantity = line_item.get("OrderQty", 0)
|
|
uom = line_item.get("OrderQtyUOM")
|
|
amount = line_item.get("PurchasePrice", 0.0)
|
|
|
|
description = next(
|
|
(desc.get("ProductDescription") for desc in item.get("ProductOrItemDescription", [])), ""
|
|
)
|
|
|
|
item_row = create_sps_sales_order_item_row(sku, quantity, amount, uom, description, setting_doc)
|
|
sales_order.append("items", item_row)
|
|
|
|
try:
|
|
sales_order.save()
|
|
sales_order.submit()
|
|
frappe.db.commit()
|
|
return sales_order.name
|
|
|
|
except Exception as e:
|
|
error_msg = frappe.get_traceback()
|
|
frappe.log_error(
|
|
title="Sales Order Creation Error",
|
|
message=f"Marketplace: SPS\n\nTraceback:\n{error_msg}",
|
|
)
|
|
|
|
|
|
|
|
|
|
|