Merge pull request #29789 from nextchamp-saqib/fix-opening-inv-tool

fix: disable rounded total in opening invoice creation tool
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
deleted file mode 100644
index 29bc36f..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ /dev/null
@@ -1,524 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies and contributors
-# For license information, please see license.txt
-
-
-import csv
-import math
-import time
-from io import StringIO
-
-import dateutil
-import frappe
-from frappe import _
-
-import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
-
-
-#Get and Create Products
-def get_products_details():
-	products = get_products_instance()
-	reports = get_reports_instance()
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	market_place_list = return_as_list(mws_settings.market_place_id)
-
-	for marketplace in market_place_list:
-		report_id = request_and_fetch_report_id("_GET_FLAT_FILE_OPEN_LISTINGS_DATA_", None, None, market_place_list)
-
-		if report_id:
-			listings_response = reports.get_report(report_id=report_id)
-
-			#Get ASIN Codes
-			string_io = StringIO(frappe.safe_decode(listings_response.original))
-			csv_rows = list(csv.reader(string_io, delimiter='\t'))
-			asin_list = list(set([row[1] for row in csv_rows[1:]]))
-			#break into chunks of 10
-			asin_chunked_list = list(chunks(asin_list, 10))
-
-			#Map ASIN Codes to SKUs
-			sku_asin = [{"asin":row[1],"sku":row[0]} for row in csv_rows[1:]]
-
-			#Fetch Products List from ASIN
-			for asin_list in asin_chunked_list:
-				products_response = call_mws_method(products.get_matching_product,marketplaceid=marketplace,
-					asins=asin_list)
-
-				matching_products_list = products_response.parsed
-				for product in matching_products_list:
-					skus = [row["sku"] for row in sku_asin if row["asin"]==product.ASIN]
-					for sku in skus:
-						create_item_code(product, sku)
-
-def get_products_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	products = mws.Products(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region = mws_settings.region,
-			domain = mws_settings.domain
-			)
-
-	return products
-
-def get_reports_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	reports = mws.Reports(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region = mws_settings.region,
-			domain = mws_settings.domain
-	)
-
-	return reports
-
-#returns list as expected by amazon API
-def return_as_list(input_value):
-	if isinstance(input_value, list):
-		return input_value
-	else:
-		return [input_value]
-
-#function to chunk product data
-def chunks(l, n):
-	for i in range(0, len(l), n):
-		yield l[i:i+n]
-
-def request_and_fetch_report_id(report_type, start_date=None, end_date=None, marketplaceids=None):
-	reports = get_reports_instance()
-	report_response = reports.request_report(report_type=report_type,
-			start_date=start_date,
-			end_date=end_date,
-			marketplaceids=marketplaceids)
-
-	report_request_id = report_response.parsed["ReportRequestInfo"]["ReportRequestId"]["value"]
-	generated_report_id = None
-	#poll to get generated report
-	for x in range(1,10):
-		report_request_list_response = reports.get_report_request_list(requestids=[report_request_id])
-		report_status = report_request_list_response.parsed["ReportRequestInfo"]["ReportProcessingStatus"]["value"]
-
-		if report_status == "_SUBMITTED_" or report_status == "_IN_PROGRESS_":
-			#add time delay to wait for amazon to generate report
-			time.sleep(15)
-			continue
-		elif report_status == "_CANCELLED_":
-			break
-		elif report_status == "_DONE_NO_DATA_":
-			break
-		elif report_status == "_DONE_":
-			generated_report_id =  report_request_list_response.parsed["ReportRequestInfo"]["GeneratedReportId"]["value"]
-			break
-	return generated_report_id
-
-def call_mws_method(mws_method, *args, **kwargs):
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	max_retries = mws_settings.max_retry_limit
-
-	for x in range(0, max_retries):
-		try:
-			response = mws_method(*args, **kwargs)
-			return response
-		except Exception as e:
-			delay = math.pow(4, x) * 125
-			frappe.log_error(message=e, title=f'Method "{mws_method.__name__}" failed')
-			time.sleep(delay)
-			continue
-
-	mws_settings.enable_sync = 0
-	mws_settings.save()
-
-	frappe.throw(_("Sync has been temporarily disabled because maximum retries have been exceeded"))
-
-def create_item_code(amazon_item_json, sku):
-	if frappe.db.get_value("Item", sku):
-		return
-
-	item = frappe.new_doc("Item")
-
-	new_manufacturer = create_manufacturer(amazon_item_json)
-	new_brand = create_brand(amazon_item_json)
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	item.item_code = sku
-	item.amazon_item_code = amazon_item_json.ASIN
-	item.item_group = mws_settings.item_group
-	item.description = amazon_item_json.Product.AttributeSets.ItemAttributes.Title
-	item.brand = new_brand
-	item.manufacturer = new_manufacturer
-
-	item.image = amazon_item_json.Product.AttributeSets.ItemAttributes.SmallImage.URL
-
-	temp_item_group = amazon_item_json.Product.AttributeSets.ItemAttributes.ProductGroup
-
-	item_group = frappe.db.get_value("Item Group",filters={"item_group_name": temp_item_group})
-
-	if not item_group:
-		igroup = frappe.new_doc("Item Group")
-		igroup.item_group_name = temp_item_group
-		igroup.parent_item_group =  mws_settings.item_group
-		igroup.insert()
-
-	item.append("item_defaults", {'company':mws_settings.company})
-
-	item.insert(ignore_permissions=True)
-	create_item_price(amazon_item_json, item.item_code)
-
-	return item.name
-
-def create_manufacturer(amazon_item_json):
-	if not amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer:
-		return None
-
-	existing_manufacturer = frappe.db.get_value("Manufacturer",
-		filters={"short_name":amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer})
-
-	if not existing_manufacturer:
-		manufacturer = frappe.new_doc("Manufacturer")
-		manufacturer.short_name = amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer
-		manufacturer.insert()
-		return manufacturer.short_name
-	else:
-		return existing_manufacturer
-
-def create_brand(amazon_item_json):
-	if not amazon_item_json.Product.AttributeSets.ItemAttributes.Brand:
-		return None
-
-	existing_brand = frappe.db.get_value("Brand",
-		filters={"brand":amazon_item_json.Product.AttributeSets.ItemAttributes.Brand})
-	if not existing_brand:
-		brand = frappe.new_doc("Brand")
-		brand.brand = amazon_item_json.Product.AttributeSets.ItemAttributes.Brand
-		brand.insert()
-		return brand.brand
-	else:
-		return existing_brand
-
-def create_item_price(amazon_item_json, item_code):
-	item_price = frappe.new_doc("Item Price")
-	item_price.price_list = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "price_list")
-	if not("ListPrice" in amazon_item_json.Product.AttributeSets.ItemAttributes):
-		item_price.price_list_rate = 0
-	else:
-		item_price.price_list_rate = amazon_item_json.Product.AttributeSets.ItemAttributes.ListPrice.Amount
-
-	item_price.item_code = item_code
-	item_price.insert()
-
-#Get and create Orders
-def get_orders(after_date):
-	try:
-		orders = get_orders_instance()
-		statuses = ["PartiallyShipped", "Unshipped", "Shipped", "Canceled"]
-		mws_settings = frappe.get_doc("Amazon MWS Settings")
-		market_place_list = return_as_list(mws_settings.market_place_id)
-
-		orders_response = call_mws_method(orders.list_orders, marketplaceids=market_place_list,
-			fulfillment_channels=["MFN", "AFN"],
-			lastupdatedafter=after_date,
-			orderstatus=statuses,
-			max_results='50')
-
-		while True:
-			orders_list = []
-
-			if "Order" in orders_response.parsed.Orders:
-				orders_list = return_as_list(orders_response.parsed.Orders.Order)
-
-			if len(orders_list) == 0:
-				break
-
-			for order in orders_list:
-				create_sales_order(order, after_date)
-
-			if not "NextToken" in orders_response.parsed:
-				break
-
-			next_token = orders_response.parsed.NextToken
-			orders_response = call_mws_method(orders.list_orders_by_next_token, next_token)
-
-	except Exception as e:
-		frappe.log_error(title="get_orders", message=e)
-
-def get_orders_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	orders = mws.Orders(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region= mws_settings.region,
-			domain= mws_settings.domain,
-			version="2013-09-01"
-		)
-
-	return orders
-
-def create_sales_order(order_json,after_date):
-	customer_name = create_customer(order_json)
-	create_address(order_json, customer_name)
-
-	market_place_order_id = order_json.AmazonOrderId
-
-	so = frappe.db.get_value("Sales Order",
-			filters={"amazon_order_id": market_place_order_id},
-			fieldname="name")
-
-	taxes_and_charges = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "taxes_charges")
-
-	if so:
-		return
-
-	if not so:
-		items = get_order_items(market_place_order_id)
-		delivery_date = dateutil.parser.parse(order_json.LatestShipDate).strftime("%Y-%m-%d")
-		transaction_date = dateutil.parser.parse(order_json.PurchaseDate).strftime("%Y-%m-%d")
-
-		so = frappe.get_doc({
-				"doctype": "Sales Order",
-				"naming_series": "SO-",
-				"amazon_order_id": market_place_order_id,
-				"marketplace_id": order_json.MarketplaceId,
-				"customer": customer_name,
-				"delivery_date": delivery_date,
-				"transaction_date": transaction_date,
-				"items": items,
-				"company": frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "company")
-			})
-
-		try:
-			if taxes_and_charges:
-				charges_and_fees = get_charges_and_fees(market_place_order_id)
-				for charge in charges_and_fees.get("charges"):
-					so.append('taxes', charge)
-
-				for fee in charges_and_fees.get("fees"):
-					so.append('taxes', fee)
-
-			so.insert(ignore_permissions=True)
-			so.submit()
-
-		except Exception as e:
-			import traceback
-			frappe.log_error(message=traceback.format_exc(), title="Create Sales Order")
-
-def create_customer(order_json):
-	order_customer_name = ""
-
-	if not("BuyerName" in order_json):
-		order_customer_name = "Buyer - " + order_json.AmazonOrderId
-	else:
-		order_customer_name = order_json.BuyerName
-
-	existing_customer_name = frappe.db.get_value("Customer",
-			filters={"name": order_customer_name}, fieldname="name")
-
-	if existing_customer_name:
-		filters = [
-				["Dynamic Link", "link_doctype", "=", "Customer"],
-				["Dynamic Link", "link_name", "=", existing_customer_name],
-				["Dynamic Link", "parenttype", "=", "Contact"]
-			]
-
-		existing_contacts = frappe.get_list("Contact", filters)
-
-		if existing_contacts:
-			pass
-		else:
-			new_contact = frappe.new_doc("Contact")
-			new_contact.first_name = order_customer_name
-			new_contact.append('links', {
-				"link_doctype": "Customer",
-				"link_name": existing_customer_name
-			})
-			new_contact.insert()
-
-		return existing_customer_name
-	else:
-		mws_customer_settings = frappe.get_doc("Amazon MWS Settings")
-		new_customer = frappe.new_doc("Customer")
-		new_customer.customer_name = order_customer_name
-		new_customer.customer_group = mws_customer_settings.customer_group
-		new_customer.territory = mws_customer_settings.territory
-		new_customer.customer_type = mws_customer_settings.customer_type
-		new_customer.save()
-
-		new_contact = frappe.new_doc("Contact")
-		new_contact.first_name = order_customer_name
-		new_contact.append('links', {
-			"link_doctype": "Customer",
-			"link_name": new_customer.name
-		})
-
-		new_contact.insert()
-
-		return new_customer.name
-
-def create_address(amazon_order_item_json, customer_name):
-
-	filters = [
-			["Dynamic Link", "link_doctype", "=", "Customer"],
-			["Dynamic Link", "link_name", "=", customer_name],
-			["Dynamic Link", "parenttype", "=", "Address"]
-		]
-
-	existing_address = frappe.get_list("Address", filters)
-
-	if not("ShippingAddress" in amazon_order_item_json):
-		return None
-	else:
-		make_address = frappe.new_doc("Address")
-
-		if "AddressLine1" in amazon_order_item_json.ShippingAddress:
-			make_address.address_line1 = amazon_order_item_json.ShippingAddress.AddressLine1
-		else:
-			make_address.address_line1 = "Not Provided"
-
-		if "City" in amazon_order_item_json.ShippingAddress:
-			make_address.city = amazon_order_item_json.ShippingAddress.City
-		else:
-			make_address.city = "Not Provided"
-
-		if "StateOrRegion" in amazon_order_item_json.ShippingAddress:
-			make_address.state = amazon_order_item_json.ShippingAddress.StateOrRegion
-
-		if "PostalCode" in amazon_order_item_json.ShippingAddress:
-			make_address.pincode = amazon_order_item_json.ShippingAddress.PostalCode
-
-		for address in existing_address:
-			address_doc = frappe.get_doc("Address", address["name"])
-			if (address_doc.address_line1 == make_address.address_line1 and
-				address_doc.pincode == make_address.pincode):
-				return address
-
-		make_address.append("links", {
-			"link_doctype": "Customer",
-			"link_name": customer_name
-		})
-		make_address.address_type = "Shipping"
-		make_address.insert()
-
-def get_order_items(market_place_order_id):
-	mws_orders = get_orders_instance()
-
-	order_items_response = call_mws_method(mws_orders.list_order_items, amazon_order_id=market_place_order_id)
-	final_order_items = []
-
-	order_items_list = return_as_list(order_items_response.parsed.OrderItems.OrderItem)
-
-	warehouse = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "warehouse")
-
-	while True:
-		for order_item in order_items_list:
-
-			if not "ItemPrice" in order_item:
-				price = 0
-			else:
-				price = order_item.ItemPrice.Amount
-
-			final_order_items.append({
-				"item_code": get_item_code(order_item),
-				"item_name": order_item.SellerSKU,
-				"description": order_item.Title,
-				"rate": price,
-				"qty": order_item.QuantityOrdered,
-				"stock_uom": "Nos",
-				"warehouse": warehouse,
-				"conversion_factor": "1.0"
-			})
-
-		if not "NextToken" in order_items_response.parsed:
-			break
-
-		next_token = order_items_response.parsed.NextToken
-
-		order_items_response = call_mws_method(mws_orders.list_order_items_by_next_token, next_token)
-		order_items_list = return_as_list(order_items_response.parsed.OrderItems.OrderItem)
-
-	return final_order_items
-
-def get_item_code(order_item):
-	sku = order_item.SellerSKU
-	item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code")
-	if item_code:
-		return item_code
-
-def get_charges_and_fees(market_place_order_id):
-	finances = get_finances_instance()
-
-	charges_fees = {"charges":[], "fees":[]}
-
-	response = call_mws_method(finances.list_financial_events, amazon_order_id=market_place_order_id)
-
-	shipment_event_list = return_as_list(response.parsed.FinancialEvents.ShipmentEventList)
-
-	for shipment_event in shipment_event_list:
-		if shipment_event:
-			shipment_item_list = return_as_list(shipment_event.ShipmentEvent.ShipmentItemList.ShipmentItem)
-
-			for shipment_item in shipment_item_list:
-				charges, fees = [], []
-
-				if 'ItemChargeList' in shipment_item.keys():
-					charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
-
-				if 'ItemFeeList' in shipment_item.keys():
-					fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
-
-				for charge in charges:
-					if(charge.ChargeType != "Principal") and float(charge.ChargeAmount.CurrencyAmount) != 0:
-						charge_account = get_account(charge.ChargeType)
-						charges_fees.get("charges").append({
-							"charge_type":"Actual",
-							"account_head": charge_account,
-							"tax_amount": charge.ChargeAmount.CurrencyAmount,
-							"description": charge.ChargeType + " for " + shipment_item.SellerSKU
-							})
-
-				for fee in fees:
-					if float(fee.FeeAmount.CurrencyAmount) != 0:
-						fee_account = get_account(fee.FeeType)
-						charges_fees.get("fees").append({
-							"charge_type":"Actual",
-							"account_head": fee_account,
-							"tax_amount": fee.FeeAmount.CurrencyAmount,
-							"description": fee.FeeType + " for " + shipment_item.SellerSKU
-							})
-
-	return charges_fees
-
-def get_finances_instance():
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	finances = mws.Finances(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region= mws_settings.region,
-			domain= mws_settings.domain,
-			version="2015-05-01"
-		)
-
-	return finances
-
-def get_account(name):
-	existing_account = frappe.db.get_value("Account", {"account_name": "Amazon {0}".format(name)})
-	account_name = existing_account
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	if not existing_account:
-		try:
-			new_account = frappe.new_doc("Account")
-			new_account.account_name = "Amazon {0}".format(name)
-			new_account.company = mws_settings.company
-			new_account.parent_account = mws_settings.market_place_account_group
-			new_account.insert(ignore_permissions=True)
-			account_name = new_account.name
-		except Exception as e:
-			frappe.log_error(message=e, title="Create Account")
-
-	return account_name
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
deleted file mode 100755
index 4caf137..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ /dev/null
@@ -1,651 +0,0 @@
-#!/usr/bin/env python
-#
-# Basic interface to Amazon MWS
-# Based on http://code.google.com/p/amazon-mws-python
-# Extended to include finances object
-
-import base64
-import hashlib
-import hmac
-import re
-from urllib.parse import quote
-
-from erpnext.erpnext_integrations.doctype.amazon_mws_settings import xml_utils
-
-try:
-	from xml.etree.ElementTree import ParseError as XMLError
-except ImportError:
-	from xml.parsers.expat import ExpatError as XMLError
-
-from time import gmtime, strftime
-
-from requests import request
-from requests.exceptions import HTTPError
-
-__all__ = [
-	'Feeds',
-	'Inventory',
-	'MWSError',
-	'Reports',
-	'Orders',
-	'Products',
-	'Recommendations',
-	'Sellers',
-	'Finances'
-]
-
-# See https://images-na.ssl-images-amazon.com/images/G/01/mwsportal/doc/en_US/bde/MWSDeveloperGuide._V357736853_.pdf page 8
-# for a list of the end points and marketplace IDs
-
-MARKETPLACES = {
-	"CA": "https://mws.amazonservices.ca", #A2EUQ1WTGCTBG2
-	"US": "https://mws.amazonservices.com", #ATVPDKIKX0DER",
-	"DE": "https://mws-eu.amazonservices.com", #A1PA6795UKMFR9
-	"ES": "https://mws-eu.amazonservices.com", #A1RKKUPIHCS9HS
-	"FR": "https://mws-eu.amazonservices.com", #A13V1IB3VIYZZH
-	"IN": "https://mws.amazonservices.in", #A21TJRUUN4KGV
-	"IT": "https://mws-eu.amazonservices.com", #APJ6JRA9NG5V4
-	"UK": "https://mws-eu.amazonservices.com", #A1F83G8C2ARO7P
-	"JP": "https://mws.amazonservices.jp", #A1VC38T7YXB528
-	"CN": "https://mws.amazonservices.com.cn", #AAHKV2X7AFYLW
-	"AE": "	https://mws.amazonservices.ae", #A2VIGQ35RCS4UG
-	"MX": "https://mws.amazonservices.com.mx", #A1AM78C64UM0Y8
-	"BR": "https://mws.amazonservices.com", #A2Q3Y263D00KWC
-}
-
-
-class MWSError(Exception):
-	"""
-		Main MWS Exception class
-	"""
-	# Allows quick access to the response object.
-	# Do not rely on this attribute, always check if its not None.
-	response = None
-
-def calc_md5(string):
-	"""Calculates the MD5 encryption for the given string
-	"""
-	md = hashlib.md5()
-	md.update(string)
-	return base64.encodebytes(md.digest()).decode().strip()
-
-
-
-def remove_empty(d):
-	"""
-		Helper function that removes all keys from a dictionary (d),
-	that have an empty value.
-	"""
-	for key in list(d):
-		if not d[key]:
-			del d[key]
-	return d
-
-def remove_namespace(xml):
-	xml = xml.decode('utf-8')
-	regex = re.compile(' xmlns(:ns2)?="[^"]+"|(ns2:)|(xml:)')
-	return regex.sub('', xml)
-
-class DictWrapper(object):
-	def __init__(self, xml, rootkey=None):
-		self.original = xml
-		self._rootkey = rootkey
-		self._mydict = xml_utils.xml2dict().fromstring(remove_namespace(xml))
-		self._response_dict = self._mydict.get(list(self._mydict)[0], self._mydict)
-
-	@property
-	def parsed(self):
-		if self._rootkey:
-			return self._response_dict.get(self._rootkey)
-		else:
-			return self._response_dict
-
-class DataWrapper(object):
-	"""
-		Text wrapper in charge of validating the hash sent by Amazon.
-	"""
-	def __init__(self, data, header):
-		self.original = data
-		if 'content-md5' in header:
-			hash_ = calc_md5(self.original)
-			if header['content-md5'] != hash_:
-				raise MWSError("Wrong Contentlength, maybe amazon error...")
-
-	@property
-	def parsed(self):
-		return self.original
-
-class MWS(object):
-	""" Base Amazon API class """
-
-	# This is used to post/get to the different uris used by amazon per api
-	# ie. /Orders/2011-01-01
-	# All subclasses must define their own URI only if needed
-	URI = "/"
-
-	# The API version varies in most amazon APIs
-	VERSION = "2009-01-01"
-
-	# There seem to be some xml namespace issues. therefore every api subclass
-	# is recommended to define its namespace, so that it can be referenced
-	# like so AmazonAPISubclass.NS.
-	# For more information see http://stackoverflow.com/a/8719461/389453
-	NS = ''
-
-	# Some APIs are available only to either a "Merchant" or "Seller"
-	# the type of account needs to be sent in every call to the amazon MWS.
-	# This constant defines the exact name of the parameter Amazon expects
-	# for the specific API being used.
-	# All subclasses need to define this if they require another account type
-	# like "Merchant" in which case you define it like so.
-	# ACCOUNT_TYPE = "Merchant"
-	# Which is the name of the parameter for that specific account type.
-	ACCOUNT_TYPE = "SellerId"
-
-	def __init__(self, access_key, secret_key, account_id, region='US', domain='', uri="", version=""):
-		self.access_key = access_key
-		self.secret_key = secret_key
-		self.account_id = account_id
-		self.version = version or self.VERSION
-		self.uri = uri or self.URI
-
-		if domain:
-			self.domain = domain
-		elif region in MARKETPLACES:
-			self.domain = MARKETPLACES[region]
-		else:
-			error_msg = "Incorrect region supplied ('%(region)s'). Must be one of the following: %(marketplaces)s" % {
-				"marketplaces" : ', '.join(MARKETPLACES.keys()),
-				"region" : region,
-			}
-			raise MWSError(error_msg)
-
-	def make_request(self, extra_data, method="GET", **kwargs):
-		"""Make request to Amazon MWS API with these parameters
-		"""
-
-		# Remove all keys with an empty value because
-		# Amazon's MWS does not allow such a thing.
-		extra_data = remove_empty(extra_data)
-
-		params = {
-			'AWSAccessKeyId': self.access_key,
-			self.ACCOUNT_TYPE: self.account_id,
-			'SignatureVersion': '2',
-			'Timestamp': self.get_timestamp(),
-			'Version': self.version,
-			'SignatureMethod': 'HmacSHA256',
-		}
-		params.update(extra_data)
-		request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
-		signature = self.calc_signature(method, request_description)
-		url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
-		headers = {'User-Agent': 'python-amazon-mws/0.0.1 (Language=Python)'}
-		headers.update(kwargs.get('extra_headers', {}))
-
-		try:
-			# Some might wonder as to why i don't pass the params dict as the params argument to request.
-			# My answer is, here i have to get the url parsed string of params in order to sign it, so
-			# if i pass the params dict as params to request, request will repeat that step because it will need
-			# to convert the dict to a url parsed string, so why do it twice if i can just pass the full url :).
-			response = request(method, url, data=kwargs.get('body', ''), headers=headers)
-			response.raise_for_status()
-			# When retrieving data from the response object,
-			# be aware that response.content returns the content in bytes while response.text calls
-			# response.content and converts it to unicode.
-			data = response.content
-
-			# I do not check the headers to decide which content structure to server simply because sometimes
-			# Amazon's MWS API returns XML error responses with "text/plain" as the Content-Type.
-			try:
-				parsed_response = DictWrapper(data, extra_data.get("Action") + "Result")
-			except XMLError:
-				parsed_response = DataWrapper(data, response.headers)
-
-		except HTTPError as e:
-			error = MWSError(str(e))
-			error.response = e.response
-			raise error
-
-		# Store the response object in the parsed_response for quick access
-		parsed_response.response = response
-		return parsed_response
-
-	def get_service_status(self):
-		"""
-			Returns a GREEN, GREEN_I, YELLOW or RED status.
-			Depending on the status/availability of the API its being called from.
-		"""
-
-		return self.make_request(extra_data=dict(Action='GetServiceStatus'))
-
-	def calc_signature(self, method, request_description):
-		"""Calculate MWS signature to interface with Amazon
-		"""
-		sig_data = method + '\n' + self.domain.replace('https://', '').lower() + '\n' + self.uri + '\n' + request_description
-		sig_data = sig_data.encode('utf-8')
-		secret_key = self.secret_key.encode('utf-8')
-		digest = hmac.new(secret_key, sig_data, hashlib.sha256).digest()
-		return base64.b64encode(digest).decode('utf-8')
-
-	def get_timestamp(self):
-		"""
-			Returns the current timestamp in proper format.
-		"""
-		return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
-
-	def enumerate_param(self, param, values):
-		"""
-			Builds a dictionary of an enumerated parameter.
-			Takes any iterable and returns a dictionary.
-			ie.
-			enumerate_param('MarketplaceIdList.Id', (123, 345, 4343))
-			returns
-			{
-				MarketplaceIdList.Id.1: 123,
-				MarketplaceIdList.Id.2: 345,
-				MarketplaceIdList.Id.3: 4343
-			}
-		"""
-		params = {}
-		if values is not None:
-			if not param.endswith('.'):
-				param = "%s." % param
-			for num, value in enumerate(values):
-				params['%s%d' % (param, (num + 1))] = value
-		return params
-
-
-class Feeds(MWS):
-	""" Amazon MWS Feeds API """
-
-	ACCOUNT_TYPE = "Merchant"
-
-	def submit_feed(self, feed, feed_type, marketplaceids=None,
-					content_type="text/xml", purge='false'):
-		"""
-		Uploads a feed ( xml or .tsv ) to the seller's inventory.
-		Can be used for creating/updating products on Amazon.
-		"""
-		data = dict(Action='SubmitFeed',
-					FeedType=feed_type,
-					PurgeAndReplace=purge)
-		data.update(self.enumerate_param('MarketplaceIdList.Id.', marketplaceids))
-		md = calc_md5(feed)
-		return self.make_request(data, method="POST", body=feed,
-								extra_headers={'Content-MD5': md, 'Content-Type': content_type})
-
-	def get_feed_submission_list(self, feedids=None, max_count=None, feedtypes=None,
-								processingstatuses=None, fromdate=None, todate=None):
-		"""
-		Returns a list of all feed submissions submitted in the previous 90 days.
-		That match the query parameters.
-		"""
-
-		data = dict(Action='GetFeedSubmissionList',
-					MaxCount=max_count,
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate,)
-		data.update(self.enumerate_param('FeedSubmissionIdList.Id', feedids))
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		data.update(self.enumerate_param('FeedProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_submission_list_by_next_token(self, token):
-		data = dict(Action='GetFeedSubmissionListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_feed_submission_count(self, feedtypes=None, processingstatuses=None, fromdate=None, todate=None):
-		data = dict(Action='GetFeedSubmissionCount',
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate)
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		data.update(self.enumerate_param('FeedProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def cancel_feed_submissions(self, feedids=None, feedtypes=None, fromdate=None, todate=None):
-		data = dict(Action='CancelFeedSubmissions',
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate)
-		data.update(self.enumerate_param('FeedSubmissionIdList.Id.', feedids))
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		return self.make_request(data)
-
-	def get_feed_submission_result(self, feedid):
-		data = dict(Action='GetFeedSubmissionResult', FeedSubmissionId=feedid)
-		return self.make_request(data)
-
-class Reports(MWS):
-	""" Amazon MWS Reports API """
-
-	ACCOUNT_TYPE = "Merchant"
-
-	## REPORTS ###
-
-	def get_report(self, report_id):
-		data = dict(Action='GetReport', ReportId=report_id)
-		return self.make_request(data)
-
-	def get_report_count(self, report_types=(), acknowledged=None, fromdate=None, todate=None):
-		data = dict(Action='GetReportCount',
-					Acknowledged=acknowledged,
-					AvailableFromDate=fromdate,
-					AvailableToDate=todate)
-		data.update(self.enumerate_param('ReportTypeList.Type.', report_types))
-		return self.make_request(data)
-
-	def get_report_list(self, requestids=(), max_count=None, types=(), acknowledged=None,
-						fromdate=None, todate=None):
-		data = dict(Action='GetReportList',
-					Acknowledged=acknowledged,
-					AvailableFromDate=fromdate,
-					AvailableToDate=todate,
-					MaxCount=max_count)
-		data.update(self.enumerate_param('ReportRequestIdList.Id.', requestids))
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-	def get_report_list_by_next_token(self, token):
-		data = dict(Action='GetReportListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_report_request_count(self, report_types=(), processingstatuses=(), fromdate=None, todate=None):
-		data = dict(Action='GetReportRequestCount',
-					RequestedFromDate=fromdate,
-					RequestedToDate=todate)
-		data.update(self.enumerate_param('ReportTypeList.Type.', report_types))
-		data.update(self.enumerate_param('ReportProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_report_request_list(self, requestids=(), types=(), processingstatuses=(),
-								max_count=None, fromdate=None, todate=None):
-		data = dict(Action='GetReportRequestList',
-					MaxCount=max_count,
-					RequestedFromDate=fromdate,
-					RequestedToDate=todate)
-		data.update(self.enumerate_param('ReportRequestIdList.Id.', requestids))
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		data.update(self.enumerate_param('ReportProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_report_request_list_by_next_token(self, token):
-		data = dict(Action='GetReportRequestListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def request_report(self, report_type, start_date=None, end_date=None, marketplaceids=()):
-		data = dict(Action='RequestReport',
-					ReportType=report_type,
-					StartDate=start_date,
-					EndDate=end_date)
-		data.update(self.enumerate_param('MarketplaceIdList.Id.', marketplaceids))
-		return self.make_request(data)
-
-	### ReportSchedule ###
-
-	def get_report_schedule_list(self, types=()):
-		data = dict(Action='GetReportScheduleList')
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-	def get_report_schedule_count(self, types=()):
-		data = dict(Action='GetReportScheduleCount')
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-
-class Orders(MWS):
-	""" Amazon Orders API """
-
-	URI = "/Orders/2013-09-01"
-	VERSION = "2013-09-01"
-	NS = '{https://mws.amazonservices.com/Orders/2011-01-01}'
-
-	def list_orders(self, marketplaceids, created_after=None, created_before=None, lastupdatedafter=None,
-					lastupdatedbefore=None, orderstatus=(), fulfillment_channels=(),
-					payment_methods=(), buyer_email=None, seller_orderid=None, max_results='100'):
-
-		data = dict(Action='ListOrders',
-					CreatedAfter=created_after,
-					CreatedBefore=created_before,
-					LastUpdatedAfter=lastupdatedafter,
-					LastUpdatedBefore=lastupdatedbefore,
-					BuyerEmail=buyer_email,
-					SellerOrderId=seller_orderid,
-					MaxResultsPerPage=max_results,
-					)
-		data.update(self.enumerate_param('OrderStatus.Status.', orderstatus))
-		data.update(self.enumerate_param('MarketplaceId.Id.', marketplaceids))
-		data.update(self.enumerate_param('FulfillmentChannel.Channel.', fulfillment_channels))
-		data.update(self.enumerate_param('PaymentMethod.Method.', payment_methods))
-		return self.make_request(data)
-
-	def list_orders_by_next_token(self, token):
-		data = dict(Action='ListOrdersByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_order(self, amazon_order_ids):
-		data = dict(Action='GetOrder')
-		data.update(self.enumerate_param('AmazonOrderId.Id.', amazon_order_ids))
-		return self.make_request(data)
-
-	def list_order_items(self, amazon_order_id):
-		data = dict(Action='ListOrderItems', AmazonOrderId=amazon_order_id)
-		return self.make_request(data)
-
-	def list_order_items_by_next_token(self, token):
-		data = dict(Action='ListOrderItemsByNextToken', NextToken=token)
-		return self.make_request(data)
-
-
-class Products(MWS):
-	""" Amazon MWS Products API """
-
-	URI = '/Products/2011-10-01'
-	VERSION = '2011-10-01'
-	NS = '{http://mws.amazonservices.com/schema/Products/2011-10-01}'
-
-	def list_matching_products(self, marketplaceid, query, contextid=None):
-		""" Returns a list of products and their attributes, ordered by
-			relevancy, based on a search query that you specify.
-			Your search query can be a phrase that describes the product
-			or it can be a product identifier such as a UPC, EAN, ISBN, or JAN.
-		"""
-		data = dict(Action='ListMatchingProducts',
-					MarketplaceId=marketplaceid,
-					Query=query,
-					QueryContextId=contextid)
-		return self.make_request(data)
-
-	def get_matching_product(self, marketplaceid, asins):
-		""" Returns a list of products and their attributes, based on a list of
-			ASIN values that you specify.
-		"""
-		data = dict(Action='GetMatchingProduct', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_matching_product_for_id(self, marketplaceid, type, id):
-		""" Returns a list of products and their attributes, based on a list of
-			product identifier values (asin, sellersku, upc, ean, isbn and JAN)
-			Added in Fourth Release, API version 2011-10-01
-		"""
-		data = dict(Action='GetMatchingProductForId',
-					MarketplaceId=marketplaceid,
-					IdType=type)
-		data.update(self.enumerate_param('IdList.Id', id))
-		return self.make_request(data)
-
-	def get_competitive_pricing_for_sku(self, marketplaceid, skus):
-		""" Returns the current competitive pricing of a product,
-			based on the SellerSKU and MarketplaceId that you specify.
-		"""
-		data = dict(Action='GetCompetitivePricingForSKU', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_competitive_pricing_for_asin(self, marketplaceid, asins):
-		""" Returns the current competitive pricing of a product,
-			based on the ASIN and MarketplaceId that you specify.
-		"""
-		data = dict(Action='GetCompetitivePricingForASIN', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_lowest_offer_listings_for_sku(self, marketplaceid, skus, condition="Any", excludeme="False"):
-		data = dict(Action='GetLowestOfferListingsForSKU',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition,
-					ExcludeMe=excludeme)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_lowest_offer_listings_for_asin(self, marketplaceid, asins, condition="Any", excludeme="False"):
-		data = dict(Action='GetLowestOfferListingsForASIN',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition,
-					ExcludeMe=excludeme)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_product_categories_for_sku(self, marketplaceid, sku):
-		data = dict(Action='GetProductCategoriesForSKU',
-					MarketplaceId=marketplaceid,
-					SellerSKU=sku)
-		return self.make_request(data)
-
-	def get_product_categories_for_asin(self, marketplaceid, asin):
-		data = dict(Action='GetProductCategoriesForASIN',
-					MarketplaceId=marketplaceid,
-					ASIN=asin)
-		return self.make_request(data)
-
-	def get_my_price_for_sku(self, marketplaceid, skus, condition=None):
-		data = dict(Action='GetMyPriceForSKU',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_my_price_for_asin(self, marketplaceid, asins, condition=None):
-		data = dict(Action='GetMyPriceForASIN',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-
-class Sellers(MWS):
-	""" Amazon MWS Sellers API """
-
-	URI = '/Sellers/2011-07-01'
-	VERSION = '2011-07-01'
-	NS = '{http://mws.amazonservices.com/schema/Sellers/2011-07-01}'
-
-	def list_marketplace_participations(self):
-		"""
-			Returns a list of marketplaces a seller can participate in and
-			a list of participations that include seller-specific information in that marketplace.
-			The operation returns only those marketplaces where the seller's account is in an active state.
-		"""
-
-		data = dict(Action='ListMarketplaceParticipations')
-		return self.make_request(data)
-
-	def list_marketplace_participations_by_next_token(self, token):
-		"""
-			Takes a "NextToken" and returns the same information as "list_marketplace_participations".
-			Based on the "NextToken".
-		"""
-		data = dict(Action='ListMarketplaceParticipations', NextToken=token)
-		return self.make_request(data)
-
-#### Fulfillment APIs ####
-
-class InboundShipments(MWS):
-	URI = "/FulfillmentInboundShipment/2010-10-01"
-	VERSION = '2010-10-01'
-
-	# To be completed
-
-
-class Inventory(MWS):
-	""" Amazon MWS Inventory Fulfillment API """
-
-	URI = '/FulfillmentInventory/2010-10-01'
-	VERSION = '2010-10-01'
-	NS = "{http://mws.amazonaws.com/FulfillmentInventory/2010-10-01}"
-
-	def list_inventory_supply(self, skus=(), datetime=None, response_group='Basic'):
-		""" Returns information on available inventory """
-
-		data = dict(Action='ListInventorySupply',
-					QueryStartDateTime=datetime,
-					ResponseGroup=response_group,
-					)
-		data.update(self.enumerate_param('SellerSkus.member.', skus))
-		return self.make_request(data, "POST")
-
-	def list_inventory_supply_by_next_token(self, token):
-		data = dict(Action='ListInventorySupplyByNextToken', NextToken=token)
-		return self.make_request(data, "POST")
-
-
-class OutboundShipments(MWS):
-	URI = "/FulfillmentOutboundShipment/2010-10-01"
-	VERSION = "2010-10-01"
-	# To be completed
-
-
-class Recommendations(MWS):
-
-	""" Amazon MWS Recommendations API """
-
-	URI = '/Recommendations/2013-04-01'
-	VERSION = '2013-04-01'
-	NS = "{https://mws.amazonservices.com/Recommendations/2013-04-01}"
-
-	def get_last_updated_time_for_recommendations(self, marketplaceid):
-		"""
-		Checks whether there are active recommendations for each category for the given marketplace, and if there are,
-		returns the time when recommendations were last updated for each category.
-		"""
-
-		data = dict(Action='GetLastUpdatedTimeForRecommendations',
-					MarketplaceId=marketplaceid)
-		return self.make_request(data, "POST")
-
-	def list_recommendations(self, marketplaceid, recommendationcategory=None):
-		"""
-		Returns your active recommendations for a specific category or for all categories for a specific marketplace.
-		"""
-
-		data = dict(Action="ListRecommendations",
-					MarketplaceId=marketplaceid,
-					RecommendationCategory=recommendationcategory)
-		return self.make_request(data, "POST")
-
-	def list_recommendations_by_next_token(self, token):
-		"""
-		Returns the next page of recommendations using the NextToken parameter.
-		"""
-
-		data = dict(Action="ListRecommendationsByNextToken",
-					NextToken=token)
-		return self.make_request(data, "POST")
-
-class Finances(MWS):
-	""" Amazon Finances API"""
-	URI = '/Finances/2015-05-01'
-	VERSION = '2015-05-01'
-	NS = "{https://mws.amazonservices.com/Finances/2015-05-01}"
-
-	def list_financial_events(self , posted_after=None, posted_before=None,
-		 					amazon_order_id=None, max_results='100'):
-
-		data = dict(Action='ListFinancialEvents',
-					PostedAfter=posted_after,
-					PostedBefore=posted_before,
-					AmazonOrderId=amazon_order_id,
-					MaxResultsPerPage=max_results,
-					)
-		return self.make_request(data)
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js
deleted file mode 100644
index f5ea804..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
deleted file mode 100644
index 5a678e7..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
+++ /dev/null
@@ -1,237 +0,0 @@
-{
- "actions": [],
- "creation": "2018-07-31 05:51:41.357047",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "enable_amazon",
-  "mws_credentials",
-  "seller_id",
-  "aws_access_key_id",
-  "mws_auth_token",
-  "secret_key",
-  "column_break_4",
-  "market_place_id",
-  "region",
-  "domain",
-  "section_break_13",
-  "company",
-  "warehouse",
-  "item_group",
-  "price_list",
-  "column_break_17",
-  "customer_group",
-  "territory",
-  "customer_type",
-  "market_place_account_group",
-  "section_break_12",
-  "after_date",
-  "taxes_charges",
-  "sync_products",
-  "sync_orders",
-  "column_break_10",
-  "enable_sync",
-  "max_retry_limit"
- ],
- "fields": [
-  {
-   "default": "0",
-   "fieldname": "enable_amazon",
-   "fieldtype": "Check",
-   "label": "Enable Amazon"
-  },
-  {
-   "fieldname": "mws_credentials",
-   "fieldtype": "Section Break",
-   "label": "MWS Credentials"
-  },
-  {
-   "fieldname": "seller_id",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Seller ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "aws_access_key_id",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "AWS Access Key ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "mws_auth_token",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "MWS Auth Token",
-   "reqd": 1
-  },
-  {
-   "fieldname": "secret_key",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Secret Key",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "market_place_id",
-   "fieldtype": "Data",
-   "label": "Market Place ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "region",
-   "fieldtype": "Select",
-   "label": "Region",
-   "options": "\nAE\nAU\nBR\nCA\nCN\nDE\nES\nFR\nIN\nJP\nIT\nMX\nUK\nUS",
-   "reqd": 1
-  },
-  {
-   "fieldname": "domain",
-   "fieldtype": "Data",
-   "label": "Domain",
-   "reqd": 1
-  },
-  {
-   "fieldname": "section_break_13",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "fieldname": "warehouse",
-   "fieldtype": "Link",
-   "label": "Warehouse",
-   "options": "Warehouse",
-   "reqd": 1
-  },
-  {
-   "fieldname": "item_group",
-   "fieldtype": "Link",
-   "label": "Item Group",
-   "options": "Item Group",
-   "reqd": 1
-  },
-  {
-   "fieldname": "price_list",
-   "fieldtype": "Link",
-   "label": "Price List",
-   "options": "Price List",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_17",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "customer_group",
-   "fieldtype": "Link",
-   "label": "Customer Group",
-   "options": "Customer Group",
-   "reqd": 1
-  },
-  {
-   "fieldname": "territory",
-   "fieldtype": "Link",
-   "label": "Territory",
-   "options": "Territory",
-   "reqd": 1
-  },
-  {
-   "fieldname": "customer_type",
-   "fieldtype": "Select",
-   "label": "Customer Type",
-   "options": "Individual\nCompany",
-   "reqd": 1
-  },
-  {
-   "fieldname": "market_place_account_group",
-   "fieldtype": "Link",
-   "label": "Market Place Account Group",
-   "options": "Account",
-   "reqd": 1
-  },
-  {
-   "fieldname": "section_break_12",
-   "fieldtype": "Section Break"
-  },
-  {
-   "description": "Amazon will synch data updated after this date",
-   "fieldname": "after_date",
-   "fieldtype": "Datetime",
-   "label": "After Date",
-   "reqd": 1
-  },
-  {
-   "default": "0",
-   "description": "Get financial breakup of Taxes and charges data by Amazon ",
-   "fieldname": "taxes_charges",
-   "fieldtype": "Check",
-   "label": "Sync Taxes and Charges"
-  },
-  {
-   "fieldname": "column_break_10",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "3",
-   "fieldname": "max_retry_limit",
-   "fieldtype": "Int",
-   "label": "Max Retry Limit"
-  },
-  {
-   "description": "Always sync your products from Amazon MWS before synching the Orders details",
-   "fieldname": "sync_products",
-   "fieldtype": "Button",
-   "label": "Sync Products",
-   "options": "get_products_details"
-  },
-  {
-   "description": "Click this button to pull your Sales Order data from Amazon MWS.",
-   "fieldname": "sync_orders",
-   "fieldtype": "Button",
-   "label": "Sync Orders",
-   "options": "get_order_details"
-  },
-  {
-   "default": "0",
-   "description": "Check this to enable a scheduled Daily synchronization routine via scheduler",
-   "fieldname": "enable_sync",
-   "fieldtype": "Check",
-   "label": "Enable Scheduled Sync"
-  }
- ],
- "issingle": 1,
- "links": [],
- "modified": "2020-04-07 14:26:20.174848",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Amazon MWS Settings",
- "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/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
deleted file mode 100644
index c1f460f..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies and contributors
-# For license information, please see license.txt
-
-
-import dateutil
-import frappe
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from frappe.model.document import Document
-
-from erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods import get_orders
-
-
-class AmazonMWSSettings(Document):
-	def validate(self):
-		if self.enable_amazon == 1:
-			self.enable_sync = 1
-			setup_custom_fields()
-		else:
-			self.enable_sync = 0
-
-	@frappe.whitelist()
-	def get_products_details(self):
-		if self.enable_amazon == 1:
-			frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
-
-	@frappe.whitelist()
-	def get_order_details(self):
-		if self.enable_amazon == 1:
-			after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
-			frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_orders', after_date=after_date)
-
-def schedule_get_order_details():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	if mws_settings.enable_sync and mws_settings.enable_amazon:
-		after_date = dateutil.parser.parse(mws_settings.after_date).strftime("%Y-%m-%d")
-		get_orders(after_date = after_date)
-
-def setup_custom_fields():
-	custom_fields = {
-		"Item": [dict(fieldname='amazon_item_code', label='Amazon Item Code',
-			fieldtype='Data', insert_after='series', read_only=1, print_hide=1)],
-		"Sales Order": [dict(fieldname='amazon_order_id', label='Amazon Order ID',
-			fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
-	}
-
-	create_custom_fields(custom_fields)
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py
deleted file mode 100644
index 4be7960..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestAmazonMWSSettings(unittest.TestCase):
-	pass
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py
deleted file mode 100644
index d9dfc6f..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-Created on Tue Jun 26 15:42:07 2012
-
-Borrowed from https://github.com/timotheus/ebaysdk-python
-
-@author: pierre
-"""
-
-import re
-import xml.etree.ElementTree as ET
-
-
-class object_dict(dict):
-	"""object view of dict, you can
-	>>> a = object_dict()
-	>>> a.fish = 'fish'
-	>>> a['fish']
-	'fish'
-	>>> a['water'] = 'water'
-	>>> a.water
-	'water'
- 	>>> a.test = {'value': 1}
-	>>> a.test2 = object_dict({'name': 'test2', 'value': 2})
-	>>> a.test, a.test2.name, a.test2.value
-	(1, 'test2', 2)
-	"""
-	def __init__(self, initd=None):
-		if initd is None:
-			initd = {}
-		dict.__init__(self, initd)
-
-	def __getattr__(self, item):
-
-		try:
-			d = self.__getitem__(item)
-		except KeyError:
-			return None
-
-		if isinstance(d, dict) and 'value' in d and len(d) == 1:
-			return d['value']
-		else:
-			return d
-
-	# if value is the only key in object, you can omit it
-	def __setstate__(self, item):
-		return False
-
-	def __setattr__(self, item, value):
-		self.__setitem__(item, value)
-
-	def getvalue(self, item, value=None):
-		return self.get(item, {}).get('value', value)
-
-
-class xml2dict(object):
-
-	def __init__(self):
-		pass
-
-	def _parse_node(self, node):
-		node_tree = object_dict()
-		# Save attrs and text, hope there will not be a child with same name
-		if node.text:
-			node_tree.value = node.text
-		for (k, v) in node.attrib.items():
-			k, v = self._namespace_split(k, object_dict({'value':v}))
-			node_tree[k] = v
-		#Save childrens
-		for child in node.getchildren():
-			tag, tree = self._namespace_split(child.tag,
-											self._parse_node(child))
-			if tag not in node_tree:  # the first time, so store it in dict
-				node_tree[tag] = tree
-				continue
-			old = node_tree[tag]
-			if not isinstance(old, list):
-				node_tree.pop(tag)
-				node_tree[tag] = [old]  # multi times, so change old dict to a list
-			node_tree[tag].append(tree)  # add the new one
-
-		return node_tree
-
-	def _namespace_split(self, tag, value):
-		"""
-		Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
-		ns = http://cs.sfsu.edu/csc867/myscheduler
-		name = patients
-		"""
-		result = re.compile(r"\{(.*)\}(.*)").search(tag)
-		if result:
-			value.namespace, tag = result.groups()
-
-		return (tag, value)
-
-	def parse(self, file):
-		"""parse a xml file to a dict"""
-		f = open(file, 'r')
-		return self.fromstring(f.read())
-
-	def fromstring(self, s):
-		"""parse a string"""
-		t = ET.fromstring(s)
-		root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
-		return object_dict({root_tag: root_tree})
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 45077aa..1f2619b 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -30,17 +30,6 @@
    "type": "Link"
   },
   {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Amazon MWS Settings",
-   "link_count": 0,
-   "link_to": "Amazon MWS Settings",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Payments",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index d99f23e..38fa691 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -333,7 +333,6 @@
 	"hourly": [
 		'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
 		"erpnext.accounts.doctype.subscription.subscription.process_all",
-		"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
 		"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
 		"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
 		"erpnext.projects.doctype.project.project.hourly_reminder",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 55054bb..0a468f1 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -28,9 +28,24 @@
 
 class ProductionPlan(Document):
 	def validate(self):
+		self.set_pending_qty_in_row_without_reference()
 		self.calculate_total_planned_qty()
 		self.set_status()
 
+	def set_pending_qty_in_row_without_reference(self):
+		"Set Pending Qty in independent rows (not from SO or MR)."
+		if self.docstatus > 0: # set only to initialise value before submit
+			return
+
+		for item in self.po_items:
+			if not item.get("sales_order") or not item.get("material_request"):
+				item.pending_qty = item.planned_qty
+
+	def calculate_total_planned_qty(self):
+		self.total_planned_qty = 0
+		for d in self.po_items:
+			self.total_planned_qty += flt(d.planned_qty)
+
 	def validate_data(self):
 		for d in self.get('po_items'):
 			if not d.bom_no:
@@ -263,11 +278,6 @@
 						'qty': so_detail['qty']
 				})
 
-	def calculate_total_planned_qty(self):
-		self.total_planned_qty = 0
-		for d in self.po_items:
-			self.total_planned_qty += flt(d.planned_qty)
-
 	def calculate_total_produced_qty(self):
 		self.total_produced_qty = 0
 		for d in self.po_items:
@@ -275,10 +285,11 @@
 
 		self.db_set("total_produced_qty", self.total_produced_qty, update_modified=False)
 
-	def update_produced_qty(self, produced_qty, production_plan_item):
+	def update_produced_pending_qty(self, produced_qty, production_plan_item):
 		for data in self.po_items:
 			if data.name == production_plan_item:
 				data.produced_qty = produced_qty
+				data.pending_qty = flt(data.planned_qty - produced_qty)
 				data.db_update()
 
 		self.calculate_total_produced_qty()
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 276e708..afa1501 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -11,6 +11,7 @@
 )
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
 	create_stock_reconciliation,
 )
@@ -36,15 +37,21 @@
 			if not frappe.db.get_value('BOM', {'item': item}):
 				make_bom(item = item, raw_materials = raw_materials)
 
-	def test_production_plan(self):
+	def test_production_plan_mr_creation(self):
+		"Test if MRs are created for unavailable raw materials."
 		pln = create_production_plan(item_code='Test Production Item 1')
 		self.assertTrue(len(pln.mr_items), 2)
-		pln.make_material_request()
 
-		pln = frappe.get_doc('Production Plan', pln.name)
+		pln.make_material_request()
+		pln.reload()
 		self.assertTrue(pln.status, 'Material Requested')
-		material_requests = frappe.get_all('Material Request Item', fields = ['distinct parent'],
-			filters = {'production_plan': pln.name}, as_list=1)
+
+		material_requests = frappe.get_all(
+			'Material Request Item',
+			fields = ['distinct parent'],
+			filters = {'production_plan': pln.name},
+			as_list=1
+		)
 
 		self.assertTrue(len(material_requests), 2)
 
@@ -66,27 +73,42 @@
 		pln.cancel()
 
 	def test_production_plan_start_date(self):
+		"Test if Work Order has same Planned Start Date as Prod Plan."
 		planned_date = add_to_date(date=None, days=3)
-		plan = create_production_plan(item_code='Test Production Item 1', planned_start_date=planned_date)
+		plan = create_production_plan(
+			item_code='Test Production Item 1',
+			planned_start_date=planned_date
+		)
 		plan.make_work_order()
 
-		work_orders = frappe.get_all('Work Order', fields = ['name', 'planned_start_date'],
-			filters = {'production_plan': plan.name})
+		work_orders = frappe.get_all(
+			'Work Order',
+			fields = ['name', 'planned_start_date'],
+			filters = {'production_plan': plan.name}
+		)
 
 		self.assertEqual(work_orders[0].planned_start_date, planned_date)
 
 		for wo in work_orders:
 			frappe.delete_doc('Work Order', wo.name)
 
-		frappe.get_doc('Production Plan', plan.name).cancel()
+		plan.reload()
+		plan.cancel()
 
 	def test_production_plan_for_existing_ordered_qty(self):
+		"""
+		- Enable 'ignore_existing_ordered_qty'.
+		- Test if MR Planning table pulls Raw Material Qty even if it is in stock.
+		"""
 		sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=110)
 		sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
 			target="_Test Warehouse - _TC", qty=1, rate=120)
 
-		pln = create_production_plan(item_code='Test Production Item 1', ignore_existing_ordered_qty=0)
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			ignore_existing_ordered_qty=1
+		)
 		self.assertTrue(len(pln.mr_items), 1)
 		self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
 
@@ -95,23 +117,39 @@
 		pln.cancel()
 
 	def test_production_plan_with_non_stock_item(self):
-		pln = create_production_plan(item_code='Test Production Item 1', include_non_stock_items=0)
+		"Test if MR Planning table includes Non Stock RM."
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			include_non_stock_items=1
+		)
 		self.assertTrue(len(pln.mr_items), 3)
 		pln.cancel()
 
 	def test_production_plan_without_multi_level(self):
-		pln = create_production_plan(item_code='Test Production Item 1', use_multi_level_bom=0)
+		"Test MR Planning table for non exploded BOM."
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			use_multi_level_bom=0
+		)
 		self.assertTrue(len(pln.mr_items), 2)
 		pln.cancel()
 
 	def test_production_plan_without_multi_level_for_existing_ordered_qty(self):
+		"""
+		- Disable 'ignore_existing_ordered_qty'.
+		- Test if MR Planning table avoids pulling Raw Material Qty as it is in stock for
+		non exploded BOM.
+		"""
 		sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=130)
 		sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=140)
 
-		pln = create_production_plan(item_code='Test Production Item 1',
-			use_multi_level_bom=0, ignore_existing_ordered_qty=0)
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			use_multi_level_bom=0,
+			ignore_existing_ordered_qty=0
+		)
 		self.assertTrue(len(pln.mr_items), 0)
 
 		sr1.cancel()
@@ -119,6 +157,7 @@
 		pln.cancel()
 
 	def test_production_plan_sales_orders(self):
+		"Test if previously fulfilled SO (with WO) is pulled into Prod Plan."
 		item = 'Test Production Item 1'
 		so = make_sales_order(item_code=item, qty=1)
 		sales_order = so.name
@@ -166,24 +205,25 @@
 		self.assertEqual(sales_orders, [])
 
 	def test_production_plan_combine_items(self):
+		"Test combining FG items in Production Plan."
 		item = 'Test Production Item 1'
-		so = make_sales_order(item_code=item, qty=1)
+		so1 = make_sales_order(item_code=item, qty=1)
 
 		pln = frappe.new_doc('Production Plan')
-		pln.company = so.company
+		pln.company = so1.company
 		pln.get_items_from = 'Sales Order'
 		pln.append('sales_orders', {
-			'sales_order': so.name,
-			'sales_order_date': so.transaction_date,
-			'customer': so.customer,
-			'grand_total': so.grand_total
+			'sales_order': so1.name,
+			'sales_order_date': so1.transaction_date,
+			'customer': so1.customer,
+			'grand_total': so1.grand_total
 		})
-		so = make_sales_order(item_code=item, qty=2)
+		so2 = make_sales_order(item_code=item, qty=2)
 		pln.append('sales_orders', {
-			'sales_order': so.name,
-			'sales_order_date': so.transaction_date,
-			'customer': so.customer,
-			'grand_total': so.grand_total
+			'sales_order': so2.name,
+			'sales_order_date': so2.transaction_date,
+			'customer': so2.customer,
+			'grand_total': so2.grand_total
 		})
 		pln.combine_items = 1
 		pln.get_items()
@@ -214,28 +254,37 @@
 			so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
 			self.assertEqual(so_wo_qty, 0.0)
 
-		latest_plan = frappe.get_doc('Production Plan', pln.name)
-		latest_plan.cancel()
+		pln.reload()
+		pln.cancel()
 
 	def test_pp_to_mr_customer_provided(self):
-		#Material Request from Production Plan for Customer Provided
+		" Test Material Request from Production Plan for Customer Provided Item."
 		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
 		create_item('Production Item CUST')
+
 		for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
 			if not frappe.db.get_value('BOM', {'item': item}):
 				make_bom(item = item, raw_materials = raw_materials)
 		production_plan = create_production_plan(item_code = 'Production Item CUST')
 		production_plan.make_material_request()
-		material_request = frappe.db.get_value('Material Request Item', {'production_plan': production_plan.name, 'item_code': 'CUST-0987'}, 'parent')
+
+		material_request = frappe.db.get_value(
+			'Material Request Item',
+			{'production_plan': production_plan.name, 'item_code': 'CUST-0987'},
+			'parent'
+		)
 		mr = frappe.get_doc('Material Request', material_request)
+
 		self.assertTrue(mr.material_request_type, 'Customer Provided')
 		self.assertTrue(mr.customer, '_Test Customer')
 
 	def test_production_plan_with_multi_level_bom(self):
-		#|Item Code			|	Qty	|
-		#|Test BOM 1	 		|	1	|
-		#|	Test BOM 2		|	2	|
-		#|		Test BOM 3	|	3	|
+		"""
+		Item Code	|	Qty	|
+		|Test BOM 1	|	1	|
+		|Test BOM 2	|	2	|
+		|Test BOM 3	|	3	|
+		"""
 
 		for item_code in ["Test BOM 1", "Test BOM 2", "Test BOM 3", "Test RM BOM 1"]:
 			create_item(item_code, is_stock_item=1)
@@ -264,15 +313,18 @@
 		pln.make_work_order()
 
 		#last level sub-assembly work order produce qty
-		to_produce_qty = frappe.db.get_value("Work Order",
-			{"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty")
+		to_produce_qty = frappe.db.get_value(
+			"Work Order",
+			{"production_plan": pln.name, "production_item": "Test BOM 3"},
+			"qty"
+		)
 
 		self.assertEqual(to_produce_qty, 18.0)
 		pln.cancel()
 		frappe.delete_doc("Production Plan", pln.name)
 
 	def test_get_warehouse_list_group(self):
-		"""Check if required warehouses are returned"""
+		"Check if required child warehouses are returned."
 		warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
 
 		warehouses = set(get_warehouse_list(warehouse_json))
@@ -284,6 +336,7 @@
 				msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
 
 	def test_get_warehouse_list_single(self):
+		"Check if same warehouse is returned in absence of child warehouses."
 		warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
 
 		warehouses = set(get_warehouse_list(warehouse_json))
@@ -292,6 +345,7 @@
 		self.assertEqual(warehouses, expected_warehouses)
 
 	def test_get_sales_order_with_variant(self):
+		"Check if Template BOM is fetched in absence of Variant BOM."
 		rm_item = create_item('PIV_RM', valuation_rate = 100)
 		if not frappe.db.exists('Item', {"item_code": 'PIV'}):
 			item = create_item('PIV', valuation_rate = 100)
@@ -348,7 +402,7 @@
 		frappe.db.rollback()
 
 	def test_subassmebly_sorting(self):
-		""" Test subassembly sorting in case of multiple items with nested BOMs"""
+		"Test subassembly sorting in case of multiple items with nested BOMs."
 		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
 
 		prefix = "_TestLevel_"
@@ -386,6 +440,7 @@
 		self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item)
 
 	def test_multiple_work_order_for_production_plan_item(self):
+		"Test producing Prod Plan (making WO) in parts."
 		def create_work_order(item, pln, qty):
 			# Get Production Items
 			items_data = pln.get_production_items()
@@ -441,7 +496,107 @@
 		pln.reload()
 		self.assertEqual(pln.po_items[0].ordered_qty, 0)
 
+	def test_production_plan_pending_qty_with_sales_order(self):
+		"""
+		Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
+		"""
+		from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+		from erpnext.manufacturing.doctype.work_order.work_order import (
+			make_stock_entry as make_se_from_wo,
+		)
+
+		make_stock_entry(item_code="Raw Material Item 1",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+		make_stock_entry(item_code="Raw Material Item 2",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+
+		item = 'Test Production Item 1'
+		so = make_sales_order(item_code=item, qty=1)
+
+		pln = create_production_plan(
+			company=so.company,
+			get_items_from="Sales Order",
+			sales_order=so,
+			skip_getting_mr_items=True
+		)
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+		wo = make_wo_order_test_record(
+			item_code=item, qty=1,
+			company=so.company,
+			wip_warehouse='Work In Progress - _TC',
+			fg_warehouse='Finished Goods - _TC',
+			skip_transfer=1,
+			do_not_submit=True
+		)
+		wo.production_plan = pln.name
+		wo.production_plan_item = pln.po_items[0].name
+		wo.submit()
+
+		se = frappe.get_doc(make_se_from_wo(wo.name, "Manufacture", 1))
+		se.submit()
+
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 0)
+
+		se.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+	def test_production_plan_pending_qty_independent_items(self):
+		"Test Prod Plan impact if items are added independently (no from SO or MR)."
+		from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+		from erpnext.manufacturing.doctype.work_order.work_order import (
+			make_stock_entry as make_se_from_wo,
+		)
+
+		make_stock_entry(item_code="Raw Material Item 1",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+		make_stock_entry(item_code="Raw Material Item 2",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			skip_getting_mr_items=True
+		)
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+		wo = make_wo_order_test_record(
+			item_code='Test Production Item 1', qty=1,
+			company=pln.company,
+			wip_warehouse='Work In Progress - _TC',
+			fg_warehouse='Finished Goods - _TC',
+			skip_transfer=1,
+			do_not_submit=True
+		)
+		wo.production_plan = pln.name
+		wo.production_plan_item = pln.po_items[0].name
+		wo.submit()
+
+		se = frappe.get_doc(make_se_from_wo(wo.name, "Manufacture", 1))
+		se.submit()
+
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 0)
+
+		se.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
 def create_production_plan(**args):
+	"""
+	sales_order (obj): Sales Order Doc Object
+	get_items_from (str): Sales Order/Material Request
+	skip_getting_mr_items (bool): Whether or not to plan for new MRs
+	"""
 	args = frappe._dict(args)
 
 	pln = frappe.get_doc({
@@ -449,20 +604,35 @@
 		'company': args.company or '_Test Company',
 		'customer': args.customer or '_Test Customer',
 		'posting_date': nowdate(),
-		'include_non_stock_items': args.include_non_stock_items or 1,
-		'include_subcontracted_items': args.include_subcontracted_items or 1,
-		'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 1,
-		'po_items': [{
+		'include_non_stock_items': args.include_non_stock_items or 0,
+		'include_subcontracted_items': args.include_subcontracted_items or 0,
+		'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 0,
+		'get_items_from': 'Sales Order'
+	})
+
+	if not args.get("sales_order"):
+		pln.append('po_items', {
 			'use_multi_level_bom': args.use_multi_level_bom or 1,
 			'item_code': args.item_code,
 			'bom_no': frappe.db.get_value('Item', args.item_code, 'default_bom'),
 			'planned_qty': args.planned_qty or 1,
 			'planned_start_date': args.planned_start_date or now_datetime()
-		}]
-	})
-	mr_items = get_items_for_material_requests(pln.as_dict())
-	for d in mr_items:
-		pln.append('mr_items', d)
+		})
+
+	if args.get("get_items_from") == "Sales Order" and args.get("sales_order"):
+		so = args.get("sales_order")
+		pln.append('sales_orders', {
+			'sales_order': so.name,
+			'sales_order_date': so.transaction_date,
+			'customer': so.customer,
+			'grand_total': so.grand_total
+		})
+		pln.get_items()
+
+	if not args.get("skip_getting_mr_items"):
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			pln.append('mr_items', d)
 
 	if not args.do_not_save:
 		pln.insert()
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 9dd3fa7..ed6a029 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -272,7 +272,7 @@
 
 			produced_qty = total_qty[0][0] if total_qty else 0
 
-		production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
+		production_plan.run_method("update_produced_pending_qty", produced_qty, self.production_plan_item)
 
 	def before_submit(self):
 		self.create_serial_no_batch_no()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index d300340..d104bc0 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -351,3 +351,4 @@
 erpnext.patches.v13_0.shopping_cart_to_ecommerce
 erpnext.patches.v13_0.update_disbursement_account
 erpnext.patches.v13_0.update_reserved_qty_closed_wo
+erpnext.patches.v14_0.delete_amazon_mws_doctype
diff --git a/erpnext/patches/v12_0/rename_mws_settings_fields.py b/erpnext/patches/v12_0/rename_mws_settings_fields.py
deleted file mode 100644
index d5bf38d..0000000
--- a/erpnext/patches/v12_0/rename_mws_settings_fields.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (c) 2020, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-
-
-def execute():
-	count = frappe.db.sql("SELECT COUNT(*) FROM `tabSingles` WHERE doctype='Amazon MWS Settings' AND field='enable_sync';")[0][0]
-	if count == 0:
-		frappe.db.sql("UPDATE `tabSingles` SET field='enable_sync' WHERE doctype='Amazon MWS Settings' AND field='enable_synch';")
-
-	frappe.reload_doc("ERPNext Integrations", "doctype", "Amazon MWS Settings")
diff --git a/erpnext/patches/v14_0/delete_amazon_mws_doctype.py b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
new file mode 100644
index 0000000..525da6c
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
@@ -0,0 +1,5 @@
+import frappe
+
+
+def execute():
+	frappe.delete_doc("DocType", "Amazon MWS Settings", ignore_missing=True)
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index e6dfc97..a89a403 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -252,6 +252,7 @@
 			key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
 
 			if d.voucher_type == "Stock Reconciliation":
+				# get difference in qty shift as actual qty
 				prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
 				d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
 
@@ -264,12 +265,16 @@
 
 			self.__update_balances(d, key)
 
+		if not self.filters.get("show_warehouse_wise_stock"):
+			# (Item 1, WH 1), (Item 1, WH 2) => (Item 1)
+			self.item_details = self.__aggregate_details_by_item(self.item_details)
+
 		return self.item_details
 
 	def __init_key_stores(self, row: Dict) -> Tuple:
 		"Initialise keys and FIFO Queue."
 
-		key = (row.name, row.warehouse) if self.filters.get('show_warehouse_wise_stock') else row.name
+		key = (row.name, row.warehouse)
 		self.item_details.setdefault(key, {"details": row, "fifo_queue": []})
 		fifo_queue = self.item_details[key]["fifo_queue"]
 
@@ -338,6 +343,27 @@
 
 		self.item_details[key]["has_serial_no"] = row.has_serial_no
 
+	def __aggregate_details_by_item(self, wh_wise_data: Dict) -> Dict:
+		"Aggregate Item-Wh wise data into single Item entry."
+		item_aggregated_data = {}
+		for key,row in wh_wise_data.items():
+			item = key[0]
+			if not item_aggregated_data.get(item):
+				item_aggregated_data.setdefault(item, {
+					"details": frappe._dict(),
+					"fifo_queue": [],
+					"qty_after_transaction": 0.0,
+					"total_qty": 0.0
+				})
+			item_row = item_aggregated_data.get(item)
+			item_row["details"].update(row["details"])
+			item_row["fifo_queue"].extend(row["fifo_queue"])
+			item_row["qty_after_transaction"] += flt(row["qty_after_transaction"])
+			item_row["total_qty"] += flt(row["total_qty"])
+			item_row["has_serial_no"] = row["has_serial_no"]
+
+		return item_aggregated_data
+
 	def __get_stock_ledger_entries(self) -> List[Dict]:
 		sle = frappe.qb.DocType("Stock Ledger Entry")
 		item = self.__get_item_query() # used as derived table in sle query
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
index 5ffe97f..9e9bed4 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
+++ b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
@@ -15,6 +15,7 @@
 50 qty is (today-the 1st) days old
 20 qty is (today-the 2nd) days old
 
+> Note: We generate FIFO slots warehouse wise as stock reconciliations from different warehouses can cause incorrect values.
 ### Calculation of FIFO Slots
 
 #### Case 1: Outward from sufficient balance qty
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index 949bb7c..66d2f6b 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -15,11 +15,12 @@
 		)
 
 	def test_normal_inward_outward_queue(self):
-		"Reference: Case 1 in stock_ageing_fifo_logic.md"
+		"Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)"
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=30, qty_after_transaction=30,
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -27,6 +28,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=50,
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Entry",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -34,6 +36,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-10), qty_after_transaction=40,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -50,11 +53,12 @@
 		self.assertEqual(queue[0][0], 20.0)
 
 	def test_insufficient_balance(self):
-		"Reference: Case 3 in stock_ageing_fifo_logic.md"
+		"Reference: Case 3 in stock_ageing_fifo_logic.md (same wh)"
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-30), qty_after_transaction=(-30),
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -62,6 +66,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=(-10),
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Entry",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -69,6 +74,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=10,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -76,6 +82,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=10, qty_after_transaction=20,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="004",
 				has_serial_no=False, serial_no=None
@@ -91,11 +98,16 @@
 		self.assertEqual(queue[0][0], 10.0)
 		self.assertEqual(queue[1][0], 10.0)
 
-	def test_stock_reconciliation(self):
+	def test_basic_stock_reconciliation(self):
+		"""
+		Ledger (same wh): [+30, reco reset >> 50, -10]
+		Bal: 40
+		"""
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=30, qty_after_transaction=30,
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -103,6 +115,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=0, qty_after_transaction=50,
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -110,6 +123,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-10), qty_after_transaction=40,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -122,5 +136,112 @@
 		queue = result["fifo_queue"]
 
 		self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+		self.assertEqual(result["total_qty"], 40.0)
 		self.assertEqual(queue[0][0], 20.0)
 		self.assertEqual(queue[1][0], 20.0)
+
+	def test_sequential_stock_reco_same_warehouse(self):
+		"""
+		Test back to back stock recos (same warehouse).
+		Ledger: [reco opening >> +1000, reco reset >> 400, -10]
+		Bal: 390
+		"""
+		sle = [
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=1000,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=400,
+				warehouse="WH 1",
+				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-10), qty_after_transaction=390,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			)
+		]
+		slots = FIFOSlots(self.filters, sle).generate()
+
+		result = slots["Flask Item"]
+		queue = result["fifo_queue"]
+
+		self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+		self.assertEqual(result["total_qty"], 390.0)
+		self.assertEqual(queue[0][0], 390.0)
+
+	def test_sequential_stock_reco_different_warehouse(self):
+		"""
+		Ledger:
+		WH	| Voucher | Qty
+		-------------------
+		WH1 | Reco	  | 1000
+		WH2 | Reco	  | 400
+		WH1 | SE	  | -10
+
+		Bal: WH1 bal + WH2 bal = 990 + 400 = 1390
+		"""
+		sle = [
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=1000,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=400,
+				warehouse="WH 2",
+				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-10), qty_after_transaction=990,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="004",
+				has_serial_no=False, serial_no=None
+			)
+		]
+
+		item_wise_slots, item_wh_wise_slots = generate_item_and_item_wh_wise_slots(
+			filters=self.filters,sle=sle
+		)
+
+		# test without 'show_warehouse_wise_stock'
+		item_result = item_wise_slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["qty_after_transaction"], item_result["total_qty"])
+		self.assertEqual(item_result["total_qty"], 1390.0)
+		self.assertEqual(queue[0][0], 990.0)
+		self.assertEqual(queue[1][0], 400.0)
+
+		# test with 'show_warehouse_wise_stock' checked
+		item_wh_balances = [item_wh_wise_slots.get(i).get("qty_after_transaction") for i in item_wh_wise_slots]
+		self.assertEqual(sum(item_wh_balances), item_result["qty_after_transaction"])
+
+def generate_item_and_item_wh_wise_slots(filters, sle):
+	"Return results with and without 'show_warehouse_wise_stock'"
+	item_wise_slots = FIFOSlots(filters, sle).generate()
+
+	filters.show_warehouse_wise_stock = True
+	item_wh_wise_slots = FIFOSlots(filters, sle).generate()
+	filters.show_warehouse_wise_stock = False
+
+	return item_wise_slots, item_wh_wise_slots
\ No newline at end of file