From 5f4a2bfc61b8f2151f7ecd7f0ddd1ca5d1495392 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 16 Mar 2021 20:08:51 +0530 Subject: [PATCH 1/4] fix(Non Profit): Membership and Donation API fixes (#24900) * fix: Donation fixes - differentiate between subscription payment and payment - issue with donation amount * fix: existing membership validation * fix: ignore subscription payments while capturing donations --- erpnext/non_profit/doctype/donation/donation.py | 6 +++++- erpnext/non_profit/doctype/membership/membership.py | 4 ++-- .../tax_exemption_80g_certificate.py | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index e947588482..6a2a06dbc8 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -91,6 +91,10 @@ def capture_razorpay_donations(*args, **kwargs): if not data.event == 'payment.captured': return + # to avoid capturing subscription payments as donations + if payment.description and 'subscription' in str(payment.description).lower(): + return + donor = get_donor(payment.email) if not donor: donor = create_donor(payment) @@ -119,7 +123,7 @@ def create_donation(donor, payment): 'donor_name': donor.donor_name, 'email': donor.email, 'date': getdate(), - 'amount': flt(payment.amount), + 'amount': flt(payment.amount) / 100, # Convert to rupees from paise 'mode_of_payment': payment.method, 'razorpay_payment_id': payment.id }).insert(ignore_mandatory=True) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index 191281f4ce..c41a2f5165 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and not frappe.session.user == "Administrator": + if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -287,7 +287,7 @@ def trigger_razorpay_subscription(*args, **kwargs): membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: - message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), __("Payment ID"), payment.id) + message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id) log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name)) notify_failure(log) return { "status": "Failed", "reason": e} diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index d734a18c3a..ef384d4602 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -29,7 +29,10 @@ class TaxExemption80GCertificate(Document): def validate_duplicates(self): if self.recipient == 'Donor': - certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) + certificate = frappe.db.exists(self.doctype, { + 'donation': self.donation, + 'name': ('!=', self.name) + }) if certificate: frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format( get_link_to_form(self.doctype, certificate), frappe.bold(self.donation) From 146cbb10f8f1e033c78d91e71da5b1b803882e73 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 17 Mar 2021 19:49:27 +0530 Subject: [PATCH 2/4] fix: calculate 80g certificate amount on validate for memberships (#24925) --- .../tax_exemption_80g_certificate.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index ef384d4602..5bbd5750f9 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -16,6 +16,7 @@ class TaxExemption80GCertificate(Document): self.validate_duplicates() self.validate_company_details() self.set_company_address() + self.calculate_total() self.set_title() def validate_date(self): @@ -54,8 +55,17 @@ class TaxExemption80GCertificate(Document): self.company_address = address.company_address self.company_address_display = address.company_address_display + def calculate_total(self): + if self.recipient == 'Donor': + return + + total = 0 + for entry in self.payments: + total += flt(entry.amount) + self.total = total + def set_title(self): - if self.recipient == "Member": + if self.recipient == 'Member': self.title = self.member_name else: self.title = self.donor_name From a54eecb230748b4541f02b71b7a5f9a5d086d219 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 20 Mar 2021 22:22:01 +0530 Subject: [PATCH 3/4] fix: membership renewal validation (#24963) --- erpnext/non_profit/doctype/membership/membership.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py index c41a2f5165..52447e4386 100644 --- a/erpnext/non_profit/doctype/membership/membership.py +++ b/erpnext/non_profit/doctype/membership/membership.py @@ -48,7 +48,7 @@ class Membership(Document): last_membership = erpnext.get_last_membership(self.member) # if person applied for offline membership - if last_membership and last_membership != self.name and not frappe.session.user == "Administrator": + if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator": # if last membership does not expire in 30 days, then do not allow to renew if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) : frappe.throw(_("You can only renew if your membership expires within 30 days")) @@ -90,6 +90,7 @@ class Membership(Document): self.validate_membership_type_and_settings(plan, settings) invoice = make_invoice(self, member, plan, settings) + self.reload() self.invoice = invoice.name if with_payment_entry: @@ -284,6 +285,7 @@ def trigger_razorpay_subscription(*args, **kwargs): settings = frappe.get_doc("Non Profit Settings") if settings.allow_invoicing and settings.automate_membership_invoicing: + membership.reload() membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True) except Exception as e: From 04c02965584f55d08756e3694c8bbbd7436f28c8 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 30 Mar 2021 12:30:40 +0530 Subject: [PATCH 4/4] fix: add filters to account link fields in Non Profit Settings - fetch memberships ordered by date in certificate --- .../non_profit/doctype/donation/donation.py | 5 ++-- .../non_profit_settings.js | 23 ++++++++++++++++++- .../tax_exemption_80g_certificate.py | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py index 6a2a06dbc8..4fd1a30ab9 100644 --- a/erpnext/non_profit/doctype/donation/donation.py +++ b/erpnext/non_profit/doctype/donation/donation.py @@ -42,7 +42,7 @@ class Donation(Document): self.load_from_db() self.create_payment_entry() - def create_payment_entry(self): + def create_payment_entry(self, date=None): settings = frappe.get_doc('Non Profit Settings') if not settings.automate_donation_payment_entries: return @@ -58,8 +58,9 @@ class Donation(Document): frappe.flags.ignore_account_permission = False pe.paid_from = settings.donation_debit_account pe.paid_to = settings.donation_payment_account + pe.posting_date = date or getdate() pe.reference_no = self.name - pe.reference_date = getdate() + pe.reference_date = date or getdate() pe.flags.ignore_mandatory = True pe.insert() pe.submit() diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js index cff92b42ab..4c4ca9834b 100644 --- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js +++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js @@ -19,7 +19,7 @@ frappe.ui.form.on("Non Profit Settings", { }; }); - frm.set_query("debit_account", function() { + frm.set_query("membership_debit_account", function() { return { filters: { "account_type": "Receivable", @@ -29,6 +29,16 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_debit_account", function() { + return { + filters: { + "account_type": "Receivable", + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + frm.set_query("membership_payment_account", function () { var account_types = ["Bank", "Cash"]; return { @@ -40,6 +50,17 @@ frappe.ui.form.on("Non Profit Settings", { }; }); + frm.set_query("donation_payment_account", function () { + var account_types = ["Bank", "Cash"]; + return { + filters: { + "account_type": ["in", account_types], + "is_group": 0, + "company": frm.doc.donation_company + } + }; + }); + let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership"; frm.set_intro(__("You can learn more about memberships in the manual. ") + `${__('ERPNext Docs')}`, true); diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py index 5bbd5750f9..41c7b23146 100644 --- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py +++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py @@ -81,7 +81,7 @@ class TaxExemption80GCertificate(Document): 'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)], 'membership_status': ('!=', 'Cancelled') - }, ['from_date', 'amount', 'name', 'invoice', 'payment_id']) + }, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date') if not memberships: frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member))