From 79ac5a5fa35c38e932a54971e94947e475ec4ee5 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Mon, 20 Jan 2025 17:40:25 +0530 Subject: [PATCH 01/22] feat: add shipment doctype --- .../doctype/pickup_location/__init__.py | 0 .../pickup_location/pickup_location.json | 40 ++++++ .../pickup_location/pickup_location.py | 9 ++ .../doctype/service_provider/__init__.py | 0 .../service_provider/service_provider.js | 8 ++ .../service_provider/service_provider.json | 46 +++++++ .../service_provider/service_provider.py | 9 ++ .../service_provider/test_service_provider.py | 9 ++ .../doctype/shipping_provider/__init__.py | 0 .../shipping_provider/shipping_provider.js | 8 ++ .../shipping_provider/shipping_provider.json | 120 ++++++++++++++++++ .../shipping_provider/shipping_provider.py | 9 ++ .../test_shipping_provider.py | 9 ++ 13 files changed, 267 insertions(+) create mode 100644 erpnext_shipping/erpnext_shipping/doctype/pickup_location/__init__.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.json create mode 100644 erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/service_provider/__init__.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.js create mode 100644 erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.json create mode 100644 erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/service_provider/test_service_provider.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/shipping_provider/__init__.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.js create mode 100644 erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json create mode 100644 erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py create mode 100644 erpnext_shipping/erpnext_shipping/doctype/shipping_provider/test_shipping_provider.py diff --git a/erpnext_shipping/erpnext_shipping/doctype/pickup_location/__init__.py b/erpnext_shipping/erpnext_shipping/doctype/pickup_location/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.json b/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.json new file mode 100644 index 0000000..14bcfaf --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-20 17:28:14.715524", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "location" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company" + }, + { + "fieldname": "location", + "fieldtype": "Data", + "in_list_view": 1, + "label": " Location", + "mandatory_depends_on": "eval:doc.company" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2025-01-20 17:35:31.288582", + "modified_by": "Administrator", + "module": "ERPNext Shipping", + "name": "Pickup Location", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} \ No newline at end of file diff --git a/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.py b/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.py new file mode 100644 index 0000000..26aa77f --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/pickup_location/pickup_location.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class PickupLocation(Document): + pass diff --git a/erpnext_shipping/erpnext_shipping/doctype/service_provider/__init__.py b/erpnext_shipping/erpnext_shipping/doctype/service_provider/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.js b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.js new file mode 100644 index 0000000..e99e706 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Service Provider", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.json b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.json new file mode 100644 index 0000000..ff546c2 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.json @@ -0,0 +1,46 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:provider_name", + "creation": "2025-01-20 15:46:31.823082", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "provider_name" + ], + "fields": [ + { + "fieldname": "provider_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Provider Name", + "reqd": 1, + "unique": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-01-20 15:46:31.823082", + "modified_by": "Administrator", + "module": "ERPNext Shipping", + "name": "Service Provider", + "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/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.py b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.py new file mode 100644 index 0000000..6b1a09f --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/service_provider/service_provider.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ServiceProvider(Document): + pass diff --git a/erpnext_shipping/erpnext_shipping/doctype/service_provider/test_service_provider.py b/erpnext_shipping/erpnext_shipping/doctype/service_provider/test_service_provider.py new file mode 100644 index 0000000..0adcecb --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/service_provider/test_service_provider.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestServiceProvider(FrappeTestCase): + pass diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/__init__.py b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.js b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.js new file mode 100644 index 0000000..d4c4d32 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.js @@ -0,0 +1,8 @@ +// Copyright (c) 2025, Frappe and contributors +// For license information, please see license.txt + +// frappe.ui.form.on("Shipping Provider", { +// refresh(frm) { + +// }, +// }); diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json new file mode 100644 index 0000000..a41c1e7 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json @@ -0,0 +1,120 @@ +{ + "actions": [], + "allow_rename": 1, + "creation": "2025-01-20 15:47:26.192870", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "enable", + "prefer", + "service_provider", + "source_country", + "company", + "reference_url", + "authentication_type", + "user_key", + "user_secret", + "api_key", + "pickup_location" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable", + "fieldtype": "Check", + "label": "Enable" + }, + { + "default": "0", + "fieldname": "prefer", + "fieldtype": "Check", + "label": "Prefer" + }, + { + "fieldname": "service_provider", + "fieldtype": "Link", + "label": "Service provider", + "options": "Service Provider", + "reqd": 1 + }, + { + "depends_on": "eval:doc.service_provider", + "fieldname": "source_country", + "fieldtype": "Link", + "label": "Source Country", + "options": "Country" + }, + { + "depends_on": "eval:doc.service_provider", + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "mandatory_depends_on": "eval:doc.service_provider", + "options": "Company" + }, + { + "depends_on": "eval:doc.service_provider", + "fieldname": "reference_url", + "fieldtype": "Data", + "label": "Reference URL" + }, + { + "depends_on": "eval:doc.authentication_type == \"OAuth\"", + "fieldname": "user_key", + "fieldtype": "Data", + "label": "User Key" + }, + { + "depends_on": "eval:doc.authentication_type == \"OAuth\"", + "fieldname": "user_secret", + "fieldtype": "Password", + "label": "User Secret" + }, + { + "depends_on": "eval:doc.authentication_type == \"API Key\"", + "fieldname": "api_key", + "fieldtype": "Password", + "label": "API key" + }, + { + "depends_on": "eval:doc.service_provider", + "fieldname": "authentication_type", + "fieldtype": "Select", + "label": "Authentication Type", + "options": "\nOAuth\nAPI Key", + "reqd": 1 + }, + { + "depends_on": "eval:doc.authentication_type", + "fieldname": "pickup_location", + "fieldtype": "Table", + "label": "Pickup Location", + "options": "Pickup Location" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2025-01-20 17:34:58.420952", + "modified_by": "Administrator", + "module": "ERPNext Shipping", + "name": "Shipping Provider", + "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/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py new file mode 100644 index 0000000..2280e2a --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and contributors +# For license information, please see license.txt + +# import frappe +from frappe.model.document import Document + + +class ShippingProvider(Document): + pass diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/test_shipping_provider.py b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/test_shipping_provider.py new file mode 100644 index 0000000..61b7db2 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/test_shipping_provider.py @@ -0,0 +1,9 @@ +# Copyright (c) 2025, Frappe and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestShippingProvider(FrappeTestCase): + pass From f53ea85e30bbfeb6116d4b63adf1b8a6d52e32e9 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 21 Jan 2025 10:58:59 +0530 Subject: [PATCH 02/22] feat:add shiprocket integration --- .../doctype/shipping_provider/shipping_provider.json | 12 ++++++++++-- .../erpnext_shipping/shiprocket/shiprocket.py | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json index a41c1e7..483008d 100644 --- a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json @@ -15,7 +15,8 @@ "user_key", "user_secret", "api_key", - "pickup_location" + "pickup_location", + "barer_key" ], "fields": [ { @@ -91,11 +92,18 @@ "fieldtype": "Table", "label": "Pickup Location", "options": "Pickup Location" + }, + { + "fieldname": "barer_key", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Barer Key", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-20 17:34:58.420952", + "modified": "2025-01-21 10:39:01.316933", "modified_by": "Administrator", "module": "ERPNext Shipping", "name": "Shipping Provider", diff --git a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py new file mode 100644 index 0000000..978f491 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py @@ -0,0 +1,2 @@ +import frappe +import request From 21adba702e8d33a4a6e470ec99a7f66846c7cbb2 Mon Sep 17 00:00:00 2001 From: rethik Date: Tue, 21 Jan 2025 12:14:05 +0530 Subject: [PATCH 03/22] feat: delhivery integration --- erpnext_shipping/erpnext_shipping/shipping.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/erpnext_shipping/erpnext_shipping/shipping.py b/erpnext_shipping/erpnext_shipping/shipping.py index 0db203d..946ee9f 100644 --- a/erpnext_shipping/erpnext_shipping/shipping.py +++ b/erpnext_shipping/erpnext_shipping/shipping.py @@ -5,6 +5,7 @@ import frappe from erpnext.stock.doctype.shipment.shipment import get_company_contact +from erpnext_shipping.erpnext_shipping.delhivery_one.delhivery_one import DelhiveryOneUtils from erpnext_shipping.erpnext_shipping.doctype.letmeship.letmeship import ( LETMESHIP_PROVIDER, get_letmeship_utils, @@ -34,6 +35,7 @@ def fetch_shipping_rates( shipment_prices = [] letmeship_enabled = frappe.db.get_single_value("LetMeShip", "enabled") sendcloud_enabled = frappe.db.get_single_value("SendCloud", "enabled") + delhivery_one_enabled = frappe.db.get_value("Shipping Provider", "c0jp3n9u1g", "enable") pickup_address = get_address(pickup_address_name) delivery_address = get_address(delivery_address_name) parcels = json.loads(parcels) @@ -79,6 +81,15 @@ def fetch_shipping_rates( sendcloud_prices = match_parcel_service_type_carrier(sendcloud_prices, "carrier", "service_name") shipment_prices += sendcloud_prices + if delhivery_one_enabled and pickup_from_type == "Company": + delhivery = DelhiveryOneUtils() + delhivery_prices = ( + delhivery.get_available_services( + delivery_address=delivery_address, pickup_address=pickup_address, weight=1000 + ) + or [] + ) + shipment_prices = sorted(shipment_prices, key=lambda k: k["total_price"]) return shipment_prices From 5fc9712afbf2f9c9eda6db2047b1a9776b189139 Mon Sep 17 00:00:00 2001 From: rethik Date: Tue, 21 Jan 2025 12:17:23 +0530 Subject: [PATCH 04/22] feat: get available service --- .../delhivery_one/delhivery_one.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 erpnext_shipping/erpnext_shipping/delhivery_one/delhivery_one.py diff --git a/erpnext_shipping/erpnext_shipping/delhivery_one/delhivery_one.py b/erpnext_shipping/erpnext_shipping/delhivery_one/delhivery_one.py new file mode 100644 index 0000000..e4c2d08 --- /dev/null +++ b/erpnext_shipping/erpnext_shipping/delhivery_one/delhivery_one.py @@ -0,0 +1,82 @@ +import frappe +import requests +from frappe import _ +from requests.exceptions import HTTPError + +from erpnext_shipping.erpnext_shipping.utils import show_error_alert + + +class DelhiveryOneUtils: + def __init__(self): + settings = frappe.get_doc("Shipping Provider", "c0jp3n9u1g") + self.service_provider = settings.service_provider + self.company = settings.company + self.api_key = settings.get_password("api_key") + self.enable = settings.enable + + if not self.enable: + frappe.throw( + "Delhivery One Integration is disabled. Please enable it in Shipping Provider Settings." + ) + + def get_available_services(self, delivery_address, pickup_address, weight=1000): + if not self.enable and not self.api_key: + return [] + self.get_waybill() + pickup_code = pickup_address.pickup_code + delhivery_code = pickup_address.delhivery_code + + headers = {"Content-Type": "application/json"} + + url = "https://staging-express.delhivery.com/api/kinko/v1/invoice/charges/json" + params = { + "token": self.api_key, + "ss": "Delivered", + "d_pin": delhivery_code, + "o_pin": pickup_code, + "cgm": weight, + "pt": "Pre-paid", + "cod": 0, + } + + try: + response = requests.get(url, headers=headers, params=params) + + print("Response Status Code:", response.status_code) + print("Response Content:", response.text) + + if response.status_code == 200: + return response.json() + else: + print(f"Failed to fetch waybill: {response.status_code} - {response.text}") + return [] + except HTTPError as http_err: + print(f"HTTP error occurred: {http_err}") + return [] + except Exception as err: + print(f"An error occurred: {err}") + return [] + + def get_waybill(self): + headers = {"Content-Type": "application/json"} + + url = "https://track.delhivery.com/waybill/api/bulk/json/" + params = {"token": self.api_key, "count": 1} + + try: + response = requests.get(url, headers=headers, params=params) + + print("Response Status Code:", response.status_code) + print("Response Content:", response.text) + + if response.status_code == 200: + return response.json() + else: + print(f"Failed to fetch waybill: {response.status_code} - {response.text}") + return [] + except HTTPError as http_err: + print(f"HTTP error occurred: {http_err}") + return [] + except Exception as err: + print(f"An error occurred: {err}") + return [] From 371adfe4653e5ecde2b0f5dd19aa5944c23c0d9c Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 21 Jan 2025 13:26:40 +0530 Subject: [PATCH 05/22] feat: generate the barer key --- .../shipping_provider/shipping_provider.json | 4 ++- .../shipping_provider/shipping_provider.py | 11 ++++++-- .../erpnext_shipping/shiprocket/shiprocket.py | 26 ++++++++++++++++++- erpnext_shipping/hooks.py | 11 +++++++- 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json index 483008d..972a60e 100644 --- a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.json @@ -1,6 +1,7 @@ { "actions": [], "allow_rename": 1, + "autoname": "format:{service_provider}-{company}-{####}", "creation": "2025-01-20 15:47:26.192870", "doctype": "DocType", "engine": "InnoDB", @@ -103,10 +104,11 @@ ], "index_web_pages_for_search": 1, "links": [], - "modified": "2025-01-21 10:39:01.316933", + "modified": "2025-01-21 12:13:00.226898", "modified_by": "Administrator", "module": "ERPNext Shipping", "name": "Shipping Provider", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { diff --git a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py index 2280e2a..8a673e7 100644 --- a/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py +++ b/erpnext_shipping/erpnext_shipping/doctype/shipping_provider/shipping_provider.py @@ -1,9 +1,16 @@ # Copyright (c) 2025, Frappe and contributors # For license information, please see license.txt -# import frappe +import frappe from frappe.model.document import Document +from erpnext_shipping.erpnext_shipping.shiprocket.shiprocket import generate_token + class ShippingProvider(Document): - pass + def validate(self): + if self.service_provider == "Shiprocket": + try: + generate_token(self) + except Exception as e: + frappe.log_error(title="Error generating token", message=str(e)) diff --git a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py index 978f491..05b86c2 100644 --- a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py +++ b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py @@ -1,2 +1,26 @@ +import json + import frappe -import request +import requests +from frappe.utils.password import get_decrypted_password + + +@frappe.whitelist() +def generate_token(doc): + try: + url = "https://apiv2.shiprocket.in/v1/external/auth/login" + + payload = json.dumps({"email": doc.user_key, "password": doc.get_password("user_secret")}) + headers = {"Content-Type": "application/json"} + + response = requests.request("POST", url, headers=headers, data=payload) + + response_dict = json.loads(response.text) + if "token" in response_dict: + token = response_dict["token"] + doc.barer_key = token + else: + frappe.throw("Invalid email and password combination.") + + except requests.exceptions.RequestException as e: + frappe.log_error(title="Shiprocket Authentication Error", message=str(e)) diff --git a/erpnext_shipping/hooks.py b/erpnext_shipping/hooks.py index 9f7fdf8..fded565 100644 --- a/erpnext_shipping/hooks.py +++ b/erpnext_shipping/hooks.py @@ -195,5 +195,14 @@ "translatable": 0, "insert_after": "tracking_status", }, - ] + ], + "Shipment": [ + { + "fieldname": "net_total_weight", + "label": "Total weight", + "fieldtype": "Float", + "insert_after": "shipment_parcel", + "read_only": 1, + }, + ], } From 79fa2b01eb3e383afc992feb4c0d6d9ed31ccebf Mon Sep 17 00:00:00 2001 From: Sowmiya Date: Tue, 21 Jan 2025 17:15:01 +0530 Subject: [PATCH 06/22] feat: add the total weight --- .../erpnext_shipping/shiprocket/shiprocket.py | 7 +++++++ erpnext_shipping/public/js/shipment.js | 18 +++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py index 05b86c2..6fce6c7 100644 --- a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py +++ b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py @@ -24,3 +24,10 @@ def generate_token(doc): except requests.exceptions.RequestException as e: frappe.log_error(title="Shiprocket Authentication Error", message=str(e)) + + + +@frappe.whitelist() +def calculate_total_weight(shipment_parcel): + total_weight = sum([parcels.get("weight",0) for parcels in shipment_parcel]) + return total_weight \ No newline at end of file diff --git a/erpnext_shipping/public/js/shipment.js b/erpnext_shipping/public/js/shipment.js index 3424eaa..99fe92d 100644 --- a/erpnext_shipping/public/js/shipment.js +++ b/erpnext_shipping/public/js/shipment.js @@ -114,7 +114,7 @@ frappe.ui.form.on("Shipment", { }, }); }, - + update_tracking: function (frm, service_provider, shipment_id) { let delivery_notes = []; (frm.doc.shipment_delivery_note || []).forEach((d) => { @@ -229,3 +229,19 @@ function select_from_available_services(frm, available_services) { }; dialog.show(); } + + +frappe.ui.form.on('Shipment Parcel', { + weight: function (frm, cdt, cdn){ + let row = frappe.get_doc(cdt, cdn); + if (row.weight) { + let net_total_weight = 0; + frm.doc.shipment_parcel.forEach(function (item) { + net_total_weight += item.weight || 0; + }); + + frm.set_value('net_total_weight', net_total_weight); + console.log(net_total_weight) + } + } +}); \ No newline at end of file From b24f1a8db73b4c86eb04cbe0a56489a05c7bdca1 Mon Sep 17 00:00:00 2001 From: ravibharathi656 Date: Tue, 21 Jan 2025 18:17:04 +0530 Subject: [PATCH 07/22] feat: update to fetch the shiprocket shipment rates --- erpnext_shipping/erpnext_shipping/shipping.py | 34 ++++ .../erpnext_shipping/shiprocket/shiprocket.py | 173 ++++++++++++++++++ erpnext_shipping/public/js/shipment.js | 1 + .../public/js/shipment_service_selector.html | 4 +- 4 files changed, 210 insertions(+), 2 deletions(-) diff --git a/erpnext_shipping/erpnext_shipping/shipping.py b/erpnext_shipping/erpnext_shipping/shipping.py index 0db203d..bfc1568 100644 --- a/erpnext_shipping/erpnext_shipping/shipping.py +++ b/erpnext_shipping/erpnext_shipping/shipping.py @@ -10,6 +10,11 @@ get_letmeship_utils, ) from erpnext_shipping.erpnext_shipping.doctype.sendcloud.sendcloud import SENDCLOUD_PROVIDER, SendCloudUtils +from erpnext_shipping.erpnext_shipping.shiprocket.shiprocket import ( + SHIPROCKET_PROVIDER, + create_shiprocket_shipment, + get_available_services, +) from erpnext_shipping.erpnext_shipping.utils import ( get_address, get_contact, @@ -29,6 +34,7 @@ def fetch_shipping_rates( value_of_goods, pickup_contact_name=None, delivery_contact_name=None, + pickup_company=None, ): # Return Shipping Rates for the various Shipping Providers shipment_prices = [] @@ -38,6 +44,17 @@ def fetch_shipping_rates( delivery_address = get_address(delivery_address_name) parcels = json.loads(parcels) + if pickup_company: + shipping_providers = frappe.get_list( + "Shipping Provider", + filters={"service_provider": "Shiprocket", "company": pickup_company, "enable": True}, + pluck="barer_key", + ) + shiprocket_prices = get_available_services( + shipping_providers[0], parcels, delivery_address_name, pickup_address_name + ) + shiprocket_prices = match_parcel_service_type_carrier(shiprocket_prices, "carrier", "service_name") + shipment_prices += shiprocket_prices if letmeship_enabled: pickup_contact = None delivery_contact = None @@ -151,6 +168,23 @@ def create_shipment( service_info=service_info, ) + if service_info["service_provider"] == SHIPROCKET_PROVIDER: + shipment = frappe.get_doc("Shipment", shipment) + create_shiprocket_shipment( + shipment=shipment, + token=service_info.get("token"), + pickup_address=pickup_address, + delivery_company_name=delivery_company_name, + delivery_address=delivery_address, + shipment_parcel=shipment_parcel, + description_of_content=description_of_content, + pickup_date=pickup_date, + value_of_goods=value_of_goods, + pickup_contact=pickup_contact, + delivery_contact=delivery_contact, + service_info=service_info, + ) + if shipment_info: shipment = frappe.get_doc("Shipment", shipment) shipment.db_set( diff --git a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py index 05b86c2..8e0bab3 100644 --- a/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py +++ b/erpnext_shipping/erpnext_shipping/shiprocket/shiprocket.py @@ -4,6 +4,8 @@ import requests from frappe.utils.password import get_decrypted_password +SHIPROCKET_PROVIDER = "Shiprocket" + @frappe.whitelist() def generate_token(doc): @@ -24,3 +26,174 @@ def generate_token(doc): except requests.exceptions.RequestException as e: frappe.log_error(title="Shiprocket Authentication Error", message=str(e)) + + +@frappe.whitelist() +def get_shiprocket_shippments(): + pass + + +def get_available_services(token, parcels, delivery_address_name, pickup_address_name, total_weight=None): + weight = "10" + pickup_postcode = get_post_code(pickup_address_name) + delivery_postcode = get_post_code(delivery_address_name) + if not token: + return + url = "https://apiv2.shiprocket.in/v1/external/courier/serviceability" + headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} + payload = { + "pickup_postcode": pickup_postcode, + "delivery_postcode": delivery_postcode, + "cod": 0, + "weight": weight, + } + response = requests.get(url, json=payload, headers=headers) + if response.status_code == 200: + services_available = response.json().get("data", []) + services_available = services_available["available_courier_companies"] + available_services = [] + for services in services_available: + if services and check_weight(parcels, services): + available_service = get_service_dict(services, parcels, token) + available_services.append(available_service) + return available_services + else: + return {"error": response.json()} + + +def check_weight(parcels, services): + chnarge_weight = float(services["charge_weight"]) + + +def get_post_code(address_name): + if not frappe.db.exists("Address", address_name): + return + address = frappe.get_doc("Address", address_name) + return address.pincode + + +def get_service_dict(service, parcels: list[dict], token): + """Returns a dictionary with service info.""" + available_service = frappe._dict() + available_service.service_provider = "Shiprocket" + available_service.carrier = str(service["courier_company_id"]) + available_service.service_name = service["courier_name"] + available_service.currency = "INR" + available_service.token = token + price = service["freight_charge"] + available_service.total_price = total_parcel_price(price, parcels) + + available_service.service_id = service["id"] + + return available_service + + +def total_parcel_price(price, parcels): + count = 0 + for parcel in parcels: + count += parcel.get("count") + return float(price) * count + + +def create_shiprocket_shipment( + shipment, + token, + pickup_address, + delivery_company_name, + delivery_address, + shipment_parcel, + description_of_content, + pickup_date, + value_of_goods, + service_info, + pickup_contact=None, + delivery_contact=None, +): + if not (shipment, token): + return + shipment_name = shipment.name + + url = "https://apiv2.shiprocket.in/v1/external/orders/create/adhoc" + + shipment_parcel = json.loads(shipment_parcel) + parcels = [ + { + "length": parcel["length"], + "breadth": parcel["width"], + "height": parcel["height"], + "weight": parcel["weight"], + "units": parcel["count"], + } + for parcel in shipment_parcel + ] + + payload = { + "order_id": shipment_name, + "order_date": pickup_date, + "channel_id": "", + "comment": "", + "reseller_name": "", + "company_name": delivery_company_name, + "billing_customer_name": delivery_contact["first_name"], + "billing_last_name": delivery_contact["last_name"], + "billing_address": delivery_address["address_line1"], + "billing_address_2": delivery_address["address_line2"], + "billing_isd_code": "", + "billing_city": delivery_address["city"], + "billing_pincode": delivery_address["pincode"], + "billing_state": "Tamil Nadu", + "billing_country": delivery_address["country"], + "billing_email": delivery_contact["email_id"], + "billing_phone": delivery_contact["phone"], + "billing_alternate_phone": "", + "shipping_is_billing": True, + "shipping_customer_name": pickup_contact["first_name"], + "shipping_last_name": pickup_contact["last_name"], + "shipping_address": pickup_address["address_line1"], + "shipping_address_2": pickup_address["address_line2"], + "shipping_city": pickup_address["city"], + "shipping_pincode": pickup_address["pincode"], + "shipping_country": pickup_address["country"], + "shipping_state": "Tamil Nadu", + "shipping_email": pickup_contact["email_id"], + "shipping_phone": pickup_contact["phone"], + "order_items": [ + { + "name": description_of_content, + "sku": "N/A", + "units": sum(parcel["count"] for parcel in shipment_parcel), + "selling_price": value_of_goods, + "discount": "", + "tax": "", + "hsn": "", + } + ], + "payment_method": "cod", + "shipping_charges": service_info["total_price"], + "giftwrap_charges": "", + "transaction_charges": "", + "total_discount": "", + "sub_total": int(value_of_goods) * sum(parcel["count"] for parcel in shipment_parcel), + "length": parcels[0]["length"], + "breadth": parcels[0]["breadth"], + "height": parcels[0]["height"], + "weight": parcels[0]["weight"], + "ewaybill_no": "", + "customer_gstin": "", + "invoice_number": "", + "order_type": "", + "pickup_location": "work", + "courier_company_id": "", + } + + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {token}", + } + + response = requests.post(url, json=payload, headers=headers) + + if response.status_code == 200: + print("Shipment created successfully:", response.json()) + else: + print("Failed to create shipment:", response.json()) diff --git a/erpnext_shipping/public/js/shipment.js b/erpnext_shipping/public/js/shipment.js index 3424eaa..e132499 100644 --- a/erpnext_shipping/public/js/shipment.js +++ b/erpnext_shipping/public/js/shipment.js @@ -69,6 +69,7 @@ frappe.ui.form.on("Shipment", { : frm.doc.pickup_contact_name, delivery_contact_name: frm.doc.delivery_contact_name, value_of_goods: frm.doc.value_of_goods, + pickup_company: frm.doc.pickup_company, }, callback: function (r) { if (r.message && r.message.length) { diff --git a/erpnext_shipping/public/js/shipment_service_selector.html b/erpnext_shipping/public/js/shipment_service_selector.html index 0aed8ea..c6f41bb 100644 --- a/erpnext_shipping/public/js/shipment_service_selector.html +++ b/erpnext_shipping/public/js/shipment_service_selector.html @@ -16,7 +16,7 @@
{{ __("Preferred Services") }}
{{ data.preferred_services[i].service_provider }} {{ data.preferred_services[i].carrier }} {{ data.preferred_services[i].service_name }} - {{ format_currency(data.preferred_services[i].total_price, "EUR", 2) }} + {{ format_currency(data.preferred_services[i].total_price, data.preferred_services[i].currency, 2) }}