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

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}",
)