SPS Integration

main
vrashank 10 months ago
parent b5d6a1edde
commit 464b59bb51

@ -132,7 +132,14 @@ app_license = "mit"
# Scheduled Tasks
# ---------------
# comment by unifyxperts
# scheduler_events = {
# "cron":{
# "0 * * * *" : [
# "sps_integration.sps_integration.order_sync.sps_order_sync_job"
# ]
# }
# }
# scheduler_events = {
# "all": [
# "sps_integration.tasks.all"

@ -0,0 +1,19 @@
import frappe
SALES_ORDER_ACKNOWLEDGEMENT_URL = "https://api.spscommerce.com/transactions/v5/"
SPS_SO_URL = "https://api.spscommerce.com/transactions/v5/data/out/"
BASE_AUTH_URL = "https://auth.spscommerce.com"
def AUTHORIZATION_URL(
audience: str,
client_id: str,
redirect_uri: str,
state: str,
response_type : str = "code",
scope="offline_access"
) -> str:
result = f"{BASE_AUTH_URL}/authorize?audience={audience}&client_id={client_id}&redirect_uri={redirect_uri}&response_type={response_type}&state={state}"
if scope != None:
result += f"&scope={scope}"
return result

@ -0,0 +1,32 @@
{
"custom_fields": [],
"custom_perms": [],
"doctype": "Packed Item",
"links": [],
"property_setters": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"creation": "2024-04-03 00:11:59.728639",
"default_value": null,
"doc_type": "Packed Item",
"docstatus": 0,
"doctype_or_field": "DocField",
"field_name": "rate",
"idx": 0,
"is_system_generated": 0,
"modified": "2024-04-03 00:11:59.728639",
"modified_by": "Administrator",
"module": null,
"name": "Packed Item-rate-read_only",
"owner": "Administrator",
"property": "read_only",
"property_type": "Check",
"row_name": null,
"value": "1"
}
],
"sync_on_migrate": 1
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,715 @@
{
"custom_fields": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2025-02-21 19:35:28.155094",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_shopify_fulfillment_line_item_id",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 10,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "item_name",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Shopify Fulfillment Line Item Id",
"length": 0,
"mandatory_depends_on": null,
"modified": "2025-02-21 19:35:28.155094",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_shopify_fulfillment_line_item_id",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-09-09 00:45:56.927625",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_drop_ship_fee_breakdown",
"fieldtype": "Small Text",
"hidden": 1,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 46,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_shipping_fee_breakdown",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Drop Ship Fee Breakdown",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-09-09 00:45:56.927625",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_drop_ship_fee_breakdown",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-09-09 00:45:56.589647",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_shipping_fee_breakdown",
"fieldtype": "Small Text",
"hidden": 1,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 45,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_drop_ship_fee",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Shipping Fee Breakdown",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-09-09 00:45:56.589647",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_shipping_fee_breakdown",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-09-09 00:24:04.004874",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_drop_ship_fee",
"fieldtype": "Currency",
"hidden": 1,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 44,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_shipping_fee",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Drop Ship Fee",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-09-09 00:24:04.004874",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_drop_ship_fee",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-08-12 22:44:28.200605",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_shipping_fee",
"fieldtype": "Currency",
"hidden": 1,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 43,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "item_tax_template",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Shipping Fee",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-08-12 22:44:28.200605",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_shipping_fee",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-08-05 22:28:23.425753",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_item_zone",
"fieldtype": "Link",
"hidden": 1,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 76,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "quotation_item",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Item Zone",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-08-05 22:28:23.425753",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_item_zone",
"no_copy": 0,
"non_negative": 0,
"options": "Freight Rates for Zone",
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-11 00:16:49.489163",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "marketplace_order_id",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "accounting_dimensions_section",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Marketplace Order ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-11 00:16:49.489163",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-marketplace_order_id",
"no_copy": 0,
"non_negative": 0,
"options": "Marketplace Order ID",
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-11 00:12:53.971293",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "marketplace",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "dimension_col_break",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Marketplace",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-11 00:12:53.971293",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-marketplace",
"no_copy": 0,
"non_negative": 0,
"options": "Marketplace",
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-07 22:33:32.121127",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": "item_code.custom_ecommerce_sku",
"fetch_if_empty": 0,
"fieldname": "custom_sku",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 7,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_extensiv_order_item_id",
"is_system_generated": 0,
"is_virtual": 0,
"label": "SKU",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-07 22:33:32.121127",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_sku",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 1,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-04 22:28:52.823146",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_extensiv_order_item_id",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 6,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "reserve_stock",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Extensiv Order Item Id",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-04 22:28:52.823146",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-custom_extensiv_order_item_id",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-03 03:31:54.410586",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Order Item",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "shopify_item_discount",
"fieldtype": "Float",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 27,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "discount_and_margin",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Shopify Discount per unit",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-04 22:01:03.999479",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-shopify_item_discount",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 1,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "Sales Order Item",
"links": [],
"property_setters": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"creation": "2025-02-21 19:35:27.983137",
"default_value": null,
"doc_type": "Sales Order Item",
"docstatus": 0,
"doctype_or_field": "DocType",
"field_name": null,
"idx": 0,
"is_system_generated": 0,
"modified": "2025-02-21 19:35:27.983137",
"modified_by": "Administrator",
"module": null,
"name": "Sales Order Item-main-field_order",
"owner": "Administrator",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"item_code\", \"customer_item_code\", \"ensure_delivery_based_on_produced_serial_no\", \"is_stock_item\", \"reserve_stock\", \"custom_extensiv_order_item_id\", \"custom_sku\", \"col_break1\", \"delivery_date\", \"item_name\", \"shopify_fulfillment_line_item_id\", \"section_break_5\", \"description\", \"item_group\", \"brand\", \"image_section\", \"image\", \"image_view\", \"quantity_and_rate\", \"qty\", \"stock_uom\", \"col_break2\", \"uom\", \"conversion_factor\", \"stock_qty\", \"stock_reserved_qty\", \"section_break_16\", \"price_list_rate\", \"base_price_list_rate\", \"discount_and_margin\", \"shopify_item_discount\", \"margin_type\", \"margin_rate_or_amount\", \"rate_with_margin\", \"column_break_19\", \"discount_percentage\", \"discount_amount\", \"base_rate_with_margin\", \"section_break_simple1\", \"rate\", \"amount\", \"item_tax_template\", \"custom_shipping_fee\", \"custom_drop_ship_fee\", \"custom_shipping_fee_breakdown\", \"custom_drop_ship_fee_breakdown\", \"col_break3\", \"base_rate\", \"base_amount\", \"pricing_rules\", \"stock_uom_rate\", \"is_free_item\", \"grant_commission\", \"section_break_24\", \"net_rate\", \"net_amount\", \"column_break_27\", \"base_net_rate\", \"base_net_amount\", \"billed_amt\", \"valuation_rate\", \"gross_profit\", \"drop_ship_section\", \"delivered_by_supplier\", \"supplier\", \"item_weight_details\", \"weight_per_unit\", \"total_weight\", \"column_break_21\", \"weight_uom\", \"warehouse_and_reference\", \"warehouse\", \"target_warehouse\", \"prevdoc_docname\", \"quotation_item\", \"custom_item_zone\", \"col_break4\", \"against_blanket_order\", \"blanket_order\", \"blanket_order_rate\", \"manufacturing_section_section\", \"bom_no\", \"planning_section\", \"projected_qty\", \"actual_qty\", \"ordered_qty\", \"planned_qty\", \"production_plan_qty\", \"column_break_69\", \"work_order_qty\", \"delivered_qty\", \"produced_qty\", \"returned_qty\", \"picked_qty\", \"shopping_cart_section\", \"additional_notes\", \"section_break_63\", \"page_break\", \"item_tax_rate\", \"transaction_date\", \"inter_transfer_reference_section\", \"material_request\", \"purchase_order\", \"column_break_89\", \"material_request_item\", \"purchase_order_item\", \"marketplace\", \"marketplace_order_id\"]"
}
],
"sync_on_migrate": 1
}

@ -0,0 +1,133 @@
{
"custom_fields": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-11 00:16:50.544770",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Taxes and Charges",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "marketplace_order_id",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "accounting_dimensions_section",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Marketplace Order ID",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-11 00:16:50.544770",
"modified_by": "Administrator",
"module": null,
"name": "Sales Taxes and Charges-marketplace_order_id",
"no_copy": 0,
"non_negative": 0,
"options": "Marketplace Order ID",
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2024-04-11 00:12:54.982066",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "Sales Taxes and Charges",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "marketplace",
"fieldtype": "Link",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "dimension_col_break",
"is_system_generated": 1,
"is_virtual": 0,
"label": "Marketplace",
"length": 0,
"mandatory_depends_on": null,
"modified": "2024-04-11 00:12:54.982066",
"modified_by": "Administrator",
"module": null,
"name": "Sales Taxes and Charges-marketplace",
"no_copy": 0,
"non_negative": 0,
"options": "Marketplace",
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "Sales Taxes and Charges",
"links": [],
"property_setters": [],
"sync_on_migrate": 1
}

@ -0,0 +1,219 @@
{
"custom_fields": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2025-02-02 20:31:11.575254",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "SPS Integration Settings",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_default_item_group",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 15,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_default_uom",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Default Item Group",
"length": 0,
"mandatory_depends_on": null,
"modified": "2025-02-02 20:31:11.575254",
"modified_by": "Administrator",
"module": null,
"name": "SPS Integration Settings-custom_default_item_group",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2025-02-02 20:31:11.376121",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "SPS Integration Settings",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_default_uom",
"fieldtype": "Data",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 14,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "custom_defaults",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Default UOM",
"length": 0,
"mandatory_depends_on": null,
"modified": "2025-02-02 20:31:11.376121",
"modified_by": "Administrator",
"module": null,
"name": "SPS Integration Settings-custom_default_uom",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 1,
"unique": 0,
"width": null
},
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"creation": "2025-02-02 20:31:11.198473",
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"dt": "SPS Integration Settings",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "custom_defaults",
"fieldtype": "Tab Break",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"idx": 13,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "expires_on",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Defaults",
"length": 0,
"mandatory_depends_on": null,
"modified": "2025-02-02 20:31:11.198473",
"modified_by": "Administrator",
"module": null,
"name": "SPS Integration Settings-custom_defaults",
"no_copy": 0,
"non_negative": 0,
"options": null,
"owner": "Administrator",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"show_dashboard": 0,
"sort_options": 0,
"translatable": 0,
"unique": 0,
"width": null
}
],
"custom_perms": [],
"doctype": "SPS Integration Settings",
"links": [],
"property_setters": [
{
"_assign": null,
"_comments": null,
"_liked_by": null,
"_user_tags": null,
"creation": "2025-02-02 20:31:11.118583",
"default_value": null,
"doc_type": "SPS Integration Settings",
"docstatus": 0,
"doctype_or_field": "DocType",
"field_name": null,
"idx": 0,
"is_system_generated": 0,
"modified": "2025-02-02 20:31:11.118583",
"modified_by": "Administrator",
"module": null,
"name": "SPS Integration Settings-main-field_order",
"owner": "Administrator",
"property": "field_order",
"property_type": "Data",
"row_name": null,
"value": "[\"settings_name\", \"authenticate\", \"refresh_access_token\", \"redirect_uri\", \"sps_api_uri\", \"enabled\", \"column_break_ynjw\", \"client_secret\", \"client_id\", \"state\", \"access_token\", \"refresh_token\", \"expires_on\", \"defaults_tab\", \"default_uom\", \"default_item_group\"]"
}
],
"sync_on_migrate": 1
}

@ -0,0 +1,32 @@
// Copyright (c) 2025, UnifyXperts and contributors
// For license information, please see license.txt
frappe.ui.form.on("SPS Integration Settings", {
authenticate(frm) {
frappe.call({
method: "sps_integration.sps_integration.oauth.authenticate",
args: {
setting_doc_name: frm.doc.name
},
callback: function(r){
if(!r.exc){
window.open(r.message)
}
}
})
},
refresh_access_token(frm){
frappe.call({
method: "sps_integration.sps_integration.oauth.refresh_access_token",
args: {
setting_doc_name: frm.doc.name
},
// callback: function(r){
// if(!r.exc){
// window.open(r.message)
// }
// }
})
}
});

@ -0,0 +1,202 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:settings_name",
"creation": "2025-01-08 01:28:09.776008",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"settings_name",
"authenticate",
"refresh_access_token",
"redirect_uri",
"sps_api_uri",
"enabled",
"column_break_ynjw",
"client_secret",
"client_id",
"state",
"access_token",
"refresh_token",
"expires_on",
"default_tab",
"default_uom",
"default_item_group",
"marketplace",
"column_break_rxhv",
"customer_type",
"shipping_category",
"days",
"order_syncing_tab",
"sync_start_date",
"sync_orders",
"section_break_lafq",
"sync_duration"
],
"fields": [
{
"fieldname": "authenticate",
"fieldtype": "Button",
"label": "Authenticate"
},
{
"fieldname": "access_token",
"fieldtype": "Password",
"label": "Access Token"
},
{
"fieldname": "refresh_token",
"fieldtype": "Password",
"label": "Refresh Token"
},
{
"fieldname": "expires_on",
"fieldtype": "Datetime",
"label": "Expires on"
},
{
"fieldname": "settings_name",
"fieldtype": "Data",
"label": "Settings Name",
"unique": 1
},
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"fieldname": "sps_api_uri",
"fieldtype": "Data",
"label": "SPS API URI"
},
{
"fieldname": "redirect_uri",
"fieldtype": "Data",
"label": "Redirect URI",
"reqd": 1
},
{
"fieldname": "client_id",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Client ID",
"reqd": 1
},
{
"fieldname": "client_secret",
"fieldtype": "Password",
"in_list_view": 1,
"label": "Client Secret",
"reqd": 1
},
{
"fieldname": "column_break_ynjw",
"fieldtype": "Column Break"
},
{
"fieldname": "state",
"fieldtype": "Data",
"label": "State"
},
{
"fieldname": "refresh_access_token",
"fieldtype": "Button",
"label": "Refresh Access Token"
},
{
"fieldname": "default_tab",
"fieldtype": "Tab Break",
"label": "Default"
},
{
"fieldname": "default_uom",
"fieldtype": "Link",
"label": "Default UOM",
"options": "UOM"
},
{
"fieldname": "default_item_group",
"fieldtype": "Link",
"label": "Default Item Group",
"options": "Item Group"
},
{
"fieldname": "marketplace",
"fieldtype": "Link",
"label": "Marketplace",
"options": "Marketplace"
},
{
"fieldname": "column_break_rxhv",
"fieldtype": "Column Break"
},
{
"fieldname": "customer_type",
"fieldtype": "Data",
"label": "Customer Type"
},
{
"fieldname": "shipping_category",
"fieldtype": "Link",
"label": "Shipping Category",
"options": "Shipping Category for Item"
},
{
"fieldname": "days",
"fieldtype": "Int",
"label": "Days",
"non_negative": 1
},
{
"fieldname": "order_syncing_tab",
"fieldtype": "Tab Break",
"label": "Order Syncing"
},
{
"fieldname": "sync_start_date",
"fieldtype": "Date",
"label": "Sync Start Date"
},
{
"fieldname": "sync_orders",
"fieldtype": "Button",
"label": "Sync Orders"
},
{
"fieldname": "section_break_lafq",
"fieldtype": "Section Break"
},
{
"fieldname": "sync_duration",
"fieldtype": "Data",
"label": "Sync Duration"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-03-26 21:29:14.654011",
"modified_by": "Administrator",
"module": "SPS Integration",
"name": "SPS Integration Settings",
"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": []
}

@ -0,0 +1,10 @@
# Copyright (c) 2025, UnifyXperts and contributors
# For license information, please see license.txt
# import frappe
from frappe.model.document import Document
class SPSIntegrationSettings(Document):
pass

@ -0,0 +1,9 @@
# Copyright (c) 2025, UnifyXperts and Contributors
# See license.txt
# import frappe
from frappe.tests.utils import FrappeTestCase
class TestSPSIntegrationSettings(FrappeTestCase):
pass

@ -0,0 +1,112 @@
import requests
import frappe
from sps_integration.sps_integration.constants import AUTHORIZATION_URL
import secrets
import json
@frappe.whitelist()
def authenticate(setting_doc_name:str,**kwargs) -> None:
"""
"""
# Maintain Secret State
state = secrets.token_hex(10)
frappe.cache().set_value("state",state)
# Pre-requisites
setting_doc = frappe.get_doc("SPS Integration Settings",setting_doc_name)
setting_doc.state = state
setting_doc.save()
frappe.db.commit()
return AUTHORIZATION_URL(
audience="api://api.spscommerce.com/",
client_id=setting_doc.get_password("client_id"),
redirect_uri = setting_doc.redirect_uri,
response_type = "code",
state = state,
scope = "offline_access"
)
@frappe.whitelist(allow_guest=True)
def redirect_uri(**kwargs) -> None:
"""
"""
code = kwargs.get("code")
state = kwargs.get("state")
frappe.log_error(title="Code",message=f"{code},{state}")
if not state == frappe.cache().get_value("state"):
# frappe.respond_as_webpages()
return
setting_doc = frappe.get_doc("SPS Integration Settings",{"state":state})
# setting_doc.state = ""
# setting_doc.save()
# frappe.db.commit()
client_id = setting_doc.get_password('client_id')
client_secret = setting_doc.get_password('client_secret')
redirect_uri = setting_doc.redirect_uri
api_url = setting_doc.sps_api_uri
grant_type = "authorization_code"
headers = {
'Content-Type': 'application/json'
}
data = {
"client_id":client_id,
"client_secret":client_secret,
"grant_type":grant_type,
"code":code,
"redirect_uri":redirect_uri
}
url = api_url + "oauth/token"
response = requests.post(url = url,data=json.dumps(data),headers = headers)
if response.status_code in [200,201]:
response_json = response.json()
access_token = response_json.get("access_token")
refresh_token = response_json.get("refresh_token")
expires_in_sec = response_json.get('expires_in')
expires_in = frappe.utils.add_to_date(frappe.utils.now_datetime(),seconds=expires_in_sec)
setting_doc.access_token = access_token
setting_doc.refresh_token = refresh_token
setting_doc.expires_on = expires_in
setting_doc.state = ""
setting_doc.save()
frappe.db.commit()
else:
frappe.log_error(title= 'SPS API Error',message =f'Error in Access Token API Call \n\n Response:{response.text}')
# return data
@frappe.whitelist()
def refresh_access_token(setting_doc_name:str) -> None:
"""
"""
setting_doc = frappe.get_doc("SPS Integration Settings",setting_doc_name)
client_id = setting_doc.get_password('client_id')
client_secret = setting_doc.get_password('client_secret')
refresh_token = setting_doc.get_password('refresh_token')
api_url = setting_doc.sps_api_uri
url = api_url + "oauth/token"
headers = {
'Content-Type': 'application/json'
}
data = {
"client_id":client_id,
"client_secret":client_secret,
"grant_type":"refresh_token",
"refresh_token":refresh_token,
}
response = requests.post(url = url,data=json.dumps(data),headers = headers)
frappe.log_error(title="Response",message=f"{response.text}")
if response.status_code in [200,201]:
response_json = response.json()
access_token = response_json.get("access_token")
expires_in_sec = response_json.get('expires_in')
expires_in = frappe.utils.add_to_date(frappe.utils.now_datetime(),seconds=expires_in_sec)
setting_doc.access_token = access_token
setting_doc.expires_on = expires_in
setting_doc.save()
frappe.db.commit()
else:
frappe.log_error(title= 'SPS API Error',message =f'Error in Refreshing access Token \n\n Response:{response.text}')

@ -0,0 +1,328 @@
# import frappe
# import requests
# from .constants import SALES_ORDER_ACKNOWLEDGEMENT_URL
# def format_sales_order_ack(data: dict) -> dict:
# """
# Format sales order acknowledgement.
# Args:
# data (dict): Sales order payload.
# Returns:
# dict: A formatted sales order acknowledgement.
# """
# try:
# order_header = data.get("Header", {}).get("OrderHeader", {})
# line_item = data.get("LineItem", [])
# if not order_header or not line_item:
# frappe.log_error(title="Invalid Order Payload", message=str(data))
# print("Invalid order payload received.")
# return {}
# acknowledgement_payload = {
# "Header": {
# "OrderHeader": {
# "TradingPartnerId": order_header.get("TradingPartnerId", ""),
# "PurchaseOrderNumber": order_header.get("PurchaseOrderNumber"),
# "TsetPurposeCode": "00",
# "AcknowledgementType": "AK",
# "AcknowledgementNumber": "",
# "AcknowledgementDate": order_header.get("PurchaseOrderDate"),
# },
# "Dates": [
# {
# "DateTimeQualifier": "064",
# "Date": order_header.get("PurchaseOrderDate")
# }
# ]
# },
# "LineItem": [
# {
# "OrderLine": {
# "LineSequenceNumber": item.get("OrderLine", {}).get("LineSequenceNumber"),
# "VendorPartNumber": item.get("OrderLine", {}).get("VendorPartNumber"),
# "ConsumerPackageCode": item.get("OrderLine", {}).get("ConsumerPackageCode"),
# "OrderQty": item.get("OrderLine", {}).get("OrderQty"),
# "OrderQtyUOM": item.get("OrderLine", {}).get("OrderQtyUOM"),
# "PurchasePrice": item.get("OrderLine", {}).get("PurchasePrice"),
# },
# "LineItemAcknowledgement": [
# {
# "ItemStatusCode": "IA",
# "ItemScheduleQty": item.get("OrderLine", {}).get("OrderQty"),
# "ItemScheduleUOM": item.get("OrderLine", {}).get("OrderQtyUOM"),
# "ItemScheduleQualifier": "",
# "ItemScheduleDate": order_header.get("PurchaseOrderDate"),
# }
# ]
# }
# for item in line_item
# ]
# }
# print("Formatted Sales Order Acknowledgement Payload:", acknowledgement_payload)
# return acknowledgement_payload
# except Exception as e:
# frappe.log_error(title="SO ACK Payload Formatting Error", message=str(e))
# print(f"Error formatting payload: {str(e)}")
# return {}
# def sales_order_acknowledgement(data: dict):
# """Send Sales Order Acknowledgement."""
# try:
# print("Fetching SPS Integration Settings...")
# doc = frappe.get_doc("SPS Integration Settings", "Develop")
# access_token = doc.get_password("access_token")
# headers = {
# "Content-Type": "application/json; charset=utf-8",
# "Authorization": f"Bearer {access_token}"
# }
# formatted_data = format_sales_order_ack(data)
# if not formatted_data:
# print("Error: No data to send in Sales Order Acknowledgement.")
# return
# print("Sending Sales Order Acknowledgement...")
# response = requests.post(url=SALES_ORDER_ACKNOWLEDGEMENT_URL, headers=headers, json=formatted_data)
# print(f"Response Status Code: {response.status_code}")
# if response.status_code in [200, 201]:
# response_json = response.json()
# frappe.log_error(title="SO Acknowledgement Success", message=str(response_json))
# print("Sales Order Acknowledgement Successful:", response_json)
# else:
# frappe.log_error(title="SO Acknowledgement Failure", message=response.text)
# print("Sales Order Acknowledgement Failed:", response.text)
# except Exception as e:
# frappe.log_error(title="SO Acknowledgement Error", message=str(e))
# print(f"Error in Sales Order Acknowledgement: {str(e)}")
# data = {
# "Meta" : {
# "OrderManagement" : "SA"
# },
# "Header" : {
# "OrderHeader" : {
# "TradingPartnerId" : "0RXALLALPHARDGO",
# "PurchaseOrderNumber" : "WGE680227-29",
# "TsetPurposeCode" : "00",
# "PrimaryPOTypeCode" : "SA",
# "PurchaseOrderDate" : "2025-02-10",
# "Vendor" : "ALPHAR"
# },
# "Dates" : [ {
# "DateTimeQualifier" : "010",
# "Date" : "2025-02-28"
# }, {
# "DateTimeQualifier" : "001",
# "Date" : "2025-03-30"
# } ],
# "Contacts" : [ {
# "ContactTypeCode" : "BD",
# "ContactName" : "XAX XAX"
# } ],
# "Address" : [ {
# "AddressTypeCode" : "BT",
# "LocationCodeQualifier" : "92",
# "AddressLocationNumber" : "00",
# "AddressName" : "Worldwide Golf Enterprises",
# "Address1" : "1430 Village Way",
# "Address2" : "Suite J",
# "City" : "Santa Ana",
# "State" : "CA",
# "PostalCode" : "92705"
# }, {
# "AddressTypeCode" : "ST",
# "LocationCodeQualifier" : "92",
# "AddressLocationNumber" : "29",
# "AddressName" : "ROGER DUNN",
# "Address1" : "11849 Foothill Blvd. Ste. D",
# "City" : "RANCHO CUCAMONGA",
# "State" : "CA",
# "PostalCode" : "91730",
# "Country" : "US"
# } ],
# "Notes" : [ {
# "Note" : "https://commerce.spscommerce.com/fulfillment/redirect/?searchType=exact&documentType=Order&keyword=WGE680227-29"
# }, {
# "Note" : "https: //commerce.spscommerce.com/fulfillment-monitor"
# } ]
# },
# "LineItem" : [ {
# "OrderLine" : {
# "LineSequenceNumber" : "1",
# "VendorPartNumber" : "CYBERE-BL",
# "ConsumerPackageCode" : "849650001612",
# "OrderQty" : 1.0,
# "OrderQtyUOM" : "EA",
# "PurchasePrice" : 949.0
# },
# "ProductOrItemDescription" : [ {
# "ProductCharacteristicCode" : "08",
# "ProductDescription" : "CYBERCART"
# } ]
# } ],
# "Summary" : {
# "TotalAmount" : 949.0
# }
# }
import frappe
import requests
import json
# SPS Commerce API URL (Updated)
SPS_API_URL = "https://api.spscommerce.com/transactions/v5/data/testin/"
import json
import frappe
from datetime import datetime, timedelta
def format_sales_order_ack(data: dict) -> dict:
"""
Format sales order acknowledgment for SPS Commerce API.
Args:
data (dict): Sales order payload.
Returns:
dict: A formatted sales order acknowledgment.
"""
try:
order_header = data.get("Header", {}).get("OrderHeader", {})
line_items = data.get("LineItem", [])
if not order_header or not line_items:
frappe.log_error(title="Invalid Order Payload", message=str(data))
print("Invalid order payload received.")
return {}
# Get purchase order date and add 1 day
purchase_order_date_str = order_header.get("PurchaseOrderDate", "")
try:
purchase_order_date = datetime.strptime(purchase_order_date_str, "%Y-%m-%d")
acknowledgment_date = (purchase_order_date + timedelta(days=1)).strftime("%Y-%m-%d")
except ValueError:
# Default to current date +1 if purchaseOrderDate is invalid or missing
acknowledgment_date = (datetime.utcnow() + timedelta(days=1)).strftime("%Y-%m-%d")
acknowledgement_payload = {
"meta": {
"transactionType": "855"
},
"Header": {
"OrderHeader": {
"tradingPartnerId": order_header.get("TradingPartnerId", ""),
"purchaseOrderNumber": order_header.get("PurchaseOrderNumber"),
"acknowledgmentType": "AK",
"acknowledgmentDate": acknowledgment_date,
"purchaseOrderDate": purchase_order_date_str,
"vendor": order_header.get("Vendor")
}
},
"lineItems": [
{
"lineSequenceNumber": item.get("OrderLine", {}).get("LineSequenceNumber"),
"vendorPartNumber": item.get("OrderLine", {}).get("VendorPartNumber"),
"orderQty": item.get("OrderLine", {}).get("OrderQty"),
"orderQtyUOM": item.get("OrderLine", {}).get("OrderQtyUOM"),
"purchasePrice": item.get("OrderLine", {}).get("PurchasePrice"),
"acknowledgmentStatus": "IA",
"lineItemAcknowledgement": [
{
"ItemStatusCode": "IA",
"ItemScheduleQty": item.get("OrderLine", {}).get("OrderQty", 0),
"ItemScheduleUOM": item.get("OrderLine", {}).get("OrderQtyUOM", ""),
"ItemScheduleQualifier": "064",
"ItemScheduleDate": acknowledgment_date
}
]
}
for item in line_items
],
"notes": [
{
"noteCode": "GEN",
"noteDescription": ""
}
],
"summary": {
"totalAmount": sum(
float(item.get("OrderLine", {}).get("PurchasePrice", 0) or 0) *
float(item.get("OrderLine", {}).get("OrderQty", 0) or 0)
for item in line_items
)
}
}
print("Formatted Sales Order Acknowledgment Payload:", json.dumps(acknowledgement_payload, indent=2))
return acknowledgement_payload
except Exception as e:
frappe.log_error(title="SO ACK Payload Formatting Error", message=str(e))
print(f"Error formatting payload: {str(e)}")
return {}
def sales_order_acknowledgement(data: dict):
"""Send Sales Order Acknowledgment to SPS Commerce API."""
try:
print("Fetching SPS Integration Settings...")
doc = frappe.get_doc("SPS Integration Settings", "Develop")
access_token = doc.get_password("access_token")
headers = {
"Content-Type": "application/octet-stream",
"Authorization": f"Bearer {access_token}"
}
formatted_data = format_sales_order_ack(data)
if not formatted_data:
print("Error: No data to send in Sales Order Acknowledgment.")
return
# Debugging prints
print("Final JSON Payload:", json.dumps(formatted_data, indent=2))
print(f"Request URL: {SPS_API_URL}")
print(f"Headers: {headers}")
print(f"Sending Request to SPS Commerce...")
# Corrected API request (Using json= instead of data=)
response = requests.post(url=SPS_API_URL, headers=headers, data=json.dumps(formatted_data))
print(f"Response Status Code: {response.status_code}")
print(f"Response Headers: {response.headers}")
print(f"Response Text: {response.text}")
if response.status_code in [200, 201]:
response_json = response.json()
frappe.log_error(title="SO Acknowledgment Success", message=f"{response.text}")
print("Sales Order Acknowledgment Successful:", response_json)
else:
frappe.log_error(title="SO Acknowledgment Failure", message=f"{response.text}")
print("Sales Order Acknowledgment Failed:", response.text)
except Exception as e:
frappe.log_error(title="SO Acknowledgment Error", message=str(e))
print(f"Error in Sales Order Acknowledgment: {str(e)}")
# format_sales_order_ack(data)
# sales_order_acknowledgement(data)

@ -0,0 +1,363 @@
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:
create_sps_sales_order(
data=response.json(),
setting_doc=doc,
path=path
)
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=SPS_SO_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.
"""
print("Initializing Sales Order creation...")
# 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)
# print(f"Extracted CustomerOrderNumber: {consumer_order_number}, Marketplace Order ID: {mo_id}, PO Date: {po_date}")
# Get delivery date
delivery_date = next(
(date.get("Date") for date in data.get("Header", {}).get("Dates", [])
if date.get("DateTimeQualifier") == "001"), None
)
print(f"Extracted Delivery Date: {delivery_date}")
# Check if customer exists, otherwise create
_, customer_name = check_customer_if_exists(data)
# if not customer_exists:
# print("Customer not found, creating new customer...")
# customer_name = create_customer_if_not_exists(data, setting_doc)
# print(f"New customer created: {customer_name}")
# Check and create contact if not exists
contact_name = check_and_create_contact(data, setting_doc)
print("")
if contact_name:
print(f"Contact verified/created: {contact_name}")
# Create addresses if not exists
billing_address, shipping_address = create_address_contact(data, customer_name)
print(f"Billing Address: {billing_address}, Shipping Address: {shipping_address}")
# Get marketplace details
marketplace_data = frappe.get_list(
"Marketplace",
filters={"is_wholesale": 1},
fields=["name", "naming_series"],
as_list=True,
)
if not marketplace_data:
frappe.log_error("SPS Order Error", "No wholesale marketplace found.")
print("No wholesale marketplace found. Aborting order creation.")
return
marketplace_name, series_name = marketplace_data[0]
print(f"Marketplace found: {marketplace_name}, Series: {series_name}")
# 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
print(f"Created Sales Order Document: {sales_order.customer}")
# 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)
print(f"Added item: {sku}, Quantity: {quantity}, Amount: {amount}")
try:
print("Saving and submitting Sales Order...")
print(sales_order.__dict__) # Debugging
sales_order.save()
sales_order.submit()
frappe.db.commit()
print(f"Sales order successfully created and submitted: {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}",
)
print(f"Error while creating sales order: {e}")
data = {
"Meta" : {
"OrderManagement" : "SA"
},
"Header" : {
"OrderHeader" : {
"TradingPartnerId" : "0RXALLALPHARDGO",
"PurchaseOrderNumber" : "WGE686124-176",
"TsetPurposeCode" : "00",
"PrimaryPOTypeCode" : "SA",
"PurchaseOrderDate" : "2025-02-27",
"Vendor" : "ALPHAR"
},
"Dates" : [ {
"DateTimeQualifier" : "010",
"Date" : "2025-02-27"
}, {
"DateTimeQualifier" : "001",
"Date" : "2025-03-27"
} ],
"Contacts" : [ {
"ContactTypeCode" : "BD",
"ContactName" : "Roland Anderson"
} ],
"Address" : [ {
"AddressTypeCode" : "BT",
"LocationCodeQualifier" : "92",
"AddressLocationNumber" : "00",
"AddressName" : "Worldwide Golf Enterprises",
"Address1" : "1430 Village Way",
"Address2" : "Suite J",
"City" : "Santa Ana",
"State" : "CA",
"PostalCode" : "92705"
}, {
"AddressTypeCode" : "ST",
"LocationCodeQualifier" : "92",
"AddressLocationNumber" : "176",
"AddressName" : "GOLF & SKI WAREHOUSE",
"Address1" : "290 PLAINFIELD ROAD",
"City" : "WEST LEBANON",
"State" : "NH",
"PostalCode" : "03784",
"Country" : "US"
} ],
"Notes" : [ {
"Note" : "https://commerce.spscommerce.com/fulfillment/redirect/?searchType=exact&documentType=Order&keyword=WGE686124-176"
}, {
"Note" : "https: //commerce.spscommerce.com/fulfillment-monitor"
} ]
},
"LineItem" : [ {
"OrderLine" : {
"LineSequenceNumber" : "1",
"VendorPartNumber" : "CYBERE-BL",
"ConsumerPackageCode" : "849650001612",
"OrderQty" : 6.0,
"OrderQtyUOM" : "EA",
"PurchasePrice" : 949.0
},
"ProductOrItemDescription" : [ {
"ProductCharacteristicCode" : "08",
"ProductDescription" : "CYBERCART"
} ]
} ],
"Summary" : {
"TotalAmount" : 5694.0
}
}
# Trigger the order processing
create_sps_sales_order(data, setting_doc="Develop", path="")
# from sps_integration.sps_integration import order_sync

@ -0,0 +1,304 @@
import frappe
from typing import List, Dict, Tuple, Optional
def check_customer_if_exists(data: dict) -> Tuple[bool, Optional[str]]:
"""Check if customer exists."""
order_header = data.get("Header", {}).get("OrderHeader", {})
trading_partner_id = order_header.get("TradingPartnerId")
if trading_partner_id:
customer = frappe.db.get_value("Customer", {"custom_trading_partner_id": trading_partner_id}, "name")
return bool(customer), customer
return False, None
def check_address_exists_or_not(address: dict) -> Optional[str]:
"""Check if address exists and return its name."""
return frappe.db.exists("Address", {"address_line1": address.get("Address1")})
def check_postal_code_exists_or_not(address_name: str) -> bool:
"""Check if postal code exists for a given address."""
return bool(frappe.db.get_value("Address", address_name, "pincode"))
def create_or_update_address(address: dict, address_type: str, customer: str) -> str:
"""Create or update an address."""
address_name = check_address_exists_or_not(address)
postal_code = address.get("PostalCode")
if address_name:
# If address exists, ensure postal code is present only for shipping address
if address_type == "Shipping" and not check_postal_code_exists_or_not(address_name):
assert postal_code, f"Postal code is required for existing shipping address: {address_name}"
# Update address if postal code is provided in the payload
frappe.db.set_value("Address", address_name, "pincode", postal_code)
frappe.db.commit()
return address_name
# If address does not exist, ensure postal code is provided only for shipping address
if address_type == "Shipping":
assert postal_code, "Postal code is required for a new shipping address."
address_doc = frappe.new_doc("Address")
country = "United States" if address.get("Country") in ["US", "USA", "United States"] else address.get("Country")
address_doc.update({
"title": address.get("AddressName"),
"address_line1": address.get("Address1"),
"address_line2": address.get("Address2"),
"city": address.get("City"),
"state": address.get("State"),
"pincode": postal_code,
"country": country,
"address_type": address_type,
})
address_doc.append("links", {"link_doctype": "Customer", "link_name": customer})
address_doc.save()
frappe.db.commit()
return address_doc.name
def create_address_contact(data: dict, customer: str) -> Tuple[str, str]:
"""Create or retrieve billing and shipping addresses for the customer."""
addresses = data.get("Header", {}).get("Address", [])
billing_address_name, shipping_address_name = None, None
for address in addresses:
address_type = address.get("AddressTypeCode")
if address_type == "BT":
billing_address_name = create_or_update_address(address, "Billing", customer)
elif address_type == "ST":
shipping_address_name = create_or_update_address(address, "Shipping", customer)
return billing_address_name, shipping_address_name
def get_sps_item_code_if_exists(order_uom: str, description: str, sku: str, setting_doc: str) -> str:
"""Get or create item code."""
if frappe.db.exists("Item", {"item_code": sku}):
return sku
item_doc = frappe.new_doc("Item")
item_doc.update({
"item_code": sku,
"item_name": description,
"stock_uom": get_uom(order_uom) if order_uom != "EA" and order_uom != None else frappe.get_value("SPS Integration Settings", setting_doc, "default_uom"),
"item_group": frappe.get_value("SPS Integration Settings", setting_doc, "default_item_group"),
"custom_item_shipping_category": frappe.get_value("SPS Integration Settings", setting_doc, "shipping_category")
})
try:
item_doc.save()
frappe.db.commit()
except:
print(frappe.get_traceback())
return item_doc.name
def get_uom(uom: str) -> str:
"""Get or create UOM."""
if frappe.db.exists("UOM", uom):
return uom
new_uom = frappe.new_doc("UOM")
new_uom.uom_name = uom
new_uom.enabled = 1
new_uom.save()
frappe.db.commit()
return uom
def create_sps_sales_order_item_row(sku: str, qty: int, amount: float, uom: str, description: str, setting_doc: str) -> Dict:
"""Create sales order item row."""
return {
"item_code": get_sps_item_code_if_exists(uom, description, sku, setting_doc),
"qty": qty,
"rate": amount,
"custom_sku": sku,
}
# def get_sps_market_place_order_ID(market_place_order_id: str, setting_doc: str) -> str:
# """Get or create Marketplace Order ID."""
# if frappe.db.exists("Marketplace Order ID", market_place_order_id):
# return market_place_order_id
# doc = frappe.new_doc("Marketplace Order ID")
# doc.update({
# "marketplace_order_id": market_place_order_id,
# "marketplace": frappe.get_value("SPS Integration Settings", setting_doc, "marketplace"),
# })
# doc.save()
# frappe.db.commit()
# return doc.name
def get_sps_market_place_order_ID(market_place_order_id: str, setting_doc: str) -> str:
"""Get or create Marketplace Order ID."""
# Skip if market_place_order_id is empty or None
if not market_place_order_id:
frappe.log_error(
title="Missing Marketplace Order ID",
message=f"Marketplace Order ID is missing for setting_doc: {setting_doc}. Skipping creation."
)
return ""
# Check if the Marketplace Order ID already exists
if frappe.db.exists("Marketplace Order ID", market_place_order_id):
return market_place_order_id
# Create new Marketplace Order ID
try:
doc = frappe.new_doc("Marketplace Order ID")
doc.update({
"marketplace_order_id": market_place_order_id,
"marketplace": frappe.get_value("SPS Integration Settings", setting_doc, "marketplace"),
})
doc.save()
frappe.db.commit()
return doc.name
except Exception as e:
frappe.log_error(title="Error Creating Marketplace Order ID", message=frappe.get_traceback())
return ""
def get_link_row(docname: str, link_name: str) -> Dict:
"""Creates link doctype table row"""
row = {}
row["link_doctype"] = docname
row["link_name"] = link_name
return row
def check_and_create_contact(data, setting_doc):
"""Check if contacts exist, else create new contacts"""
print("Checking for contacts in data...")
addresses = data.get('Header', {}).get('Address', [])
contacts = None
for address in addresses:
if address.get("AddressTypeCode") == "BT" and address.get("Contacts"):
contacts = address["Contacts"]
print(f"Billing contact found: {contacts}")
break # Stop once billing contacts are found
if not contacts:
for address in addresses:
if address.get("AddressTypeCode") == "ST" and address.get("Contacts"):
contacts = address["Contacts"]
print(f"Shipping contact found: {contacts}")
break # Stop once shipping contacts are found
if not contacts:
print("No contacts available in the provided data.")
return None
# Check if customer exists
print("Checking if customer exists...")
customer_exists, customer_name = check_customer_if_exists(data)
print(f"Customer Exists: {customer_exists}, Customer Name: {customer_name}")
if customer_exists:
# Check if the contact email already exists
email_id = contacts[0].get("PrimaryEmail", "")
print(f"Checking if contact email {email_id} exists in the system...")
if frappe.db.exists("Contact Email", {"email_id": email_id}):
contact_name = frappe.get_value(
"Contact Email", {"email_id": email_id}, "parent"
)
print(f"Existing contact found: {contact_name}")
else:
print("No existing contact found. Creating a new contact...")
doc = frappe.new_doc("Contact")
doc.first_name = customer_name
print(f"Setting contact first name: {customer_name}")
if email_id:
email_row = {
"email_id": email_id,
"is_primary": 1
}
doc.append("email_ids", email_row)
print(f"Added email: {email_id}")
phone_number = contacts[0].get("PrimaryPhone", "")
if phone_number:
num_row = {
"phone": phone_number,
"is_primary_phone": 1
}
doc.append("phone_nos", num_row)
print(f"Added phone number: {phone_number}")
link_row = get_link_row("Customer", customer_name)
doc.append("links", link_row)
print(f"Linked contact to customer: {customer_name}")
doc.save()
frappe.db.commit()
contact_name = doc.name
return contact_name
def create_customer_if_not_exists(data: dict, setting_doc: str) -> str:
"""
Create new customer if not exists.
Args:
data (Dict): Payload from webhook
setting_doc (str): setting doc name
Returns:
customer_name (str): Return customer name after creation of customer.
"""
print("Checking Trading Partner ID in the payload...")
trading_id = data.get("Header", {}).get("OrderHeader", {}).get("TradingPartnerId")
print(f"Trading Partner ID: {trading_id}")
print("Fetching addresses from payload...")
addresses = data.get("Header", {}).get("Address", [])
address_names = [address.get("AddressName") for address in addresses]
print(f"Extracted Address Names: {address_names}")
print("Checking if customer already exists...")
if frappe.db.exists("Customer", {"custom_trading_partner_id": trading_id}):
customer_name = frappe.get_value(
"Customer",
{"custom_trading_partner_id": trading_id},
"name"
)
print(f"Customer already exists: {customer_name}")
else:
print("Customer does not exist. Creating a new customer...")
doc = frappe.new_doc("Customer")
doc.custom_trading_partner_id = trading_id
doc.customer_name = address_names[0].capitalize()
print(f"Assigned customer name: {doc.customer_name}")
doc.customer_type = frappe.get_value(
"SPS Integration Settings", setting_doc, "customer_type"
)
print(f"Fetched customer type from settings: {doc.customer_type}")
doc.save()
frappe.db.commit()
print(f"New customer created: {doc.name}")
for address in addresses:
address_type = address.get("AddressTypeCode")
print(f"Processing address type: {address_type}")
if address_type == "BT":
print("Billing address found, creating or updating address...")
doc.customer_primary_contact = create_or_update_address(address, "Billing", doc.name)
print(f"Primary contact assigned: {doc.customer_primary_contact}")
doc.save()
frappe.db.commit()
print(f"Customer document saved: {doc.name}")
return doc.name

@ -0,0 +1,27 @@
import frappe
def fetch_SPS_label():
"""
Enqueue
"""
pass
def push_SPS_label_extensiv():
"""
"""
pass
def push_asn_to_sps():
"""
Enqueue
"""
pass
def on_submit(doc,method):
try:
if doc.doctype == "Delivery Note":
pass
else:
raise Exception("Invalid Doctype")
except:
frappe.log_error(title="",message="")

@ -0,0 +1,33 @@
import frappe
def validate_api_user(api_key: str, api_secret: str):
"""
Validate the api user's credentials.
args:
api_key: str
api_secret: str
return:
True | False: bool
"""
try:
if not api_key or not api_secret:
raise frappe.AuthenticationError("Missing API key or secret")
user = frappe.db.get_value("User", {"api_key": api_key}, "name")
if not user:
raise frappe.AuthenticationError("Invalid API key")
stored_api_secret = frappe.utils.password.get_decrypted_password("User", user, "api_secret")
if stored_api_secret != api_secret:
raise frappe.AuthenticationError("Invalid API secret")
return True
except frappe.AuthenticationError as auth_err:
frappe.log_error(message=str(auth_err), title="API Authentication Failed")
return False
except Exception as e:
frappe.log_error(message=frappe.get_traceback(), title="Unexpected Error in API Authentication")
return False
Loading…
Cancel
Save