From 1c9410e5e835ca81ffc4507a1bb0b3f4e0a801a1 Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Mon, 13 Jul 2020 16:25:09 +0800 Subject: [PATCH 1/7] feat(shipment): Shipment Doctype with Integrations --- .../doctype/letmeship/__init__.py | 0 .../doctype/letmeship/letmeship.js | 8 + .../doctype/letmeship/letmeship.json | 55 ++ .../doctype/letmeship/letmeship.py | 396 +++++++++ .../doctype/letmeship/test_letmeship.py | 10 + .../doctype/packlink/__init__.py | 0 .../doctype/packlink/packlink.js | 8 + .../doctype/packlink/packlink.json | 48 ++ .../doctype/packlink/packlink.py | 237 ++++++ .../doctype/packlink/test_packlink.py | 10 + .../doctype/sendcloud/__init__.py | 0 .../doctype/sendcloud/sendcloud.js | 8 + .../doctype/sendcloud/sendcloud.json | 56 ++ .../doctype/sendcloud/sendcloud.py | 171 ++++ .../doctype/sendcloud/test_sendcloud.py | 10 + erpnext/erpnext_integrations/utils.py | 12 +- .../doctype/delivery_note/delivery_note.js | 12 + .../doctype/delivery_note/delivery_note.py | 53 ++ .../stock/doctype/parcel_service/__init__.py | 0 .../doctype/parcel_service/parcel_service.js | 8 + .../parcel_service/parcel_service.json | 56 ++ .../doctype/parcel_service/parcel_service.py | 10 + .../parcel_service/test_parcel_service.py | 10 + .../doctype/parcel_service_type/__init__.py | 0 .../parcel_service_type.js | 12 + .../parcel_service_type.json | 89 ++ .../parcel_service_type.py | 22 + .../test_parcel_service_type.py | 10 + .../parcel_service_type_alias/__init__.py | 0 .../parcel_service_type_alias.json | 41 + .../parcel_service_type_alias.py | 10 + erpnext/stock/doctype/shipment/__init__.py | 0 erpnext/stock/doctype/shipment/api/utils.py | 67 ++ erpnext/stock/doctype/shipment/shipment.js | 772 ++++++++++++++++++ erpnext/stock/doctype/shipment/shipment.json | 478 +++++++++++ erpnext/stock/doctype/shipment/shipment.py | 300 +++++++ .../stock/doctype/shipment/shipment_list.js | 8 + .../shipment/shipment_service_selector.html | 70 ++ .../stock/doctype/shipment/test_shipment.py | 333 ++++++++ .../shipment_delivery_notes/__init__.py | 0 .../shipment_delivery_notes.json | 41 + .../shipment_delivery_notes.py | 10 + .../__init__.py | 0 .../shipment_notification_subscriptions.json | 40 + .../shipment_notification_subscriptions.py | 10 + .../stock/doctype/shipment_parcel/__init__.py | 0 .../shipment_parcel/shipment_parcel.json | 65 ++ .../shipment_parcel/shipment_parcel.py | 10 + .../shipment_parcel_template/__init__.py | 0 .../shipment_parcel_template.js | 8 + .../shipment_parcel_template.json | 78 ++ .../shipment_parcel_template.py | 10 + .../test_shipment_parcel_template.py | 10 + .../__init__.py | 0 .../shipment_status_update_subscriptions.json | 40 + .../shipment_status_update_subscriptions.py | 10 + 56 files changed, 3721 insertions(+), 1 deletion(-) create mode 100644 erpnext/erpnext_integrations/doctype/letmeship/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.js create mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.json create mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.py create mode 100644 erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py create mode 100644 erpnext/erpnext_integrations/doctype/packlink/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.js create mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.json create mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.py create mode 100644 erpnext/erpnext_integrations/doctype/packlink/test_packlink.py create mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/__init__.py create mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js create mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json create mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py create mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py create mode 100644 erpnext/stock/doctype/parcel_service/__init__.py create mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.js create mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.json create mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.py create mode 100644 erpnext/stock/doctype/parcel_service/test_parcel_service.py create mode 100644 erpnext/stock/doctype/parcel_service_type/__init__.py create mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.js create mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.json create mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.py create mode 100644 erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py create mode 100644 erpnext/stock/doctype/parcel_service_type_alias/__init__.py create mode 100644 erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json create mode 100644 erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py create mode 100644 erpnext/stock/doctype/shipment/__init__.py create mode 100644 erpnext/stock/doctype/shipment/api/utils.py create mode 100644 erpnext/stock/doctype/shipment/shipment.js create mode 100644 erpnext/stock/doctype/shipment/shipment.json create mode 100644 erpnext/stock/doctype/shipment/shipment.py create mode 100644 erpnext/stock/doctype/shipment/shipment_list.js create mode 100644 erpnext/stock/doctype/shipment/shipment_service_selector.html create mode 100644 erpnext/stock/doctype/shipment/test_shipment.py create mode 100644 erpnext/stock/doctype/shipment_delivery_notes/__init__.py create mode 100644 erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json create mode 100644 erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py create mode 100644 erpnext/stock/doctype/shipment_notification_subscriptions/__init__.py create mode 100644 erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json create mode 100644 erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py create mode 100644 erpnext/stock/doctype/shipment_parcel/__init__.py create mode 100644 erpnext/stock/doctype/shipment_parcel/shipment_parcel.json create mode 100644 erpnext/stock/doctype/shipment_parcel/shipment_parcel.py create mode 100644 erpnext/stock/doctype/shipment_parcel_template/__init__.py create mode 100644 erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js create mode 100644 erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json create mode 100644 erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py create mode 100644 erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py create mode 100644 erpnext/stock/doctype/shipment_status_update_subscriptions/__init__.py create mode 100644 erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json create mode 100644 erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py diff --git a/erpnext/erpnext_integrations/doctype/letmeship/__init__.py b/erpnext/erpnext_integrations/doctype/letmeship/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js new file mode 100644 index 0000000000..1e5e372dff --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js @@ -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) { + + // } +}); diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json new file mode 100644 index 0000000000..4a9a70f251 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py new file mode 100644 index 0000000000..3ad06dbb58 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py @@ -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' \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py b/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py new file mode 100644 index 0000000000..3439e4fd72 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py @@ -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 diff --git a/erpnext/erpnext_integrations/doctype/packlink/__init__.py b/erpnext/erpnext_integrations/doctype/packlink/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.js b/erpnext/erpnext_integrations/doctype/packlink/packlink.js new file mode 100644 index 0000000000..da864584f6 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/packlink/packlink.js @@ -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) { + + // } +}); diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.json b/erpnext/erpnext_integrations/doctype/packlink/packlink.json new file mode 100644 index 0000000000..a56595e9a1 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/packlink/packlink.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.py b/erpnext/erpnext_integrations/doctype/packlink/packlink.py new file mode 100644 index 0000000000..7fdb053cf8 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/packlink/packlink.py @@ -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('-', '/') \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py b/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py new file mode 100644 index 0000000000..106ae51f7c --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py @@ -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 diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/__init__.py b/erpnext/erpnext_integrations/doctype/sendcloud/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js new file mode 100644 index 0000000000..3b85236863 --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js @@ -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) { + + // } +}); diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json new file mode 100644 index 0000000000..dab54cba6c --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json @@ -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 +} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py new file mode 100644 index 0000000000..85c94388dc --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py @@ -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 = \ + '{{ _("Click here to Track Shipment") }}
' + 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 \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py b/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py new file mode 100644 index 0000000000..5cbe80e8ac --- /dev/null +++ b/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py @@ -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 diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index e278fd7807..e7ef4c8ebd 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -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) \ No newline at end of file + 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 = '{{ _("Click here to Track Shipment") }}' + tracking_url = frappe.render_template(tracking_url_template, {'tracking_url': tracking_url}) + return tracking_url diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index 251a26a592..03921c554e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -156,6 +156,11 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( } if (!doc.is_return && doc.status!="Closed") { + if(doc.docstatus == 1) { + this.frm.add_custom_button(__('Shipment'), function() { + me.make_shipment() }, __('Create')); + } + if(flt(doc.per_installed, 2) < 100 && doc.docstatus==1) this.frm.add_custom_button(__('Installation Note'), function() { me.make_installation_note() }, __('Create')); @@ -220,6 +225,13 @@ erpnext.stock.DeliveryNoteController = erpnext.selling.SellingController.extend( } }, + make_shipment: function() { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_shipment", + frm: this.frm + }) + }, + make_sales_invoice: function() { frappe.model.open_mapped_doc({ method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice", diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index d04cf785ab..00a66fa48e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -569,6 +569,59 @@ def make_packing_slip(source_name, target_doc=None): return doclist +@frappe.whitelist() +def make_shipment(source_name, target_doc=None): + def postprocess(source, target): + user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1) + target.pickup_contact_email = user.email + pickup_contact_display = '{}'.format(user.full_name) + if user.email: + pickup_contact_display += '
' + user.email + if user.phone: + pickup_contact_display += '
' + user.phone + if user.mobile_no and not user.phone: + pickup_contact_display += '
' + user.mobile_no + target.pickup_contact = pickup_contact_display + + contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1) + delivery_contact_display = '{}'.format(source.contact_display) + if contact.email_id: + delivery_contact_display += '
' + contact.email_id + if contact.phone: + delivery_contact_display += '
' + contact.phone + if contact.mobile_no and not contact.phone: + delivery_contact_display += '
' + contact.mobile_no + target.delivery_contact = delivery_contact_display + + doclist = get_mapped_doc("Delivery Note", source_name, { + "Delivery Note": { + "doctype": "Shipment", + "field_map": { + "grand_total": "value_of_goods", + "company": "pickup_company", + "company_address": "pickup_address_name", + "company_address_display": "pickup_address", + "address_display": "delivery_address", + "customer": "delivery_customer", + "shipping_address_name": "delivery_address_name", + "contact_person": "delivery_contact_name", + "contact_email": "delivery_contact_email" + }, + "validation": { + "docstatus": ["=", 1] + } + }, + "Delivery Note Item": { + "doctype": "Shipment Delivery Notes", + "field_map": { + "name": "prevdoc_detail_docname", + "parent": "prevdoc_docname", + "parenttype": "prevdoc_doctype", + } + } + }, target_doc, postprocess) + + return doclist @frappe.whitelist() def make_sales_return(source_name, target_doc=None): diff --git a/erpnext/stock/doctype/parcel_service/__init__.py b/erpnext/stock/doctype/parcel_service/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.js b/erpnext/stock/doctype/parcel_service/parcel_service.js new file mode 100644 index 0000000000..43b8ed5bf8 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service/parcel_service.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Parcel Service', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.json b/erpnext/stock/doctype/parcel_service/parcel_service.json new file mode 100644 index 0000000000..9960acf4ae --- /dev/null +++ b/erpnext/stock/doctype/parcel_service/parcel_service.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "field:parcel_service_name", + "creation": "2020-07-23 10:35:38.211715", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parcel_service_name", + "parcel_service_code", + "url_reference" + ], + "fields": [ + { + "fieldname": "parcel_service_name", + "fieldtype": "Data", + "label": "Parcel Service Name", + "unique": 1 + }, + { + "fieldname": "parcel_service_code", + "fieldtype": "Data", + "label": "Parcel Service Code" + }, + { + "fieldname": "url_reference", + "fieldtype": "Data", + "label": "URL Reference" + } + ], + "links": [], + "modified": "2020-07-23 10:35:38.211715", + "modified_by": "Administrator", + "module": "Stock", + "name": "Parcel Service", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.py b/erpnext/stock/doctype/parcel_service/parcel_service.py new file mode 100644 index 0000000000..e46ac76ef7 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service/parcel_service.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ParcelService(Document): + pass diff --git a/erpnext/stock/doctype/parcel_service/test_parcel_service.py b/erpnext/stock/doctype/parcel_service/test_parcel_service.py new file mode 100644 index 0000000000..c2f96d9cb0 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service/test_parcel_service.py @@ -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 TestParcelService(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/parcel_service_type/__init__.py b/erpnext/stock/doctype/parcel_service_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js new file mode 100644 index 0000000000..31d54536c0 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js @@ -0,0 +1,12 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Parcel Service Type Alias', { + parcel_type_alias: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.parcel_type_alias) { + frappe.model.set_value(cdt, cdn, 'parcel_service', frm.doc.parcel_service); + frm.refresh_field('parcel_service_type_alias'); + } + } +}); diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json new file mode 100644 index 0000000000..3c0c4d5f80 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json @@ -0,0 +1,89 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "format: {parcel_service} - {parcel_service_type}", + "creation": "2020-07-23 10:47:43.794083", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parcel_service", + "parcel_service_type", + "description", + "section_break_4", + "parcel_service_type_alias", + "column_break_6", + "section_break_7", + "show_in_preferred_services_list" + ], + "fields": [ + { + "fieldname": "parcel_service", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parcel Service", + "options": "Parcel Service", + "reqd": 1 + }, + { + "fieldname": "parcel_service_type", + "fieldtype": "Data", + "label": "Parcel Service Type", + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Description" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "parcel_service_type_alias", + "fieldtype": "Table", + "label": "Parcel Service Type Alias", + "options": "Parcel Service Type Alias" + }, + { + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "show_in_preferred_services_list", + "fieldtype": "Check", + "label": "Show in Preferred Services List" + } + ], + "links": [], + "modified": "2020-07-23 10:47:43.794083", + "modified_by": "Administrator", + "module": "Stock", + "name": "Parcel Service Type", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py new file mode 100644 index 0000000000..b55528c359 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py @@ -0,0 +1,22 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ParcelServiceType(Document): + pass + +def match_parcel_service_type_alias(parcel_service_type, parcel_service): + # Match and return Parcel Service Type Alias to Parcel Service Type if exists. + if frappe.db.exists('Parcel Service', parcel_service): + matched_parcel_service_type = \ + frappe.db.get_value('Parcel Service Type Alias', { + 'parcel_type_alias': parcel_service_type, + 'parcel_service': parcel_service + }, 'parent') + if matched_parcel_service_type: + parcel_service_type = matched_parcel_service_type + return parcel_service_type diff --git a/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py b/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py new file mode 100644 index 0000000000..e214264acc --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py @@ -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 TestParcelServiceType(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/parcel_service_type_alias/__init__.py b/erpnext/stock/doctype/parcel_service_type_alias/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json new file mode 100644 index 0000000000..8e7731e6c1 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "creation": "2020-07-23 10:47:23.626510", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parcel_service", + "parcel_type_alias" + ], + "fields": [ + { + "fieldname": "parcel_service", + "fieldtype": "Link", + "hidden": 1, + "in_list_view": 1, + "label": "Parcel Service", + "options": "Parcel Service", + "read_only": 1 + }, + { + "fieldname": "parcel_type_alias", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Parcel Type Alias", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-23 10:47:23.626510", + "modified_by": "Administrator", + "module": "Stock", + "name": "Parcel Service Type Alias", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py new file mode 100644 index 0000000000..fd0a7d8b49 --- /dev/null +++ b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ParcelServiceTypeAlias(Document): + pass diff --git a/erpnext/stock/doctype/shipment/__init__.py b/erpnext/stock/doctype/shipment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment/api/utils.py b/erpnext/stock/doctype/shipment/api/utils.py new file mode 100644 index 0000000000..1153933e81 --- /dev/null +++ b/erpnext/stock/doctype/shipment/api/utils.py @@ -0,0 +1,67 @@ +# -*- 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 frappe +from frappe import _ +import re + +def get_address(address_name): + address = frappe.db.get_value('Address', address_name, [ + 'address_title', + 'address_line1', + 'address_line2', + 'city', + 'pincode', + 'country', + ], as_dict=1) + address.country_code = frappe.db.get_value('Country', address.country, 'code').upper() + if not address.pincode or address.pincode == '': + frappe.throw(_("Postal Code is mandatory to continue.
\ + Please set Postal Code for Address {1}" + ).format(address_name, address_name)) + address.pincode = address.pincode.replace(' ', '') + address.city = address.city.strip() + return address + +def get_contact(contact_name): + contact = frappe.db.get_value('Contact', contact_name, [ + 'first_name', + 'last_name', + 'email_id', + 'phone', + 'mobile_no', + 'gender', + ], as_dict=1) + if not contact.last_name: + frappe.throw(_("Last Name is mandatory to continue.
\ + Please set Last Name for Contact {1}" + ).format(contact_name, contact_name)) + if not contact.phone: + contact.phone = contact.mobile_no + contact.phone_prefix = contact.phone[:3] + contact.phone = re.sub('[^A-Za-z0-9]+', '', contact.phone[3:]) + contact.email = contact.email_id + contact.title = 'MS' + if contact.gender == 'Male': + contact.title = 'MR' + return contact + +def get_company_contact(): + contact = frappe.db.get_value('User', frappe.session.user, [ + 'first_name', + 'last_name', + 'email', + 'phone', + 'mobile_no', + 'gender', + ], as_dict=1) + if not contact.phone: + contact.phone = contact.mobile_no + contact.phone_prefix = contact.phone[:3] + contact.phone = re.sub('[^A-Za-z0-9]+', '', contact.phone[3:]) + contact.title = 'MS' + if contact.gender == 'Male': + contact.title = 'MR' + return contact diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js new file mode 100644 index 0000000000..e9f4484ab1 --- /dev/null +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -0,0 +1,772 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Shipment', { + setup: function(frm) { + if (frm.doc.__islocal) { + frm.trigger('pickup_type'); + } + }, + address_query: function(frm, link_doctype, link_name, is_your_company_address) { + return { + query: 'frappe.contacts.doctype.address.address.address_query', + filters: { + link_doctype: link_doctype, + link_name: link_name, + is_your_company_address: is_your_company_address + } + }; + }, + contact_query: function(frm, link_doctype, link_name) { + return { + query: 'frappe.contacts.doctype.contact.contact.contact_query', + filters: { + link_doctype: link_doctype, + link_name: link_name + } + }; + }, + onload: function(frm) { + frm.set_query("delivery_address_name", () => { + let link_doctype = ''; + let link_name = ''; + let is_your_company_address = 0; + if (frm.doc.delivery_to_type == 'Customer') { + link_doctype = 'Customer'; + link_name = frm.doc.delivery_customer; + } + if (frm.doc.delivery_to_type == 'Supplier') { + link_doctype = 'Supplier'; + link_name = frm.doc.delivery_supplier; + } + if (frm.doc.delivery_to_type == 'Company') { + link_doctype = 'Company'; + link_name = frm.doc.delivery_company; + is_your_company_address = 1; + } + return frm.events.address_query(frm, link_doctype, link_name, is_your_company_address); + }); + frm.set_query("pickup_address_name", () => { + let link_doctype = ''; + let link_name = ''; + let is_your_company_address = 0; + if (frm.doc.pickup_from_type == 'Customer') { + link_doctype = 'Customer'; + link_name = frm.doc.pickup_customer; + } + if (frm.doc.pickup_from_type == 'Supplier') { + link_doctype = 'Supplier'; + link_name = frm.doc.pickup_supplier; + } + if (frm.doc.pickup_from_type == 'Company') { + link_doctype = 'Company'; + link_name = frm.doc.pickup_company; + is_your_company_address = 1; + } + return frm.events.address_query(frm, link_doctype, link_name, is_your_company_address); + }); + frm.set_query("delivery_contact_name", () => { + let link_doctype = ''; + let link_name = ''; + if (frm.doc.delivery_to_type == 'Customer') { + link_doctype = 'Customer'; + link_name = frm.doc.delivery_customer; + } + if (frm.doc.delivery_to_type == 'Supplier') { + link_doctype = 'Supplier'; + link_name = frm.doc.delivery_supplier; + } + if (frm.doc.delivery_to_type == 'Company') { + link_doctype = 'Company'; + link_name = frm.doc.delivery_company; + } + return frm.events.contact_query(frm, link_doctype, link_name); + }); + frm.set_query("pickup_contact_name", () => { + let link_doctype = ''; + let link_name = ''; + if (frm.doc.pickup_from_type == 'Customer') { + link_doctype = 'Customer'; + link_name = frm.doc.pickup_customer; + } + if (frm.doc.pickup_from_type == 'Supplier') { + link_doctype = 'Supplier'; + link_name = frm.doc.pickup_supplier; + } + if (frm.doc.pickup_from_type == 'Company') { + link_doctype = 'Company'; + link_name = frm.doc.pickup_company; + } + return frm.events.contact_query(frm, link_doctype, link_name); + }); + frm.set_query("delivery_note", "shipment_delivery_notes", function() { + let customer = ''; + if (frm.doc.delivery_to_type == "Customer") { + customer = frm.doc.delivery_customer; + } + if (frm.doc.delivery_to_type == "Company") { + customer = frm.doc.delivery_company; + } + if (customer) { + return { + filters: { + customer: customer, + docstatus: 1, + status: ["not in", ["Cancelled"]] + } + }; + } + }); + }, + refresh: function(frm) { + if (frm.doc.docstatus === 1 && !frm.doc.shipment_id) { + frm.add_custom_button(__('Fetch Shipping Rates'), function() { + return frm.events.fetch_shipping_rates(frm); + }); + } + if (frm.doc.shipment_id) { + frm.add_custom_button(__('Print Shipping Label'), function() { + return frm.events.print_shipping_label(frm); + }); + if (frm.doc.tracking_status != 'Delivered') { + frm.add_custom_button(__('Update Tracking'), function() { + return frm.events.update_tracking(frm, frm.doc.service_provider, frm.doc.shipment_id); + }); + } + } + $('div[data-fieldname=pickup_address] > div > .clearfix').hide(); + $('div[data-fieldname=pickup_contact] > div > .clearfix').hide(); + $('div[data-fieldname=delivery_address] > div > .clearfix').hide(); + $('div[data-fieldname=delivery_contact] > div > .clearfix').hide(); + + if (frm.doc.delivery_from_type != 'Company') { + frm.set_df_property("delivery_contact_name", "reqd", 1); + } + if (frm.doc.pickup_from_type != 'Company') { + frm.set_df_property("pickup_contact_name", "reqd", 1); + } + else { + frm.toggle_display("pickup_contact_name", false); + } + }, + before_save: function(frm) { + if (frm.doc.delivery_to_type == 'Company') { + frm.set_value("delivery_to", frm.doc.delivery_company); + } + if (frm.doc.delivery_to_type == 'Customer') { + frm.set_value("delivery_to", frm.doc.delivery_customer); + } + if (frm.doc.delivery_to_type == 'Supplier') { + frm.set_value("delivery_to", frm.doc.delivery_supplier); + } + if (frm.doc.pickup_from_type == 'Company') { + frm.set_value("pickup", frm.doc.pickup_company); + } + if (frm.doc.pickup_from_type == 'Customer') { + frm.set_value("pickup", frm.doc.pickup_customer); + } + if (frm.doc.pickup_from_type == 'Supplier') { + frm.set_value("pickup", frm.doc.pickup_supplier); + } + }, + set_pickup_company_address: function(frm) { + frappe.db.get_value('Address', { + address_title: frm.doc.pickup_company, + is_your_company_address: 1 + }, 'name', (r) => { + frm.set_value("pickup_address_name", r.name); + }); + }, + set_delivery_company_address: function(frm) { + frappe.db.get_value('Address', { + address_title: frm.doc.delivery_company, + is_your_company_address: 1 + }, 'name', (r) => { + frm.set_value("delivery_address_name", r.name); + }); + }, + pickup_from_type: function(frm) { + if (frm.doc.pickup_from_type == 'Company') { + frm.set_value("pickup_company", frappe.defaults.get_default('company')); + frm.set_df_property("pickup_contact_name", "reqd", 0); + frm.set_value("pickup_customer", ''); + frm.set_value("pickup_supplier", ''); + frm.toggle_display("pickup_contact_name", false); + } + else { + frm.set_df_property("pickup_contact_name", "reqd", 1); + frm.toggle_display("pickup_contact_name", true); + frm.trigger('clear_pickup_fields'); + } + if (frm.doc.pickup_from_type == 'Customer') { + frm.set_value("pickup_company", ''); + frm.set_value("pickup_supplier", ''); + } + if (frm.doc.pickup_from_type == 'Supplier') { + frm.set_value("pickup_customer", ''); + frm.set_value("pickup_company", ''); + } + frm.events.remove_notific_child_table(frm, 'shipment_notification_subscriptions', 'Pickup'); + frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscriptions', 'Pickup'); + }, + delivery_to_type: function(frm) { + if (frm.doc.delivery_to_type == 'Company') { + frm.set_value("delivery_company", frappe.defaults.get_default('company')); + frm.set_df_property("delivery_contact_name", "reqd", 0); + frm.set_value("delivery_customer", ''); + frm.set_value("delivery_supplier", ''); + frm.toggle_display("delivery_contact_name", false); + } + else { + frm.set_df_property("delivery_contact_name", "reqd", 1); + frm.toggle_display("delivery_contact_name", true); + frm.trigger('clear_delivery_fields'); + } + if (frm.doc.delivery_to_type == 'Customer') { + frm.set_value("delivery_company", ''); + frm.set_value("delivery_supplier", ''); + } + if (frm.doc.delivery_to_type == 'Supplier') { + frm.set_value("delivery_customer", ''); + frm.set_value("delivery_company", ''); + frm.toggle_display("shipment_delivery_notes", false); + } + else { + frm.toggle_display("shipment_delivery_notes", true); + } + frm.events.remove_notific_child_table(frm, 'shipment_notification_subscriptions', 'Delivery'); + frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscriptions', 'Delivery'); + }, + delivery_address_name: function(frm) { + if (frm.doc.delivery_to_type == 'Company') { + erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true); + } + else { + erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false); + } + }, + pickup_address_name: function(frm) { + if (frm.doc.pickup_from_type == 'Company') { + erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true); + } + else { + erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false); + } + }, + get_contact_display: function(frm, contact_name, contact_type) { + frappe.call({ + method: "frappe.contacts.doctype.contact.contact.get_contact_details", + args: { contact: contact_name }, + callback: function(r) { + if(r.message) { + if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) { + if (contact_type == 'Delivery') { + frm.set_value('delivery_contact_name', ''); + frm.set_value('delivery_contact', ''); + } + else { + frm.set_value('pickup_contact_name', ''); + frm.set_value('pickup_contact', ''); + } + frappe.throw(__(`Email or Phone/Mobile of the Contact are mandatory to continue.
+ Please set Email/Phone for the contact ${contact_name}`)); + } + let contact_display = r.message.contact_display; + if (r.message.contact_email) { + contact_display += '
' + r.message.contact_email; + } + if (r.message.contact_phone) { + contact_display += '
' + r.message.contact_phone; + } + if (r.message.contact_mobile && !r.message.contact_phone) { + contact_display += '
' + r.message.contact_mobile; + } + if (contact_type == 'Delivery'){ + frm.set_value('delivery_contact', contact_display); + if (r.message.contact_email) { + frm.set_value('delivery_contact_email', r.message.contact_email); + } + } + else { + frm.set_value('pickup_contact', contact_display); + if (r.message.contact_email) { + frm.set_value('pickup_contact_email', r.message.contact_email); + } + } + } + } + }); + }, + delivery_contact_name: function(frm) { + if (frm.doc.delivery_contact_name) { + frm.events.get_contact_display(frm, frm.doc.delivery_contact_name, 'Delivery'); + } + }, + pickup_contact_name: function(frm) { + if (frm.doc.pickup_contact_name) { + frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup'); + } + }, + set_company_contact: function(frm, delivery_type) { + frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => { + if (!(r.last_name && r.email && (r.phone || r.mobile_no))) { + if (delivery_type == 'Delivery') { + frm.set_value('delivery_company', ''); + frm.set_value('delivery_contact', ''); + } + else { + frm.set_value('pickup_company', ''); + frm.set_value('pickup_contact', ''); + } + frappe.throw(__(`Last Name, Email or Phone/Mobile of the user are mandatory to continue.
+ Please first set Last Name, Email and Phone for the user ${frappe.session.user}`)); + } + let contact_display = r.full_name; + if (r.email) { + contact_display += '
' + r.email; + } + if (r.phone) { + contact_display += '
' + r.phone; + } + if (r.mobile_no && !r.phone) { + contact_display += '
' + r.mobile_no; + } + if (delivery_type == 'Delivery') { + frm.set_value('delivery_contact', contact_display); + if (r.email) { + frm.set_value('delivery_contact_email', r.email); + } + } + else { + frm.set_value('pickup_contact', contact_display); + if (r.email) { + frm.set_value('pickup_contact_email', r.email); + } + } + }); + }, + pickup_company: function(frm) { + if (frm.doc.pickup_from_type == 'Company' && frm.doc.pickup_company) { + frm.trigger('set_pickup_company_address'); + frm.events.set_company_contact(frm, 'Pickup'); + } + }, + delivery_company: function(frm) { + if (frm.doc.delivery_to_type == 'Company' && frm.doc.delivery_company) { + frm.trigger('set_delivery_company_address'); + frm.events.set_company_contact(frm, 'Delivery'); + } + }, + delivery_customer: function(frm) { + frm.trigger('clear_delivery_fields'); + if (frm.doc.delivery_customer) { + frm.events.set_address_name(frm,'Customer',frm.doc.delivery_customer, 'Delivery'); + frm.events.set_contact_name(frm,'Customer',frm.doc.delivery_customer, 'Delivery'); + } + }, + delivery_supplier: function(frm) { + frm.trigger('clear_delivery_fields'); + if (frm.doc.delivery_supplier) { + frm.events.set_address_name(frm,'Supplier',frm.doc.delivery_supplier, 'Delivery'); + frm.events.set_contact_name(frm,'Supplier',frm.doc.delivery_supplier, 'Delivery'); + } + }, + pickup_customer: function(frm) { + frm.trigger('clear_pickup_fields'); + if (frm.doc.pickup_customer) { + frm.events.set_address_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); + frm.events.set_contact_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); + } + }, + pickup_supplier: function(frm) { + frm.trigger('clear_pickup_fields'); + if (frm.doc.pickup_supplier) { + frm.events.set_address_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); + frm.events.set_contact_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); + } + }, + set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.get_address_name", + args: { + ref_doctype: ref_doctype, + docname: ref_docname + }, + callback: function(r) { + if(r.message) { + if (delivery_type == 'Delivery') { + frm.set_value('delivery_address_name', r.message); + } + else { + frm.set_value('pickup_address_name', r.message); + } + } + } + }); + }, + set_contact_name: function(frm, ref_doctype, ref_docname, delivery_type) { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.get_contact_name", + args: { + ref_doctype: ref_doctype, + docname: ref_docname + }, + callback: function(r) { + if(r.message) { + if (delivery_type == 'Delivery') { + frm.set_value('delivery_contact_name', r.message); + } + else { + frm.set_value('pickup_contact_name', r.message); + } + } + } + }); + }, + add_template: function(frm) { + if (frm.doc.parcel_template) { + frappe.model.with_doc("Shipment Parcel Template", frm.doc.parcel_template, () => { + let parcel_template = frappe.model.get_doc("Shipment Parcel Template", frm.doc.parcel_template); + let row = frappe.model.add_child(frm.doc, "Shipment Parcel", "shipment_parcel"); + row.length = parcel_template.length; + row.width = parcel_template.width; + row.height = parcel_template.height; + row.weight = parcel_template.weight; + frm.refresh_fields("shipment_parcel"); + }); + } + }, + pickup_date: function(frm) { + if (frm.doc.pickup_date < frappe.datetime.get_today()) { + frappe.throw(__("Pickup Date cannot be in the past")); + } + if (frm.doc.pickup_date == frappe.datetime.get_today()) { + var pickup_time = frm.events.get_pickup_time(frm); + frm.set_value("pickup_from", pickup_time); + frm.trigger('set_pickup_to_time'); + } + }, + pickup_from: function(frm) { + var pickup_time = frm.events.get_pickup_time(frm); + if (frm.doc.pickup_from && frm.doc.pickup_date == frappe.datetime.get_today()) { + let current_hour = pickup_time.split(':')[0]; + let current_min = pickup_time.split(':')[1]; + let pickup_hour = frm.doc.pickup_from.split(':')[0]; + let pickup_min = frm.doc.pickup_from.split(':')[1]; + if (pickup_hour < current_hour || (pickup_hour == current_hour && pickup_min < current_min)) { + frm.set_value("pickup_from", pickup_time); + frappe.throw(__("Pickup Time cannot be in the past")); + } + } + frm.trigger('set_pickup_to_time'); + }, + get_pickup_time: function() { + let current_hour = new Date().getHours(); + let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'}); + if (current_min < 30) { + current_min = '30'; + } + else { + current_min = '00'; + current_hour = Number(current_hour)+1; + } + if (Number(current_hour) > 19 || Number(current_hour) === 19){ + frappe.throw(__("Today's pickup time is over, please select different date")); + } + current_hour = (current_hour < 10) ? '0' + current_hour : current_hour; + let pickup_time = current_hour +':'+ current_min; + return pickup_time; + }, + set_pickup_to_time: function(frm) { + let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5; + if (Number(pickup_to_hour) > 19 || Number(pickup_to_hour) === 19){ + pickup_to_hour = 19; + } + let pickup_to_min = frm.doc.pickup_from.split(':')[1]; + let pickup_to = pickup_to_hour +':'+ pickup_to_min; + frm.set_value("pickup_to", pickup_to); + }, + clear_pickup_fields: function(frm) { + frm.set_value("pickup_address_name", ''); + frm.set_value("pickup_contact_name", ''); + frm.set_value("pickup_address", ''); + frm.set_value("pickup_contact", ''); + frm.set_value("pickup_contact_email", ''); + }, + clear_delivery_fields: function(frm) { + frm.set_value("delivery_address_name", ''); + frm.set_value("delivery_contact_name", ''); + frm.set_value("delivery_address", ''); + frm.set_value("delivery_contact", ''); + frm.set_value("delivery_contact_email", ''); + }, + pickup_from_send_shipping_notification: function(frm, cdt, cdn) { + if (frm.doc.pickup_contact_email && frm.doc.pickup_from_send_shipping_notification + && !validate_duplicate(frm, 'shipment_notification_subscriptions', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscriptions", "shipment_notification_subscriptions"); + row.email = frm.doc.pickup_contact_email; + frm.refresh_fields("shipment_notification_subscriptions"); + } + if (!frm.doc.pickup_from_send_shipping_notification) { + frm.events.remove_email_row(frm, 'shipment_notification_subscriptions', frm.doc.pickup_contact_email); + frm.refresh_fields("shipment_notification_subscriptions"); + } + }, + pickup_from_subscribe_to_status_updates: function(frm, cdt, cdn) { + if (frm.doc.pickup_contact_email && frm.doc.pickup_from_subscribe_to_status_updates + && !validate_duplicate(frm, 'shipment_status_update_subscriptions', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscriptions", "shipment_status_update_subscriptions"); + row.email = frm.doc.pickup_contact_email; + frm.refresh_fields("shipment_status_update_subscriptions"); + } + if (!frm.doc.pickup_from_subscribe_to_status_updates) { + frm.events.remove_email_row(frm, 'shipment_status_update_subscriptions', frm.doc.pickup_contact_email); + frm.refresh_fields("shipment_status_update_subscriptions"); + } + }, + delivery_to_send_shipping_notification: function(frm, cdt, cdn) { + if (frm.doc.delivery_contact_email && frm.doc.delivery_to_send_shipping_notification + && !validate_duplicate(frm, 'shipment_notification_subscriptions', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)){ + let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscriptions", "shipment_notification_subscriptions"); + row.email = frm.doc.delivery_contact_email; + frm.refresh_fields("shipment_notification_subscriptions"); + } + if (!frm.doc.delivery_to_send_shipping_notification) { + frm.events.remove_email_row(frm, 'shipment_notification_subscriptions', frm.doc.delivery_contact_email); + frm.refresh_fields("shipment_notification_subscriptions"); + } + }, + delivery_to_subscribe_to_status_updates: function(frm, cdt, cdn) { + if (frm.doc.delivery_contact_email && frm.doc.delivery_to_subscribe_to_status_updates + && !validate_duplicate(frm, 'shipment_status_update_subscriptions', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscriptions", "shipment_status_update_subscriptions"); + row.email = frm.doc.delivery_contact_email; + frm.refresh_fields("shipment_status_update_subscriptions"); + } + if (!frm.doc.delivery_to_subscribe_to_status_updates) { + frm.events.remove_email_row(frm, 'shipment_status_update_subscriptions', frm.doc.delivery_contact_email); + frm.refresh_fields("shipment_status_update_subscriptions"); + } + }, + remove_email_row: function(frm, table, fieldname) { + $.each(frm.doc[table] || [], function(i, detail) { + if(detail.email === fieldname){ + cur_frm.get_field(table).grid.grid_rows[i].remove(); + } + }); + }, + remove_notific_child_table: function(frm, table, delivery_type) { + $.each(frm.doc[table] || [], function(i, detail) { + if (detail.email != frm.doc.pickup_email || detail.email != frm.doc.delivery_email){ + cur_frm.get_field(table).grid.grid_rows[i].remove(); + } + }); + frm.refresh_fields(table); + if (delivery_type == 'Delivery') { + frm.set_value("delivery_to_send_shipping_notification", 0); + frm.set_value("delivery_to_subscribe_to_status_updates", 0); + frm.refresh_fields("delivery_to_send_shipping_notification"); + frm.refresh_fields("delivery_to_subscribe_to_status_updates"); + } + else { + frm.set_value("pickup_from_send_shipping_notification", 0); + frm.set_value("pickup_from_subscribe_to_status_updates", 0); + frm.refresh_fields("pickup_from_send_shipping_notification"); + frm.refresh_fields("pickup_from_subscribe_to_status_updates"); + } + }, + fetch_shipping_rates: function(frm) { + if (!frm.doc.shipment_id) { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.fetch_shipping_rates", + freeze: true, + freeze_message: __("Fetching Shipping Rates"), + args: { + pickup_from_type: frm.doc.pickup_from_type, + delivery_to_type: frm.doc.delivery_to_type, + pickup_address_name: frm.doc.pickup_address_name, + delivery_address_name: frm.doc.delivery_address_name, + shipment_parcel: frm.doc.shipment_parcel, + description_of_content: frm.doc.description_of_content, + pickup_date: frm.doc.pickup_date, + pickup_contact_name: frm.doc.pickup_contact_name, + delivery_contact_name: frm.doc.delivery_contact_name, + value_of_goods: frm.doc.value_of_goods + }, + callback: function(r) { + if (r.message) { + select_from_available_services(frm, r.message); + } + else { + frappe.throw(__("No Shipment Services available")); + } + } + }); + } + else { + frappe.throw(__("Shipment already created")); + } + }, + print_shipping_label: function(frm) { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.print_shipping_label", + freeze: true, + freeze_message: __("Printing Shipping Label"), + args: { + shipment_id: frm.doc.shipment_id, + service_provider: frm.doc.service_provider + }, + callback: function(r) { + if (r.message) { + if (frm.doc.service_provider == "LetMeShip") { + var array = JSON.parse(r.message); + // Uint8Array for unsigned bytes + array = new Uint8Array(array); + const file = new Blob([array], {type: "application/pdf"}); + const file_url = URL.createObjectURL(file); + window.open(file_url); + } + else { + if (Array.isArray(r.message)) { + r.message.forEach(url => window.open(url)); + } else { + window.open(r.message); + } + } + } + } + }); + }, + update_tracking: function(frm, service_provider, shipment_id) { + let delivery_notes = []; + (frm.doc.shipment_delivery_notes || []).forEach((d) => { + delivery_notes.push(d.delivery_note); + }); + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.update_tracking", + freeze: true, + freeze_message: __("Updating Tracking"), + args: { + shipment: frm.doc.name, + shipment_id: shipment_id, + service_provider: service_provider, + delivery_notes: delivery_notes + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + } + } + }); + } +}); + +frappe.ui.form.on('Shipment Delivery Notes', { + delivery_note: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.delivery_note) { + let row_index = row.idx - 1; + if(validate_duplicate(frm, 'shipment_delivery_notes', row.delivery_note, row_index)) { + cur_frm.get_field('shipment_delivery_notes').grid.grid_rows[row_index].remove(); + frappe.throw(__(`You have entered duplicate Delivery Notes. Please rectify and try again.`)); + } + } + }, + grand_total: function(frm, cdt, cdn) { + let row = locals[cdt][cdn]; + if (row.grand_total) { + var value_of_goods = parseFloat(frm.doc.value_of_goods)+parseFloat(row.grand_total); + frm.set_value("value_of_goods", Math.round(value_of_goods)); + frm.refresh_fields("value_of_goods"); + } + }, +}); + +var validate_duplicate = function(frm, table, fieldname, index){ + let duplicate = false; + $.each(frm.doc[table], function(i, detail) { + // Email duplicate validation + if(detail.email === fieldname && !(index === i)) { + duplicate = true; + return; + } + + // Delivery Note duplicate validation + if(detail.delivery_note === fieldname && !(index === i)) { + duplicate = true; + return; + } + }); + return duplicate; +}; + +function select_from_available_services(frm, available_services) { + var headers = [ __("Service Provider"), __("Carrier"), __("Carrier’s Service"), __("Price"), "" ]; + cur_frm.render_available_services = function(d, headers, data){ + d.fields_dict.available_services.$wrapper.html( + frappe.render_template('shipment_service_selector', + {'header_columns': headers, 'data': data} + ) + ); + }; + const d = new frappe.ui.Dialog({ + title: __("Select Shipment Service to create Shipment"), + fields: [ + { + fieldtype:'HTML', + fieldname:"available_services", + label: __('Available Services') + } + ] + }); + cur_frm.render_available_services(d, headers, available_services); + let shipment_notific_email = []; + let tracking_notific_email = []; + (frm.doc.shipment_notification_subscriptions || []).forEach((d) => { + if (!d.unsubscribed) { + shipment_notific_email.push(d.email); + } + }); + (frm.doc.shipment_status_update_subscriptions || []).forEach((d) => { + if (!d.unsubscribed) { + tracking_notific_email.push(d.email); + } + }); + let delivery_notes = []; + (frm.doc.shipment_delivery_notes || []).forEach((d) => { + delivery_notes.push(d.delivery_note); + }); + cur_frm.select_row = function(service_data){ + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.create_shipment", + freeze: true, + freeze_message: __("Creating Shipment"), + args: { + shipment: frm.doc.name, + pickup_from_type: frm.doc.pickup_from_type, + delivery_to_type: frm.doc.delivery_to_type, + pickup_address_name: frm.doc.pickup_address_name, + delivery_address_name: frm.doc.delivery_address_name, + shipment_parcel: frm.doc.shipment_parcel, + description_of_content: frm.doc.description_of_content, + pickup_date: frm.doc.pickup_date, + pickup_contact_name: frm.doc.pickup_contact_name, + delivery_contact_name: frm.doc.delivery_contact_name, + value_of_goods: frm.doc.value_of_goods, + service_data: service_data, + shipment_notific_email: shipment_notific_email, + tracking_notific_email: tracking_notific_email, + delivery_notes: delivery_notes + }, + callback: function(r) { + if (!r.exc) { + frm.reload_doc(); + frappe.msgprint(__("Shipment created with {0}, ID is {1}", [r.message.service_provider, r.message.shipment_id])); + frm.events.update_tracking(frm, r.message.service_provider, r.message.shipment_id); + } + } + }); + d.hide(); + }; + d.show(); +} diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json new file mode 100644 index 0000000000..b6656a2b72 --- /dev/null +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -0,0 +1,478 @@ +{ + "actions": [], + "autoname": "SHIPMENT-.#####", + "creation": "2020-07-09 10:58:52.508703", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "heading_pickup_from", + "pickup_from_type", + "pickup_company", + "pickup_customer", + "pickup_supplier", + "pickup", + "pickup_address_name", + "pickup_address", + "pickup_contact_name", + "pickup_contact_email", + "pickup_contact", + "column_break_2", + "heading_delivery_to", + "delivery_to_type", + "delivery_company", + "delivery_customer", + "delivery_supplier", + "delivery_to", + "delivery_address_name", + "delivery_address", + "delivery_contact_name", + "delivery_contact_email", + "delivery_contact", + "notification_details_section", + "pickup_from_send_shipping_notification", + "pickup_from_subscribe_to_status_updates", + "shipment_notification_subscriptions", + "column_break_27", + "delivery_to_send_shipping_notification", + "delivery_to_subscribe_to_status_updates", + "shipment_status_update_subscriptions", + "parcels_section", + "shipment_parcel", + "parcel_template", + "add_template", + "column_break_28", + "shipment_delivery_notes", + "shipment_details_section", + "pallets", + "value_of_goods", + "pickup_date", + "pickup_from", + "pickup_to", + "column_break_36", + "shipment_type", + "pickup_type", + "incoterm", + "description_of_content", + "section_break_40", + "shipment_information_section", + "service_provider", + "shipment_id", + "shipment_amount", + "status", + "tracking_url", + "column_break_55", + "carrier", + "carrier_service", + "awb_number", + "tracking_status", + "tracking_status_info", + "amended_from" + ], + "fields": [ + { + "fieldname": "heading_pickup_from", + "fieldtype": "Heading", + "label": "Pickup from" + }, + { + "default": "Company", + "fieldname": "pickup_from_type", + "fieldtype": "Select", + "label": "Pickup from", + "options": "Company\nCustomer\nSupplier" + }, + { + "depends_on": "eval:doc.pickup_from_type == 'Company'", + "fieldname": "pickup_company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.pickup_from_type == 'Customer'", + "fieldname": "pickup_customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "depends_on": "eval:doc.pickup_from_type == 'Supplier'", + "fieldname": "pickup_supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "pickup", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Pickup From", + "read_only": 1 + }, + { + "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"", + "fieldname": "pickup_address_name", + "fieldtype": "Link", + "label": "Address", + "options": "Address", + "reqd": 1 + }, + { + "fieldname": "pickup_address", + "fieldtype": "Small Text", + "read_only": 1 + }, + { + "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"", + "fieldname": "pickup_contact_name", + "fieldtype": "Link", + "label": "Contact", + "options": "Contact" + }, + { + "fieldname": "pickup_contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "read_only": 1 + }, + { + "fieldname": "pickup_contact", + "fieldtype": "Small Text", + "read_only": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "heading_delivery_to", + "fieldtype": "Heading", + "label": "Delivery to" + }, + { + "default": "Customer", + "fieldname": "delivery_to_type", + "fieldtype": "Select", + "label": "Delivery to", + "options": "Company\nCustomer\nSupplier" + }, + { + "depends_on": "eval:doc.delivery_to_type == 'Company'", + "fieldname": "delivery_company", + "fieldtype": "Link", + "label": "Company", + "options": "Company" + }, + { + "depends_on": "eval:doc.delivery_to_type == 'Customer'", + "fieldname": "delivery_customer", + "fieldtype": "Link", + "label": "Customer", + "options": "Customer" + }, + { + "depends_on": "eval:doc.delivery_to_type == 'Supplier'", + "fieldname": "delivery_supplier", + "fieldtype": "Link", + "label": "Supplier", + "options": "Supplier" + }, + { + "fieldname": "delivery_to", + "fieldtype": "Data", + "hidden": 1, + "in_list_view": 1, + "label": "Delivery To", + "read_only": 1 + }, + { + "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"", + "fieldname": "delivery_address_name", + "fieldtype": "Link", + "label": "Address", + "options": "Address", + "reqd": 1 + }, + { + "fieldname": "delivery_address", + "fieldtype": "Small Text", + "read_only": 1 + }, + { + "depends_on": "eval: doc.delivery_customer || doc.delivery_supplier || doc.delivery_to_type == \"Company\"", + "fieldname": "delivery_contact_name", + "fieldtype": "Link", + "label": "Contact", + "options": "Contact" + }, + { + "fieldname": "delivery_contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "read_only": 1 + }, + { + "depends_on": "eval:doc.delivery_contact_name", + "fieldname": "delivery_contact", + "fieldtype": "Small Text", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "notification_details_section", + "fieldtype": "Section Break", + "label": "Notification Details" + }, + { + "default": "0", + "fieldname": "pickup_from_send_shipping_notification", + "fieldtype": "Check", + "label": "Send shipping notification" + }, + { + "default": "0", + "fieldname": "pickup_from_subscribe_to_status_updates", + "fieldtype": "Check", + "label": "Subscribe to status updates" + }, + { + "fieldname": "shipment_notification_subscriptions", + "fieldtype": "Table", + "label": "Shipment Notification Subscriptions", + "options": "Shipment Notification Subscriptions" + }, + { + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "delivery_to_send_shipping_notification", + "fieldtype": "Check", + "label": "Send shipping notification" + }, + { + "default": "0", + "fieldname": "delivery_to_subscribe_to_status_updates", + "fieldtype": "Check", + "label": "Subscribe to status updates" + }, + { + "fieldname": "shipment_status_update_subscriptions", + "fieldtype": "Table", + "label": "Shipment Status Update Subscriptions", + "options": "Shipment Status Update Subscriptions" + }, + { + "fieldname": "parcels_section", + "fieldtype": "Section Break", + "label": "Parcels" + }, + { + "fieldname": "shipment_parcel", + "fieldtype": "Table", + "label": "Shipment Parcel", + "options": "Shipment Parcel" + }, + { + "fieldname": "parcel_template", + "fieldtype": "Link", + "label": "Parcel Template", + "options": "Shipment Parcel Template" + }, + { + "fieldname": "add_template", + "fieldtype": "Button", + "label": "Add Template" + }, + { + "fieldname": "column_break_28", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipment_delivery_notes", + "fieldtype": "Table", + "label": "Shipment Delivery Notes", + "options": "Shipment Delivery Notes" + }, + { + "fieldname": "shipment_details_section", + "fieldtype": "Section Break", + "label": "Shipment details" + }, + { + "default": "No", + "fieldname": "pallets", + "fieldtype": "Select", + "label": "Pallets", + "options": "No\nYes" + }, + { + "fieldname": "value_of_goods", + "fieldtype": "Currency", + "label": "Value of Goods", + "precision": "2", + "reqd": 1 + }, + { + "fieldname": "pickup_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Pickup Date", + "reqd": 1 + }, + { + "default": "09:00", + "fieldname": "pickup_from", + "fieldtype": "Select", + "label": "Pickup from", + "options": "09:00\n09:30\n10:00\n10:30\n11:00\n11:30\n12:00\n12:30\n13:00\n13:30\n14:00\n14:30\n15:00\n15:30\n16:00\n16:30\n17:00\n17:30\n18:00\n18:30\n19:00" + }, + { + "default": "17:00", + "fieldname": "pickup_to", + "fieldtype": "Select", + "label": "Pickup to", + "options": "09:00\n09:30\n10:00\n10:30\n11:00\n11:30\n12:00\n12:30\n13:00\n13:30\n14:00\n14:30\n15:00\n15:30\n16:00\n16:30\n17:00\n17:30\n18:00\n18:30\n19:00" + }, + { + "fieldname": "column_break_36", + "fieldtype": "Column Break" + }, + { + "default": "Goods", + "fieldname": "shipment_type", + "fieldtype": "Select", + "label": "Shipment Type", + "options": "Goods\nDocuments" + }, + { + "default": "Pickup", + "fieldname": "pickup_type", + "fieldtype": "Select", + "label": "Pickup Type", + "options": "Pickup\nSelf delivery" + }, + { + "fieldname": "description_of_content", + "fieldtype": "Small Text", + "label": "Description of Content", + "reqd": 1 + }, + { + "fieldname": "section_break_40", + "fieldtype": "Section Break" + }, + { + "fieldname": "shipment_information_section", + "fieldtype": "Section Break", + "label": "Shipment Information" + }, + { + "fieldname": "service_provider", + "fieldtype": "Read Only", + "label": "Service Provider" + }, + { + "fieldname": "shipment_id", + "fieldtype": "Read Only", + "label": "Shipment ID" + }, + { + "fieldname": "shipment_amount", + "fieldtype": "Currency", + "label": "Shipment Amount", + "precision": "2", + "read_only": 1 + }, + { + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted", + "read_only": 1 + }, + { + "fieldname": "tracking_url", + "fieldtype": "Small Text", + "label": "Tracking URL", + "read_only": 1 + }, + { + "fieldname": "carrier", + "fieldtype": "Read Only", + "label": "Carrier" + }, + { + "fieldname": "carrier_service", + "fieldtype": "Read Only", + "label": "Carrier Service" + }, + { + "fieldname": "awb_number", + "fieldtype": "Read Only", + "label": "AWB Number" + }, + { + "fieldname": "tracking_status", + "fieldtype": "Select", + "label": "Tracking Status", + "options": "\nIn Progress\nDelivered\nReturned\nLost", + "read_only": 1 + }, + { + "fieldname": "tracking_status_info", + "fieldtype": "Data", + "label": "Tracking Status Info", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Shipment", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_55", + "fieldtype": "Column Break" + }, + { + "fieldname": "incoterm", + "fieldtype": "Select", + "label": "Incoterm", + "options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)" + } + ], + "is_submittable": 1, + "links": [], + "modified": "2020-07-24 11:44:30.904612", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment", + "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", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py new file mode 100644 index 0000000000..e059bacfa1 --- /dev/null +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -0,0 +1,300 @@ +# -*- 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 frappe +import json +from frappe import _ +from frappe.model.document import Document +from erpnext.accounts.party import get_party_shipping_address +from frappe.contacts.doctype.contact.contact import get_default_contact +from erpnext.erpnext_integrations.doctype.letmeship.letmeship import LETMESHIP_PROVIDER, get_letmeship_available_services, create_letmeship_shipment, get_letmeship_label, get_letmeship_tracking_data +from erpnext.erpnext_integrations.doctype.packlink.packlink import PACKLINK_PROVIDER, get_packlink_available_services, create_packlink_shipment, get_packlink_label, get_packlink_tracking_data +from erpnext.erpnext_integrations.doctype.sendcloud.sendcloud import SENDCLOUD_PROVIDER, get_sendcloud_available_services, create_sendcloud_shipment, get_sendcloud_label, get_sendcloud_tracking_data +from erpnext.stock.doctype.parcel_service_type.parcel_service_type import match_parcel_service_type_alias + +class Shipment(Document): + def validate(self): + self.validate_weight() + if self.docstatus == 0: + self.status = 'Draft' + + def on_submit(self): + if not self.shipment_parcel: + frappe.throw(_('Please enter Shipment Parcel information')) + if self.value_of_goods == 0: + frappe.throw(_('Value of goods cannot be 0')) + self.status = 'Submitted' + + def on_cancel(self): + self.status = 'Cancelled' + + def validate_weight(self): + for parcel in self.shipment_parcel: + if parcel.weight <= 0: + frappe.throw(_('Parcel weight cannot be 0')) + +@frappe.whitelist() +def fetch_shipping_rates(pickup_from_type, delivery_to_type, pickup_address_name, delivery_address_name, + shipment_parcel, description_of_content, pickup_date, value_of_goods, + pickup_contact_name=None, delivery_contact_name=None): + # Return Shipping Rates for the various Shipping Providers + shipment_prices = [] + letmeship_enabled = frappe.db.get_single_value('LetMeShip','enabled') + packlink_enabled = frappe.db.get_single_value('Packlink','enabled') + sendcloud_enabled = frappe.db.get_single_value('SendCloud','enabled') + pickup_address = get_address(pickup_address_name) + delivery_address = get_address(delivery_address_name) + if letmeship_enabled: + pickup_contact = None + delivery_contact = None + if pickup_from_type != 'Company': + pickup_contact = get_contact(pickup_contact_name) + else: + pickup_contact = get_company_contact() + + if delivery_to_type != 'Company': + delivery_contact = get_contact(delivery_contact_name) + else: + delivery_contact = get_company_contact() + letmeship_prices = get_letmeship_available_services( + delivery_to_type=delivery_to_type, + pickup_address=pickup_address, + 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, + ) + letmeship_prices = match_parcel_service_type_carrier(letmeship_prices, ['carrier', 'carrier_name']) + shipment_prices = shipment_prices + letmeship_prices + if packlink_enabled: + packlink_prices = get_packlink_available_services( + pickup_address=pickup_address, + delivery_address=delivery_address, + shipment_parcel=shipment_parcel, + pickup_date=pickup_date + ) + packlink_prices = match_parcel_service_type_carrier(packlink_prices, ['carrier_name', 'carrier']) + shipment_prices = shipment_prices + packlink_prices + if sendcloud_enabled and pickup_from_type == 'Company': + sendcloud_prices = get_sendcloud_available_services( + delivery_address=delivery_address, + shipment_parcel=shipment_parcel + ) + shipment_prices = shipment_prices + sendcloud_prices + shipment_prices = sorted(shipment_prices, key=lambda k:k['total_price']) + return shipment_prices + +@frappe.whitelist() +def create_shipment(shipment, pickup_from_type, delivery_to_type, pickup_address_name, + delivery_address_name, shipment_parcel, description_of_content, pickup_date, + value_of_goods, service_data, shipment_notific_email, tracking_notific_email, + pickup_contact_name=None, delivery_contact_name=None, delivery_notes=[]): + # Create Shipment for the selected provider + service_info = json.loads(service_data) + shipment_info = None + pickup_contact = None + delivery_contact = None + pickup_address = get_address(pickup_address_name) + delivery_address = get_address(delivery_address_name) + if pickup_from_type != 'Company': + pickup_contact = get_contact(pickup_contact_name) + else: + pickup_contact = get_company_contact() + + if delivery_to_type != 'Company': + delivery_contact = get_contact(delivery_contact_name) + else: + delivery_contact = get_company_contact() + if service_info['service_provider'] == LETMESHIP_PROVIDER: + shipment_info = create_letmeship_shipment( + pickup_address=pickup_address, + 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, + shipment_notific_email=shipment_notific_email, + tracking_notific_email=tracking_notific_email, + ) + + if service_info['service_provider'] == PACKLINK_PROVIDER: + shipment_info = create_packlink_shipment( + pickup_address=pickup_address, + 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 service_info['service_provider'] == SENDCLOUD_PROVIDER: + shipment_info = create_sendcloud_shipment( + shipment=shipment, + delivery_address=delivery_address, + shipment_parcel=shipment_parcel, + description_of_content=description_of_content, + value_of_goods=value_of_goods, + delivery_contact=delivery_contact, + service_info=service_info, + ) + + if shipment_info: + frappe.db.set_value('Shipment', shipment, 'service_provider', shipment_info.get('service_provider')) + frappe.db.set_value('Shipment', shipment, 'carrier', shipment_info.get('carrier')) + frappe.db.set_value('Shipment', shipment, 'carrier_service', shipment_info.get('carrier_service')) + frappe.db.set_value('Shipment', shipment, 'shipment_id', shipment_info.get('shipment_id')) + frappe.db.set_value('Shipment', shipment, 'shipment_amount', shipment_info.get('shipment_amount')) + frappe.db.set_value('Shipment', shipment, 'awb_number', shipment_info.get('awb_number')) + frappe.db.set_value('Shipment', shipment, 'status', 'Booked') + if delivery_notes: + update_delivery_note(delivery_notes=delivery_notes, shipment_info=shipment_info) + return shipment_info + + +@frappe.whitelist() +def print_shipping_label(service_provider, shipment_id): + if service_provider == LETMESHIP_PROVIDER: + shipping_label = get_letmeship_label(shipment_id) + elif service_provider == PACKLINK_PROVIDER: + shipping_label = get_packlink_label(shipment_id) + elif service_provider == SENDCLOUD_PROVIDER: + shipping_label = get_sendcloud_label(shipment_id) + return shipping_label + + +@frappe.whitelist() +def update_tracking(shipment, service_provider, shipment_id, delivery_notes=[]): + # Update Tracking info in Shipment + tracking_data = None + if service_provider == LETMESHIP_PROVIDER: + tracking_data = get_letmeship_tracking_data(shipment_id) + elif service_provider == PACKLINK_PROVIDER: + tracking_data = get_packlink_tracking_data(shipment_id) + elif service_provider == SENDCLOUD_PROVIDER: + tracking_data = get_sendcloud_tracking_data(shipment_id) + if tracking_data: + if delivery_notes: + update_delivery_note(delivery_notes=delivery_notes, tracking_info=tracking_data) + frappe.db.set_value('Shipment', shipment, 'awb_number', tracking_data.get('awb_number')) + frappe.db.set_value('Shipment', shipment, 'tracking_status', tracking_data.get('tracking_status')) + frappe.db.set_value('Shipment', shipment, 'tracking_status_info', tracking_data.get('tracking_status_info')) + frappe.db.set_value('Shipment', shipment, 'tracking_url', tracking_data.get('tracking_url')) + +@frappe.whitelist() +def get_address_name(ref_doctype, docname): + # Return address name + return get_party_shipping_address(ref_doctype, docname) + +@frappe.whitelist() +def get_contact_name(ref_doctype, docname): + # Return address name + return get_default_contact(ref_doctype, docname) + +def update_delivery_note(delivery_notes, shipment_info=None, tracking_info=None): + # Update Shipment Info in Delivery Note + # Using db_set since some services might not exist + for delivery_note in json.loads(delivery_notes): + dl_doc = frappe.get_doc('Delivery Note', delivery_note) + if shipment_info: + dl_doc.db_set('delivery_type', 'Parcel Service') + dl_doc.db_set('parcel_service', shipment_info.get('carrier')) + dl_doc.db_set('parcel_service_type', shipment_info.get('carrier_service')) + if tracking_info: + dl_doc.db_set('tracking_number', tracking_info.get('awb_number')) + dl_doc.db_set('tracking_url', tracking_info.get('tracking_url')) + dl_doc.db_set('tracking_status', tracking_info.get('tracking_status')) + dl_doc.db_set('tracking_status_info', tracking_info.get('tracking_status_info')) + + +def update_tracking_info(): + # Daily scheduled event to update Tracking info for not delivered Shipments + # Also Updates the related Delivery Notes + shipments = frappe.get_all('Shipment', filters={ + 'docstatus': 1, + 'status': 'Booked', + 'shipment_id': ['!=', ''], + 'tracking_status': ['!=', 'Delivered'], + }) + for shipment in shipments: + shipment_doc = frappe.get_doc('Shipment', shipment.name) + tracking_info = \ + update_tracking( + shipment_doc.service_provider, + shipment_doc.shipment_id, + shipment_doc.shipment_delivery_notes + ) + if tracking_info: + shipment_doc.db_set('awb_number', tracking_info.get('awb_number')) + shipment_doc.db_set('tracking_url', tracking_info.get('tracking_url')) + shipment_doc.db_set('tracking_status', tracking_info.get('tracking_status')) + shipment_doc.db_set('tracking_status_info', tracking_info.get('tracking_status_info')) + + +def get_address(address_name): + address = frappe.db.get_value('Address', address_name, [ + 'address_title', + 'address_line1', + 'address_line2', + 'city', + 'pincode', + 'country', + ], as_dict=1) + address.country_code = frappe.db.get_value('Country', address.country, 'code').upper() + if not address.pincode or address.pincode == '': + frappe.throw(_("Postal Code is mandatory to continue.
\ + Please set Postal Code for Address {1}" + ).format(address_name, address_name)) + address.pincode = address.pincode.replace(' ', '') + address.city = address.city.strip() + return address + + +def get_contact(contact_name): + contact = frappe.db.get_value('Contact', contact_name, [ + 'first_name', + 'last_name', + 'email_id', + 'phone', + 'mobile_no', + 'gender', + ], as_dict=1) + if not contact.last_name: + frappe.throw(_("Last Name is mandatory to continue.
\ + Please set Last Name for Contact {1}" + ).format(contact_name, contact_name)) + if not contact.phone: + contact.phone = contact.mobile_no + return contact + + +def get_company_contact(): + contact = frappe.db.get_value('User', frappe.session.user, [ + 'first_name', + 'last_name', + 'email', + 'phone', + 'mobile_no', + 'gender', + ], as_dict=1) + if not contact.phone: + contact.phone = contact.mobile_no + return contact + +def match_parcel_service_type_carrier(shipment_prices, reference): + for idx, prices in enumerate(shipment_prices): + service_name = match_parcel_service_type_alias(prices.get(reference[0]), prices.get(reference[1])) + is_preferred = frappe.db.get_value('Parcel Service Type', service_name, 'show_in_preferred_services_list') + shipment_prices[idx].service_name = service_name + shipment_prices[idx].is_preferred = is_preferred + return shipment_prices diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js new file mode 100644 index 0000000000..57e92099cb --- /dev/null +++ b/erpnext/stock/doctype/shipment/shipment_list.js @@ -0,0 +1,8 @@ +frappe.listview_settings['Shipment'] = { + add_fields: ["status"], + get_indicator: function(doc) { + if(doc.status=='Booked') { + return [__("Booked"), "green"]; + } + } +}; \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment/shipment_service_selector.html b/erpnext/stock/doctype/shipment/shipment_service_selector.html new file mode 100644 index 0000000000..ed9b8bf400 --- /dev/null +++ b/erpnext/stock/doctype/shipment/shipment_service_selector.html @@ -0,0 +1,70 @@ +{% if (data.length) { %} +
+
{{ __("Preferred Services") }}
+ + + + {% for (var i = 0; i < header_columns.length; i++) { %} + + {% } %} + + + + {% for (var i = 0; i < data.length; i++) { %} + {% if (data[i].is_preferred) { %} + + + + + + + + {% } %} + {% } %} + +
{{ header_columns[i] }}
{{ data[i].service_provider }}{{ data[i].carrier }}{{ data[i].service_name }}{{ format_currency(data[i].total_price, 'EUR', 2) }} + +
+
{{ __("Other Services") }}
+ + + + {% for (var i = 0; i < header_columns.length; i++) { %} + + {% } %} + + + + {% for (var i = 0; i < data.length; i++) { %} + {% if (!data[i].is_preferred) { %} + + + + + + + + {% } %} + {% } %} + +
{{ header_columns[i] }}
{{ data[i].service_provider }}{{ data[i].carrier }}{{ data[i].service_name }}{{ format_currency(data[i].total_price, 'EUR', 2) }} + +
+
+{% } else { %} +
{{ __("No Services Available") }}
+{% } %} + + \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py new file mode 100644 index 0000000000..6a06930e82 --- /dev/null +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -0,0 +1,333 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals +import json +from datetime import date, timedelta + +import frappe +import unittest +from erpnext.stock.doctype.shipment.shipment import fetch_shipping_rates +from erpnext.stock.doctype.shipment.shipment import create_shipment +from erpnext.stock.doctype.shipment.shipment import update_tracking + +class TestShipment(unittest.TestCase): + pass + + def test_shipment_booking(self): + shipment = create_test_shipment() + try: + shipment.submit() + except: + frappe.throw('Error occurred on submit shipment') + doc, rate, tracking_data = make_shipment_transaction(shipment) + if doc and rate and tracking_data: + self.assertEqual(doc.service_provider, rate.get('service_provider')) + self.assertEqual(doc.shipment_amount, rate.get('actual_price')) + self.assertEqual(doc.carrier, rate.get('carrier')) + self.assertEqual(doc.tracking_status, tracking_data.get('tracking_status')) + self.assertEqual(doc.tracking_url, tracking_data.get('tracking_url')) + + def test_shipment_from_delivery_note(self): + delivery_note = create_test_delivery_note() + try: + delivery_note.submit() + except: + frappe.throw('An error occurred.') + + shipment = create_test_shipment([ delivery_note ]) + try: + shipment.submit() + except: + frappe.throw('Error occurred on submit shipment') + doc, rate, tracking_data = make_shipment_transaction(shipment) + if doc and rate and tracking_data: + self.assertEqual(doc.service_provider, rate.get('service_provider')) + self.assertEqual(doc.shipment_amount, rate.get('actual_price')) + self.assertEqual(doc.carrier, rate.get('carrier')) + self.assertEqual(doc.tracking_status, tracking_data.get('tracking_status')) + self.assertEqual(doc.tracking_url, tracking_data.get('tracking_url')) + + + +def make_shipment_transaction(shipment): + shipment_parcel = convert_shipmet_parcel(shipment.shipment_parcel) + shipment_rates = fetch_shipping_rates(shipment.pickup_from_type, shipment.delivery_to_type, + shipment.pickup_address_name, shipment.delivery_address_name, + shipment_parcel, shipment.description_of_content, + shipment.pickup_date, shipment.value_of_goods, + pickup_contact_name=shipment.pickup_contact_name, + delivery_contact_name=shipment.delivery_contact_name + ) + if len(shipment_rates) > 0: + # We are taking the first shipment rate + rate = shipment_rates[0] + new_shipment = create_shipment( + shipment=shipment.name, + pickup_from_type=shipment.pickup_from_type, + delivery_to_type=shipment.delivery_to_type, + pickup_address_name=shipment.pickup_address_name, + delivery_address_name=shipment.delivery_address_name, + shipment_parcel=shipment_parcel, + description_of_content=shipment.description_of_content, + pickup_date=shipment.pickup_date, + pickup_contact_name=shipment.pickup_contact_name, + delivery_contact_name=shipment.delivery_contact_name, + value_of_goods=shipment.value_of_goods, + service_data=json.dumps(rate), + shipment_notific_email=None, + tracking_notific_email=None, + delivery_notes=None + ) + service_provider = rate.get('service_provider') + shipment_id = new_shipment.get('shipment_id') + tracking_data = update_tracking( + shipment.name, + service_provider, + shipment_id, + delivery_notes=None + ) + doc = frappe.get_doc('Shipment', shipment.name) + return doc, rate, tracking_data + return None, None, None + +def create_test_delivery_note(): + company = get_shipment_company() + customer = get_shipment_customer() + item = get_shipment_item(company.name) + posting_date = date.today() + timedelta(days=1) + + create_material_receipt(item, company.name) + delivery_note = frappe.new_doc("Delivery Note") + delivery_note.company = company.name + delivery_note.posting_date = posting_date.strftime("%Y-%m-%d") + delivery_note.posting_time = '10:00' + delivery_note.customer = customer.name + delivery_note.append('items', + { + "item_code": item.name, + "item_name": item.item_name, + "description": 'Test delivery note for shipment', + "qty": 5, + "uom": 'Nos', + "warehouse": 'Stores - SC', + "rate": item.standard_rate, + "cost_center": 'Main - SC' + } + ) + delivery_note.insert() + frappe.db.commit() + return delivery_note + + +def create_test_shipment(delivery_notes=[]): + company = get_shipment_company() + company_address = get_shipment_company_address(company.name) + customer = get_shipment_customer() + customer_address = get_shipment_customer_address(customer.name) + customer_contact = get_shipment_customer_contact(customer.name) + posting_date = date.today() + timedelta(days=5) + + shipment = frappe.new_doc("Shipment") + shipment.pickup_from_type = 'Company' + shipment.pickup_company = company.name + shipment.pickup_address_name = company_address.name + shipment.delivery_to_type = 'Customer' + shipment.delivery_customer = customer.name + shipment.delivery_address_name = customer_address.name + shipment.delivery_contact_name = customer_contact.name + shipment.pallets = 'No' + shipment.shipment_type = 'Goods' + shipment.value_of_goods = 1000 + shipment.pickup_type = 'Pickup' + shipment.pickup_date = posting_date.strftime("%Y-%m-%d") + shipment.pickup_from = '09:00' + shipment.pickup_to = '17:00' + shipment.description_of_content = 'unit test entry' + for delivery_note in delivery_notes: + shipment.append('shipment_delivery_notes', + { + "delivery_note": delivery_note.name + } + ) + shipment.append('shipment_parcel', + { + "length": 5, + "width": 5, + "height": 5, + "weight": 5, + "count": 5 + } + ) + shipment.insert() + frappe.db.commit() + return shipment + + +def get_shipment_customer_contact(customer_name): + contact_fname = 'Customer Shipment' + contact_lname = 'Testing' + customer_name = contact_fname + ' ' + contact_lname + contacts = frappe.get_all("Contact", fields=["name"], filters = {"name": customer_name}) + if len(contacts): + return contacts[0] + else: + return create_customer_contact(contact_fname, contact_lname) + + +def get_shipment_customer_address(customer_name): + address_title = customer_name + ' address 123' + customer_address = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title}) + if len(customer_address): + return customer_address[0] + else: + return create_shipment_address(address_title, customer_name, 81929) + +def get_shipment_customer(): + customer_name = 'Shipment Customer' + customer = frappe.get_all("Customer", fields=["name"], filters = {"name": customer_name}) + if len(customer): + return customer[0] + else: + return create_shipment_customer(customer_name) + +def get_shipment_company_address(company_name): + address_title = company_name + ' address 123' + addresses = frappe.get_all("Address", fields=["name"], filters = {"address_title": address_title}) + if len(addresses): + return addresses[0] + else: + return create_shipment_address(address_title, company_name, 80331) + +def get_shipment_company(): + company_name = 'Shipment Company' + abbr = 'SC' + companies = frappe.get_all("Company", fields=["name"], filters = {"company_name": company_name}) + if len(companies): + return companies[0] + else: + return create_shipment_company(company_name, abbr) + +def get_shipment_item(company_name): + item_name = 'Testing Shipment item' + items = frappe.get_all("Item", + fields=["name", "item_name", "item_code", "standard_rate"], + filters = {"item_name": item_name} + ) + if len(items): + return items[0] + else: + return create_shipment_item(item_name, company_name) + +def create_shipment_address(address_title, company_name, postal_code): + address = frappe.new_doc("Address") + address.address_title = address_title + address.address_type = 'Shipping' + address.address_line1 = company_name + ' address line 1' + address.city = 'Random City' + address.postal_code = postal_code + address.country = 'Germany' + address.insert() + return address + + +def create_customer_contact(fname, lname): + customer = frappe.new_doc("Contact") + customer.customer_name = fname + ' ' + lname + customer.first_name = fname + customer.last_name = lname + customer.is_primary_contact = 1 + customer.is_billing_contact = 1 + customer.append('email_ids', + { + 'email_id': 'randomme@email.com', + 'is_primary': 1 + } + ) + customer.append('phone_nos', + { + 'phone': '123123123', + 'is_primary_phone': 1, + 'is_primary_mobile_no': 1 + } + ) + customer.status = 'Passive' + customer.insert() + return customer + + +def create_shipment_company(company_name, abbr): + company = frappe.new_doc("Company") + company.company_name = company_name + company.abbr = abbr + company.default_currency = 'EUR' + company.country = 'Germany' + company.insert() + return company + +def create_shipment_customer(customer_name): + customer = frappe.new_doc("Customer") + customer.customer_name = customer_name + customer.customer_type = 'Company' + customer.customer_group = 'All Customer Groups' + customer.territory = 'All Territories' + customer.gst_category = 'Unregistered' + customer.insert() + return customer + +def create_material_receipt(item, company): + posting_date = date.today() + stock = frappe.new_doc("Stock Entry") + stock.company = company + stock.stock_entry_type = 'Material Receipt' + stock.posting_date = posting_date.strftime("%Y-%m-%d") + stock.append('items', + { + "t_warehouse": 'Stores - SC', + "item_code": item.name, + "qty": 5, + "uom": 'Nos', + "basic_rate": item.standard_rate, + "cost_center": 'Main - SC' + } + ) + stock.insert() + try: + stock.submit() + except: + frappe.throw('An error occurred.') + + +def create_shipment_item(item_name, company_name): + item = frappe.new_doc("Item") + item.item_name = item_name + item.item_code = item_name + item.item_group = 'All Item Groups' + item.opening_stock = 'Nos' + item.standard_rate = 50 + item.append('item_defaults', + { + "company": company_name, + "default_warehouse": 'Stores - SC' + } + ) + try: + item.insert() + except: + frappe.throw('An error occurred.') + return item + + +def convert_shipmet_parcel(shipmet_parcel): + data = [] + for parcel in shipmet_parcel: + data.append( + { + "length": parcel.length, + "width": parcel.width, + "height": parcel.height, + "weight": parcel.weight, + "count": parcel.count + } + ) + return json.dumps(data) diff --git a/erpnext/stock/doctype/shipment_delivery_notes/__init__.py b/erpnext/stock/doctype/shipment_delivery_notes/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json b/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json new file mode 100644 index 0000000000..fbc01d9a24 --- /dev/null +++ b/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json @@ -0,0 +1,41 @@ +{ + "actions": [], + "creation": "2020-07-09 11:52:57.939021", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "delivery_note", + "grand_total" + ], + "fields": [ + { + "fieldname": "delivery_note", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Delivery Note", + "options": "Delivery Note", + "reqd": 1 + }, + { + "fetch_from": "delivery_note.grand_total", + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Value", + "read_only": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-09 12:55:01.134270", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment Delivery Notes", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py b/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py new file mode 100644 index 0000000000..ed936c60f8 --- /dev/null +++ b/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ShipmentDeliveryNotes(Document): + pass diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/__init__.py b/erpnext/stock/doctype/shipment_notification_subscriptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json b/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json new file mode 100644 index 0000000000..bd9b8003a8 --- /dev/null +++ b/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "creation": "2020-07-09 12:49:09.185552", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "email", + "unsubscribed" + ], + "fields": [ + { + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "email", + "reqd": 1, + "unique": 1 + }, + { + "default": "0", + "fieldname": "unsubscribed", + "fieldtype": "Check", + "in_list_view": 1, + "label": "unsubscribed" + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-09 12:55:14.217387", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment Notification Subscriptions", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py b/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py new file mode 100644 index 0000000000..28ead7fab8 --- /dev/null +++ b/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ShipmentNotificationSubscriptions(Document): + pass diff --git a/erpnext/stock/doctype/shipment_parcel/__init__.py b/erpnext/stock/doctype/shipment_parcel/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json new file mode 100644 index 0000000000..6943edcdc9 --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.json @@ -0,0 +1,65 @@ +{ + "actions": [], + "creation": "2020-07-09 11:28:48.887737", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "length", + "width", + "height", + "weight", + "count" + ], + "fields": [ + { + "fieldname": "length", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Length (cm)", + "reqd": 1 + }, + { + "fieldname": "width", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Width (cm)", + "reqd": 1 + }, + { + "fieldname": "height", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Height (cm)", + "reqd": 1 + }, + { + "fieldname": "weight", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Weight (kg)", + "precision": "1", + "reqd": 1 + }, + { + "default": "1", + "fieldname": "count", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Count", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-09 12:54:14.847170", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment Parcel", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py new file mode 100644 index 0000000000..53e6ed55dd --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel/shipment_parcel.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ShipmentParcel(Document): + pass diff --git a/erpnext/stock/doctype/shipment_parcel_template/__init__.py b/erpnext/stock/doctype/shipment_parcel_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js new file mode 100644 index 0000000000..785a3b304d --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Shipment Parcel Template', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json new file mode 100644 index 0000000000..ec2bb1c9b3 --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "autoname": "field:preset_name", + "creation": "2020-07-09 11:43:43.470339", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "preset_name", + "length", + "width", + "height", + "weight" + ], + "fields": [ + { + "fieldname": "preset_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Preset Name", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "length", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Length (cm)", + "reqd": 1 + }, + { + "fieldname": "width", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Width (cm)", + "reqd": 1 + }, + { + "fieldname": "height", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Height (cm)", + "reqd": 1 + }, + { + "fieldname": "weight", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Weight (kg)", + "precision": "1", + "reqd": 1 + } + ], + "links": [], + "modified": "2020-07-10 12:53:22.772826", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment Parcel Template", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py new file mode 100644 index 0000000000..2a8d58d830 --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ShipmentParcelTemplate(Document): + pass diff --git a/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py new file mode 100644 index 0000000000..6e2caa768b --- /dev/null +++ b/erpnext/stock/doctype/shipment_parcel_template/test_shipment_parcel_template.py @@ -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 TestShipmentParcelTemplate(unittest.TestCase): + pass diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/__init__.py b/erpnext/stock/doctype/shipment_status_update_subscriptions/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json b/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json new file mode 100644 index 0000000000..3b86b400d8 --- /dev/null +++ b/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json @@ -0,0 +1,40 @@ +{ + "actions": [], + "creation": "2020-07-09 12:51:10.656612", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "email", + "unsubscribed" + ], + "fields": [ + { + "fieldname": "email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "email", + "reqd": 1, + "unique": 1 + }, + { + "default": "0", + "fieldname": "unsubscribed", + "fieldtype": "Check", + "in_list_view": 1, + "label": "unsubscribed" + } + ], + "istable": 1, + "links": [], + "modified": "2020-07-09 12:55:27.615463", + "modified_by": "Administrator", + "module": "Stock", + "name": "Shipment Status Update Subscriptions", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py b/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py new file mode 100644 index 0000000000..a8e31ea778 --- /dev/null +++ b/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py @@ -0,0 +1,10 @@ +# -*- 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 frappe +from frappe.model.document import Document + +class ShipmentStatusUpdateSubscriptions(Document): + pass From 000494f54856fd8abedd71c44c173bd4698bd8f8 Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Thu, 1 Oct 2020 01:23:29 +0000 Subject: [PATCH 2/7] fix(shipment): apply code review --- .../doctype/letmeship/letmeship.json | 4 +- .../doctype/letmeship/letmeship.py | 262 +++++++++--------- .../doctype/packlink/packlink.py | 3 + .../doctype/sendcloud/sendcloud.json | 4 +- .../doctype/sendcloud/sendcloud.py | 19 +- erpnext/erpnext_integrations/utils.py | 2 - .../doctype/delivery_note/delivery_note.py | 28 +- erpnext/stock/doctype/shipment/api/utils.py | 67 ----- erpnext/stock/doctype/shipment/shipment.js | 203 ++++++++------ erpnext/stock/doctype/shipment/shipment.json | 93 ++++--- erpnext/stock/doctype/shipment/shipment.py | 42 ++- .../shipment/shipment_service_selector.html | 88 +++--- .../__init__.py | 0 .../shipment_delivery_note.json} | 2 +- .../shipment_delivery_note.py} | 2 +- .../__init__.py | 0 .../shipment_notification_subscription.json} | 2 +- .../shipment_notification_subscription.py} | 2 +- .../shipment_parcel_template.json | 22 +- .../__init__.py | 0 .../shipment_status_update_subscription.json} | 2 +- .../shipment_status_update_subscription.py} | 2 +- 22 files changed, 405 insertions(+), 444 deletions(-) delete mode 100644 erpnext/stock/doctype/shipment/api/utils.py rename erpnext/stock/doctype/{shipment_delivery_notes => shipment_delivery_note}/__init__.py (100%) rename erpnext/stock/doctype/{shipment_delivery_notes/shipment_delivery_notes.json => shipment_delivery_note/shipment_delivery_note.json} (95%) rename erpnext/stock/doctype/{shipment_delivery_notes/shipment_delivery_notes.py => shipment_delivery_note/shipment_delivery_note.py} (86%) rename erpnext/stock/doctype/{shipment_notification_subscriptions => shipment_notification_subscription}/__init__.py (100%) rename erpnext/stock/doctype/{shipment_notification_subscriptions/shipment_notification_subscriptions.json => shipment_notification_subscription/shipment_notification_subscription.json} (93%) rename erpnext/stock/doctype/{shipment_notification_subscriptions/shipment_notification_subscriptions.py => shipment_notification_subscription/shipment_notification_subscription.py} (83%) rename erpnext/stock/doctype/{shipment_status_update_subscriptions => shipment_status_update_subscription}/__init__.py (100%) rename erpnext/stock/doctype/{shipment_status_update_subscriptions/shipment_status_update_subscriptions.json => shipment_status_update_subscription/shipment_status_update_subscription.json} (93%) rename erpnext/stock/doctype/{shipment_status_update_subscriptions/shipment_status_update_subscriptions.py => shipment_status_update_subscription/shipment_status_update_subscription.py} (83%) diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json index 4a9a70f251..94b001ed08 100644 --- a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json +++ b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json @@ -24,14 +24,14 @@ }, { "fieldname": "api_password", - "fieldtype": "Data", + "fieldtype": "Password", "label": "API Password", "read_only_depends_on": "eval:doc.enabled == 0" } ], "issingle": 1, "links": [], - "modified": "2020-08-05 16:33:44.548230", + "modified": "2020-10-21 10:28:37.607717", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "LetMeShip", diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py index 3ad06dbb58..162c1edb37 100644 --- a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py +++ b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py @@ -20,9 +20,7 @@ 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') + api_id, api_password, enabled = frappe.db.get_value('LetMeShip', 'LetMeShip', ['enabled', 'api_password', 'api_id']) if not enabled or not api_id or not api_password: return [] @@ -41,62 +39,16 @@ def get_letmeship_available_services(delivery_to_type, pickup_address, '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}, - }} + payload = generate_payload( + pickup_address=pickup_address, + pickup_contact=pickup_contact, + delivery_address=delivery_address, + delivery_contact=delivery_contact, + description_of_content=description_of_content, + value_of_goods=value_of_goods, + parcel_list=parcel_list, + pickup_date=pickup_date + ) try: available_services = [] response_data = requests.post( @@ -128,6 +80,7 @@ def get_letmeship_available_services(delivery_to_type, pickup_address, .format(response_data['message']) ) except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint( _('Error occurred while fetching LetMeShip Prices: {0}') .format(str(exc)), @@ -142,9 +95,7 @@ def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, 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') + api_id, api_password, enabled = frappe.db.get_value('LetMeShip', 'LetMeShip', ['enabled', 'api_password', 'api_id']) if not enabled or not api_id or not api_password: return [] @@ -162,6 +113,72 @@ def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, 'Accept': 'application/json', 'Access-Control-Allow-Origin': 'string' } + payload = generate_payload( + pickup_address=pickup_address, + pickup_contact=pickup_contact, + delivery_address=delivery_address, + delivery_contact=delivery_contact, + description_of_content=description_of_content, + value_of_goods=value_of_goods, + parcel_list=parcel_list, + pickup_date=pickup_date, + service_info=service_info, + tracking_notific_email=tracking_notific_email, + shipment_notific_email=shipment_notific_email + ) + 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.log_error(frappe.get_traceback()) + frappe.msgprint( + _('Error occurred while creating Shipment: {0}') + .format(str(exc)), + indicator='orange', + alert=True + ) + +def generate_payload( + pickup_address, + pickup_contact, + delivery_address, + delivery_contact, + description_of_content, + value_of_goods, + parcel_list, + pickup_date, + service_info=None, + tracking_notific_email=None, + shipment_notific_email=None +): payload = { 'pickupInfo': { 'address': { @@ -205,18 +222,6 @@ def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, }, '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', @@ -233,10 +238,24 @@ def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, 'parcelList': parcel_list, 'pickupInterval': { 'date': pickup_date + } + } + } + + if service_info: + payload['service'] = { + 'baseServiceDetails': { + 'id': service_info['id'], + 'name': service_info['service_name'], + 'carrier': service_info['carrier'], + 'priceInfo': service_info['price_info'], }, - 'contentDescription': description_of_content, - }, - 'shipmentNotification': { + 'supportedExWorkType': [], + 'messages': [''], + 'description': '', + 'serviceInfo': '', + } + payload['shipmentNotification'] = { 'trackingNotification': { 'deliveryNotification': True, 'problemNotification': True, @@ -247,77 +266,47 @@ def create_letmeship_shipment(pickup_address, delivery_address, shipment_parcel, 'notificationText': '', 'emails': [ shipment_notific_email ] } - }, - 'labelEmail': True, - } + } + payload['labelEmail'] = True + return payload + +def get_letmeship_label(shipment_id): try: - response_data = requests.post( - url=url, - auth=(api_id, api_password), - headers=headers, - data=json.dumps(payload) + # 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 ) - 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: + 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 creating Shipment: {0}') - .format(response_data['message']) + _('Error occurred while printing Shipment: {0}') + .format(shipment_label_response_data['message']) ) except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint( - _('Error occurred while creating Shipment: {0}') + _('Error occurred while printing 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') @@ -359,6 +348,7 @@ def get_letmeship_tracking_data(shipment_id): .format(tracking_data['message']) ) except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint( _('Error occurred while updating Shipment: {0}') .format(str(exc)), diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.py b/erpnext/erpnext_integrations/doctype/packlink/packlink.py index 7fdb053cf8..1db08c3149 100644 --- a/erpnext/erpnext_integrations/doctype/packlink/packlink.py +++ b/erpnext/erpnext_integrations/doctype/packlink/packlink.py @@ -72,6 +72,7 @@ def get_packlink_available_services(pickup_address, delivery_address, shipment_p return available_services except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint( _('Error occurred on Packlink: {0}') .format(str(exc)), indicator='orange', @@ -152,6 +153,7 @@ def create_packlink_shipment(pickup_address, delivery_address, shipment_parcel, 'awb_number': '', } except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint( _('Error occurred while creating Shipment: {0}') .format(str(exc)), @@ -215,6 +217,7 @@ def get_packlink_tracking_data(shipment_id): 'tracking_url': tracking_url } except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint(_('Error occurred while updating Shipment: {0}').format( str(exc)), indicator='orange', alert=True) return [] diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json index dab54cba6c..37b6898cba 100644 --- a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json +++ b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json @@ -24,7 +24,7 @@ }, { "fieldname": "api_secret", - "fieldtype": "Data", + "fieldtype": "Password", "label": "API Secret", "read_only_depends_on": "eval:doc.enabled == 0" } @@ -32,7 +32,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-08-18 09:48:50.836233", + "modified": "2020-10-21 10:28:57.710549", "modified_by": "Administrator", "module": "ERPNext Integrations", "name": "SendCloud", diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py index 85c94388dc..d30af15eb5 100644 --- a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py +++ b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py @@ -16,9 +16,7 @@ class SendCloud(Document): 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') + api_key, api_secret, enabled = frappe.db.get_value('SendCloud', 'SendCloud', ['enabled', 'api_key', 'api_secret']) if not enabled or not api_key or not api_secret: return [] @@ -40,6 +38,7 @@ def get_sendcloud_available_services(delivery_address, shipment_parcel): available_services.append(available_service) return available_services except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint(_('Error occurred on SendCloud: {0}').format( str(exc)), indicator='orange', alert=True) @@ -53,9 +52,7 @@ def create_sendcloud_shipment( 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') + api_key, api_secret, enabled = frappe.db.get_value('SendCloud', 'SendCloud', ['enabled', 'api_key', 'api_secret']) if not enabled or not api_key or not api_secret: return [] @@ -105,6 +102,7 @@ def create_sendcloud_shipment( 'awb_number': awb_number } except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint(_('Error occurred while creating Shipment: {0}').format( str(exc)), indicator='orange', alert=True) @@ -130,17 +128,15 @@ def get_sendcloud_tracking_data(shipment_id): 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 = [] + tracking_urls = [] 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 = \ - '{{ _("Click here to Track Shipment") }}
' - tracking_url += frappe.render_template(tracking_url_template, {'tracking_url': tracking_data['parcel']['tracking_url']}) + tracking_urls.append(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']) @@ -148,9 +144,10 @@ def get_sendcloud_tracking_data(shipment_id): 'awb_number': ', '.join(awb_number), 'tracking_status': ', '.join(tracking_status), 'tracking_status_info': ', '.join(tracking_status_info), - 'tracking_url': tracking_url + 'tracking_url': ', '.join(tracking_urls) } except Exception as exc: + frappe.log_error(frappe.get_traceback()) frappe.msgprint(_('Error occurred while updating Shipment: {0}').format( str(exc)), indicator='orange', alert=True) diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py index e7ef4c8ebd..362f6cf88e 100644 --- a/erpnext/erpnext_integrations/utils.py +++ b/erpnext/erpnext_integrations/utils.py @@ -68,6 +68,4 @@ def get_tracking_url(carrier, tracking_number): 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 = '{{ _("Click here to Track Shipment") }}' - tracking_url = frappe.render_template(tracking_url_template, {'tracking_url': tracking_url}) return tracking_url diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 00a66fa48e..26e4f1633e 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -575,22 +575,24 @@ def make_shipment(source_name, target_doc=None): user = frappe.db.get_value("User", frappe.session.user, ['email', 'full_name', 'phone', 'mobile_no'], as_dict=1) target.pickup_contact_email = user.email pickup_contact_display = '{}'.format(user.full_name) - if user.email: - pickup_contact_display += '
' + user.email - if user.phone: - pickup_contact_display += '
' + user.phone - if user.mobile_no and not user.phone: - pickup_contact_display += '
' + user.mobile_no + if user: + if user.email: + pickup_contact_display += '
' + user.email + if user.phone: + pickup_contact_display += '
' + user.phone + if user.mobile_no and not user.phone: + pickup_contact_display += '
' + user.mobile_no target.pickup_contact = pickup_contact_display contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1) delivery_contact_display = '{}'.format(source.contact_display) - if contact.email_id: - delivery_contact_display += '
' + contact.email_id - if contact.phone: - delivery_contact_display += '
' + contact.phone - if contact.mobile_no and not contact.phone: - delivery_contact_display += '
' + contact.mobile_no + if contact: + if contact.email_id: + delivery_contact_display += '
' + contact.email_id + if contact.phone: + delivery_contact_display += '
' + contact.phone + if contact.mobile_no and not contact.phone: + delivery_contact_display += '
' + contact.mobile_no target.delivery_contact = delivery_contact_display doclist = get_mapped_doc("Delivery Note", source_name, { @@ -612,7 +614,7 @@ def make_shipment(source_name, target_doc=None): } }, "Delivery Note Item": { - "doctype": "Shipment Delivery Notes", + "doctype": "Shipment Delivery Note", "field_map": { "name": "prevdoc_detail_docname", "parent": "prevdoc_docname", diff --git a/erpnext/stock/doctype/shipment/api/utils.py b/erpnext/stock/doctype/shipment/api/utils.py deleted file mode 100644 index 1153933e81..0000000000 --- a/erpnext/stock/doctype/shipment/api/utils.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- 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 frappe -from frappe import _ -import re - -def get_address(address_name): - address = frappe.db.get_value('Address', address_name, [ - 'address_title', - 'address_line1', - 'address_line2', - 'city', - 'pincode', - 'country', - ], as_dict=1) - address.country_code = frappe.db.get_value('Country', address.country, 'code').upper() - if not address.pincode or address.pincode == '': - frappe.throw(_("Postal Code is mandatory to continue.
\ - Please set Postal Code for Address {1}" - ).format(address_name, address_name)) - address.pincode = address.pincode.replace(' ', '') - address.city = address.city.strip() - return address - -def get_contact(contact_name): - contact = frappe.db.get_value('Contact', contact_name, [ - 'first_name', - 'last_name', - 'email_id', - 'phone', - 'mobile_no', - 'gender', - ], as_dict=1) - if not contact.last_name: - frappe.throw(_("Last Name is mandatory to continue.
\ - Please set Last Name for Contact {1}" - ).format(contact_name, contact_name)) - if not contact.phone: - contact.phone = contact.mobile_no - contact.phone_prefix = contact.phone[:3] - contact.phone = re.sub('[^A-Za-z0-9]+', '', contact.phone[3:]) - contact.email = contact.email_id - contact.title = 'MS' - if contact.gender == 'Male': - contact.title = 'MR' - return contact - -def get_company_contact(): - contact = frappe.db.get_value('User', frappe.session.user, [ - 'first_name', - 'last_name', - 'email', - 'phone', - 'mobile_no', - 'gender', - ], as_dict=1) - if not contact.phone: - contact.phone = contact.mobile_no - contact.phone_prefix = contact.phone[:3] - contact.phone = re.sub('[^A-Za-z0-9]+', '', contact.phone[3:]) - contact.title = 'MS' - if contact.gender == 'Male': - contact.title = 'MR' - return contact diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index e9f4484ab1..fc0b05f8af 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -2,11 +2,6 @@ // For license information, please see license.txt frappe.ui.form.on('Shipment', { - setup: function(frm) { - if (frm.doc.__islocal) { - frm.trigger('pickup_type'); - } - }, address_query: function(frm, link_doctype, link_name, is_your_company_address) { return { query: 'frappe.contacts.doctype.address.address.address_query', @@ -99,7 +94,7 @@ frappe.ui.form.on('Shipment', { } return frm.events.contact_query(frm, link_doctype, link_name); }); - frm.set_query("delivery_note", "shipment_delivery_notes", function() { + frm.set_query("delivery_note", "shipment_delivery_note", function() { let customer = ''; if (frm.doc.delivery_to_type == "Customer") { customer = frm.doc.delivery_customer; @@ -127,27 +122,22 @@ frappe.ui.form.on('Shipment', { if (frm.doc.shipment_id) { frm.add_custom_button(__('Print Shipping Label'), function() { return frm.events.print_shipping_label(frm); - }); + }, __('Tools')); if (frm.doc.tracking_status != 'Delivered') { frm.add_custom_button(__('Update Tracking'), function() { return frm.events.update_tracking(frm, frm.doc.service_provider, frm.doc.shipment_id); - }); + }, __('Tools')); + + frm.add_custom_button(__('Track Status'), function() { + const urls = frm.doc.tracking_url.split(', '); + urls.forEach(url => window.open(url)); + }, __('View')); } } $('div[data-fieldname=pickup_address] > div > .clearfix').hide(); $('div[data-fieldname=pickup_contact] > div > .clearfix').hide(); $('div[data-fieldname=delivery_address] > div > .clearfix').hide(); $('div[data-fieldname=delivery_contact] > div > .clearfix').hide(); - - if (frm.doc.delivery_from_type != 'Company') { - frm.set_df_property("delivery_contact_name", "reqd", 1); - } - if (frm.doc.pickup_from_type != 'Company') { - frm.set_df_property("pickup_contact_name", "reqd", 1); - } - else { - frm.toggle_display("pickup_contact_name", false); - } }, before_save: function(frm) { if (frm.doc.delivery_to_type == 'Company') { @@ -188,14 +178,10 @@ frappe.ui.form.on('Shipment', { pickup_from_type: function(frm) { if (frm.doc.pickup_from_type == 'Company') { frm.set_value("pickup_company", frappe.defaults.get_default('company')); - frm.set_df_property("pickup_contact_name", "reqd", 0); frm.set_value("pickup_customer", ''); frm.set_value("pickup_supplier", ''); - frm.toggle_display("pickup_contact_name", false); } else { - frm.set_df_property("pickup_contact_name", "reqd", 1); - frm.toggle_display("pickup_contact_name", true); frm.trigger('clear_pickup_fields'); } if (frm.doc.pickup_from_type == 'Customer') { @@ -206,20 +192,16 @@ frappe.ui.form.on('Shipment', { frm.set_value("pickup_customer", ''); frm.set_value("pickup_company", ''); } - frm.events.remove_notific_child_table(frm, 'shipment_notification_subscriptions', 'Pickup'); - frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscriptions', 'Pickup'); + frm.events.remove_notific_child_table(frm, 'shipment_notification_subscription', 'Pickup'); + frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscription', 'Pickup'); }, delivery_to_type: function(frm) { if (frm.doc.delivery_to_type == 'Company') { frm.set_value("delivery_company", frappe.defaults.get_default('company')); - frm.set_df_property("delivery_contact_name", "reqd", 0); frm.set_value("delivery_customer", ''); frm.set_value("delivery_supplier", ''); - frm.toggle_display("delivery_contact_name", false); } else { - frm.set_df_property("delivery_contact_name", "reqd", 1); - frm.toggle_display("delivery_contact_name", true); frm.trigger('clear_delivery_fields'); } if (frm.doc.delivery_to_type == 'Customer') { @@ -229,13 +211,13 @@ frappe.ui.form.on('Shipment', { if (frm.doc.delivery_to_type == 'Supplier') { frm.set_value("delivery_customer", ''); frm.set_value("delivery_company", ''); - frm.toggle_display("shipment_delivery_notes", false); + frm.toggle_display("shipment_delivery_note", false); } else { - frm.toggle_display("shipment_delivery_notes", true); + frm.toggle_display("shipment_delivery_note", true); } - frm.events.remove_notific_child_table(frm, 'shipment_notification_subscriptions', 'Delivery'); - frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscriptions', 'Delivery'); + frm.events.remove_notific_child_table(frm, 'shipment_notification_subscription', 'Delivery'); + frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscription', 'Delivery'); }, delivery_address_name: function(frm) { if (frm.doc.delivery_to_type == 'Company') { @@ -307,6 +289,51 @@ frappe.ui.form.on('Shipment', { frm.events.get_contact_display(frm, frm.doc.pickup_contact_name, 'Pickup'); } }, + pickup_contact_person: function(frm) { + if (frm.doc.pickup_contact_person) { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.get_company_contact", + args: { user: frm.doc.pickup_contact_person }, + callback: function({ message }) { + const r = message; + let contact_display = `${r.first_name} ${r.last_name}`; + if (r.email) { + contact_display += `
${ r.email }`; + frm.set_value('pickup_contact_email', r.email); + } + if (r.phone) { + contact_display += `
${ r.phone }`; + } + if (r.mobile_no && !r.phone) { + contact_display += `
${ r.mobile_no }`; + } + frm.set_value('pickup_contact', contact_display); + } + }); + } else { + if (frm.doc.pickup_from_type === 'Company') { + frappe.call({ + method: "erpnext.stock.doctype.shipment.shipment.get_company_contact", + args: { user: frappe.session.user }, + callback: function({ message }) { + const r = message; + let contact_display = `${r.first_name} ${r.last_name}`; + if (r.email) { + contact_display += `
${ r.email }`; + frm.set_value('pickup_contact_email', r.email); + } + if (r.phone) { + contact_display += `
${ r.phone }`; + } + if (r.mobile_no && !r.phone) { + contact_display += `
${ r.mobile_no }`; + } + frm.set_value('pickup_contact', contact_display); + } + }); + } + } + }, set_company_contact: function(frm, delivery_type) { frappe.db.get_value('User', { name: frappe.session.user }, ['full_name', 'last_name', 'email', 'phone', 'mobile_no'], (r) => { if (!(r.last_name && r.email && (r.phone || r.mobile_no))) { @@ -344,6 +371,7 @@ frappe.ui.form.on('Shipment', { } } }); + frm.set_value('pickup_contact_person', frappe.session.user); }, pickup_company: function(frm) { if (frm.doc.pickup_from_type == 'Company' && frm.doc.pickup_company) { @@ -372,14 +400,12 @@ frappe.ui.form.on('Shipment', { } }, pickup_customer: function(frm) { - frm.trigger('clear_pickup_fields'); if (frm.doc.pickup_customer) { frm.events.set_address_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); frm.events.set_contact_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); } }, pickup_supplier: function(frm) { - frm.trigger('clear_pickup_fields'); if (frm.doc.pickup_supplier) { frm.events.set_address_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); frm.events.set_contact_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); @@ -438,7 +464,7 @@ frappe.ui.form.on('Shipment', { }, pickup_date: function(frm) { if (frm.doc.pickup_date < frappe.datetime.get_today()) { - frappe.throw(__("Pickup Date cannot be in the past")); + frappe.throw(__("Pickup Date cannot be before this day")); } if (frm.doc.pickup_date == frappe.datetime.get_today()) { var pickup_time = frm.events.get_pickup_time(frm); @@ -487,65 +513,63 @@ frappe.ui.form.on('Shipment', { frm.set_value("pickup_to", pickup_to); }, clear_pickup_fields: function(frm) { - frm.set_value("pickup_address_name", ''); - frm.set_value("pickup_contact_name", ''); - frm.set_value("pickup_address", ''); - frm.set_value("pickup_contact", ''); - frm.set_value("pickup_contact_email", ''); + let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"]; + for (let field of fields){ + frm.set_value(field, ''); + } }, clear_delivery_fields: function(frm) { - frm.set_value("delivery_address_name", ''); - frm.set_value("delivery_contact_name", ''); - frm.set_value("delivery_address", ''); - frm.set_value("delivery_contact", ''); - frm.set_value("delivery_contact_email", ''); + let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"]; + for (let field of fields){ + frm.set_value(field, ''); + } }, pickup_from_send_shipping_notification: function(frm, cdt, cdn) { if (frm.doc.pickup_contact_email && frm.doc.pickup_from_send_shipping_notification - && !validate_duplicate(frm, 'shipment_notification_subscriptions', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscriptions", "shipment_notification_subscriptions"); + && !validate_duplicate(frm, 'shipment_notification_subscription', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscription", "shipment_notification_subscription"); row.email = frm.doc.pickup_contact_email; - frm.refresh_fields("shipment_notification_subscriptions"); + frm.refresh_fields("shipment_notification_subscription"); } if (!frm.doc.pickup_from_send_shipping_notification) { - frm.events.remove_email_row(frm, 'shipment_notification_subscriptions', frm.doc.pickup_contact_email); - frm.refresh_fields("shipment_notification_subscriptions"); + frm.events.remove_email_row(frm, 'shipment_notification_subscription', frm.doc.pickup_contact_email); + frm.refresh_fields("shipment_notification_subscription"); } }, pickup_from_subscribe_to_status_updates: function(frm, cdt, cdn) { if (frm.doc.pickup_contact_email && frm.doc.pickup_from_subscribe_to_status_updates - && !validate_duplicate(frm, 'shipment_status_update_subscriptions', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscriptions", "shipment_status_update_subscriptions"); + && !validate_duplicate(frm, 'shipment_status_update_subscription', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscription", "shipment_status_update_subscription"); row.email = frm.doc.pickup_contact_email; - frm.refresh_fields("shipment_status_update_subscriptions"); + frm.refresh_fields("shipment_status_update_subscription"); } if (!frm.doc.pickup_from_subscribe_to_status_updates) { - frm.events.remove_email_row(frm, 'shipment_status_update_subscriptions', frm.doc.pickup_contact_email); - frm.refresh_fields("shipment_status_update_subscriptions"); + frm.events.remove_email_row(frm, 'shipment_status_update_subscription', frm.doc.pickup_contact_email); + frm.refresh_fields("shipment_status_update_subscription"); } }, delivery_to_send_shipping_notification: function(frm, cdt, cdn) { if (frm.doc.delivery_contact_email && frm.doc.delivery_to_send_shipping_notification - && !validate_duplicate(frm, 'shipment_notification_subscriptions', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)){ - let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscriptions", "shipment_notification_subscriptions"); + && !validate_duplicate(frm, 'shipment_notification_subscription', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)){ + let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscription", "shipment_notification_subscription"); row.email = frm.doc.delivery_contact_email; - frm.refresh_fields("shipment_notification_subscriptions"); + frm.refresh_fields("shipment_notification_subscription"); } if (!frm.doc.delivery_to_send_shipping_notification) { - frm.events.remove_email_row(frm, 'shipment_notification_subscriptions', frm.doc.delivery_contact_email); - frm.refresh_fields("shipment_notification_subscriptions"); + frm.events.remove_email_row(frm, 'shipment_notification_subscription', frm.doc.delivery_contact_email); + frm.refresh_fields("shipment_notification_subscription"); } }, delivery_to_subscribe_to_status_updates: function(frm, cdt, cdn) { if (frm.doc.delivery_contact_email && frm.doc.delivery_to_subscribe_to_status_updates - && !validate_duplicate(frm, 'shipment_status_update_subscriptions', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscriptions", "shipment_status_update_subscriptions"); + && !validate_duplicate(frm, 'shipment_status_update_subscription', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)) { + let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscription", "shipment_status_update_subscription"); row.email = frm.doc.delivery_contact_email; - frm.refresh_fields("shipment_status_update_subscriptions"); + frm.refresh_fields("shipment_status_update_subscription"); } if (!frm.doc.delivery_to_subscribe_to_status_updates) { - frm.events.remove_email_row(frm, 'shipment_status_update_subscriptions', frm.doc.delivery_contact_email); - frm.refresh_fields("shipment_status_update_subscriptions"); + frm.events.remove_email_row(frm, 'shipment_status_update_subscription', frm.doc.delivery_contact_email); + frm.refresh_fields("shipment_status_update_subscription"); } }, remove_email_row: function(frm, table, fieldname) { @@ -589,7 +613,7 @@ frappe.ui.form.on('Shipment', { shipment_parcel: frm.doc.shipment_parcel, description_of_content: frm.doc.description_of_content, pickup_date: frm.doc.pickup_date, - pickup_contact_name: frm.doc.pickup_contact_name, + pickup_contact_name: frm.doc.pickup_from_type === 'Company' ? frm.doc.pickup_contact_person : frm.doc.pickup_contact_name, delivery_contact_name: frm.doc.delivery_contact_name, value_of_goods: frm.doc.value_of_goods }, @@ -639,7 +663,7 @@ frappe.ui.form.on('Shipment', { }, update_tracking: function(frm, service_provider, shipment_id) { let delivery_notes = []; - (frm.doc.shipment_delivery_notes || []).forEach((d) => { + (frm.doc.shipment_delivery_note || []).forEach((d) => { delivery_notes.push(d.delivery_note); }); frappe.call({ @@ -661,14 +685,13 @@ frappe.ui.form.on('Shipment', { } }); -frappe.ui.form.on('Shipment Delivery Notes', { +frappe.ui.form.on('Shipment Delivery Note', { delivery_note: function(frm, cdt, cdn) { let row = locals[cdt][cdn]; if (row.delivery_note) { let row_index = row.idx - 1; - if(validate_duplicate(frm, 'shipment_delivery_notes', row.delivery_note, row_index)) { - cur_frm.get_field('shipment_delivery_notes').grid.grid_rows[row_index].remove(); - frappe.throw(__(`You have entered duplicate Delivery Notes. Please rectify and try again.`)); + if(validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) { + frappe.throw(__(`You have entered a duplicate Delivery Note on Row ${row.idx}. Please rectify and try again.`)); } } }, @@ -683,29 +706,27 @@ frappe.ui.form.on('Shipment Delivery Notes', { }); var validate_duplicate = function(frm, table, fieldname, index){ - let duplicate = false; - $.each(frm.doc[table], function(i, detail) { - // Email duplicate validation - if(detail.email === fieldname && !(index === i)) { - duplicate = true; - return; - } - - // Delivery Note duplicate validation - if(detail.delivery_note === fieldname && !(index === i)) { - duplicate = true; - return; - } - }); - return duplicate; + return ( + table === 'shipment_delivery_note' + ? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i)) + : frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i)) + ); }; function select_from_available_services(frm, available_services) { var headers = [ __("Service Provider"), __("Carrier"), __("Carrier’s Service"), __("Price"), "" ]; cur_frm.render_available_services = function(d, headers, data){ + const arranged_data = data.reduce((prev, curr) => { + if (curr.is_preferred) { + prev.preferred_services.push(curr); + } else { + prev.other_services.push(curr); + } + return prev; + }, { preferred_services: [], other_services: [] }); d.fields_dict.available_services.$wrapper.html( frappe.render_template('shipment_service_selector', - {'header_columns': headers, 'data': data} + {'header_columns': headers, 'data': arranged_data} ) ); }; @@ -722,18 +743,18 @@ function select_from_available_services(frm, available_services) { cur_frm.render_available_services(d, headers, available_services); let shipment_notific_email = []; let tracking_notific_email = []; - (frm.doc.shipment_notification_subscriptions || []).forEach((d) => { + (frm.doc.shipment_notification_subscription || []).forEach((d) => { if (!d.unsubscribed) { shipment_notific_email.push(d.email); } }); - (frm.doc.shipment_status_update_subscriptions || []).forEach((d) => { + (frm.doc.shipment_status_update_subscription || []).forEach((d) => { if (!d.unsubscribed) { tracking_notific_email.push(d.email); } }); let delivery_notes = []; - (frm.doc.shipment_delivery_notes || []).forEach((d) => { + (frm.doc.shipment_delivery_note || []).forEach((d) => { delivery_notes.push(d.delivery_note); }); cur_frm.select_row = function(service_data){ @@ -750,7 +771,7 @@ function select_from_available_services(frm, available_services) { shipment_parcel: frm.doc.shipment_parcel, description_of_content: frm.doc.description_of_content, pickup_date: frm.doc.pickup_date, - pickup_contact_name: frm.doc.pickup_contact_name, + pickup_contact_name: frm.doc.pickup_from_type === 'Company' ? frm.doc.pickup_contact_person : frm.doc.pickup_contact_name, delivery_contact_name: frm.doc.delivery_contact_name, value_of_goods: frm.doc.value_of_goods, service_data: service_data, diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index b6656a2b72..bbfbb719be 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -14,6 +14,7 @@ "pickup", "pickup_address_name", "pickup_address", + "pickup_contact_person", "pickup_contact_name", "pickup_contact_email", "pickup_contact", @@ -32,17 +33,17 @@ "notification_details_section", "pickup_from_send_shipping_notification", "pickup_from_subscribe_to_status_updates", - "shipment_notification_subscriptions", + "shipment_notification_subscription", "column_break_27", "delivery_to_send_shipping_notification", "delivery_to_subscribe_to_status_updates", - "shipment_status_update_subscriptions", + "shipment_status_update_subscription", "parcels_section", "shipment_parcel", "parcel_template", "add_template", "column_break_28", - "shipment_delivery_notes", + "shipment_delivery_note", "shipment_details_section", "pallets", "value_of_goods", @@ -125,10 +126,11 @@ "read_only": 1 }, { - "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type == \"Company\"", + "depends_on": "eval: doc.pickup_customer || doc.pickup_supplier || doc.pickup_from_type !== \"Company\"", "fieldname": "pickup_contact_name", "fieldtype": "Link", "label": "Contact", + "mandatory_depends_on": "eval: doc.pickup_from_type !== 'Company'", "options": "Contact" }, { @@ -206,6 +208,7 @@ "fieldname": "delivery_contact_name", "fieldtype": "Link", "label": "Contact", + "mandatory_depends_on": "eval: doc.delivery_from_type !== 'Company'", "options": "Contact" }, { @@ -239,12 +242,6 @@ "fieldtype": "Check", "label": "Subscribe to status updates" }, - { - "fieldname": "shipment_notification_subscriptions", - "fieldtype": "Table", - "label": "Shipment Notification Subscriptions", - "options": "Shipment Notification Subscriptions" - }, { "fieldname": "column_break_27", "fieldtype": "Column Break" @@ -261,12 +258,6 @@ "fieldtype": "Check", "label": "Subscribe to status updates" }, - { - "fieldname": "shipment_status_update_subscriptions", - "fieldtype": "Table", - "label": "Shipment Status Update Subscriptions", - "options": "Shipment Status Update Subscriptions" - }, { "fieldname": "parcels_section", "fieldtype": "Section Break", @@ -293,12 +284,6 @@ "fieldname": "column_break_28", "fieldtype": "Column Break" }, - { - "fieldname": "shipment_delivery_notes", - "fieldtype": "Table", - "label": "Shipment Delivery Notes", - "options": "Shipment Delivery Notes" - }, { "fieldname": "shipment_details_section", "fieldtype": "Section Break", @@ -328,16 +313,14 @@ { "default": "09:00", "fieldname": "pickup_from", - "fieldtype": "Select", - "label": "Pickup from", - "options": "09:00\n09:30\n10:00\n10:30\n11:00\n11:30\n12:00\n12:30\n13:00\n13:30\n14:00\n14:30\n15:00\n15:30\n16:00\n16:30\n17:00\n17:30\n18:00\n18:30\n19:00" + "fieldtype": "Time", + "label": "Pickup from" }, { "default": "17:00", "fieldname": "pickup_to", - "fieldtype": "Select", - "label": "Pickup to", - "options": "09:00\n09:30\n10:00\n10:30\n11:00\n11:30\n12:00\n12:30\n13:00\n13:30\n14:00\n14:30\n15:00\n15:30\n16:00\n16:30\n17:00\n17:30\n18:00\n18:30\n19:00" + "fieldtype": "Time", + "label": "Pickup to" }, { "fieldname": "column_break_36", @@ -374,13 +357,15 @@ }, { "fieldname": "service_provider", - "fieldtype": "Read Only", - "label": "Service Provider" + "fieldtype": "Data", + "label": "Service Provider", + "read_only": 1 }, { "fieldname": "shipment_id", - "fieldtype": "Read Only", - "label": "Shipment ID" + "fieldtype": "Data", + "label": "Shipment ID", + "read_only": 1 }, { "fieldname": "shipment_amount", @@ -399,23 +384,27 @@ { "fieldname": "tracking_url", "fieldtype": "Small Text", + "hidden": 1, "label": "Tracking URL", "read_only": 1 }, { "fieldname": "carrier", - "fieldtype": "Read Only", - "label": "Carrier" + "fieldtype": "Data", + "label": "Carrier", + "read_only": 1 }, { "fieldname": "carrier_service", - "fieldtype": "Read Only", - "label": "Carrier Service" + "fieldtype": "Data", + "label": "Carrier Service", + "read_only": 1 }, { "fieldname": "awb_number", - "fieldtype": "Read Only", - "label": "AWB Number" + "fieldtype": "Data", + "label": "AWB Number", + "read_only": 1 }, { "fieldname": "tracking_status", @@ -449,11 +438,37 @@ "fieldtype": "Select", "label": "Incoterm", "options": "EXW (Ex Works)\nFCA (Free Carrier)\nCPT (Carriage Paid To)\nCIP (Carriage and Insurance Paid to)\nDPU (Delivered At Place Unloaded)\nDAP (Delivered At Place)\nDDP (Delivered Duty Paid)" + }, + { + "fieldname": "shipment_delivery_note", + "fieldtype": "Table", + "label": "Shipment Delivery Note", + "options": "Shipment Delivery Note" + }, + { + "fieldname": "shipment_notification_subscription", + "fieldtype": "Table", + "label": "Shipment Notification Subscription", + "options": "Shipment Notification Subscription" + }, + { + "fieldname": "shipment_status_update_subscription", + "fieldtype": "Table", + "label": "Shipment Status Update Subscription", + "options": "Shipment Status Update Subscription" + }, + { + "depends_on": "eval:doc.pickup_from_type === 'Company'", + "fieldname": "pickup_contact_person", + "fieldtype": "Link", + "label": "Pickup Contact Person", + "mandatory_depends_on": "eval:doc.pickup_from_type === 'Company'", + "options": "User" } ], "is_submittable": 1, "links": [], - "modified": "2020-07-24 11:44:30.904612", + "modified": "2020-09-29 13:59:50.241744", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index e059bacfa1..9b3c976ca4 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import frappe import json from frappe import _ +from frappe.utils import flt from frappe.model.document import Document from erpnext.accounts.party import get_party_shipping_address from frappe.contacts.doctype.contact.contact import get_default_contact @@ -32,7 +33,7 @@ class Shipment(Document): def validate_weight(self): for parcel in self.shipment_parcel: - if parcel.weight <= 0: + if flt(parcel.weight) <= 0: frappe.throw(_('Parcel weight cannot be 0')) @frappe.whitelist() @@ -52,12 +53,12 @@ def fetch_shipping_rates(pickup_from_type, delivery_to_type, pickup_address_name if pickup_from_type != 'Company': pickup_contact = get_contact(pickup_contact_name) else: - pickup_contact = get_company_contact() + pickup_contact = get_company_contact(user=pickup_contact_name) if delivery_to_type != 'Company': delivery_contact = get_contact(delivery_contact_name) else: - delivery_contact = get_company_contact() + delivery_contact = get_company_contact(user=pickup_contact_name) letmeship_prices = get_letmeship_available_services( delivery_to_type=delivery_to_type, pickup_address=pickup_address, @@ -104,12 +105,12 @@ def create_shipment(shipment, pickup_from_type, delivery_to_type, pickup_address if pickup_from_type != 'Company': pickup_contact = get_contact(pickup_contact_name) else: - pickup_contact = get_company_contact() + pickup_contact = get_company_contact(user=pickup_contact_name) if delivery_to_type != 'Company': delivery_contact = get_contact(delivery_contact_name) else: - delivery_contact = get_company_contact() + delivery_contact = get_company_contact(user=pickup_contact_name) if service_info['service_provider'] == LETMESHIP_PROVIDER: shipment_info = create_letmeship_shipment( pickup_address=pickup_address, @@ -150,12 +151,9 @@ def create_shipment(shipment, pickup_from_type, delivery_to_type, pickup_address ) if shipment_info: - frappe.db.set_value('Shipment', shipment, 'service_provider', shipment_info.get('service_provider')) - frappe.db.set_value('Shipment', shipment, 'carrier', shipment_info.get('carrier')) - frappe.db.set_value('Shipment', shipment, 'carrier_service', shipment_info.get('carrier_service')) - frappe.db.set_value('Shipment', shipment, 'shipment_id', shipment_info.get('shipment_id')) - frappe.db.set_value('Shipment', shipment, 'shipment_amount', shipment_info.get('shipment_amount')) - frappe.db.set_value('Shipment', shipment, 'awb_number', shipment_info.get('awb_number')) + fields = ['service_provider', 'carrier', 'carrier_service', 'shipment_id', 'shipment_amount', 'awb_number'] + for field in fields: + frappe.db.set_value('Shipment', shipment, field, shipment_info.get(field)) frappe.db.set_value('Shipment', shipment, 'status', 'Booked') if delivery_notes: update_delivery_note(delivery_notes=delivery_notes, shipment_info=shipment_info) @@ -277,9 +275,17 @@ def get_contact(contact_name): contact.phone = contact.mobile_no return contact +def match_parcel_service_type_carrier(shipment_prices, reference): + for idx, prices in enumerate(shipment_prices): + service_name = match_parcel_service_type_alias(prices.get(reference[0]), prices.get(reference[1])) + is_preferred = frappe.db.get_value('Parcel Service Type', service_name, 'show_in_preferred_services_list') + shipment_prices[idx].service_name = service_name + shipment_prices[idx].is_preferred = is_preferred + return shipment_prices -def get_company_contact(): - contact = frappe.db.get_value('User', frappe.session.user, [ +@frappe.whitelist() +def get_company_contact(user): + contact = frappe.db.get_value('User', user, [ 'first_name', 'last_name', 'email', @@ -289,12 +295,4 @@ def get_company_contact(): ], as_dict=1) if not contact.phone: contact.phone = contact.mobile_no - return contact - -def match_parcel_service_type_carrier(shipment_prices, reference): - for idx, prices in enumerate(shipment_prices): - service_name = match_parcel_service_type_alias(prices.get(reference[0]), prices.get(reference[1])) - is_preferred = frappe.db.get_value('Parcel Service Type', service_name, 'show_in_preferred_services_list') - shipment_prices[idx].service_name = service_name - shipment_prices[idx].is_preferred = is_preferred - return shipment_prices + return contact \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment/shipment_service_selector.html b/erpnext/stock/doctype/shipment/shipment_service_selector.html index ed9b8bf400..4ccbe34e9e 100644 --- a/erpnext/stock/doctype/shipment/shipment_service_selector.html +++ b/erpnext/stock/doctype/shipment/shipment_service_selector.html @@ -1,59 +1,63 @@ -{% if (data.length) { %} -
+{% if (data.preferred_services.length || data.other_services.length) { %} +
{{ __("Preferred Services") }}
- - - - {% for (var i = 0; i < header_columns.length; i++) { %} - - {% } %} - - - - {% for (var i = 0; i < data.length; i++) { %} - {% if (data[i].is_preferred) { %} + {% if (data.preferred_services.length) { %} +
{{ header_columns[i] }}
+ + + {% for (var i = 0; i < header_columns.length; i++) { %} + + {% } %} + + + + {% for (var i = 0; i < data.preferred_services.length; i++) { %} - - - - + + + + {% } %} - {% } %} - -
{{ header_columns[i] }}
{{ data[i].service_provider }}{{ data[i].carrier }}{{ data[i].service_name }}{{ format_currency(data[i].total_price, 'EUR', 2) }}{{ 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) }} -
+ + + {% } else { %} +
{{ __("No Preferred Services Available") }}
+ {% } %}
{{ __("Other Services") }}
- - - - {% for (var i = 0; i < header_columns.length; i++) { %} - - {% } %} - - - - {% for (var i = 0; i < data.length; i++) { %} - {% if (!data[i].is_preferred) { %} + {% if (data.other_services.length) { %} +
{{ header_columns[i] }}
+ + + {% for (var i = 0; i < header_columns.length; i++) { %} + + {% } %} + + + + {% for (var i = 0; i < data.other_services.length; i++) { %} - - - - + + + + {% } %} - {% } %} - -
{{ header_columns[i] }}
{{ data[i].service_provider }}{{ data[i].carrier }}{{ data[i].service_name }}{{ format_currency(data[i].total_price, 'EUR', 2) }}{{ data.other_services[i].service_provider }}{{ data.other_services[i].carrier }}{{ data.other_services[i].service_name }}{{ format_currency(data.other_services[i].total_price, 'EUR', 2) }} -
+ + + {% } else { %} +
{{ __("No Services Available") }}
+ {% } %}
{% } else { %}
{{ __("No Services Available") }}
diff --git a/erpnext/stock/doctype/shipment_delivery_notes/__init__.py b/erpnext/stock/doctype/shipment_delivery_note/__init__.py similarity index 100% rename from erpnext/stock/doctype/shipment_delivery_notes/__init__.py rename to erpnext/stock/doctype/shipment_delivery_note/__init__.py diff --git a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json similarity index 95% rename from erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json rename to erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json index fbc01d9a24..9651e3f945 100644 --- a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.json +++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json @@ -31,7 +31,7 @@ "modified": "2020-07-09 12:55:01.134270", "modified_by": "Administrator", "module": "Stock", - "name": "Shipment Delivery Notes", + "name": "Shipment Delivery Note", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py similarity index 86% rename from erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py rename to erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py index ed936c60f8..4342151605 100644 --- a/erpnext/stock/doctype/shipment_delivery_notes/shipment_delivery_notes.py +++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class ShipmentDeliveryNotes(Document): +class ShipmentDeliveryNote(Document): pass diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/__init__.py b/erpnext/stock/doctype/shipment_notification_subscription/__init__.py similarity index 100% rename from erpnext/stock/doctype/shipment_notification_subscriptions/__init__.py rename to erpnext/stock/doctype/shipment_notification_subscription/__init__.py diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json similarity index 93% rename from erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json rename to erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json index bd9b8003a8..d927d9902e 100644 --- a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.json +++ b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json @@ -30,7 +30,7 @@ "modified": "2020-07-09 12:55:14.217387", "modified_by": "Administrator", "module": "Stock", - "name": "Shipment Notification Subscriptions", + "name": "Shipment Notification Subscription", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py similarity index 83% rename from erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py rename to erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py index 28ead7fab8..c816e4343c 100644 --- a/erpnext/stock/doctype/shipment_notification_subscriptions/shipment_notification_subscriptions.py +++ b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class ShipmentNotificationSubscriptions(Document): +class ShipmentNotificationSubscription(Document): pass diff --git a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json index ec2bb1c9b3..4735d9f886 100644 --- a/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json +++ b/erpnext/stock/doctype/shipment_parcel_template/shipment_parcel_template.json @@ -1,26 +1,18 @@ { "actions": [], - "autoname": "field:preset_name", + "autoname": "field:parcel_template_name", "creation": "2020-07-09 11:43:43.470339", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "preset_name", + "parcel_template_name", "length", "width", "height", "weight" ], "fields": [ - { - "fieldname": "preset_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Preset Name", - "reqd": 1, - "unique": 1 - }, { "fieldname": "length", "fieldtype": "Int", @@ -49,10 +41,18 @@ "label": "Weight (kg)", "precision": "1", "reqd": 1 + }, + { + "fieldname": "parcel_template_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Parcel Template Name", + "reqd": 1, + "unique": 1 } ], "links": [], - "modified": "2020-07-10 12:53:22.772826", + "modified": "2020-09-28 12:51:00.320421", "modified_by": "Administrator", "module": "Stock", "name": "Shipment Parcel Template", diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/__init__.py b/erpnext/stock/doctype/shipment_status_update_subscription/__init__.py similarity index 100% rename from erpnext/stock/doctype/shipment_status_update_subscriptions/__init__.py rename to erpnext/stock/doctype/shipment_status_update_subscription/__init__.py diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json similarity index 93% rename from erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json rename to erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json index 3b86b400d8..a7fe4a4a0a 100644 --- a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.json +++ b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json @@ -30,7 +30,7 @@ "modified": "2020-07-09 12:55:27.615463", "modified_by": "Administrator", "module": "Stock", - "name": "Shipment Status Update Subscriptions", + "name": "Shipment Status Update Subscription", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py similarity index 83% rename from erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py rename to erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py index a8e31ea778..1b006d7efc 100644 --- a/erpnext/stock/doctype/shipment_status_update_subscriptions/shipment_status_update_subscriptions.py +++ b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class ShipmentStatusUpdateSubscriptions(Document): +class ShipmentStatusUpdateSubscription(Document): pass From ac3c1f14938655ed0c01315c8ca675d1fbb442aa Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Fri, 20 Nov 2020 08:12:29 +0000 Subject: [PATCH 3/7] chore: remove packlink, letmeship, and sendcloud files --- .../doctype/letmeship/__init__.py | 0 .../doctype/letmeship/letmeship.js | 8 - .../doctype/letmeship/letmeship.json | 55 --- .../doctype/letmeship/letmeship.py | 386 ------------------ .../doctype/letmeship/test_letmeship.py | 10 - .../doctype/packlink/__init__.py | 0 .../doctype/packlink/packlink.js | 8 - .../doctype/packlink/packlink.json | 48 --- .../doctype/packlink/packlink.py | 240 ----------- .../doctype/packlink/test_packlink.py | 10 - .../doctype/sendcloud/__init__.py | 0 .../doctype/sendcloud/sendcloud.js | 8 - .../doctype/sendcloud/sendcloud.json | 56 --- .../doctype/sendcloud/sendcloud.py | 168 -------- .../doctype/sendcloud/test_sendcloud.py | 10 - .../stock/doctype/parcel_service/__init__.py | 0 .../doctype/parcel_service/parcel_service.js | 8 - .../parcel_service/parcel_service.json | 56 --- .../doctype/parcel_service/parcel_service.py | 10 - .../parcel_service/test_parcel_service.py | 10 - .../doctype/parcel_service_type/__init__.py | 0 .../parcel_service_type.js | 12 - .../parcel_service_type.json | 89 ---- .../parcel_service_type.py | 22 - .../test_parcel_service_type.py | 10 - .../parcel_service_type_alias/__init__.py | 0 .../parcel_service_type_alias.json | 41 -- .../parcel_service_type_alias.py | 10 - erpnext/stock/doctype/shipment/shipment.js | 183 --------- erpnext/stock/doctype/shipment/shipment.json | 26 +- erpnext/stock/doctype/shipment/shipment.py | 243 +---------- .../shipment/shipment_service_selector.html | 74 ---- 32 files changed, 10 insertions(+), 1791 deletions(-) delete mode 100644 erpnext/erpnext_integrations/doctype/letmeship/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.js delete mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.json delete mode 100644 erpnext/erpnext_integrations/doctype/letmeship/letmeship.py delete mode 100644 erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py delete mode 100644 erpnext/erpnext_integrations/doctype/packlink/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.js delete mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.json delete mode 100644 erpnext/erpnext_integrations/doctype/packlink/packlink.py delete mode 100644 erpnext/erpnext_integrations/doctype/packlink/test_packlink.py delete mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/__init__.py delete mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js delete mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json delete mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py delete mode 100644 erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py delete mode 100644 erpnext/stock/doctype/parcel_service/__init__.py delete mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.js delete mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.json delete mode 100644 erpnext/stock/doctype/parcel_service/parcel_service.py delete mode 100644 erpnext/stock/doctype/parcel_service/test_parcel_service.py delete mode 100644 erpnext/stock/doctype/parcel_service_type/__init__.py delete mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.js delete mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.json delete mode 100644 erpnext/stock/doctype/parcel_service_type/parcel_service_type.py delete mode 100644 erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py delete mode 100644 erpnext/stock/doctype/parcel_service_type_alias/__init__.py delete mode 100644 erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json delete mode 100644 erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py delete mode 100644 erpnext/stock/doctype/shipment/shipment_service_selector.html diff --git a/erpnext/erpnext_integrations/doctype/letmeship/__init__.py b/erpnext/erpnext_integrations/doctype/letmeship/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js deleted file mode 100644 index 1e5e372dff..0000000000 --- a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('LetMeShip', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json deleted file mode 100644 index 94b001ed08..0000000000 --- a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "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": "Password", - "label": "API Password", - "read_only_depends_on": "eval:doc.enabled == 0" - } - ], - "issingle": 1, - "links": [], - "modified": "2020-10-21 10:28:37.607717", - "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 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py b/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py deleted file mode 100644 index 162c1edb37..0000000000 --- a/erpnext/erpnext_integrations/doctype/letmeship/letmeship.py +++ /dev/null @@ -1,386 +0,0 @@ -# -*- 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. - api_id, api_password, enabled = frappe.db.get_value('LetMeShip', 'LetMeShip', ['enabled', 'api_password', 'api_id']) - 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 = generate_payload( - pickup_address=pickup_address, - pickup_contact=pickup_contact, - delivery_address=delivery_address, - delivery_contact=delivery_contact, - description_of_content=description_of_content, - value_of_goods=value_of_goods, - parcel_list=parcel_list, - pickup_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.log_error(frappe.get_traceback()) - 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 - api_id, api_password, enabled = frappe.db.get_value('LetMeShip', 'LetMeShip', ['enabled', 'api_password', 'api_id']) - 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 = generate_payload( - pickup_address=pickup_address, - pickup_contact=pickup_contact, - delivery_address=delivery_address, - delivery_contact=delivery_contact, - description_of_content=description_of_content, - value_of_goods=value_of_goods, - parcel_list=parcel_list, - pickup_date=pickup_date, - service_info=service_info, - tracking_notific_email=tracking_notific_email, - shipment_notific_email=shipment_notific_email - ) - 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.log_error(frappe.get_traceback()) - frappe.msgprint( - _('Error occurred while creating Shipment: {0}') - .format(str(exc)), - indicator='orange', - alert=True - ) - -def generate_payload( - pickup_address, - pickup_contact, - delivery_address, - delivery_contact, - description_of_content, - value_of_goods, - parcel_list, - pickup_date, - service_info=None, - tracking_notific_email=None, - shipment_notific_email=None -): - 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 - } - } - } - - if service_info: - payload['service'] = { - 'baseServiceDetails': { - 'id': service_info['id'], - 'name': service_info['service_name'], - 'carrier': service_info['carrier'], - 'priceInfo': service_info['price_info'], - }, - 'supportedExWorkType': [], - 'messages': [''], - 'description': '', - 'serviceInfo': '', - } - payload['shipmentNotification'] = { - 'trackingNotification': { - 'deliveryNotification': True, - 'problemNotification': True, - 'emails': [tracking_notific_email], - 'notificationText': '', - }, - 'recipientNotification': { - 'notificationText': '', - 'emails': [ shipment_notific_email ] - } - } - payload['labelEmail'] = True - return payload - -def get_letmeship_label(shipment_id): - try: - # 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']) - ) - except Exception as exc: - frappe.log_error(frappe.get_traceback()) - frappe.msgprint( - _('Error occurred while printing Shipment: {0}') - .format(str(exc)), - indicator='orange', - alert=True - ) - - -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.log_error(frappe.get_traceback()) - 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' \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py b/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py deleted file mode 100644 index 3439e4fd72..0000000000 --- a/erpnext/erpnext_integrations/doctype/letmeship/test_letmeship.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 diff --git a/erpnext/erpnext_integrations/doctype/packlink/__init__.py b/erpnext/erpnext_integrations/doctype/packlink/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.js b/erpnext/erpnext_integrations/doctype/packlink/packlink.js deleted file mode 100644 index da864584f6..0000000000 --- a/erpnext/erpnext_integrations/doctype/packlink/packlink.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Packlink', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.json b/erpnext/erpnext_integrations/doctype/packlink/packlink.json deleted file mode 100644 index a56595e9a1..0000000000 --- a/erpnext/erpnext_integrations/doctype/packlink/packlink.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "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 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/packlink/packlink.py b/erpnext/erpnext_integrations/doctype/packlink/packlink.py deleted file mode 100644 index 1db08c3149..0000000000 --- a/erpnext/erpnext_integrations/doctype/packlink/packlink.py +++ /dev/null @@ -1,240 +0,0 @@ -# -*- 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.log_error(frappe.get_traceback()) - 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.log_error(frappe.get_traceback()) - 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.log_error(frappe.get_traceback()) - 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('-', '/') \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py b/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py deleted file mode 100644 index 106ae51f7c..0000000000 --- a/erpnext/erpnext_integrations/doctype/packlink/test_packlink.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/__init__.py b/erpnext/erpnext_integrations/doctype/sendcloud/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js deleted file mode 100644 index 3b85236863..0000000000 --- a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('SendCloud', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json deleted file mode 100644 index 37b6898cba..0000000000 --- a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "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": "Password", - "label": "API Secret", - "read_only_depends_on": "eval:doc.enabled == 0" - } - ], - "index_web_pages_for_search": 1, - "issingle": 1, - "links": [], - "modified": "2020-10-21 10:28:57.710549", - "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 -} \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py b/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py deleted file mode 100644 index d30af15eb5..0000000000 --- a/erpnext/erpnext_integrations/doctype/sendcloud/sendcloud.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- 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. - api_key, api_secret, enabled = frappe.db.get_value('SendCloud', 'SendCloud', ['enabled', 'api_key', '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.log_error(frappe.get_traceback()) - 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 - api_key, api_secret, enabled = frappe.db.get_value('SendCloud', 'SendCloud', ['enabled', 'api_key', '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.log_error(frappe.get_traceback()) - 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(', ') - awb_number = [] - tracking_status = [] - tracking_status_info = [] - tracking_urls = [] - 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_urls.append(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': ', '.join(tracking_urls) - } - except Exception as exc: - frappe.log_error(frappe.get_traceback()) - 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 \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py b/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py deleted file mode 100644 index 5cbe80e8ac..0000000000 --- a/erpnext/erpnext_integrations/doctype/sendcloud/test_sendcloud.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 diff --git a/erpnext/stock/doctype/parcel_service/__init__.py b/erpnext/stock/doctype/parcel_service/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.js b/erpnext/stock/doctype/parcel_service/parcel_service.js deleted file mode 100644 index 43b8ed5bf8..0000000000 --- a/erpnext/stock/doctype/parcel_service/parcel_service.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Parcel Service', { - // refresh: function(frm) { - - // } -}); diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.json b/erpnext/stock/doctype/parcel_service/parcel_service.json deleted file mode 100644 index 9960acf4ae..0000000000 --- a/erpnext/stock/doctype/parcel_service/parcel_service.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "field:parcel_service_name", - "creation": "2020-07-23 10:35:38.211715", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "parcel_service_name", - "parcel_service_code", - "url_reference" - ], - "fields": [ - { - "fieldname": "parcel_service_name", - "fieldtype": "Data", - "label": "Parcel Service Name", - "unique": 1 - }, - { - "fieldname": "parcel_service_code", - "fieldtype": "Data", - "label": "Parcel Service Code" - }, - { - "fieldname": "url_reference", - "fieldtype": "Data", - "label": "URL Reference" - } - ], - "links": [], - "modified": "2020-07-23 10:35:38.211715", - "modified_by": "Administrator", - "module": "Stock", - "name": "Parcel Service", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service/parcel_service.py b/erpnext/stock/doctype/parcel_service/parcel_service.py deleted file mode 100644 index e46ac76ef7..0000000000 --- a/erpnext/stock/doctype/parcel_service/parcel_service.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 frappe -from frappe.model.document import Document - -class ParcelService(Document): - pass diff --git a/erpnext/stock/doctype/parcel_service/test_parcel_service.py b/erpnext/stock/doctype/parcel_service/test_parcel_service.py deleted file mode 100644 index c2f96d9cb0..0000000000 --- a/erpnext/stock/doctype/parcel_service/test_parcel_service.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 TestParcelService(unittest.TestCase): - pass diff --git a/erpnext/stock/doctype/parcel_service_type/__init__.py b/erpnext/stock/doctype/parcel_service_type/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js deleted file mode 100644 index 31d54536c0..0000000000 --- a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.js +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Parcel Service Type Alias', { - parcel_type_alias: function(frm, cdt, cdn) { - let row = locals[cdt][cdn]; - if (row.parcel_type_alias) { - frappe.model.set_value(cdt, cdn, 'parcel_service', frm.doc.parcel_service); - frm.refresh_field('parcel_service_type_alias'); - } - } -}); diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json deleted file mode 100644 index 3c0c4d5f80..0000000000 --- a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "actions": [], - "allow_rename": 1, - "autoname": "format: {parcel_service} - {parcel_service_type}", - "creation": "2020-07-23 10:47:43.794083", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "parcel_service", - "parcel_service_type", - "description", - "section_break_4", - "parcel_service_type_alias", - "column_break_6", - "section_break_7", - "show_in_preferred_services_list" - ], - "fields": [ - { - "fieldname": "parcel_service", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Parcel Service", - "options": "Parcel Service", - "reqd": 1 - }, - { - "fieldname": "parcel_service_type", - "fieldtype": "Data", - "label": "Parcel Service Type", - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Description" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "fieldname": "parcel_service_type_alias", - "fieldtype": "Table", - "label": "Parcel Service Type Alias", - "options": "Parcel Service Type Alias" - }, - { - "fieldname": "column_break_6", - "fieldtype": "Column Break" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "default": "0", - "fieldname": "show_in_preferred_services_list", - "fieldtype": "Check", - "label": "Show in Preferred Services List" - } - ], - "links": [], - "modified": "2020-07-23 10:47:43.794083", - "modified_by": "Administrator", - "module": "Stock", - "name": "Parcel Service Type", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py b/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py deleted file mode 100644 index b55528c359..0000000000 --- a/erpnext/stock/doctype/parcel_service_type/parcel_service_type.py +++ /dev/null @@ -1,22 +0,0 @@ -# -*- 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 frappe -from frappe.model.document import Document - -class ParcelServiceType(Document): - pass - -def match_parcel_service_type_alias(parcel_service_type, parcel_service): - # Match and return Parcel Service Type Alias to Parcel Service Type if exists. - if frappe.db.exists('Parcel Service', parcel_service): - matched_parcel_service_type = \ - frappe.db.get_value('Parcel Service Type Alias', { - 'parcel_type_alias': parcel_service_type, - 'parcel_service': parcel_service - }, 'parent') - if matched_parcel_service_type: - parcel_service_type = matched_parcel_service_type - return parcel_service_type diff --git a/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py b/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py deleted file mode 100644 index e214264acc..0000000000 --- a/erpnext/stock/doctype/parcel_service_type/test_parcel_service_type.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 TestParcelServiceType(unittest.TestCase): - pass diff --git a/erpnext/stock/doctype/parcel_service_type_alias/__init__.py b/erpnext/stock/doctype/parcel_service_type_alias/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json deleted file mode 100644 index 8e7731e6c1..0000000000 --- a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "actions": [], - "creation": "2020-07-23 10:47:23.626510", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "parcel_service", - "parcel_type_alias" - ], - "fields": [ - { - "fieldname": "parcel_service", - "fieldtype": "Link", - "hidden": 1, - "in_list_view": 1, - "label": "Parcel Service", - "options": "Parcel Service", - "read_only": 1 - }, - { - "fieldname": "parcel_type_alias", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Parcel Type Alias", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-23 10:47:23.626510", - "modified_by": "Administrator", - "module": "Stock", - "name": "Parcel Service Type Alias", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py b/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py deleted file mode 100644 index fd0a7d8b49..0000000000 --- a/erpnext/stock/doctype/parcel_service_type_alias/parcel_service_type_alias.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 frappe -from frappe.model.document import Document - -class ParcelServiceTypeAlias(Document): - pass diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index fc0b05f8af..aa792a4883 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -114,26 +114,6 @@ frappe.ui.form.on('Shipment', { }); }, refresh: function(frm) { - if (frm.doc.docstatus === 1 && !frm.doc.shipment_id) { - frm.add_custom_button(__('Fetch Shipping Rates'), function() { - return frm.events.fetch_shipping_rates(frm); - }); - } - if (frm.doc.shipment_id) { - frm.add_custom_button(__('Print Shipping Label'), function() { - return frm.events.print_shipping_label(frm); - }, __('Tools')); - if (frm.doc.tracking_status != 'Delivered') { - frm.add_custom_button(__('Update Tracking'), function() { - return frm.events.update_tracking(frm, frm.doc.service_provider, frm.doc.shipment_id); - }, __('Tools')); - - frm.add_custom_button(__('Track Status'), function() { - const urls = frm.doc.tracking_url.split(', '); - urls.forEach(url => window.open(url)); - }, __('View')); - } - } $('div[data-fieldname=pickup_address] > div > .clearfix').hide(); $('div[data-fieldname=pickup_contact] > div > .clearfix').hide(); $('div[data-fieldname=delivery_address] > div > .clearfix').hide(); @@ -598,90 +578,6 @@ frappe.ui.form.on('Shipment', { frm.refresh_fields("pickup_from_send_shipping_notification"); frm.refresh_fields("pickup_from_subscribe_to_status_updates"); } - }, - fetch_shipping_rates: function(frm) { - if (!frm.doc.shipment_id) { - frappe.call({ - method: "erpnext.stock.doctype.shipment.shipment.fetch_shipping_rates", - freeze: true, - freeze_message: __("Fetching Shipping Rates"), - args: { - pickup_from_type: frm.doc.pickup_from_type, - delivery_to_type: frm.doc.delivery_to_type, - pickup_address_name: frm.doc.pickup_address_name, - delivery_address_name: frm.doc.delivery_address_name, - shipment_parcel: frm.doc.shipment_parcel, - description_of_content: frm.doc.description_of_content, - pickup_date: frm.doc.pickup_date, - pickup_contact_name: frm.doc.pickup_from_type === 'Company' ? frm.doc.pickup_contact_person : frm.doc.pickup_contact_name, - delivery_contact_name: frm.doc.delivery_contact_name, - value_of_goods: frm.doc.value_of_goods - }, - callback: function(r) { - if (r.message) { - select_from_available_services(frm, r.message); - } - else { - frappe.throw(__("No Shipment Services available")); - } - } - }); - } - else { - frappe.throw(__("Shipment already created")); - } - }, - print_shipping_label: function(frm) { - frappe.call({ - method: "erpnext.stock.doctype.shipment.shipment.print_shipping_label", - freeze: true, - freeze_message: __("Printing Shipping Label"), - args: { - shipment_id: frm.doc.shipment_id, - service_provider: frm.doc.service_provider - }, - callback: function(r) { - if (r.message) { - if (frm.doc.service_provider == "LetMeShip") { - var array = JSON.parse(r.message); - // Uint8Array for unsigned bytes - array = new Uint8Array(array); - const file = new Blob([array], {type: "application/pdf"}); - const file_url = URL.createObjectURL(file); - window.open(file_url); - } - else { - if (Array.isArray(r.message)) { - r.message.forEach(url => window.open(url)); - } else { - window.open(r.message); - } - } - } - } - }); - }, - update_tracking: function(frm, service_provider, shipment_id) { - let delivery_notes = []; - (frm.doc.shipment_delivery_note || []).forEach((d) => { - delivery_notes.push(d.delivery_note); - }); - frappe.call({ - method: "erpnext.stock.doctype.shipment.shipment.update_tracking", - freeze: true, - freeze_message: __("Updating Tracking"), - args: { - shipment: frm.doc.name, - shipment_id: shipment_id, - service_provider: service_provider, - delivery_notes: delivery_notes - }, - callback: function(r) { - if (!r.exc) { - frm.reload_doc(); - } - } - }); } }); @@ -712,82 +608,3 @@ var validate_duplicate = function(frm, table, fieldname, index){ : frm.doc[table].some((detail, i) => detail.email === fieldname && !(index === i)) ); }; - -function select_from_available_services(frm, available_services) { - var headers = [ __("Service Provider"), __("Carrier"), __("Carrier’s Service"), __("Price"), "" ]; - cur_frm.render_available_services = function(d, headers, data){ - const arranged_data = data.reduce((prev, curr) => { - if (curr.is_preferred) { - prev.preferred_services.push(curr); - } else { - prev.other_services.push(curr); - } - return prev; - }, { preferred_services: [], other_services: [] }); - d.fields_dict.available_services.$wrapper.html( - frappe.render_template('shipment_service_selector', - {'header_columns': headers, 'data': arranged_data} - ) - ); - }; - const d = new frappe.ui.Dialog({ - title: __("Select Shipment Service to create Shipment"), - fields: [ - { - fieldtype:'HTML', - fieldname:"available_services", - label: __('Available Services') - } - ] - }); - cur_frm.render_available_services(d, headers, available_services); - let shipment_notific_email = []; - let tracking_notific_email = []; - (frm.doc.shipment_notification_subscription || []).forEach((d) => { - if (!d.unsubscribed) { - shipment_notific_email.push(d.email); - } - }); - (frm.doc.shipment_status_update_subscription || []).forEach((d) => { - if (!d.unsubscribed) { - tracking_notific_email.push(d.email); - } - }); - let delivery_notes = []; - (frm.doc.shipment_delivery_note || []).forEach((d) => { - delivery_notes.push(d.delivery_note); - }); - cur_frm.select_row = function(service_data){ - frappe.call({ - method: "erpnext.stock.doctype.shipment.shipment.create_shipment", - freeze: true, - freeze_message: __("Creating Shipment"), - args: { - shipment: frm.doc.name, - pickup_from_type: frm.doc.pickup_from_type, - delivery_to_type: frm.doc.delivery_to_type, - pickup_address_name: frm.doc.pickup_address_name, - delivery_address_name: frm.doc.delivery_address_name, - shipment_parcel: frm.doc.shipment_parcel, - description_of_content: frm.doc.description_of_content, - pickup_date: frm.doc.pickup_date, - pickup_contact_name: frm.doc.pickup_from_type === 'Company' ? frm.doc.pickup_contact_person : frm.doc.pickup_contact_name, - delivery_contact_name: frm.doc.delivery_contact_name, - value_of_goods: frm.doc.value_of_goods, - service_data: service_data, - shipment_notific_email: shipment_notific_email, - tracking_notific_email: tracking_notific_email, - delivery_notes: delivery_notes - }, - callback: function(r) { - if (!r.exc) { - frm.reload_doc(); - frappe.msgprint(__("Shipment created with {0}, ID is {1}", [r.message.service_provider, r.message.shipment_id])); - frm.events.update_tracking(frm, r.message.service_provider, r.message.shipment_id); - } - } - }); - d.hide(); - }; - d.show(); -} diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index bbfbb719be..9ac6102ded 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -358,28 +358,24 @@ { "fieldname": "service_provider", "fieldtype": "Data", - "label": "Service Provider", - "read_only": 1 + "label": "Service Provider" }, { "fieldname": "shipment_id", "fieldtype": "Data", - "label": "Shipment ID", - "read_only": 1 + "label": "Shipment ID" }, { "fieldname": "shipment_amount", "fieldtype": "Currency", "label": "Shipment Amount", - "precision": "2", - "read_only": 1 + "precision": "2" }, { "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted", - "read_only": 1 + "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted" }, { "fieldname": "tracking_url", @@ -391,27 +387,23 @@ { "fieldname": "carrier", "fieldtype": "Data", - "label": "Carrier", - "read_only": 1 + "label": "Carrier" }, { "fieldname": "carrier_service", "fieldtype": "Data", - "label": "Carrier Service", - "read_only": 1 + "label": "Carrier Service" }, { "fieldname": "awb_number", "fieldtype": "Data", - "label": "AWB Number", - "read_only": 1 + "label": "AWB Number" }, { "fieldname": "tracking_status", "fieldtype": "Select", "label": "Tracking Status", - "options": "\nIn Progress\nDelivered\nReturned\nLost", - "read_only": 1 + "options": "\nIn Progress\nDelivered\nReturned\nLost" }, { "fieldname": "tracking_status_info", @@ -468,7 +460,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-09-29 13:59:50.241744", + "modified": "2020-11-20 16:19:06.157106", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 9b3c976ca4..4e16f95533 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -10,10 +10,6 @@ from frappe.utils import flt from frappe.model.document import Document from erpnext.accounts.party import get_party_shipping_address from frappe.contacts.doctype.contact.contact import get_default_contact -from erpnext.erpnext_integrations.doctype.letmeship.letmeship import LETMESHIP_PROVIDER, get_letmeship_available_services, create_letmeship_shipment, get_letmeship_label, get_letmeship_tracking_data -from erpnext.erpnext_integrations.doctype.packlink.packlink import PACKLINK_PROVIDER, get_packlink_available_services, create_packlink_shipment, get_packlink_label, get_packlink_tracking_data -from erpnext.erpnext_integrations.doctype.sendcloud.sendcloud import SENDCLOUD_PROVIDER, get_sendcloud_available_services, create_sendcloud_shipment, get_sendcloud_label, get_sendcloud_tracking_data -from erpnext.stock.doctype.parcel_service_type.parcel_service_type import match_parcel_service_type_alias class Shipment(Document): def validate(self): @@ -36,159 +32,6 @@ class Shipment(Document): if flt(parcel.weight) <= 0: frappe.throw(_('Parcel weight cannot be 0')) -@frappe.whitelist() -def fetch_shipping_rates(pickup_from_type, delivery_to_type, pickup_address_name, delivery_address_name, - shipment_parcel, description_of_content, pickup_date, value_of_goods, - pickup_contact_name=None, delivery_contact_name=None): - # Return Shipping Rates for the various Shipping Providers - shipment_prices = [] - letmeship_enabled = frappe.db.get_single_value('LetMeShip','enabled') - packlink_enabled = frappe.db.get_single_value('Packlink','enabled') - sendcloud_enabled = frappe.db.get_single_value('SendCloud','enabled') - pickup_address = get_address(pickup_address_name) - delivery_address = get_address(delivery_address_name) - if letmeship_enabled: - pickup_contact = None - delivery_contact = None - if pickup_from_type != 'Company': - pickup_contact = get_contact(pickup_contact_name) - else: - pickup_contact = get_company_contact(user=pickup_contact_name) - - if delivery_to_type != 'Company': - delivery_contact = get_contact(delivery_contact_name) - else: - delivery_contact = get_company_contact(user=pickup_contact_name) - letmeship_prices = get_letmeship_available_services( - delivery_to_type=delivery_to_type, - pickup_address=pickup_address, - 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, - ) - letmeship_prices = match_parcel_service_type_carrier(letmeship_prices, ['carrier', 'carrier_name']) - shipment_prices = shipment_prices + letmeship_prices - if packlink_enabled: - packlink_prices = get_packlink_available_services( - pickup_address=pickup_address, - delivery_address=delivery_address, - shipment_parcel=shipment_parcel, - pickup_date=pickup_date - ) - packlink_prices = match_parcel_service_type_carrier(packlink_prices, ['carrier_name', 'carrier']) - shipment_prices = shipment_prices + packlink_prices - if sendcloud_enabled and pickup_from_type == 'Company': - sendcloud_prices = get_sendcloud_available_services( - delivery_address=delivery_address, - shipment_parcel=shipment_parcel - ) - shipment_prices = shipment_prices + sendcloud_prices - shipment_prices = sorted(shipment_prices, key=lambda k:k['total_price']) - return shipment_prices - -@frappe.whitelist() -def create_shipment(shipment, pickup_from_type, delivery_to_type, pickup_address_name, - delivery_address_name, shipment_parcel, description_of_content, pickup_date, - value_of_goods, service_data, shipment_notific_email, tracking_notific_email, - pickup_contact_name=None, delivery_contact_name=None, delivery_notes=[]): - # Create Shipment for the selected provider - service_info = json.loads(service_data) - shipment_info = None - pickup_contact = None - delivery_contact = None - pickup_address = get_address(pickup_address_name) - delivery_address = get_address(delivery_address_name) - if pickup_from_type != 'Company': - pickup_contact = get_contact(pickup_contact_name) - else: - pickup_contact = get_company_contact(user=pickup_contact_name) - - if delivery_to_type != 'Company': - delivery_contact = get_contact(delivery_contact_name) - else: - delivery_contact = get_company_contact(user=pickup_contact_name) - if service_info['service_provider'] == LETMESHIP_PROVIDER: - shipment_info = create_letmeship_shipment( - pickup_address=pickup_address, - 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, - shipment_notific_email=shipment_notific_email, - tracking_notific_email=tracking_notific_email, - ) - - if service_info['service_provider'] == PACKLINK_PROVIDER: - shipment_info = create_packlink_shipment( - pickup_address=pickup_address, - 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 service_info['service_provider'] == SENDCLOUD_PROVIDER: - shipment_info = create_sendcloud_shipment( - shipment=shipment, - delivery_address=delivery_address, - shipment_parcel=shipment_parcel, - description_of_content=description_of_content, - value_of_goods=value_of_goods, - delivery_contact=delivery_contact, - service_info=service_info, - ) - - if shipment_info: - fields = ['service_provider', 'carrier', 'carrier_service', 'shipment_id', 'shipment_amount', 'awb_number'] - for field in fields: - frappe.db.set_value('Shipment', shipment, field, shipment_info.get(field)) - frappe.db.set_value('Shipment', shipment, 'status', 'Booked') - if delivery_notes: - update_delivery_note(delivery_notes=delivery_notes, shipment_info=shipment_info) - return shipment_info - - -@frappe.whitelist() -def print_shipping_label(service_provider, shipment_id): - if service_provider == LETMESHIP_PROVIDER: - shipping_label = get_letmeship_label(shipment_id) - elif service_provider == PACKLINK_PROVIDER: - shipping_label = get_packlink_label(shipment_id) - elif service_provider == SENDCLOUD_PROVIDER: - shipping_label = get_sendcloud_label(shipment_id) - return shipping_label - - -@frappe.whitelist() -def update_tracking(shipment, service_provider, shipment_id, delivery_notes=[]): - # Update Tracking info in Shipment - tracking_data = None - if service_provider == LETMESHIP_PROVIDER: - tracking_data = get_letmeship_tracking_data(shipment_id) - elif service_provider == PACKLINK_PROVIDER: - tracking_data = get_packlink_tracking_data(shipment_id) - elif service_provider == SENDCLOUD_PROVIDER: - tracking_data = get_sendcloud_tracking_data(shipment_id) - if tracking_data: - if delivery_notes: - update_delivery_note(delivery_notes=delivery_notes, tracking_info=tracking_data) - frappe.db.set_value('Shipment', shipment, 'awb_number', tracking_data.get('awb_number')) - frappe.db.set_value('Shipment', shipment, 'tracking_status', tracking_data.get('tracking_status')) - frappe.db.set_value('Shipment', shipment, 'tracking_status_info', tracking_data.get('tracking_status_info')) - frappe.db.set_value('Shipment', shipment, 'tracking_url', tracking_data.get('tracking_url')) - @frappe.whitelist() def get_address_name(ref_doctype, docname): # Return address name @@ -199,90 +42,6 @@ def get_contact_name(ref_doctype, docname): # Return address name return get_default_contact(ref_doctype, docname) -def update_delivery_note(delivery_notes, shipment_info=None, tracking_info=None): - # Update Shipment Info in Delivery Note - # Using db_set since some services might not exist - for delivery_note in json.loads(delivery_notes): - dl_doc = frappe.get_doc('Delivery Note', delivery_note) - if shipment_info: - dl_doc.db_set('delivery_type', 'Parcel Service') - dl_doc.db_set('parcel_service', shipment_info.get('carrier')) - dl_doc.db_set('parcel_service_type', shipment_info.get('carrier_service')) - if tracking_info: - dl_doc.db_set('tracking_number', tracking_info.get('awb_number')) - dl_doc.db_set('tracking_url', tracking_info.get('tracking_url')) - dl_doc.db_set('tracking_status', tracking_info.get('tracking_status')) - dl_doc.db_set('tracking_status_info', tracking_info.get('tracking_status_info')) - - -def update_tracking_info(): - # Daily scheduled event to update Tracking info for not delivered Shipments - # Also Updates the related Delivery Notes - shipments = frappe.get_all('Shipment', filters={ - 'docstatus': 1, - 'status': 'Booked', - 'shipment_id': ['!=', ''], - 'tracking_status': ['!=', 'Delivered'], - }) - for shipment in shipments: - shipment_doc = frappe.get_doc('Shipment', shipment.name) - tracking_info = \ - update_tracking( - shipment_doc.service_provider, - shipment_doc.shipment_id, - shipment_doc.shipment_delivery_notes - ) - if tracking_info: - shipment_doc.db_set('awb_number', tracking_info.get('awb_number')) - shipment_doc.db_set('tracking_url', tracking_info.get('tracking_url')) - shipment_doc.db_set('tracking_status', tracking_info.get('tracking_status')) - shipment_doc.db_set('tracking_status_info', tracking_info.get('tracking_status_info')) - - -def get_address(address_name): - address = frappe.db.get_value('Address', address_name, [ - 'address_title', - 'address_line1', - 'address_line2', - 'city', - 'pincode', - 'country', - ], as_dict=1) - address.country_code = frappe.db.get_value('Country', address.country, 'code').upper() - if not address.pincode or address.pincode == '': - frappe.throw(_("Postal Code is mandatory to continue.
\ - Please set Postal Code for Address {1}" - ).format(address_name, address_name)) - address.pincode = address.pincode.replace(' ', '') - address.city = address.city.strip() - return address - - -def get_contact(contact_name): - contact = frappe.db.get_value('Contact', contact_name, [ - 'first_name', - 'last_name', - 'email_id', - 'phone', - 'mobile_no', - 'gender', - ], as_dict=1) - if not contact.last_name: - frappe.throw(_("Last Name is mandatory to continue.
\ - Please set Last Name for Contact {1}" - ).format(contact_name, contact_name)) - if not contact.phone: - contact.phone = contact.mobile_no - return contact - -def match_parcel_service_type_carrier(shipment_prices, reference): - for idx, prices in enumerate(shipment_prices): - service_name = match_parcel_service_type_alias(prices.get(reference[0]), prices.get(reference[1])) - is_preferred = frappe.db.get_value('Parcel Service Type', service_name, 'show_in_preferred_services_list') - shipment_prices[idx].service_name = service_name - shipment_prices[idx].is_preferred = is_preferred - return shipment_prices - @frappe.whitelist() def get_company_contact(user): contact = frappe.db.get_value('User', user, [ @@ -295,4 +54,4 @@ def get_company_contact(user): ], as_dict=1) if not contact.phone: contact.phone = contact.mobile_no - return contact \ No newline at end of file + return contact diff --git a/erpnext/stock/doctype/shipment/shipment_service_selector.html b/erpnext/stock/doctype/shipment/shipment_service_selector.html deleted file mode 100644 index 4ccbe34e9e..0000000000 --- a/erpnext/stock/doctype/shipment/shipment_service_selector.html +++ /dev/null @@ -1,74 +0,0 @@ -{% if (data.preferred_services.length || data.other_services.length) { %} -
-
{{ __("Preferred Services") }}
- {% if (data.preferred_services.length) { %} - - - - {% for (var i = 0; i < header_columns.length; i++) { %} - - {% } %} - - - - {% for (var i = 0; i < data.preferred_services.length; i++) { %} - - - - - - - - {% } %} - -
{{ header_columns[i] }}
{{ 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) }} - -
- {% } else { %} -
{{ __("No Preferred Services Available") }}
- {% } %} -
{{ __("Other Services") }}
- {% if (data.other_services.length) { %} - - - - {% for (var i = 0; i < header_columns.length; i++) { %} - - {% } %} - - - - {% for (var i = 0; i < data.other_services.length; i++) { %} - - - - - - - - {% } %} - -
{{ header_columns[i] }}
{{ data.other_services[i].service_provider }}{{ data.other_services[i].carrier }}{{ data.other_services[i].service_name }}{{ format_currency(data.other_services[i].total_price, 'EUR', 2) }} - -
- {% } else { %} -
{{ __("No Services Available") }}
- {% } %} -
-{% } else { %} -
{{ __("No Services Available") }}
-{% } %} - - \ No newline at end of file From 28055f483dff5c31c3babff2f21902c77ea5f6d7 Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Mon, 23 Nov 2020 06:12:50 +0000 Subject: [PATCH 4/7] fix(shipment): change shipment test and refactor shipment.js --- .../doctype/delivery_note/delivery_note.py | 1 + erpnext/stock/doctype/shipment/shipment.js | 101 +++--------------- erpnext/stock/doctype/shipment/shipment.json | 18 +++- .../stock/doctype/shipment/test_shipment.py | 101 ++---------------- 4 files changed, 38 insertions(+), 183 deletions(-) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 26e4f1633e..979e83df69 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -619,6 +619,7 @@ def make_shipment(source_name, target_doc=None): "name": "prevdoc_detail_docname", "parent": "prevdoc_docname", "parenttype": "prevdoc_doctype", + "base_amount": "grand_total" } } }, target_doc, postprocess) diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index aa792a4883..62070e4e55 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -23,76 +23,20 @@ frappe.ui.form.on('Shipment', { }, onload: function(frm) { frm.set_query("delivery_address_name", () => { - let link_doctype = ''; - let link_name = ''; - let is_your_company_address = 0; - if (frm.doc.delivery_to_type == 'Customer') { - link_doctype = 'Customer'; - link_name = frm.doc.delivery_customer; - } - if (frm.doc.delivery_to_type == 'Supplier') { - link_doctype = 'Supplier'; - link_name = frm.doc.delivery_supplier; - } - if (frm.doc.delivery_to_type == 'Company') { - link_doctype = 'Company'; - link_name = frm.doc.delivery_company; - is_your_company_address = 1; - } - return frm.events.address_query(frm, link_doctype, link_name, is_your_company_address); + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0); }); frm.set_query("pickup_address_name", () => { - let link_doctype = ''; - let link_name = ''; - let is_your_company_address = 0; - if (frm.doc.pickup_from_type == 'Customer') { - link_doctype = 'Customer'; - link_name = frm.doc.pickup_customer; - } - if (frm.doc.pickup_from_type == 'Supplier') { - link_doctype = 'Supplier'; - link_name = frm.doc.pickup_supplier; - } - if (frm.doc.pickup_from_type == 'Company') { - link_doctype = 'Company'; - link_name = frm.doc.pickup_company; - is_your_company_address = 1; - } - return frm.events.address_query(frm, link_doctype, link_name, is_your_company_address); + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0); }); frm.set_query("delivery_contact_name", () => { - let link_doctype = ''; - let link_name = ''; - if (frm.doc.delivery_to_type == 'Customer') { - link_doctype = 'Customer'; - link_name = frm.doc.delivery_customer; - } - if (frm.doc.delivery_to_type == 'Supplier') { - link_doctype = 'Supplier'; - link_name = frm.doc.delivery_supplier; - } - if (frm.doc.delivery_to_type == 'Company') { - link_doctype = 'Company'; - link_name = frm.doc.delivery_company; - } - return frm.events.contact_query(frm, link_doctype, link_name); + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + return frm.events.contact_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to]); }); frm.set_query("pickup_contact_name", () => { - let link_doctype = ''; - let link_name = ''; - if (frm.doc.pickup_from_type == 'Customer') { - link_doctype = 'Customer'; - link_name = frm.doc.pickup_customer; - } - if (frm.doc.pickup_from_type == 'Supplier') { - link_doctype = 'Supplier'; - link_name = frm.doc.pickup_supplier; - } - if (frm.doc.pickup_from_type == 'Company') { - link_doctype = 'Company'; - link_name = frm.doc.pickup_company; - } - return frm.events.contact_query(frm, link_doctype, link_name); + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]); }); frm.set_query("delivery_note", "shipment_delivery_note", function() { let customer = ''; @@ -120,24 +64,10 @@ frappe.ui.form.on('Shipment', { $('div[data-fieldname=delivery_contact] > div > .clearfix').hide(); }, before_save: function(frm) { - if (frm.doc.delivery_to_type == 'Company') { - frm.set_value("delivery_to", frm.doc.delivery_company); - } - if (frm.doc.delivery_to_type == 'Customer') { - frm.set_value("delivery_to", frm.doc.delivery_customer); - } - if (frm.doc.delivery_to_type == 'Supplier') { - frm.set_value("delivery_to", frm.doc.delivery_supplier); - } - if (frm.doc.pickup_from_type == 'Company') { - frm.set_value("pickup", frm.doc.pickup_company); - } - if (frm.doc.pickup_from_type == 'Customer') { - frm.set_value("pickup", frm.doc.pickup_customer); - } - if (frm.doc.pickup_from_type == 'Supplier') { - frm.set_value("pickup", frm.doc.pickup_supplier); - } + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + frm.set_value("delivery_to", frm.doc[delivery_to]); + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + frm.set_value("pickup", frm.doc[pickup_from]); }, set_pickup_company_address: function(frm) { frappe.db.get_value('Address', { @@ -476,18 +406,11 @@ frappe.ui.form.on('Shipment', { current_min = '00'; current_hour = Number(current_hour)+1; } - if (Number(current_hour) > 19 || Number(current_hour) === 19){ - frappe.throw(__("Today's pickup time is over, please select different date")); - } - current_hour = (current_hour < 10) ? '0' + current_hour : current_hour; let pickup_time = current_hour +':'+ current_min; return pickup_time; }, set_pickup_to_time: function(frm) { let pickup_to_hour = Number(frm.doc.pickup_from.split(':')[0])+5; - if (Number(pickup_to_hour) > 19 || Number(pickup_to_hour) === 19){ - pickup_to_hour = 19; - } let pickup_to_min = frm.doc.pickup_from.split(':')[1]; let pickup_to = pickup_to_hour +':'+ pickup_to_min; frm.set_value("pickup_to", pickup_to); diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 9ac6102ded..7e2c5baace 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -460,13 +460,28 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-20 16:19:06.157106", + "modified": "2020-11-23 16:26:28.132608", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", "owner": "Administrator", "permissions": [ { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, "create": 1, "delete": 1, "email": 1, @@ -476,6 +491,7 @@ "report": 1, "role": "System Manager", "share": 1, + "submit": 1, "write": 1 } ], diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index 6a06930e82..f61b87fd41 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -7,89 +7,19 @@ from datetime import date, timedelta import frappe import unittest -from erpnext.stock.doctype.shipment.shipment import fetch_shipping_rates -from erpnext.stock.doctype.shipment.shipment import create_shipment -from erpnext.stock.doctype.shipment.shipment import update_tracking +from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment class TestShipment(unittest.TestCase): - pass - - def test_shipment_booking(self): - shipment = create_test_shipment() - try: - shipment.submit() - except: - frappe.throw('Error occurred on submit shipment') - doc, rate, tracking_data = make_shipment_transaction(shipment) - if doc and rate and tracking_data: - self.assertEqual(doc.service_provider, rate.get('service_provider')) - self.assertEqual(doc.shipment_amount, rate.get('actual_price')) - self.assertEqual(doc.carrier, rate.get('carrier')) - self.assertEqual(doc.tracking_status, tracking_data.get('tracking_status')) - self.assertEqual(doc.tracking_url, tracking_data.get('tracking_url')) - def test_shipment_from_delivery_note(self): delivery_note = create_test_delivery_note() - try: - delivery_note.submit() - except: - frappe.throw('An error occurred.') - + delivery_note.submit() shipment = create_test_shipment([ delivery_note ]) - try: - shipment.submit() - except: - frappe.throw('Error occurred on submit shipment') - doc, rate, tracking_data = make_shipment_transaction(shipment) - if doc and rate and tracking_data: - self.assertEqual(doc.service_provider, rate.get('service_provider')) - self.assertEqual(doc.shipment_amount, rate.get('actual_price')) - self.assertEqual(doc.carrier, rate.get('carrier')) - self.assertEqual(doc.tracking_status, tracking_data.get('tracking_status')) - self.assertEqual(doc.tracking_url, tracking_data.get('tracking_url')) - - - -def make_shipment_transaction(shipment): - shipment_parcel = convert_shipmet_parcel(shipment.shipment_parcel) - shipment_rates = fetch_shipping_rates(shipment.pickup_from_type, shipment.delivery_to_type, - shipment.pickup_address_name, shipment.delivery_address_name, - shipment_parcel, shipment.description_of_content, - shipment.pickup_date, shipment.value_of_goods, - pickup_contact_name=shipment.pickup_contact_name, - delivery_contact_name=shipment.delivery_contact_name - ) - if len(shipment_rates) > 0: - # We are taking the first shipment rate - rate = shipment_rates[0] - new_shipment = create_shipment( - shipment=shipment.name, - pickup_from_type=shipment.pickup_from_type, - delivery_to_type=shipment.delivery_to_type, - pickup_address_name=shipment.pickup_address_name, - delivery_address_name=shipment.delivery_address_name, - shipment_parcel=shipment_parcel, - description_of_content=shipment.description_of_content, - pickup_date=shipment.pickup_date, - pickup_contact_name=shipment.pickup_contact_name, - delivery_contact_name=shipment.delivery_contact_name, - value_of_goods=shipment.value_of_goods, - service_data=json.dumps(rate), - shipment_notific_email=None, - tracking_notific_email=None, - delivery_notes=None - ) - service_provider = rate.get('service_provider') - shipment_id = new_shipment.get('shipment_id') - tracking_data = update_tracking( - shipment.name, - service_provider, - shipment_id, - delivery_notes=None - ) - doc = frappe.get_doc('Shipment', shipment.name) - return doc, rate, tracking_data - return None, None, None + shipment.submit() + second_shipment = make_shipment(delivery_note.name) + self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total) + self.assertEqual(second_shipment.grand_total, delivery_note.grand_total) + self.assertEqual(len(second_shipment.shipment_delivery_note), 1) + self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name) def create_test_delivery_note(): company = get_shipment_company() @@ -316,18 +246,3 @@ def create_shipment_item(item_name, company_name): except: frappe.throw('An error occurred.') return item - - -def convert_shipmet_parcel(shipmet_parcel): - data = [] - for parcel in shipmet_parcel: - data.append( - { - "length": parcel.length, - "width": parcel.width, - "height": parcel.height, - "weight": parcel.weight, - "count": parcel.count - } - ) - return json.dumps(data) From 99361b4a9eb88b56b90474fe42ccd2ba4f081afb Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Mon, 23 Nov 2020 09:03:13 +0000 Subject: [PATCH 5/7] chore: remove notification details section --- erpnext/stock/doctype/shipment/shipment.js | 72 -------------------- erpnext/stock/doctype/shipment/shipment.json | 56 +-------------- 2 files changed, 1 insertion(+), 127 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index 62070e4e55..2832c8c72a 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -102,8 +102,6 @@ frappe.ui.form.on('Shipment', { frm.set_value("pickup_customer", ''); frm.set_value("pickup_company", ''); } - frm.events.remove_notific_child_table(frm, 'shipment_notification_subscription', 'Pickup'); - frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscription', 'Pickup'); }, delivery_to_type: function(frm) { if (frm.doc.delivery_to_type == 'Company') { @@ -126,8 +124,6 @@ frappe.ui.form.on('Shipment', { else { frm.toggle_display("shipment_delivery_note", true); } - frm.events.remove_notific_child_table(frm, 'shipment_notification_subscription', 'Delivery'); - frm.events.remove_notific_child_table(frm, 'shipment_status_update_subscription', 'Delivery'); }, delivery_address_name: function(frm) { if (frm.doc.delivery_to_type == 'Company') { @@ -427,80 +423,12 @@ frappe.ui.form.on('Shipment', { frm.set_value(field, ''); } }, - pickup_from_send_shipping_notification: function(frm, cdt, cdn) { - if (frm.doc.pickup_contact_email && frm.doc.pickup_from_send_shipping_notification - && !validate_duplicate(frm, 'shipment_notification_subscription', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscription", "shipment_notification_subscription"); - row.email = frm.doc.pickup_contact_email; - frm.refresh_fields("shipment_notification_subscription"); - } - if (!frm.doc.pickup_from_send_shipping_notification) { - frm.events.remove_email_row(frm, 'shipment_notification_subscription', frm.doc.pickup_contact_email); - frm.refresh_fields("shipment_notification_subscription"); - } - }, - pickup_from_subscribe_to_status_updates: function(frm, cdt, cdn) { - if (frm.doc.pickup_contact_email && frm.doc.pickup_from_subscribe_to_status_updates - && !validate_duplicate(frm, 'shipment_status_update_subscription', frm.doc.pickup_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscription", "shipment_status_update_subscription"); - row.email = frm.doc.pickup_contact_email; - frm.refresh_fields("shipment_status_update_subscription"); - } - if (!frm.doc.pickup_from_subscribe_to_status_updates) { - frm.events.remove_email_row(frm, 'shipment_status_update_subscription', frm.doc.pickup_contact_email); - frm.refresh_fields("shipment_status_update_subscription"); - } - }, - delivery_to_send_shipping_notification: function(frm, cdt, cdn) { - if (frm.doc.delivery_contact_email && frm.doc.delivery_to_send_shipping_notification - && !validate_duplicate(frm, 'shipment_notification_subscription', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)){ - let row = frappe.model.add_child(frm.doc, "Shipment Notification Subscription", "shipment_notification_subscription"); - row.email = frm.doc.delivery_contact_email; - frm.refresh_fields("shipment_notification_subscription"); - } - if (!frm.doc.delivery_to_send_shipping_notification) { - frm.events.remove_email_row(frm, 'shipment_notification_subscription', frm.doc.delivery_contact_email); - frm.refresh_fields("shipment_notification_subscription"); - } - }, - delivery_to_subscribe_to_status_updates: function(frm, cdt, cdn) { - if (frm.doc.delivery_contact_email && frm.doc.delivery_to_subscribe_to_status_updates - && !validate_duplicate(frm, 'shipment_status_update_subscription', frm.doc.delivery_contact_email, locals[cdt][cdn].idx)) { - let row = frappe.model.add_child(frm.doc, "Shipment Status Update Subscription", "shipment_status_update_subscription"); - row.email = frm.doc.delivery_contact_email; - frm.refresh_fields("shipment_status_update_subscription"); - } - if (!frm.doc.delivery_to_subscribe_to_status_updates) { - frm.events.remove_email_row(frm, 'shipment_status_update_subscription', frm.doc.delivery_contact_email); - frm.refresh_fields("shipment_status_update_subscription"); - } - }, remove_email_row: function(frm, table, fieldname) { $.each(frm.doc[table] || [], function(i, detail) { if(detail.email === fieldname){ cur_frm.get_field(table).grid.grid_rows[i].remove(); } }); - }, - remove_notific_child_table: function(frm, table, delivery_type) { - $.each(frm.doc[table] || [], function(i, detail) { - if (detail.email != frm.doc.pickup_email || detail.email != frm.doc.delivery_email){ - cur_frm.get_field(table).grid.grid_rows[i].remove(); - } - }); - frm.refresh_fields(table); - if (delivery_type == 'Delivery') { - frm.set_value("delivery_to_send_shipping_notification", 0); - frm.set_value("delivery_to_subscribe_to_status_updates", 0); - frm.refresh_fields("delivery_to_send_shipping_notification"); - frm.refresh_fields("delivery_to_subscribe_to_status_updates"); - } - else { - frm.set_value("pickup_from_send_shipping_notification", 0); - frm.set_value("pickup_from_subscribe_to_status_updates", 0); - frm.refresh_fields("pickup_from_send_shipping_notification"); - frm.refresh_fields("pickup_from_subscribe_to_status_updates"); - } } }); diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 7e2c5baace..1ae7862bc9 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -30,14 +30,6 @@ "delivery_contact_name", "delivery_contact_email", "delivery_contact", - "notification_details_section", - "pickup_from_send_shipping_notification", - "pickup_from_subscribe_to_status_updates", - "shipment_notification_subscription", - "column_break_27", - "delivery_to_send_shipping_notification", - "delivery_to_subscribe_to_status_updates", - "shipment_status_update_subscription", "parcels_section", "shipment_parcel", "parcel_template", @@ -224,40 +216,6 @@ "fieldtype": "Small Text", "read_only": 1 }, - { - "collapsible": 1, - "fieldname": "notification_details_section", - "fieldtype": "Section Break", - "label": "Notification Details" - }, - { - "default": "0", - "fieldname": "pickup_from_send_shipping_notification", - "fieldtype": "Check", - "label": "Send shipping notification" - }, - { - "default": "0", - "fieldname": "pickup_from_subscribe_to_status_updates", - "fieldtype": "Check", - "label": "Subscribe to status updates" - }, - { - "fieldname": "column_break_27", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "delivery_to_send_shipping_notification", - "fieldtype": "Check", - "label": "Send shipping notification" - }, - { - "default": "0", - "fieldname": "delivery_to_subscribe_to_status_updates", - "fieldtype": "Check", - "label": "Subscribe to status updates" - }, { "fieldname": "parcels_section", "fieldtype": "Section Break", @@ -437,18 +395,6 @@ "label": "Shipment Delivery Note", "options": "Shipment Delivery Note" }, - { - "fieldname": "shipment_notification_subscription", - "fieldtype": "Table", - "label": "Shipment Notification Subscription", - "options": "Shipment Notification Subscription" - }, - { - "fieldname": "shipment_status_update_subscription", - "fieldtype": "Table", - "label": "Shipment Status Update Subscription", - "options": "Shipment Status Update Subscription" - }, { "depends_on": "eval:doc.pickup_from_type === 'Company'", "fieldname": "pickup_contact_person", @@ -460,7 +406,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-23 16:26:28.132608", + "modified": "2020-11-23 17:00:51.600965", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", From b4b542d1c392b865c96a5b82aa3927fb95aca60d Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Mon, 23 Nov 2020 09:26:39 +0000 Subject: [PATCH 6/7] chore: linter issues and sider checks --- erpnext/stock/doctype/shipment/shipment.js | 92 ++++++++----------- erpnext/stock/doctype/shipment/shipment.py | 1 - .../stock/doctype/shipment/shipment_list.js | 2 +- .../stock/doctype/shipment/test_shipment.py | 11 +-- 4 files changed, 42 insertions(+), 64 deletions(-) diff --git a/erpnext/stock/doctype/shipment/shipment.js b/erpnext/stock/doctype/shipment/shipment.js index 2832c8c72a..5ccb7d2ff6 100644 --- a/erpnext/stock/doctype/shipment/shipment.js +++ b/erpnext/stock/doctype/shipment/shipment.js @@ -23,19 +23,19 @@ frappe.ui.form.on('Shipment', { }, onload: function(frm) { frm.set_query("delivery_address_name", () => { - let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`; return frm.events.address_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to], frm.doc.delivery_to_type === 'Company' ? 1 : 0); }); frm.set_query("pickup_address_name", () => { - let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`; return frm.events.address_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from], frm.doc.pickup_from_type === 'Company' ? 1 : 0); }); frm.set_query("delivery_contact_name", () => { - let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`; return frm.events.contact_query(frm, frm.doc.delivery_to_type, frm.doc[delivery_to]); }); frm.set_query("pickup_contact_name", () => { - let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`; return frm.events.contact_query(frm, frm.doc.pickup_from_type, frm.doc[pickup_from]); }); frm.set_query("delivery_note", "shipment_delivery_note", function() { @@ -57,16 +57,16 @@ frappe.ui.form.on('Shipment', { } }); }, - refresh: function(frm) { + refresh: function() { $('div[data-fieldname=pickup_address] > div > .clearfix').hide(); $('div[data-fieldname=pickup_contact] > div > .clearfix').hide(); $('div[data-fieldname=delivery_address] > div > .clearfix').hide(); $('div[data-fieldname=delivery_contact] > div > .clearfix').hide(); }, before_save: function(frm) { - let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}` + let delivery_to = `delivery_${frappe.model.scrub(frm.doc.delivery_to_type)}`; frm.set_value("delivery_to", frm.doc[delivery_to]); - let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}` + let pickup_from = `pickup_${frappe.model.scrub(frm.doc.pickup_from_type)}`; frm.set_value("pickup", frm.doc[pickup_from]); }, set_pickup_company_address: function(frm) { @@ -90,8 +90,7 @@ frappe.ui.form.on('Shipment', { frm.set_value("pickup_company", frappe.defaults.get_default('company')); frm.set_value("pickup_customer", ''); frm.set_value("pickup_supplier", ''); - } - else { + } else { frm.trigger('clear_pickup_fields'); } if (frm.doc.pickup_from_type == 'Customer') { @@ -108,8 +107,7 @@ frappe.ui.form.on('Shipment', { frm.set_value("delivery_company", frappe.defaults.get_default('company')); frm.set_value("delivery_customer", ''); frm.set_value("delivery_supplier", ''); - } - else { + } else { frm.trigger('clear_delivery_fields'); } if (frm.doc.delivery_to_type == 'Customer') { @@ -120,24 +118,21 @@ frappe.ui.form.on('Shipment', { frm.set_value("delivery_customer", ''); frm.set_value("delivery_company", ''); frm.toggle_display("shipment_delivery_note", false); - } - else { + } else { frm.toggle_display("shipment_delivery_note", true); } }, delivery_address_name: function(frm) { if (frm.doc.delivery_to_type == 'Company') { erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', true); - } - else { + } else { erpnext.utils.get_address_display(frm, 'delivery_address_name', 'delivery_address', false); } }, pickup_address_name: function(frm) { if (frm.doc.pickup_from_type == 'Company') { erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', true); - } - else { + } else { erpnext.utils.get_address_display(frm, 'pickup_address_name', 'pickup_address', false); } }, @@ -146,18 +141,16 @@ frappe.ui.form.on('Shipment', { method: "frappe.contacts.doctype.contact.contact.get_contact_details", args: { contact: contact_name }, callback: function(r) { - if(r.message) { + if (r.message) { if (!(r.message.contact_email && (r.message.contact_phone || r.message.contact_mobile))) { if (contact_type == 'Delivery') { frm.set_value('delivery_contact_name', ''); frm.set_value('delivery_contact', ''); - } - else { + } else { frm.set_value('pickup_contact_name', ''); frm.set_value('pickup_contact', ''); } - frappe.throw(__(`Email or Phone/Mobile of the Contact are mandatory to continue.
- Please set Email/Phone for the contact ${contact_name}`)); + frappe.throw(__("Email or Phone/Mobile of the Contact are mandatory to continue.") + "
" + __("Please set Email/Phone for the contact") + ` ${contact_name}`); } let contact_display = r.message.contact_display; if (r.message.contact_email) { @@ -169,13 +162,12 @@ frappe.ui.form.on('Shipment', { if (r.message.contact_mobile && !r.message.contact_phone) { contact_display += '
' + r.message.contact_mobile; } - if (contact_type == 'Delivery'){ + if (contact_type == 'Delivery') { frm.set_value('delivery_contact', contact_display); if (r.message.contact_email) { frm.set_value('delivery_contact_email', r.message.contact_email); } - } - else { + } else { frm.set_value('pickup_contact', contact_display); if (r.message.contact_email) { frm.set_value('pickup_contact_email', r.message.contact_email); @@ -246,13 +238,11 @@ frappe.ui.form.on('Shipment', { if (delivery_type == 'Delivery') { frm.set_value('delivery_company', ''); frm.set_value('delivery_contact', ''); - } - else { + } else { frm.set_value('pickup_company', ''); frm.set_value('pickup_contact', ''); } - frappe.throw(__(`Last Name, Email or Phone/Mobile of the user are mandatory to continue.
- Please first set Last Name, Email and Phone for the user ${frappe.session.user}`)); + frappe.throw(__("Last Name, Email or Phone/Mobile of the user are mandatory to continue.") + "
" + __("Please first set Last Name, Email and Phone for the user") + ` ${frappe.session.user}`); } let contact_display = r.full_name; if (r.email) { @@ -269,8 +259,7 @@ frappe.ui.form.on('Shipment', { if (r.email) { frm.set_value('delivery_contact_email', r.email); } - } - else { + } else { frm.set_value('pickup_contact', contact_display); if (r.email) { frm.set_value('pickup_contact_email', r.email); @@ -294,27 +283,27 @@ frappe.ui.form.on('Shipment', { delivery_customer: function(frm) { frm.trigger('clear_delivery_fields'); if (frm.doc.delivery_customer) { - frm.events.set_address_name(frm,'Customer',frm.doc.delivery_customer, 'Delivery'); - frm.events.set_contact_name(frm,'Customer',frm.doc.delivery_customer, 'Delivery'); + frm.events.set_address_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery'); + frm.events.set_contact_name(frm, 'Customer', frm.doc.delivery_customer, 'Delivery'); } }, delivery_supplier: function(frm) { frm.trigger('clear_delivery_fields'); if (frm.doc.delivery_supplier) { - frm.events.set_address_name(frm,'Supplier',frm.doc.delivery_supplier, 'Delivery'); - frm.events.set_contact_name(frm,'Supplier',frm.doc.delivery_supplier, 'Delivery'); + frm.events.set_address_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery'); + frm.events.set_contact_name(frm, 'Supplier', frm.doc.delivery_supplier, 'Delivery'); } }, pickup_customer: function(frm) { if (frm.doc.pickup_customer) { - frm.events.set_address_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); - frm.events.set_contact_name(frm,'Customer',frm.doc.pickup_customer, 'Pickup'); + frm.events.set_address_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup'); + frm.events.set_contact_name(frm, 'Customer', frm.doc.pickup_customer, 'Pickup'); } }, pickup_supplier: function(frm) { if (frm.doc.pickup_supplier) { - frm.events.set_address_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); - frm.events.set_contact_name(frm,'Supplier',frm.doc.pickup_supplier, 'Pickup'); + frm.events.set_address_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup'); + frm.events.set_contact_name(frm, 'Supplier', frm.doc.pickup_supplier, 'Pickup'); } }, set_address_name: function(frm, ref_doctype, ref_docname, delivery_type) { @@ -325,11 +314,10 @@ frappe.ui.form.on('Shipment', { docname: ref_docname }, callback: function(r) { - if(r.message) { + if (r.message) { if (delivery_type == 'Delivery') { frm.set_value('delivery_address_name', r.message); - } - else { + } else { frm.set_value('pickup_address_name', r.message); } } @@ -344,11 +332,10 @@ frappe.ui.form.on('Shipment', { docname: ref_docname }, callback: function(r) { - if(r.message) { + if (r.message) { if (delivery_type == 'Delivery') { frm.set_value('delivery_contact_name', r.message); - } - else { + } else { frm.set_value('pickup_contact_name', r.message); } } @@ -397,8 +384,7 @@ frappe.ui.form.on('Shipment', { let current_min = new Date().toLocaleString('en-US', {minute: 'numeric'}); if (current_min < 30) { current_min = '30'; - } - else { + } else { current_min = '00'; current_hour = Number(current_hour)+1; } @@ -413,19 +399,19 @@ frappe.ui.form.on('Shipment', { }, clear_pickup_fields: function(frm) { let fields = ["pickup_address_name", "pickup_contact_name", "pickup_address", "pickup_contact", "pickup_contact_email", "pickup_contact_person"]; - for (let field of fields){ + for (let field of fields) { frm.set_value(field, ''); } }, clear_delivery_fields: function(frm) { let fields = ["delivery_address_name", "delivery_contact_name", "delivery_address", "delivery_contact", "delivery_contact_email"]; - for (let field of fields){ + for (let field of fields) { frm.set_value(field, ''); } }, remove_email_row: function(frm, table, fieldname) { $.each(frm.doc[table] || [], function(i, detail) { - if(detail.email === fieldname){ + if (detail.email === fieldname) { cur_frm.get_field(table).grid.grid_rows[i].remove(); } }); @@ -437,8 +423,8 @@ frappe.ui.form.on('Shipment Delivery Note', { let row = locals[cdt][cdn]; if (row.delivery_note) { let row_index = row.idx - 1; - if(validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) { - frappe.throw(__(`You have entered a duplicate Delivery Note on Row ${row.idx}. Please rectify and try again.`)); + if (validate_duplicate(frm, 'shipment_delivery_note', row.delivery_note, row_index)) { + frappe.throw(__("You have entered a duplicate Delivery Note on Row") + ` ${row.idx}. ` + __("Please rectify and try again.")); } } }, @@ -452,7 +438,7 @@ frappe.ui.form.on('Shipment Delivery Note', { }, }); -var validate_duplicate = function(frm, table, fieldname, index){ +var validate_duplicate = function(frm, table, fieldname, index) { return ( table === 'shipment_delivery_note' ? frm.doc[table].some((detail, i) => detail.delivery_note === fieldname && !(index === i)) diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 4e16f95533..508af39cd5 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -4,7 +4,6 @@ from __future__ import unicode_literals import frappe -import json from frappe import _ from frappe.utils import flt from frappe.model.document import Document diff --git a/erpnext/stock/doctype/shipment/shipment_list.js b/erpnext/stock/doctype/shipment/shipment_list.js index 57e92099cb..52b052c81f 100644 --- a/erpnext/stock/doctype/shipment/shipment_list.js +++ b/erpnext/stock/doctype/shipment/shipment_list.js @@ -1,7 +1,7 @@ frappe.listview_settings['Shipment'] = { add_fields: ["status"], get_indicator: function(doc) { - if(doc.status=='Booked') { + if (doc.status=='Booked') { return [__("Booked"), "green"]; } } diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index f61b87fd41..e238e878db 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -2,7 +2,6 @@ # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals -import json from datetime import date, timedelta import frappe @@ -222,10 +221,7 @@ def create_material_receipt(item, company): } ) stock.insert() - try: - stock.submit() - except: - frappe.throw('An error occurred.') + stock.submit() def create_shipment_item(item_name, company_name): @@ -241,8 +237,5 @@ def create_shipment_item(item_name, company_name): "default_warehouse": 'Stores - SC' } ) - try: - item.insert() - except: - frappe.throw('An error occurred.') + item.insert() return item From 8e68f128c17e5f14096edf6fbc0a12f951d63393 Mon Sep 17 00:00:00 2001 From: jbienesdev Date: Wed, 2 Dec 2020 07:54:25 +0000 Subject: [PATCH 7/7] fix: travis build error - Removed shipment notification and subscription files - Minor changes on shipment field configuration - Add shipment to desk --- erpnext/stock/desk_page/stock/stock.json | 4 +- erpnext/stock/doctype/shipment/shipment.json | 42 +++++++++++++++---- erpnext/stock/doctype/shipment/shipment.py | 7 ++++ .../stock/doctype/shipment/test_shipment.py | 7 ++-- .../shipment_delivery_note.json | 3 +- .../__init__.py | 0 .../shipment_notification_subscription.json | 40 ------------------ .../shipment_notification_subscription.py | 10 ----- .../__init__.py | 0 .../shipment_status_update_subscription.json | 40 ------------------ .../shipment_status_update_subscription.py | 10 ----- 11 files changed, 46 insertions(+), 117 deletions(-) delete mode 100644 erpnext/stock/doctype/shipment_notification_subscription/__init__.py delete mode 100644 erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json delete mode 100644 erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py delete mode 100644 erpnext/stock/doctype/shipment_status_update_subscription/__init__.py delete mode 100644 erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json delete mode 100644 erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py diff --git a/erpnext/stock/desk_page/stock/stock.json b/erpnext/stock/desk_page/stock/stock.json index 390fcd91e3..9068e338c3 100644 --- a/erpnext/stock/desk_page/stock/stock.json +++ b/erpnext/stock/desk_page/stock/stock.json @@ -8,7 +8,7 @@ { "hidden": 0, "label": "Stock Transactions", - "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]" + "links": "[\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Material Request\",\n \"name\": \"Material Request\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Stock Entry\",\n \"name\": \"Stock Entry\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Customer\"\n ],\n \"label\": \"Delivery Note\",\n \"name\": \"Delivery Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\",\n \"Supplier\"\n ],\n \"label\": \"Purchase Receipt\",\n \"name\": \"Purchase Receipt\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"Item\"\n ],\n \"label\": \"Pick List\",\n \"name\": \"Pick List\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Shipment\",\n \"name\": \"Shipment\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Delivery Trip\",\n \"name\": \"Delivery Trip\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, @@ -58,7 +58,7 @@ "idx": 0, "is_standard": 1, "label": "Stock", - "modified": "2020-10-07 18:40:17.130207", + "modified": "2020-12-02 15:47:41.532942", "modified_by": "Administrator", "module": "Stock", "name": "Stock", diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json index 1ae7862bc9..37a9cc6c02 100644 --- a/erpnext/stock/doctype/shipment/shipment.json +++ b/erpnext/stock/doctype/shipment/shipment.json @@ -234,6 +234,7 @@ "options": "Shipment Parcel Template" }, { + "depends_on": "eval:doc.docstatus !== 1\n", "fieldname": "add_template", "fieldtype": "Button", "label": "Add Template" @@ -262,6 +263,7 @@ "reqd": 1 }, { + "allow_on_submit": 1, "fieldname": "pickup_date", "fieldtype": "Date", "in_list_view": 1, @@ -269,12 +271,14 @@ "reqd": 1 }, { + "allow_on_submit": 1, "default": "09:00", "fieldname": "pickup_from", "fieldtype": "Time", "label": "Pickup from" }, { + "allow_on_submit": 1, "default": "17:00", "fieldname": "pickup_to", "fieldtype": "Time", @@ -316,57 +320,77 @@ { "fieldname": "service_provider", "fieldtype": "Data", - "label": "Service Provider" + "label": "Service Provider", + "no_copy": 1, + "print_hide": 1 }, { "fieldname": "shipment_id", "fieldtype": "Data", - "label": "Shipment ID" + "label": "Shipment ID", + "no_copy": 1, + "print_hide": 1 }, { "fieldname": "shipment_amount", "fieldtype": "Currency", "label": "Shipment Amount", - "precision": "2" + "no_copy": 1, + "precision": "2", + "print_hide": 1 }, { "fieldname": "status", "fieldtype": "Select", "label": "Status", - "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted" + "no_copy": 1, + "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted", + "print_hide": 1 }, { "fieldname": "tracking_url", "fieldtype": "Small Text", "hidden": 1, "label": "Tracking URL", + "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { "fieldname": "carrier", "fieldtype": "Data", - "label": "Carrier" + "label": "Carrier", + "no_copy": 1, + "print_hide": 1 }, { "fieldname": "carrier_service", "fieldtype": "Data", - "label": "Carrier Service" + "label": "Carrier Service", + "no_copy": 1, + "print_hide": 1 }, { "fieldname": "awb_number", "fieldtype": "Data", - "label": "AWB Number" + "label": "AWB Number", + "no_copy": 1, + "print_hide": 1 }, { "fieldname": "tracking_status", "fieldtype": "Select", "label": "Tracking Status", - "options": "\nIn Progress\nDelivered\nReturned\nLost" + "no_copy": 1, + "options": "\nIn Progress\nDelivered\nReturned\nLost", + "print_hide": 1 }, { "fieldname": "tracking_status_info", "fieldtype": "Data", "label": "Tracking Status Info", + "no_copy": 1, + "print_hide": 1, "read_only": 1 }, { @@ -406,7 +430,7 @@ ], "is_submittable": 1, "links": [], - "modified": "2020-11-23 17:00:51.600965", + "modified": "2020-12-02 15:43:44.607039", "modified_by": "Administrator", "module": "Stock", "name": "Shipment", diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py index 508af39cd5..de0c243b05 100644 --- a/erpnext/stock/doctype/shipment/shipment.py +++ b/erpnext/stock/doctype/shipment/shipment.py @@ -13,6 +13,7 @@ from frappe.contacts.doctype.contact.contact import get_default_contact class Shipment(Document): def validate(self): self.validate_weight() + self.set_value_of_goods() if self.docstatus == 0: self.status = 'Draft' @@ -31,6 +32,12 @@ class Shipment(Document): if flt(parcel.weight) <= 0: frappe.throw(_('Parcel weight cannot be 0')) + def set_value_of_goods(self): + value_of_goods = 0 + for entry in self.get("shipment_delivery_note"): + value_of_goods += flt(entry.get("grand_total")) + self.value_of_goods = value_of_goods if value_of_goods else self.value_of_goods + @frappe.whitelist() def get_address_name(ref_doctype, docname): # Return address name diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py index e238e878db..e1fa207a21 100644 --- a/erpnext/stock/doctype/shipment/test_shipment.py +++ b/erpnext/stock/doctype/shipment/test_shipment.py @@ -16,7 +16,6 @@ class TestShipment(unittest.TestCase): shipment.submit() second_shipment = make_shipment(delivery_note.name) self.assertEqual(second_shipment.value_of_goods, delivery_note.grand_total) - self.assertEqual(second_shipment.grand_total, delivery_note.grand_total) self.assertEqual(len(second_shipment.shipment_delivery_note), 1) self.assertEqual(second_shipment.shipment_delivery_note[0].delivery_note, delivery_note.name) @@ -49,7 +48,7 @@ def create_test_delivery_note(): return delivery_note -def create_test_shipment(delivery_notes=[]): +def create_test_shipment(delivery_notes = None): company = get_shipment_company() company_address = get_shipment_company_address(company.name) customer = get_shipment_customer() @@ -74,7 +73,7 @@ def create_test_shipment(delivery_notes=[]): shipment.pickup_to = '17:00' shipment.description_of_content = 'unit test entry' for delivery_note in delivery_notes: - shipment.append('shipment_delivery_notes', + shipment.append('shipment_delivery_note', { "delivery_note": delivery_note.name } @@ -229,7 +228,7 @@ def create_shipment_item(item_name, company_name): item.item_name = item_name item.item_code = item_name item.item_group = 'All Item Groups' - item.opening_stock = 'Nos' + item.stock_uom = 'Nos' item.standard_rate = 50 item.append('item_defaults', { diff --git a/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json index 9651e3f945..8625913718 100644 --- a/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json +++ b/erpnext/stock/doctype/shipment_delivery_note/shipment_delivery_note.json @@ -18,7 +18,6 @@ "reqd": 1 }, { - "fetch_from": "delivery_note.grand_total", "fieldname": "grand_total", "fieldtype": "Currency", "in_list_view": 1, @@ -28,7 +27,7 @@ ], "istable": 1, "links": [], - "modified": "2020-07-09 12:55:01.134270", + "modified": "2020-12-02 15:44:34.028703", "modified_by": "Administrator", "module": "Stock", "name": "Shipment Delivery Note", diff --git a/erpnext/stock/doctype/shipment_notification_subscription/__init__.py b/erpnext/stock/doctype/shipment_notification_subscription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json deleted file mode 100644 index d927d9902e..0000000000 --- a/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "actions": [], - "creation": "2020-07-09 12:49:09.185552", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "email", - "unsubscribed" - ], - "fields": [ - { - "fieldname": "email", - "fieldtype": "Data", - "in_list_view": 1, - "label": "email", - "reqd": 1, - "unique": 1 - }, - { - "default": "0", - "fieldname": "unsubscribed", - "fieldtype": "Check", - "in_list_view": 1, - "label": "unsubscribed" - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-09 12:55:14.217387", - "modified_by": "Administrator", - "module": "Stock", - "name": "Shipment Notification Subscription", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py b/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py deleted file mode 100644 index c816e4343c..0000000000 --- a/erpnext/stock/doctype/shipment_notification_subscription/shipment_notification_subscription.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 frappe -from frappe.model.document import Document - -class ShipmentNotificationSubscription(Document): - pass diff --git a/erpnext/stock/doctype/shipment_status_update_subscription/__init__.py b/erpnext/stock/doctype/shipment_status_update_subscription/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json deleted file mode 100644 index a7fe4a4a0a..0000000000 --- a/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "actions": [], - "creation": "2020-07-09 12:51:10.656612", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "email", - "unsubscribed" - ], - "fields": [ - { - "fieldname": "email", - "fieldtype": "Data", - "in_list_view": 1, - "label": "email", - "reqd": 1, - "unique": 1 - }, - { - "default": "0", - "fieldname": "unsubscribed", - "fieldtype": "Check", - "in_list_view": 1, - "label": "unsubscribed" - } - ], - "istable": 1, - "links": [], - "modified": "2020-07-09 12:55:27.615463", - "modified_by": "Administrator", - "module": "Stock", - "name": "Shipment Status Update Subscription", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py b/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py deleted file mode 100644 index 1b006d7efc..0000000000 --- a/erpnext/stock/doctype/shipment_status_update_subscription/shipment_status_update_subscription.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- 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 frappe -from frappe.model.document import Document - -class ShipmentStatusUpdateSubscription(Document): - pass