feat(shipment): Shipment Doctype with Integrations

This commit is contained in:
jbienesdev
2020-07-13 16:25:09 +08:00
parent d8aeaed6ab
commit 1c9410e5e8
56 changed files with 3721 additions and 1 deletions

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('LetMeShip', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,55 @@
{
"actions": [],
"creation": "2020-07-23 10:55:19.669830",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"api_id",
"api_password"
],
"fields": [
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"fieldname": "api_id",
"fieldtype": "Data",
"label": "API ID",
"read_only_depends_on": "eval:doc.enabled == 0"
},
{
"fieldname": "api_password",
"fieldtype": "Data",
"label": "API Password",
"read_only_depends_on": "eval:doc.enabled == 0"
}
],
"issingle": 1,
"links": [],
"modified": "2020-08-05 16:33:44.548230",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "LetMeShip",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,396 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import requests
import frappe
import json
import re
from frappe import _
from frappe.model.document import Document
from erpnext.erpnext_integrations.utils import get_tracking_url
LETMESHIP_PROVIDER = 'LetMeShip'
class LetMeShip(Document):
pass
def get_letmeship_available_services(delivery_to_type, pickup_address,
delivery_address, shipment_parcel, description_of_content, pickup_date,
value_of_goods, pickup_contact=None, delivery_contact=None):
# Retrieve rates at LetMeShip from specification stated.
enabled = frappe.db.get_single_value('LetMeShip','enabled')
api_id = frappe.db.get_single_value('LetMeShip','api_id')
api_password = frappe.db.get_single_value('LetMeShip','api_password')
if not enabled or not api_id or not api_password:
return []
set_letmeship_specific_fields(pickup_contact, delivery_contact)
# LetMeShip have limit of 30 characters for Company field
if len(pickup_address.address_title) > 30:
pickup_address.address_title = pickup_address.address_title[:30]
if len(delivery_address.address_title) > 30:
delivery_address.address_title = delivery_address.address_title[:30]
parcel_list = get_parcel_list(json.loads(shipment_parcel), description_of_content)
url = 'https://api.letmeship.com/v1/available'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': 'string'
}
payload = {'pickupInfo': {
'address': {
'countryCode': pickup_address.country_code,
'zip': pickup_address.pincode,
'city': pickup_address.city,
'street': pickup_address.address_line1,
'addressInfo1': pickup_address.address_line2,
'houseNo': '',
},
'company': pickup_address.address_title,
'person': {
'title': pickup_contact.title,
'firstname': pickup_contact.first_name,
'lastname': pickup_contact.last_name
},
'phone': {
'phoneNumber': pickup_contact.phone,
'phoneNumberPrefix': pickup_contact.phone_prefix
},
'email': pickup_contact.email,
}, 'deliveryInfo': {
'address': {
'countryCode': delivery_address.country_code,
'zip': delivery_address.pincode,
'city': delivery_address.city,
'street': delivery_address.address_line1,
'addressInfo1': delivery_address.address_line2,
'houseNo': '',
},
'company': delivery_address.address_title,
'person': {
'title': delivery_contact.title,
'firstname': delivery_contact.first_name,
'lastname': delivery_contact.last_name
},
'phone': {
'phoneNumber': delivery_contact.phone,
'phoneNumberPrefix': delivery_contact.phone_prefix
},
'email': delivery_contact.email,
}, 'shipmentDetails': {
'contentDescription': description_of_content,
'shipmentType': 'PARCEL',
'shipmentSettings': {
'saturdayDelivery': False,
'ddp': False,
'insurance': False,
'pickupOrder': False,
'pickupTailLift': False,
'deliveryTailLift': False,
'holidayDelivery': False,
},
'goodsValue': value_of_goods,
'parcelList': parcel_list,
'pickupInterval': {'date': pickup_date},
}}
try:
available_services = []
response_data = requests.post(
url=url,
auth=(api_id, api_password),
headers=headers,
data=json.dumps(payload)
)
response_data = json.loads(response_data.text)
if 'serviceList' in response_data:
for response in response_data['serviceList']:
available_service = frappe._dict()
basic_info = response['baseServiceDetails']
price_info = basic_info['priceInfo']
available_service.service_provider = LETMESHIP_PROVIDER
available_service.id = basic_info['id']
available_service.carrier = basic_info['carrier']
available_service.carrier_name = basic_info['name']
available_service.service_name = ''
available_service.is_preferred = 0
available_service.real_weight = price_info['realWeight']
available_service.total_price = price_info['netPrice']
available_service.price_info = price_info
available_services.append(available_service)
return available_services
else:
frappe.throw(
_('Error occurred while fetching LetMeShip prices: {0}')
.format(response_data['message'])
)
except Exception as exc:
frappe.msgprint(
_('Error occurred while fetching LetMeShip Prices: {0}')
.format(str(exc)),
indicator='orange',
alert=True
)
return []
def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, description_of_content,
pickup_date, value_of_goods, service_info, shipment_notific_email, tracking_notific_email,
pickup_contact=None, delivery_contact=None):
# Create a transaction at LetMeShip
# LetMeShip have limit of 30 characters for Company field
enabled = frappe.db.get_single_value('LetMeShip','enabled')
api_id = frappe.db.get_single_value('LetMeShip','api_id')
api_password = frappe.db.get_single_value('LetMeShip','api_password')
if not enabled or not api_id or not api_password:
return []
set_letmeship_specific_fields(pickup_contact, delivery_contact)
if len(pickup_address.address_title) > 30:
pickup_address.address_title = pickup_address.address_title[:30]
if len(delivery_address.address_title) > 30:
delivery_address.address_title = delivery_address.address_title[:30]
parcel_list = get_parcel_list(json.loads(shipment_parcel), description_of_content)
url = 'https://api.letmeship.com/v1/shipments'
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': 'string'
}
payload = {
'pickupInfo': {
'address': {
'countryCode': pickup_address.country_code,
'zip': pickup_address.pincode,
'city': pickup_address.city,
'street': pickup_address.address_line1,
'addressInfo1': pickup_address.address_line2,
'houseNo': '',
},
'company': pickup_address.address_title,
'person': {
'title': pickup_contact.title,
'firstname': pickup_contact.first_name,
'lastname': pickup_contact.last_name
},
'phone': {
'phoneNumber': pickup_contact.phone,
'phoneNumberPrefix': pickup_contact.phone_prefix
},
'email': pickup_contact.email,
},
'deliveryInfo': {
'address': {
'countryCode': delivery_address.country_code,
'zip': delivery_address.pincode,
'city': delivery_address.city,
'street': delivery_address.address_line1,
'addressInfo1': delivery_address.address_line2,
'houseNo': '',
},
'company': delivery_address.address_title,
'person': {
'title': delivery_contact.title,
'firstname': delivery_contact.first_name,
'lastname': delivery_contact.last_name
},
'phone': {
'phoneNumber': delivery_contact.phone,
'phoneNumberPrefix': delivery_contact.phone_prefix
},
'email': delivery_contact.email,
},
'service': {
'baseServiceDetails': {
'id': service_info['id'],
'name': service_info['service_name'],
'carrier': service_info['carrier'],
'priceInfo': service_info['price_info'],
},
'supportedExWorkType': [],
'messages': [''],
'description': '',
'serviceInfo': '',
},
'shipmentDetails': {
'contentDescription': description_of_content,
'shipmentType': 'PARCEL',
'shipmentSettings': {
'saturdayDelivery': False,
'ddp': False,
'insurance': False,
'pickupOrder': False,
'pickupTailLift': False,
'deliveryTailLift': False,
'holidayDelivery': False,
},
'goodsValue': value_of_goods,
'parcelList': parcel_list,
'pickupInterval': {
'date': pickup_date
},
'contentDescription': description_of_content,
},
'shipmentNotification': {
'trackingNotification': {
'deliveryNotification': True,
'problemNotification': True,
'emails': [tracking_notific_email],
'notificationText': '',
},
'recipientNotification': {
'notificationText': '',
'emails': [ shipment_notific_email ]
}
},
'labelEmail': True,
}
try:
response_data = requests.post(
url=url,
auth=(api_id, api_password),
headers=headers,
data=json.dumps(payload)
)
response_data = json.loads(response_data.text)
if 'shipmentId' in response_data:
shipment_amount = response_data['service']['priceInfo']['totalPrice']
awb_number = ''
url = 'https://api.letmeship.com/v1/shipments/{id}'.format(id=response_data['shipmentId'])
tracking_response = requests.get(url, auth=(api_id, api_password),headers=headers)
tracking_response_data = json.loads(tracking_response.text)
if 'trackingData' in tracking_response_data:
for parcel in tracking_response_data['trackingData']['parcelList']:
if 'awbNumber' in parcel:
awb_number = parcel['awbNumber']
return {
'service_provider': LETMESHIP_PROVIDER,
'shipment_id': response_data['shipmentId'],
'carrier': service_info['carrier'],
'carrier_service': service_info['service_name'],
'shipment_amount': shipment_amount,
'awb_number': awb_number,
}
elif 'message' in response_data:
frappe.throw(
_('Error occurred while creating Shipment: {0}')
.format(response_data['message'])
)
except Exception as exc:
frappe.msgprint(
_('Error occurred while creating Shipment: {0}')
.format(str(exc)),
indicator='orange',
alert=True
)
def get_letmeship_label(shipment_id):
# Retrieve shipment label from LetMeShip
api_id = frappe.db.get_single_value('LetMeShip','api_id')
api_password = frappe.db.get_single_value('LetMeShip','api_password')
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': 'string'
}
url = 'https://api.letmeship.com/v1/shipments/{id}/documents?types=LABEL'\
.format(id=shipment_id)
shipment_label_response = requests.get(
url,
auth=(api_id,api_password),
headers=headers
)
shipment_label_response_data = json.loads(shipment_label_response.text)
if 'documents' in shipment_label_response_data:
for label in shipment_label_response_data['documents']:
if 'data' in label:
return json.dumps(label['data'])
else:
frappe.throw(
_('Error occurred while printing Shipment: {0}')
.format(shipment_label_response_data['message'])
)
def get_letmeship_tracking_data(shipment_id):
# return letmeship tracking data
api_id = frappe.db.get_single_value('LetMeShip','api_id')
api_password = frappe.db.get_single_value('LetMeShip','api_password')
headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Access-Control-Allow-Origin': 'string'
}
try:
url = 'https://api.letmeship.com/v1/tracking?shipmentid={id}'.format(id=shipment_id)
tracking_data_response = requests.get(
url,
auth=(api_id, api_password),
headers=headers
)
tracking_data = json.loads(tracking_data_response.text)
if 'awbNumber' in tracking_data:
tracking_status = 'In Progress'
if tracking_data['lmsTrackingStatus'].startswith('DELIVERED'):
tracking_status = 'Delivered'
if tracking_data['lmsTrackingStatus'] == 'RETURNED':
tracking_status = 'Returned'
if tracking_data['lmsTrackingStatus'] == 'LOST':
tracking_status = 'Lost'
tracking_url = get_tracking_url(
carrier=tracking_data['carrier'],
tracking_number=tracking_data['awbNumber']
)
return {
'awb_number': tracking_data['awbNumber'],
'tracking_status': tracking_status,
'tracking_status_info': tracking_data['lmsTrackingStatus'],
'tracking_url': tracking_url,
}
elif 'message' in tracking_data:
frappe.throw(
_('Error occurred while updating Shipment: {0}')
.format(tracking_data['message'])
)
except Exception as exc:
frappe.msgprint(
_('Error occurred while updating Shipment: {0}')
.format(str(exc)),
indicator='orange',
alert=True
)
def get_parcel_list(shipment_parcel, description_of_content):
parcel_list = []
for parcel in shipment_parcel:
formatted_parcel = {}
formatted_parcel['height'] = parcel.get('height')
formatted_parcel['width'] = parcel.get('width')
formatted_parcel['length'] = parcel.get('length')
formatted_parcel['weight'] = parcel.get('weight')
formatted_parcel['quantity'] = parcel.get('count')
formatted_parcel['contentDescription'] = description_of_content
parcel_list.append(formatted_parcel)
return parcel_list
def set_letmeship_specific_fields(pickup_contact, delivery_contact):
pickup_contact.phone_prefix = pickup_contact.phone[:3]
pickup_contact.phone = re.sub('[^A-Za-z0-9]+', '', pickup_contact.phone[3:])
pickup_contact.title = 'MS'
if pickup_contact.gender == 'Male':
pickup_contact.title = 'MR'
delivery_contact.phone_prefix = delivery_contact.phone[:3]
delivery_contact.phone = re.sub('[^A-Za-z0-9]+', '', delivery_contact.phone[3:])
delivery_contact.title = 'MS'
if delivery_contact.gender == 'Male':
delivery_contact.title = 'MR'

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestLetMeShip(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Packlink', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,48 @@
{
"actions": [],
"creation": "2020-07-22 10:45:17.672439",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"api_key"
],
"fields": [
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key",
"read_only_depends_on": "eval:doc.enabled == 0"
}
],
"issingle": 1,
"links": [],
"modified": "2020-08-05 16:33:59.720980",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "Packlink",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,237 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import json
import frappe
import requests
from frappe import _
from frappe.model.document import Document
from erpnext.erpnext_integrations.utils import get_tracking_url
PACKLINK_PROVIDER = 'Packlink'
class Packlink(Document):
pass
def get_packlink_available_services(pickup_address, delivery_address, shipment_parcel,pickup_date):
# Retrieve rates at PackLink from specification stated.
from_zip = pickup_address.pincode
from_country_code = pickup_address.country_code
to_zip = delivery_address.pincode
to_country_code = delivery_address.country_code
shipment_parcel_params = ''
parcel_list = packlink_get_parcel_list(json.loads(shipment_parcel))
for (index, parcel) in enumerate(parcel_list):
shipment_parcel_params += 'packages[{index}][height]={height}&packages[{index}][length]={length}&packages[{index}][weight]={weight}&packages[{index}][width]={width}&'.format(
index=index,
height=parcel['height'],
length=parcel['length'],
weight=parcel['weight'],
width=parcel['width']
)
url = 'https://api.packlink.com/v1/services?from[country]={}&from[zip]={}&to[country]={}&to[zip]={}&{}sortBy=totalPrice&source=PRO'.format(
from_country_code,
from_zip,
to_country_code,
to_zip,
shipment_parcel_params
)
api_key = frappe.db.get_single_value('Packlink', 'api_key')
enabled = frappe.db.get_single_value('Packlink', 'enabled')
if not api_key or not enabled:
return []
try:
responses = requests.get(url, headers={'Authorization': api_key})
responses_dict = json.loads(responses.text)
# If an error occured on the api. Show the error message
if 'messages' in responses_dict:
frappe.msgprint(
_('Packlink: {0}'
.format(str(responses_dict['messages'][0]['message']))
),
indicator='orange',
alert=True
)
available_services = []
for response in responses_dict:
if parse_pickup_date(pickup_date) \
in response['available_dates'].keys():
available_service = frappe._dict()
available_service.service_provider = PACKLINK_PROVIDER
available_service.carrier = response['carrier_name']
available_service.carrier_name = response['name']
available_service.service_name = ''
available_service.is_preferred = 0
available_service.total_price = response['price']['base_price']
available_service.actual_price = response['price']['total_price']
available_service.service_id = response['id']
available_service.available_dates = response['available_dates']
available_services.append(available_service)
return available_services
except Exception as exc:
frappe.msgprint(
_('Error occurred on Packlink: {0}')
.format(str(exc)), indicator='orange',
alert=True
)
return []
def create_packlink_shipment(pickup_address, delivery_address, shipment_parcel,
description_of_content, pickup_date, value_of_goods, pickup_contact,
delivery_contact, service_info):
# Create a transaction at PackLink
enabled = frappe.db.get_single_value('Packlink', 'enabled')
if not enabled:
frappe.throw(_('Packlink integration is not enabled'))
api_key = frappe.db.get_single_value('Packlink', 'api_key')
from_country_code = pickup_address.country_code
to_country_code = delivery_address.country_code
data = {
'additional_data': {
'postal_zone_id_from': '',
'postal_zone_name_from': pickup_address.country,
'postal_zone_id_to': '',
'postal_zone_name_to': delivery_address.country,
},
'collection_date': parse_pickup_date(pickup_date),
'collection_time': '',
'content': description_of_content,
'contentvalue': value_of_goods,
'content_second_hand': False,
'from': {
'city': pickup_address.city,
'company': pickup_address.address_title,
'country': from_country_code,
'email': pickup_contact.email,
'name': pickup_contact.first_name,
'phone': pickup_contact.phone,
'state': pickup_address.country,
'street1': pickup_address.address_line1,
'street2': pickup_address.address_line2,
'surname': pickup_contact.last_name,
'zip_code': pickup_address.pincode,
},
'insurance': {'amount': 0, 'insurance_selected': False},
'price': {},
'packages': packlink_get_parcel_list(json.loads(shipment_parcel)),
'service_id': service_info['service_id'],
'to': {
'city': delivery_address.city,
'company': delivery_address.address_title,
'country': to_country_code,
'email': delivery_contact.email,
'name': delivery_contact.first_name,
'phone': delivery_contact.phone,
'state': delivery_address.country,
'street1': delivery_address.address_line1,
'street2': delivery_address.address_line2,
'surname': delivery_contact.last_name,
'zip_code': delivery_address.pincode,
},
}
url = 'https://api.packlink.com/v1/shipments'
headers = {
'Authorization': api_key,
'Content-Type': 'application/json'
}
try:
response_data = requests.post(url, json=data, headers=headers)
response_data = json.loads(response_data.text)
if 'reference' in response_data:
return {
'service_provider': PACKLINK_PROVIDER,
'shipment_id': response_data['reference'],
'carrier': service_info['carrier'],
'carrier_service': service_info['service_name'],
'shipment_amount': service_info['actual_price'],
'awb_number': '',
}
except Exception as exc:
frappe.msgprint(
_('Error occurred while creating Shipment: {0}')
.format(str(exc)),
indicator='orange',
alert=True
)
def get_packlink_label(shipment_id):
# Retrieve shipment label from PackLink
enabled = frappe.db.get_single_value('Packlink', 'enabled')
if not enabled:
frappe.throw(_('Packlink integration is not enabled'))
api_key = frappe.db.get_single_value('Packlink', 'api_key')
headers = {
'Authorization': api_key,
'Content-Type': 'application/json'
}
shipment_label_response = requests.get(
'https://api.packlink.com/v1/shipments/{id}/labels'.format(id=shipment_id),
headers=headers
)
shipment_label = json.loads(shipment_label_response.text)
if shipment_label:
return shipment_label
else:
frappe.msgprint(_('Shipment ID not found'))
def get_packlink_tracking_data(shipment_id):
# Get Packlink Tracking Info
enabled = frappe.db.get_single_value('Packlink', 'enabled')
if not enabled:
frappe.throw(_('Packlink integration is not enabled'))
api_key = frappe.db.get_single_value('Packlink', 'api_key')
headers = {
'Authorization': api_key,
'Content-Type': 'application/json'
}
try:
url = 'https://api.packlink.com/v1/shipments/{id}'.format(id=shipment_id)
tracking_data_response = requests.get(url, headers=headers)
tracking_data = json.loads(tracking_data_response.text)
if 'trackings' in tracking_data:
tracking_status = 'In Progress'
if tracking_data['state'] == 'DELIVERED':
tracking_status = 'Delivered'
if tracking_data['state'] == 'RETURNED':
tracking_status = 'Returned'
if tracking_data['state'] == 'LOST':
tracking_status = 'Lost'
awb_number = None if not tracking_data['trackings'] else tracking_data['trackings'][0]
tracking_url = get_tracking_url(
carrier=tracking_data['carrier'],
tracking_number=awb_number
)
return {
'awb_number': awb_number,
'tracking_status': tracking_status,
'tracking_status_info': tracking_data['state'],
'tracking_url': tracking_url
}
except Exception as exc:
frappe.msgprint(_('Error occurred while updating Shipment: {0}').format(
str(exc)), indicator='orange', alert=True)
return []
def packlink_get_parcel_list(shipment_parcel):
parcel_list = []
for parcel in shipment_parcel:
for count in range(parcel.get('count')):
formatted_parcel = {}
formatted_parcel['height'] = parcel.get('height')
formatted_parcel['width'] = parcel.get('width')
formatted_parcel['length'] = parcel.get('length')
formatted_parcel['weight'] = parcel.get('weight')
parcel_list.append(formatted_parcel)
return parcel_list
def parse_pickup_date(pickup_date):
return pickup_date.replace('-', '/')

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestPacklink(unittest.TestCase):
pass

View File

@@ -0,0 +1,8 @@
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('SendCloud', {
// refresh: function(frm) {
// }
});

View File

@@ -0,0 +1,56 @@
{
"actions": [],
"creation": "2020-08-18 09:48:50.836233",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"enabled",
"api_key",
"api_secret"
],
"fields": [
{
"default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
"label": "Enabled"
},
{
"fieldname": "api_key",
"fieldtype": "Data",
"label": "API Key",
"read_only_depends_on": "eval:doc.enabled == 0"
},
{
"fieldname": "api_secret",
"fieldtype": "Data",
"label": "API Secret",
"read_only_depends_on": "eval:doc.enabled == 0"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2020-08-18 09:48:50.836233",
"modified_by": "Administrator",
"module": "ERPNext Integrations",
"name": "SendCloud",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View File

@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import requests
import frappe
import json
from frappe import _
from frappe.model.document import Document
SENDCLOUD_PROVIDER = 'SendCloud'
class SendCloud(Document):
pass
def get_sendcloud_available_services(delivery_address, shipment_parcel):
# Retrieve rates at SendCloud from specification stated.
enabled = frappe.db.get_single_value('SendCloud', 'enabled')
api_key = frappe.db.get_single_value('SendCloud', 'api_key')
api_secret = frappe.db.get_single_value('SendCloud', 'api_secret')
if not enabled or not api_key or not api_secret:
return []
try:
url = 'https://panel.sendcloud.sc/api/v2/shipping_methods'
responses = requests.get(url, auth=(api_key, api_secret))
responses_dict = json.loads(responses.text)
available_services = []
for service in responses_dict['shipping_methods']:
for country in service['countries']:
if country['iso_2'] == delivery_address.country_code:
available_service = frappe._dict()
available_service.service_provider = 'SendCloud'
available_service.carrier = service['carrier']
available_service.service_name = service['name']
available_service.total_price = total_parcel_price(country['price'], json.loads(shipment_parcel))
available_service.service_id = service['id']
available_services.append(available_service)
return available_services
except Exception as exc:
frappe.msgprint(_('Error occurred on SendCloud: {0}').format(
str(exc)), indicator='orange', alert=True)
def create_sendcloud_shipment(
shipment,
delivery_address,
delivery_contact,
service_info,
shipment_parcel,
description_of_content,
value_of_goods
):
# Create a transaction at SendCloud
enabled = frappe.db.get_single_value('SendCloud', 'enabled')
api_key = frappe.db.get_single_value('SendCloud', 'api_key')
api_secret = frappe.db.get_single_value('SendCloud', 'api_secret')
if not enabled or not api_key or not api_secret:
return []
parcels = []
for i, parcel in enumerate(json.loads(shipment_parcel), start=1):
parcel_data = {
'name': "{} {}".format(delivery_contact.first_name, delivery_contact.last_name),
'company_name': delivery_address.address_title,
'address': delivery_address.address_line1,
'address_2': delivery_address.address_line2 or '',
'city': delivery_address.city,
'postal_code': delivery_address.pincode,
'telephone': delivery_contact.phone,
'request_label': True,
'email': delivery_contact.email,
'data': [],
'country': delivery_address.country_code,
'shipment': {
'id': service_info['service_id']
},
'order_number': "{}-{}".format(shipment, i),
'external_reference': "{}-{}".format(shipment, i),
'weight': parcel.get('weight'),
'parcel_items': get_parcel_items(parcel, description_of_content, value_of_goods)
}
parcels.append(parcel_data)
data = {
'parcels': parcels
}
try:
url = 'https://panel.sendcloud.sc/api/v2/parcels?errors=verbose'
response_data = requests.post(url, json=data, auth=(api_key, api_secret))
response_data = json.loads(response_data.text)
if 'failed_parcels' in response_data:
frappe.msgprint(_('Error occurred while creating Shipment: {0}'
).format(response_data['failed_parcels'][0]['errors']), indicator='orange',
alert=True)
else:
shipment_id = ', '.join([str(x['id']) for x in response_data['parcels']])
awb_number = ', '.join([str(x['tracking_number']) for x in response_data['parcels']])
return {
'service_provider': 'SendCloud',
'shipment_id': shipment_id,
'carrier': service_info['carrier'],
'carrier_service': service_info['service_name'],
'shipment_amount': service_info['total_price'],
'awb_number': awb_number
}
except Exception as exc:
frappe.msgprint(_('Error occurred while creating Shipment: {0}').format(
str(exc)), indicator='orange', alert=True)
def get_sendcloud_label(shipment_id):
# Retrieve shipment label from SendCloud
api_key = frappe.db.get_single_value('SendCloud', 'api_key')
api_secret = frappe.db.get_single_value('SendCloud', 'api_secret')
shipment_id_list = shipment_id.split(', ')
label_urls = []
for ship_id in shipment_id_list:
shipment_label_response = \
requests.get('https://panel.sendcloud.sc/api/v2/labels/{id}'.format(id=ship_id), auth=(api_key, api_secret))
shipment_label = json.loads(shipment_label_response.text)
label_urls.append(shipment_label['label']['label_printer'])
if len(label_urls):
return label_urls
else:
frappe.msgprint(_('Shipment ID not found'))
def get_sendcloud_tracking_data(shipment_id):
# return SendCloud tracking data
try:
api_key = frappe.db.get_single_value('SendCloud', 'api_key')
api_secret = frappe.db.get_single_value('SendCloud', 'api_secret')
shipment_id_list = shipment_id.split(', ')
tracking_url = ''
awb_number = []
tracking_status = []
tracking_status_info = []
for ship_id in shipment_id_list:
tracking_data_response = \
requests.get('https://panel.sendcloud.sc/api/v2/parcels/{id}'.format(id=ship_id), auth=(api_key, api_secret))
tracking_data = json.loads(tracking_data_response.text)
tracking_url_template = \
'<a href="{{ tracking_url }}" target="_blank"><b>{{ _("Click here to Track Shipment") }}</b></a><br>'
tracking_url += frappe.render_template(tracking_url_template, {'tracking_url': tracking_data['parcel']['tracking_url']})
awb_number.append(tracking_data['parcel']['tracking_number'])
tracking_status.append(tracking_data['parcel']['status']['message'])
tracking_status_info.append(tracking_data['parcel']['status']['message'])
return {
'awb_number': ', '.join(awb_number),
'tracking_status': ', '.join(tracking_status),
'tracking_status_info': ', '.join(tracking_status_info),
'tracking_url': tracking_url
}
except Exception as exc:
frappe.msgprint(_('Error occurred while updating Shipment: {0}').format(
str(exc)), indicator='orange', alert=True)
def total_parcel_price(parcel_price, shipment_parcel):
count = 0
for parcel in shipment_parcel:
count += parcel.get('count')
return parcel_price * count
def get_parcel_items(parcel, description_of_content, value_of_goods):
parcel_list = []
formatted_parcel = {}
formatted_parcel['description'] = description_of_content
formatted_parcel['quantity'] = parcel.get('count')
formatted_parcel['weight'] = parcel.get('weight')
formatted_parcel['value'] = value_of_goods
parcel_list.append(formatted_parcel)
return parcel_list

View File

@@ -0,0 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
from __future__ import unicode_literals
# import frappe
import unittest
class TestSendCloud(unittest.TestCase):
pass

View File

@@ -60,4 +60,14 @@ def create_mode_of_payment(gateway, payment_type="General"):
"default_account": payment_gateway_account
}]
})
mode_of_payment.insert(ignore_permissions=True)
mode_of_payment.insert(ignore_permissions=True)
def get_tracking_url(carrier, tracking_number):
# Return the formatted Tracking URL.
tracking_url = ''
url_reference = frappe.get_value('Parcel Service', carrier, 'url_reference')
if url_reference:
tracking_url = frappe.render_template(url_reference, {'tracking_number': tracking_number})
tracking_url_template = '<a href="{{ tracking_url }}" target="_blank"><b>{{ _("Click here to Track Shipment") }}</a></b>'
tracking_url = frappe.render_template(tracking_url_template, {'tracking_url': tracking_url})
return tracking_url