Merge branch 'develop' into membership-enhancements-2
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 38d8a62..5a5c448 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -132,16 +132,10 @@
 
 	return caller
 
-def get_last_membership():
+def get_last_membership(member):
 	'''Returns last membership if exists'''
 	last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
-		dict(member=frappe.session.user, paid=1), order_by='to_date desc', limit=1)
+		dict(member=member, paid=1), order_by='to_date desc', limit=1)
 
-	return last_membership and last_membership[0]
-
-def is_member():
-	'''Returns true if the user is still a member'''
-	last_membership = get_last_membership()
-	if last_membership and getdate(last_membership.to_date) > getdate():
-		return True
-	return False
+	if last_membership:
+		return last_membership[0]
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
index 992ef16..f190cfa 100644
--- a/erpnext/non_profit/doctype/member/member.json
+++ b/erpnext/non_profit/doctype/member/member.json
@@ -12,7 +12,6 @@
   "membership_expiry_date",
   "column_break_5",
   "membership_type",
-  "email",
   "email_id",
   "image",
   "customer_section",
@@ -65,13 +64,6 @@
    "reqd": 1
   },
   {
-   "fieldname": "email",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "User",
-   "options": "User"
-  },
-  {
    "fieldname": "image",
    "fieldtype": "Attach Image",
    "hidden": 1,
@@ -178,7 +170,7 @@
  ],
  "image_field": "image",
  "links": [],
- "modified": "2020-09-16 23:44:13.596948",
+ "modified": "2020-11-09 12:12:10.174647",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Member",
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 25d6b53..f40f278 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -18,8 +18,6 @@
 
 
 	def validate(self):
-		if self.email:
-			self.validate_email_type(self.email)
 		if self.email_id:
 			self.validate_email_type(self.email_id)
 
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index 7d15aba..853d7f5 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -24,7 +24,7 @@
 				user = frappe.get_doc('User', frappe.session.user)
 				member = frappe.get_doc(dict(
 					doctype='Member',
-					email=frappe.session.user,
+					email_id=frappe.session.user,
 					membership_type=self.membership_type,
 					member_name=user.get_fullname()
 				)).insert(ignore_permissions=True)
@@ -34,7 +34,7 @@
 				self.member = member_name
 
 		# get last membership (if active)
-		last_membership = erpnext.get_last_membership()
+		last_membership = erpnext.get_last_membership(self.member)
 
 		# if person applied for offline membership
 		if last_membership and not frappe.session.user == "Administrator":
@@ -54,9 +54,14 @@
 			self.to_date = add_months(self.from_date, 1)
 
 	def on_payment_authorized(self, status_changed_to=None):
-		if status_changed_to in ("Completed", "Authorized"):
-			self.load_from_db()
-			self.db_set('paid', 1)
+		if status_changed_to not in ("Completed", "Authorized"):
+			return
+		self.load_from_db()
+		self.db_set('paid', 1)
+		settings = frappe.get_doc("Membership Settings")
+		if settings.enable_invoicing and settings.create_for_web_forms:
+			self.generate_invoice(with_payment_entry=settings.make_payment_entry, save=True)
+
 
 	def generate_invoice(self, save=True):
 		if not (self.paid or self.currency or self.amount):
@@ -81,6 +86,20 @@
 		invoice = make_invoice(self, member, plan, settings)
 		self.invoice = invoice.name
 
+		if with_payment_entry:
+			if not settings.payment_account:
+				frappe.throw(_("You need to set <b>Payment Account</b> in Membership Settings"))
+
+			from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+			frappe.flags.ignore_account_permission=True
+			pe = get_payment_entry(dt='Sales Invoice', dn=invoice.name, bank_amount=invoice.grand_total)
+			frappe.flags.ignore_account_permission=False
+			pe.paid_to = settings.payment_account
+			pe.reference_no = self.name
+			pe.reference_date = getdate()
+			pe.save(ignore_permissions=True)
+			pe.submit()
+
 		if save:
 			self.save()
 
@@ -92,8 +111,12 @@
 			frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in Membership Settings"))
 
 		member = frappe.get_doc("Member", self.member)
+
+		if not member.email_id:
+			frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
+
 		plan = frappe.get_doc("Membership Type", self.membership_type)
-		email = member.email_id if member.email_id else member.email
+		email = member.email_id
 		attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
 
 		if self.invoice and settings.send_invoice:
@@ -153,6 +176,8 @@
 		return None
 
 def verify_signature(data):
+	if frappe.flags.in_test:
+		return True
 	signature = frappe.request.headers.get('X-Razorpay-Signature')
 
 	settings = frappe.get_doc("Membership Settings")
diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py
index b23f406..ce31b91 100644
--- a/erpnext/non_profit/doctype/membership/test_membership.py
+++ b/erpnext/non_profit/doctype/membership/test_membership.py
@@ -2,8 +2,94 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 from __future__ import unicode_literals
-
 import unittest
+import frappe
+import erpnext
+from erpnext.non_profit.doctype.member.member import create_member
+from frappe.utils import nowdate, getdate, add_months
+from erpnext.stock.doctype.item.test_item import create_item
 
 class TestMembership(unittest.TestCase):
-	pass
+	def setUp(self):
+		# Get default company
+		company = frappe.get_doc("Company", erpnext.get_default_company())
+		
+		# update membership settings
+		settings = frappe.get_doc("Membership Settings")
+		# Enable razorpay
+		settings.enable_razorpay = 1
+		settings.billing_cycle = "Monthly"
+		settings.billing_frequency = 24
+		# Enable invoicing
+		settings.enable_invoicing = 1
+		settings.make_payment_entry = 1
+		settings.company = company.name
+		settings.payment_account = company.default_cash_account
+		settings.debit_account = company.default_receivable_account
+		settings.save()
+
+		# make test plan
+		plan = frappe.new_doc("Membership Type")
+		plan.membership_type = "_rzpy_test_milythm"
+		plan.amount = 100
+		plan.razorpay_plan_id = "_rzpy_test_milythm"
+		plan.linked_item = create_item("_Test Item for Non Profit Membership").name
+		plan.insert()
+
+		# make test member
+		self.member_doc = create_member(frappe._dict({
+				'fullname': "_Test_Member",
+				'email': "_test_member_erpnext@example.com",
+				'plan_id': plan.name
+		}))
+		self.member_doc.make_customer_and_link()
+		self.member = "self.member_doc.name"
+
+	def test_auto_generate_invoice_and_payment_entry(self):
+		entry = make_membership(self.member)
+
+		# Naive test to see if at all invoice was generated and attached to member
+		# In any case if details were missing, the invoicing would throw an error
+		invoice = entry.generate_invoice(save=True)
+		self.assertEqual(invoice.name, entry.invoice)
+
+	def test_renew_within_30_days(self):
+		# create a membership for two months
+		# Should work fine
+		make_membership(self.member, { "from_date": nowdate() })
+		make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
+		
+		from frappe.utils.user import add_role
+		add_role("test@example.com", "Non Profit Manager")
+		frappe.set_user("test@example.com")
+		
+		# create next membership with expiry not within 30 days
+		self.assertRaises(frappe.ValidationError, make_membership, self.member, {
+			"from_date": add_months(nowdate(), 2),
+		})
+
+		frappe.set_user("Administrator")
+		# create the same membership but as administrator
+		new_entry = make_membership(self.member, {
+			"from_date": add_months(nowdate(), 2),
+			"to_date": add_months(nowdate(), 3),
+		})
+
+def set_config(key, value):
+	frappe.db.set_value("Membership Settings", None, key, value)
+
+def make_membership(member, payload={}):
+	data = {
+		"doctype": "Membership",
+		"member": member,
+		"membership_status": "Current",
+		"membership_type": "_rzpy_test_milythm",
+		"currency": "INR",
+		"paid": 1,
+		"from_date": nowdate(),
+		"amount": 100
+	}
+	data.update(payload)
+	membership = frappe.get_doc(data)
+	membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
+	return membership
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership_settings/membership_settings.json b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
index 5b6bab5..961a9b9 100644
--- a/erpnext/non_profit/doctype/membership_settings/membership_settings.json
+++ b/erpnext/non_profit/doctype/membership_settings/membership_settings.json
@@ -11,9 +11,12 @@
   "billing_frequency",
   "webhook_secret",
   "column_break_6",
-  "enable_auto_invoicing",
+  "enable_invoicing",
+  "create_for_web_forms",
+  "make_payment_entry",
   "company",
   "debit_account",
+  "payment_account",
   "column_break_9",
   "send_email",
   "send_invoice",
@@ -58,14 +61,7 @@
    "label": "Invoicing"
   },
   {
-   "default": "0",
-   "fieldname": "enable_auto_invoicing",
-   "fieldtype": "Check",
-   "label": "Enable Auto Invoicing",
-   "mandatory_depends_on": "eval:doc.send_invoice"
-  },
-  {
-   "depends_on": "eval:doc.enable_auto_invoicing",
+   "depends_on": "eval:doc.enable_invoicing",
    "fieldname": "debit_account",
    "fieldtype": "Link",
    "label": "Debit Account",
@@ -77,7 +73,7 @@
    "fieldtype": "Column Break"
   },
   {
-   "depends_on": "eval:doc.enable_auto_invoicing",
+   "depends_on": "eval:doc.enable_invoicing",
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
@@ -86,7 +82,7 @@
   },
   {
    "default": "0",
-   "depends_on": "eval:doc.enable_auto_invoicing && doc.send_email",
+   "depends_on": "eval:doc.enable_invoicing && doc.send_email",
    "fieldname": "send_invoice",
    "fieldtype": "Check",
    "label": "Send Invoice with Email"
@@ -119,11 +115,42 @@
    "label": "Email Template",
    "mandatory_depends_on": "eval:doc.send_email",
    "options": "Email Template"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_invoicing",
+   "fieldtype": "Check",
+   "label": "Enable Invoicing",
+   "mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.enable_invoicing",
+   "fieldname": "make_payment_entry",
+   "fieldtype": "Check",
+   "label": "Make Payment Entry"
+  },
+  {
+   "depends_on": "eval:doc.make_payment_entry",
+   "fieldname": "payment_account",
+   "fieldtype": "Link",
+   "label": "Payment To",
+   "mandatory_depends_on": "eval:doc.make_payment_entry",
+   "options": "Account"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.enable_invoicing",
+   "description": "Automatically create an invoice when payment is authorized from a web form entry",
+   "fieldname": "create_for_web_forms",
+   "fieldtype": "Check",
+   "label": "Auto Create Invoice for Web Forms"
   }
  ],
+ "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-08-05 17:26:37.287395",
+ "modified": "2020-11-09 12:28:49.972434",
  "modified_by": "Administrator",
  "module": "Non Profit",
  "name": "Membership Settings",
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js
index 43311a2..94ccdd8 100644
--- a/erpnext/non_profit/doctype/membership_type/membership_type.js
+++ b/erpnext/non_profit/doctype/membership_type/membership_type.js
@@ -2,12 +2,12 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Membership Type', {
-	refresh: function(frm) {
+	refresh: function (frm) {
 		frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
 			if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
 		});
 
-		frappe.db.get_single_value("Membership Settings", "enable_auto_invoicing").then(val => {
+		frappe.db.get_single_value("Membership Settings", "enable_invoicing").then(val => {
 			if (val) frm.set_df_property('linked_item', 'hidden', false);
 		});
 	}
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7b2428e..62e9b5c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -736,8 +736,9 @@
 erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 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")
+erpnext.patches.v13_0.update_member_email_address
+erpnext.patches.v13_0.update_custom_fields_for_shopify
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.add_po_to_global_search
diff --git a/erpnext/patches/v13_0/update_member_email_address.py b/erpnext/patches/v13_0/update_member_email_address.py
new file mode 100644
index 0000000..da7828a
--- /dev/null
+++ b/erpnext/patches/v13_0/update_member_email_address.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	"""add value to email_id column from email"""
+	
+	if frappe.db.has_column("Member", "email"):
+		# Get all members
+		for member in frappe.db.get_all("Member", pluck="name"):
+			# Check if email_id already exists
+			if not frappe.db.get_value("Member", member, "email_id"):
+				# fetch email id from the user linked field email
+				email = frappe.db.get_value("Member", member, "email")
+
+				# Set the value for it
+				frappe.db.set_value("Member", member, "email_id", email)