feat: Sync old shopify orders (#23841)

* feat: Sync old shopify orders

* fix: Old orders syncing by date

* fix: Remove unnecessary code

* fix: Remove unintentional changes

Co-authored-by: Saurabh <saurabh@erpnext.com>
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
index d59f909..8aa7453 100644
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ b/erpnext/erpnext_integrations/connectors/shopify_connection.py
@@ -2,12 +2,13 @@
 import frappe
 from frappe import _
 import json
-from frappe.utils import cstr, cint, nowdate, flt
+from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
 from erpnext.erpnext_integrations.utils import validate_webhooks_request
 from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
 from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
 from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
 from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
+from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
 
 @frappe.whitelist(allow_guest=True)
 @validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
@@ -18,7 +19,7 @@
 
 	dump_request_data(order, event)
 
-def sync_sales_order(order, request_id=None):
+def sync_sales_order(order, request_id=None, old_order_sync=False):
 	frappe.set_user('Administrator')
 	shopify_settings = frappe.get_doc("Shopify Settings")
 	frappe.flags.request_id = request_id
@@ -27,7 +28,7 @@
 		try:
 			validate_customer(order, shopify_settings)
 			validate_item(order, shopify_settings)
-			create_order(order, shopify_settings)
+			create_order(order, shopify_settings, old_order_sync=old_order_sync)
 		except Exception as e:
 			make_shopify_log(status="Error", exception=e)
 
@@ -77,13 +78,13 @@
 		if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
 			sync_item_from_shopify(shopify_settings, item)
 
-def create_order(order, shopify_settings, company=None):
+def create_order(order, shopify_settings, old_order_sync=False, company=None):
 	so = create_sales_order(order, shopify_settings, company)
 	if so:
 		if order.get("financial_status") == "paid":
-			create_sales_invoice(order, shopify_settings, so)
+			create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)
 
-		if order.get("fulfillments"):
+		if order.get("fulfillments") and not old_order_sync:
 			create_delivery_note(order, shopify_settings, so)
 
 def create_sales_order(shopify_order, shopify_settings, company=None):
@@ -92,7 +93,7 @@
 	so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")
 
 	if not so:
-		items = get_order_items(shopify_order.get("line_items"), shopify_settings)
+		items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))
 
 		if not items:
 			message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
@@ -106,8 +107,10 @@
 			"doctype": "Sales Order",
 			"naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
 			"shopify_order_id": shopify_order.get("id"),
+			"shopify_order_number": shopify_order.get("name"),
 			"customer": customer or shopify_settings.default_customer,
-			"delivery_date": nowdate(),
+			"transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
+			"delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
 			"company": shopify_settings.company,
 			"selling_price_list": shopify_settings.price_list,
 			"ignore_pricing_rule": 1,
@@ -132,12 +135,20 @@
 	frappe.db.commit()
 	return so
 
-def create_sales_invoice(shopify_order, shopify_settings, so):
+def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
 	if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
 		and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):
 
+		if old_order_sync:
+			posting_date = getdate(shopify_order.get('created_at'))
+		else:
+			posting_date = nowdate()
+
 		si = make_sales_invoice(so.name, ignore_permissions=True)
 		si.shopify_order_id = shopify_order.get("id")
+		si.shopify_order_number = shopify_order.get("name")
+		si.set_posting_time = 1
+		si.posting_date = posting_date
 		si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
 		si.flags.ignore_mandatory = True
 		set_cost_center(si.items, shopify_settings.cost_center)
@@ -169,6 +180,9 @@
 
 			dn = make_delivery_note(so.name)
 			dn.shopify_order_id = fulfillment.get("order_id")
+			dn.shopify_order_number = shopify_order.get("name")
+			dn.set_posting_time = 1
+			dn.posting_date = getdate(fulfillment.get("created_at"))
 			dn.shopify_fulfillment_id = fulfillment.get("id")
 			dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
 			dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
@@ -187,7 +201,7 @@
 		discounted_amount += flt(discount.get("amount"))
 	return discounted_amount
 
-def get_order_items(order_items, shopify_settings):
+def get_order_items(order_items, shopify_settings, delivery_date):
 	items = []
 	all_product_exists = True
 	product_not_exists = []
@@ -205,7 +219,7 @@
 				"item_code": item_code,
 				"item_name": shopify_item.get("name"),
 				"rate": shopify_item.get("price"),
-				"delivery_date": nowdate(),
+				"delivery_date": delivery_date,
 				"qty": shopify_item.get("quantity"),
 				"stock_uom": shopify_item.get("uom") or _("Nos"),
 				"warehouse": shopify_settings.warehouse
@@ -265,3 +279,64 @@
 		frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
 
 	return tax_account
+
+@frappe.whitelist(allow_guest=True)
+def sync_old_orders():
+	frappe.set_user('Administrator')
+	shopify_settings = frappe.get_doc('Shopify Settings')
+
+	if not shopify_settings.sync_missing_orders:
+		return
+
+	url = get_url(shopify_settings)
+	session = get_request_session()
+
+	try:
+		res = session.get(url, headers=get_header(shopify_settings))
+		res.raise_for_status()
+		orders = res.json()["orders"]
+
+		for order in orders:
+			if is_sync_complete(shopify_settings, order):
+				stop_sync(shopify_settings)
+				return
+
+			sync_sales_order(order=order, old_order_sync=True)
+			last_order_id = order.get('id')
+
+		if last_order_id:
+			shopify_settings.load_from_db()
+			shopify_settings.last_order_id = last_order_id
+			shopify_settings.save()
+			frappe.db.commit()
+
+	except Exception as e:
+		raise e
+
+def stop_sync(shopify_settings):
+	shopify_settings.sync_missing_orders = 0
+	shopify_settings.last_order_id = ''
+	shopify_settings.save()
+	frappe.db.commit()
+
+def get_url(shopify_settings):
+	last_order_id = shopify_settings.last_order_id
+
+	if not last_order_id:
+		if shopify_settings.sync_based_on == 'Date':
+			url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&created_at_min={0}&since_id=0".format(
+				get_datetime(shopify_settings.from_date)), shopify_settings)
+		else:
+			url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(
+				shopify_settings.from_order_id), shopify_settings)
+	else:
+		url = get_shopify_url("admin/api/2020-10/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
+
+	return url
+
+def is_sync_complete(shopify_settings, order):
+	if shopify_settings.sync_based_on == 'Date':
+		return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
+	else:
+		return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)
+
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
index 2e10751..20ec063 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
@@ -1,7 +1,9 @@
 {
+ "actions": [],
  "creation": "2015-05-18 05:21:07.270859",
  "doctype": "DocType",
  "document_type": "System",
+ "engine": "InnoDB",
  "field_order": [
   "status_html",
   "enable_shopify",
@@ -40,7 +42,16 @@
   "sales_invoice_series",
   "section_break_22",
   "html_16",
-  "taxes"
+  "taxes",
+  "syncing_details_section",
+  "sync_missing_orders",
+  "sync_based_on",
+  "column_break_41",
+  "from_date",
+  "to_date",
+  "from_order_id",
+  "to_order_id",
+  "last_order_id"
  ],
  "fields": [
   {
@@ -255,10 +266,71 @@
    "fieldtype": "Table",
    "label": "Shopify Tax Account",
    "options": "Shopify Tax Account"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "syncing_details_section",
+   "fieldtype": "Section Break",
+   "label": "Syncing Missing Orders"
+  },
+  {
+   "depends_on": "eval:doc.sync_missing_orders",
+   "fieldname": "last_order_id",
+   "fieldtype": "Data",
+   "label": "Last Order Id",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_41",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "On checking this Order from the ",
+   "fieldname": "sync_missing_orders",
+   "fieldtype": "Check",
+   "label": "Sync Missing Old Shopify Orders"
+  },
+  {
+   "depends_on": "eval:doc.sync_missing_orders",
+   "fieldname": "sync_based_on",
+   "fieldtype": "Select",
+   "label": "Sync Based On",
+   "mandatory_depends_on": "eval:doc.sync_missing_orders",
+   "options": "\nDate\nShopify Order Id"
+  },
+  {
+   "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
+   "fieldname": "from_date",
+   "fieldtype": "Date",
+   "label": "From Date",
+   "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
+  },
+  {
+   "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
+   "fieldname": "to_date",
+   "fieldtype": "Date",
+   "label": "To Date",
+   "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
+  },
+  {
+   "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
+   "fieldname": "from_order_id",
+   "fieldtype": "Data",
+   "label": "From Order Id",
+   "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
+  },
+  {
+   "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
+   "fieldname": "to_order_id",
+   "fieldtype": "Data",
+   "label": "To Order Id",
+   "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
   }
  ],
  "issingle": 1,
- "modified": "2020-09-18 17:26:09.703215",
+ "links": [],
+ "modified": "2020-11-05 20:44:03.664891",
  "modified_by": "Administrator",
  "module": "ERPNext Integrations",
  "name": "Shopify Settings",
@@ -277,4 +349,4 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC"
-}
+}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
index 25ffd28..cbdf906 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
@@ -87,7 +87,7 @@
 def get_header(settings):
 	header = {'Content-Type': 'application/json'}
 
-	return header;
+	return header
 
 @frappe.whitelist()
 def get_series():
@@ -121,17 +121,23 @@
 		],
 		"Sales Order": [
 			dict(fieldname='shopify_order_id', label='Shopify Order Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
+				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
+			dict(fieldname='shopify_order_number', label='Shopify Order Number',
+				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
 		],
 		"Delivery Note":[
 			dict(fieldname='shopify_order_id', label='Shopify Order Id',
 				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
+			dict(fieldname='shopify_order_number', label='Shopify Order Number',
+				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1),
 			dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
 				fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
 		],
 		"Sales Invoice": [
 			dict(fieldname='shopify_order_id', label='Shopify Order Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
+				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
+			dict(fieldname='shopify_order_number', label='Shopify Order Number',
+				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
 		]
 	}
 
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
index 64ef3dc..30fa23c 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
@@ -58,7 +58,7 @@
 		}).save(ignore_permissions=True)
 
 		self.shopify_settings = shopify_settings
-	
+
 	def test_order(self):
 		### Create Customer ###
 		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
@@ -75,7 +75,7 @@
 		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
 			shopify_order = json.load(shopify_order)
 
-		create_order(shopify_order.get("order"), self.shopify_settings, "_Test Company")
+		create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company")
 
 		sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))})
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 21dd582..aadead2 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -308,6 +308,7 @@
 		"erpnext.projects.doctype.project.project.collect_project_status",
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
 		"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
+		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
 	],
 	"daily": [
 		"erpnext.stock.reorder_item.reorder_item",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0d8d1b4..97177de0 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -734,4 +734,5 @@
 erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
 erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
+erpnext.patches.v13_0.update_custom_fields_for_shopify
 execute:frappe.delete_doc("Report", "Quoted Item Comparison")
diff --git a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
new file mode 100644
index 0000000..f1d2ea2
--- /dev/null
+++ b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
@@ -0,0 +1,10 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields
+
+def execute():
+	if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'):
+		setup_custom_fields()