Browse Source
* feat(Non Profit): 80G Certificates and Donations * fix(Membership): Generate Invoice for membership webhook only if automation is enabled (#24849)develop
Rucha Mahabal
4 years ago
committed by
GitHub
42 changed files with 2021 additions and 330 deletions
@ -0,0 +1,26 @@ |
|||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
|||
// For license information, please see license.txt
|
|||
|
|||
frappe.ui.form.on('Donation', { |
|||
refresh: function(frm) { |
|||
if (frm.doc.docstatus === 1 && !frm.doc.paid) { |
|||
frm.add_custom_button(__('Create Payment Entry'), function() { |
|||
frm.events.make_payment_entry(frm); |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
make_payment_entry: function(frm) { |
|||
return frappe.call({ |
|||
method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry', |
|||
args: { |
|||
'dt': frm.doc.doctype, |
|||
'dn': frm.doc.name |
|||
}, |
|||
callback: function(r) { |
|||
var doc = frappe.model.sync(r.message); |
|||
frappe.set_route('Form', doc[0].doctype, doc[0].name); |
|||
} |
|||
}); |
|||
}, |
|||
}); |
@ -0,0 +1,156 @@ |
|||
{ |
|||
"actions": [], |
|||
"autoname": "naming_series:", |
|||
"creation": "2021-02-17 10:28:52.645731", |
|||
"doctype": "DocType", |
|||
"editable_grid": 1, |
|||
"engine": "InnoDB", |
|||
"field_order": [ |
|||
"naming_series", |
|||
"donor", |
|||
"donor_name", |
|||
"email", |
|||
"column_break_4", |
|||
"company", |
|||
"date", |
|||
"payment_details_section", |
|||
"paid", |
|||
"amount", |
|||
"mode_of_payment", |
|||
"razorpay_payment_id", |
|||
"amended_from" |
|||
], |
|||
"fields": [ |
|||
{ |
|||
"fieldname": "donor", |
|||
"fieldtype": "Link", |
|||
"label": "Donor", |
|||
"options": "Donor", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "donor.donor_name", |
|||
"fieldname": "donor_name", |
|||
"fieldtype": "Data", |
|||
"in_list_view": 1, |
|||
"in_standard_filter": 1, |
|||
"label": "Donor Name", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "donor.email", |
|||
"fieldname": "email", |
|||
"fieldtype": "Data", |
|||
"in_list_view": 1, |
|||
"in_standard_filter": 1, |
|||
"label": "Email", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_4", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"fieldname": "date", |
|||
"fieldtype": "Date", |
|||
"label": "Date", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "payment_details_section", |
|||
"fieldtype": "Section Break", |
|||
"label": "Payment Details" |
|||
}, |
|||
{ |
|||
"fieldname": "amount", |
|||
"fieldtype": "Currency", |
|||
"label": "Amount", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "mode_of_payment", |
|||
"fieldtype": "Link", |
|||
"label": "Mode of Payment", |
|||
"options": "Mode of Payment" |
|||
}, |
|||
{ |
|||
"fieldname": "razorpay_payment_id", |
|||
"fieldtype": "Data", |
|||
"label": "Razorpay Payment ID", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "naming_series", |
|||
"fieldtype": "Select", |
|||
"label": "Naming Series", |
|||
"options": "NPO-DTN-.YYYY.-" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "paid", |
|||
"fieldtype": "Check", |
|||
"in_list_view": 1, |
|||
"in_standard_filter": 1, |
|||
"label": "Paid" |
|||
}, |
|||
{ |
|||
"fieldname": "company", |
|||
"fieldtype": "Link", |
|||
"label": "Company", |
|||
"options": "Company", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "amended_from", |
|||
"fieldtype": "Link", |
|||
"label": "Amended From", |
|||
"no_copy": 1, |
|||
"options": "Donation", |
|||
"print_hide": 1, |
|||
"read_only": 1 |
|||
} |
|||
], |
|||
"index_web_pages_for_search": 1, |
|||
"is_submittable": 1, |
|||
"links": [], |
|||
"modified": "2021-03-11 10:53:11.269005", |
|||
"modified_by": "Administrator", |
|||
"module": "Non Profit", |
|||
"name": "Donation", |
|||
"owner": "Administrator", |
|||
"permissions": [ |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"export": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"report": 1, |
|||
"role": "System Manager", |
|||
"select": 1, |
|||
"share": 1, |
|||
"submit": 1, |
|||
"write": 1 |
|||
}, |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"export": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"report": 1, |
|||
"role": "Non Profit Manager", |
|||
"select": 1, |
|||
"share": 1, |
|||
"submit": 1, |
|||
"write": 1 |
|||
} |
|||
], |
|||
"search_fields": "donor_name, email", |
|||
"sort_field": "modified", |
|||
"sort_order": "DESC", |
|||
"title_field": "donor_name", |
|||
"track_changes": 1 |
|||
} |
@ -0,0 +1,215 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors |
|||
# For license information, please see license.txt |
|||
|
|||
from __future__ import unicode_literals |
|||
import frappe |
|||
import six |
|||
import json |
|||
from frappe.model.document import Document |
|||
from frappe import _ |
|||
from frappe.utils import getdate, flt, get_link_to_form |
|||
from frappe.email import sendmail_to_system_managers |
|||
from erpnext.non_profit.doctype.membership.membership import verify_signature |
|||
|
|||
class Donation(Document): |
|||
def validate(self): |
|||
if not self.donor or not frappe.db.exists('Donor', self.donor): |
|||
# for web forms |
|||
user_type = frappe.db.get_value('User', frappe.session.user, 'user_type') |
|||
if user_type == 'Website User': |
|||
self.create_donor_for_website_user() |
|||
else: |
|||
frappe.throw(_('Please select a Member')) |
|||
|
|||
def create_donor_for_website_user(self): |
|||
donor_name = frappe.get_value('Donor', dict(email=frappe.session.user)) |
|||
|
|||
if not donor_name: |
|||
user = frappe.get_doc('User', frappe.session.user) |
|||
donor = frappe.get_doc(dict( |
|||
doctype='Donor', |
|||
donor_type=self.get('donor_type'), |
|||
email=frappe.session.user, |
|||
member_name=user.get_fullname() |
|||
)).insert(ignore_permissions=True) |
|||
donor_name = donor.name |
|||
|
|||
if self.get('__islocal'): |
|||
self.donor = donor_name |
|||
|
|||
def on_payment_authorized(self, *args, **kwargs): |
|||
self.load_from_db() |
|||
self.create_payment_entry() |
|||
|
|||
def create_payment_entry(self): |
|||
settings = frappe.get_doc('Non Profit Settings') |
|||
if not settings.automate_donation_payment_entries: |
|||
return |
|||
|
|||
if not settings.donation_payment_account: |
|||
frappe.throw(_('You need to set <b>Payment Account</b> for Donation in {0}').format( |
|||
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))) |
|||
|
|||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry |
|||
|
|||
frappe.flags.ignore_account_permission = True |
|||
pe = get_payment_entry(dt=self.doctype, dn=self.name) |
|||
frappe.flags.ignore_account_permission = False |
|||
pe.paid_from = settings.donation_debit_account |
|||
pe.paid_to = settings.donation_payment_account |
|||
pe.reference_no = self.name |
|||
pe.reference_date = getdate() |
|||
pe.flags.ignore_mandatory = True |
|||
pe.insert() |
|||
pe.submit() |
|||
|
|||
|
|||
@frappe.whitelist(allow_guest=True) |
|||
def capture_razorpay_donations(*args, **kwargs): |
|||
""" |
|||
Creates Donation from Razorpay Webhook Request Data on payment.captured event |
|||
Creates Donor from email if not found |
|||
""" |
|||
data = frappe.request.get_data(as_text=True) |
|||
|
|||
try: |
|||
verify_signature(data, endpoint='Donation') |
|||
except Exception as e: |
|||
log = frappe.log_error(e, 'Donation Webhook Verification Error') |
|||
notify_failure(log) |
|||
return { 'status': 'Failed', 'reason': e } |
|||
|
|||
if isinstance(data, six.string_types): |
|||
data = json.loads(data) |
|||
data = frappe._dict(data) |
|||
|
|||
payment = data.payload.get('payment', {}).get('entity', {}) |
|||
payment = frappe._dict(payment) |
|||
|
|||
try: |
|||
if not data.event == 'payment.captured': |
|||
return |
|||
|
|||
donor = get_donor(payment.email) |
|||
if not donor: |
|||
donor = create_donor(payment) |
|||
|
|||
donation = create_donation(donor, payment) |
|||
donation.run_method('create_payment_entry') |
|||
|
|||
except Exception as e: |
|||
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 donation entry for {0}').format(donor.name)) |
|||
notify_failure(log) |
|||
return { 'status': 'Failed', 'reason': e } |
|||
|
|||
return { 'status': 'Success' } |
|||
|
|||
|
|||
def create_donation(donor, payment): |
|||
if not frappe.db.exists('Mode of Payment', payment.method): |
|||
create_mode_of_payment(payment.method) |
|||
|
|||
company = get_company_for_donations() |
|||
donation = frappe.get_doc({ |
|||
'doctype': 'Donation', |
|||
'company': company, |
|||
'donor': donor.name, |
|||
'donor_name': donor.donor_name, |
|||
'email': donor.email, |
|||
'date': getdate(), |
|||
'amount': flt(payment.amount), |
|||
'mode_of_payment': payment.method, |
|||
'razorpay_payment_id': payment.id |
|||
}).insert(ignore_mandatory=True) |
|||
|
|||
donation.submit() |
|||
return donation |
|||
|
|||
|
|||
def get_donor(email): |
|||
donors = frappe.get_all('Donor', |
|||
filters={'email': email}, |
|||
order_by='creation desc') |
|||
|
|||
try: |
|||
return frappe.get_doc('Donor', donors[0]['name']) |
|||
except Exception: |
|||
return None |
|||
|
|||
|
|||
@frappe.whitelist() |
|||
def create_donor(payment): |
|||
donor_details = frappe._dict(payment) |
|||
donor_type = frappe.db.get_single_value('Non Profit Settings', 'default_donor_type') |
|||
|
|||
donor = frappe.new_doc('Donor') |
|||
donor.update({ |
|||
'donor_name': donor_details.email, |
|||
'donor_type': donor_type, |
|||
'email': donor_details.email, |
|||
'contact': donor_details.contact |
|||
}) |
|||
|
|||
if donor_details.get('notes'): |
|||
donor = get_additional_notes(donor, donor_details) |
|||
|
|||
donor.insert(ignore_mandatory=True) |
|||
return donor |
|||
|
|||
|
|||
def get_company_for_donations(): |
|||
company = frappe.db.get_single_value('Non Profit Settings', 'donation_company') |
|||
if not company: |
|||
from erpnext.healthcare.setup import get_company |
|||
company = get_company() |
|||
return company |
|||
|
|||
|
|||
def get_additional_notes(donor, donor_details): |
|||
if type(donor_details.notes) == dict: |
|||
for k, v in donor_details.notes.items(): |
|||
notes = '\n'.join('{}: {}'.format(k, v)) |
|||
|
|||
# extract donor name from notes |
|||
if 'name' in k.lower(): |
|||
donor.update({ |
|||
'donor_name': donor_details.notes.get(k) |
|||
}) |
|||
|
|||
# extract pan from notes |
|||
if 'pan' in k.lower(): |
|||
donor.update({ |
|||
'pan_number': donor_details.notes.get(k) |
|||
}) |
|||
|
|||
donor.add_comment('Comment', notes) |
|||
|
|||
elif type(donor_details.notes) == str: |
|||
donor.add_comment('Comment', donor_details.notes) |
|||
|
|||
return donor |
|||
|
|||
|
|||
def create_mode_of_payment(method): |
|||
frappe.get_doc({ |
|||
'doctype': 'Mode of Payment', |
|||
'mode_of_payment': method |
|||
}).insert(ignore_mandatory=True) |
|||
|
|||
|
|||
def notify_failure(log): |
|||
try: |
|||
content = ''' |
|||
Dear System Manager, |
|||
Razorpay webhook for creating donation failed due to some reason. |
|||
Please check the error log linked below |
|||
Error Log: {0} |
|||
Regards, Administrator |
|||
'''.format(get_link_to_form('Error Log', log.name)) |
|||
|
|||
sendmail_to_system_managers(_('[Important] [ERPNext] Razorpay donation webhook failed, please check.'), content) |
|||
except Exception: |
|||
pass |
|||
|
@ -0,0 +1,16 @@ |
|||
from __future__ import unicode_literals |
|||
from frappe import _ |
|||
|
|||
def get_data(): |
|||
return { |
|||
'fieldname': 'donation', |
|||
'non_standard_fieldnames': { |
|||
'Payment Entry': 'reference_name' |
|||
}, |
|||
'transactions': [ |
|||
{ |
|||
'label': _('Payment'), |
|||
'items': ['Payment Entry'] |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,76 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors |
|||
# See license.txt |
|||
from __future__ import unicode_literals |
|||
|
|||
import frappe |
|||
import unittest |
|||
from erpnext.non_profit.doctype.donation.donation import create_donation |
|||
|
|||
class TestDonation(unittest.TestCase): |
|||
def setUp(self): |
|||
create_donor_type() |
|||
settings = frappe.get_doc('Non Profit Settings') |
|||
settings.company = '_Test Company' |
|||
settings.donation_company = '_Test Company' |
|||
settings.default_donor_type = '_Test Donor' |
|||
settings.automate_donation_payment_entries = 1 |
|||
settings.donation_debit_account = 'Debtors - _TC' |
|||
settings.donation_payment_account = 'Cash - _TC' |
|||
settings.creation_user = 'Administrator' |
|||
settings.flags.ignore_permissions = True |
|||
settings.save() |
|||
|
|||
def test_payment_entry_for_donations(self): |
|||
donor = create_donor() |
|||
create_mode_of_payment() |
|||
payment = frappe._dict({ |
|||
'amount': 100, |
|||
'method': 'Debit Card', |
|||
'id': 'pay_MeXAmsgeKOhq7O' |
|||
}) |
|||
donation = create_donation(donor, payment) |
|||
|
|||
self.assertTrue(donation.name) |
|||
|
|||
# Naive test to check if at all payment entry is generated |
|||
# This method is actually triggered from Payment Gateway |
|||
# In any case if details were missing, this would throw an error |
|||
donation.on_payment_authorized() |
|||
donation.reload() |
|||
|
|||
self.assertEquals(donation.paid, 1) |
|||
self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name})) |
|||
|
|||
|
|||
def create_donor_type(): |
|||
if not frappe.db.exists('Donor Type', '_Test Donor'): |
|||
frappe.get_doc({ |
|||
'doctype': 'Donor Type', |
|||
'donor_type': '_Test Donor' |
|||
}).insert() |
|||
|
|||
|
|||
def create_donor(): |
|||
donor = frappe.db.exists('Donor', 'donor@test.com') |
|||
if donor: |
|||
return frappe.get_doc('Donor', 'donor@test.com') |
|||
else: |
|||
return frappe.get_doc({ |
|||
'doctype': 'Donor', |
|||
'donor_name': '_Test Donor', |
|||
'donor_type': '_Test Donor', |
|||
'email': 'donor@test.com' |
|||
}).insert() |
|||
|
|||
|
|||
def create_mode_of_payment(): |
|||
if not frappe.db.exists('Mode of Payment', 'Debit Card'): |
|||
frappe.get_doc({ |
|||
'doctype': 'Mode of Payment', |
|||
'mode_of_payment': 'Debit Card', |
|||
'accounts': [{ |
|||
'company': '_Test Company', |
|||
'default_account': 'Cash - _TC' |
|||
}] |
|||
}).insert() |
@ -1,192 +0,0 @@ |
|||
{ |
|||
"actions": [], |
|||
"creation": "2020-03-29 12:57:03.005120", |
|||
"doctype": "DocType", |
|||
"editable_grid": 1, |
|||
"engine": "InnoDB", |
|||
"field_order": [ |
|||
"enable_razorpay", |
|||
"razorpay_settings_section", |
|||
"billing_cycle", |
|||
"billing_frequency", |
|||
"webhook_secret", |
|||
"column_break_6", |
|||
"enable_invoicing", |
|||
"create_for_web_forms", |
|||
"make_payment_entry", |
|||
"company", |
|||
"debit_account", |
|||
"payment_account", |
|||
"column_break_9", |
|||
"send_email", |
|||
"send_invoice", |
|||
"membership_print_format", |
|||
"inv_print_format", |
|||
"email_template" |
|||
], |
|||
"fields": [ |
|||
{ |
|||
"fieldname": "billing_cycle", |
|||
"fieldtype": "Select", |
|||
"label": "Billing Cycle", |
|||
"options": "Monthly\nYearly" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "enable_razorpay", |
|||
"fieldtype": "Check", |
|||
"label": "Enable RazorPay For Memberships" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.enable_razorpay", |
|||
"fieldname": "razorpay_settings_section", |
|||
"fieldtype": "Section Break", |
|||
"label": "RazorPay Settings" |
|||
}, |
|||
{ |
|||
"description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", |
|||
"fieldname": "billing_frequency", |
|||
"fieldtype": "Int", |
|||
"label": "Billing Frequency" |
|||
}, |
|||
{ |
|||
"fieldname": "webhook_secret", |
|||
"fieldtype": "Password", |
|||
"label": "Webhook Secret", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_6", |
|||
"fieldtype": "Section Break", |
|||
"label": "Invoicing" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.enable_invoicing", |
|||
"fieldname": "debit_account", |
|||
"fieldtype": "Link", |
|||
"label": "Debit Account", |
|||
"mandatory_depends_on": "eval:doc.enable_auto_invoicing", |
|||
"options": "Account" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_9", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.enable_invoicing", |
|||
"fieldname": "company", |
|||
"fieldtype": "Link", |
|||
"label": "Company", |
|||
"mandatory_depends_on": "eval:doc.enable_auto_invoicing", |
|||
"options": "Company" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"depends_on": "eval:doc.enable_invoicing && doc.send_email", |
|||
"fieldname": "send_invoice", |
|||
"fieldtype": "Check", |
|||
"label": "Send Invoice with Email" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "send_email", |
|||
"fieldtype": "Check", |
|||
"label": "Send Membership Acknowledgement" |
|||
}, |
|||
{ |
|||
"depends_on": "eval: doc.send_invoice", |
|||
"fieldname": "inv_print_format", |
|||
"fieldtype": "Link", |
|||
"label": "Invoice Print Format", |
|||
"mandatory_depends_on": "eval: doc.send_invoice", |
|||
"options": "Print Format" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.send_email", |
|||
"fieldname": "membership_print_format", |
|||
"fieldtype": "Link", |
|||
"label": "Membership Print Format", |
|||
"options": "Print Format" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.send_email", |
|||
"fieldname": "email_template", |
|||
"fieldtype": "Link", |
|||
"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", |
|||
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.", |
|||
"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": "2021-01-21 19:57:53.213286", |
|||
"modified_by": "Administrator", |
|||
"module": "Non Profit", |
|||
"name": "Membership Settings", |
|||
"owner": "Administrator", |
|||
"permissions": [ |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "System Manager", |
|||
"share": 1, |
|||
"write": 1 |
|||
}, |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "Non Profit Manager", |
|||
"share": 1, |
|||
"write": 1 |
|||
}, |
|||
{ |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "Non Profit Member", |
|||
"share": 1 |
|||
} |
|||
], |
|||
"quick_entry": 1, |
|||
"sort_field": "modified", |
|||
"sort_order": "DESC", |
|||
"track_changes": 1 |
|||
} |
@ -1,33 +0,0 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors |
|||
# For license information, please see license.txt |
|||
|
|||
from __future__ import unicode_literals |
|||
import frappe |
|||
from frappe import _ |
|||
from frappe.integrations.utils import get_payment_gateway_controller |
|||
from frappe.model.document import Document |
|||
|
|||
class MembershipSettings(Document): |
|||
def generate_webhook_key(self): |
|||
key = frappe.generate_hash(length=20) |
|||
self.webhook_secret = key |
|||
self.save() |
|||
|
|||
frappe.msgprint( |
|||
_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key, |
|||
_("Webhook Secret") |
|||
); |
|||
|
|||
def revoke_key(self): |
|||
self.webhook_secret = None; |
|||
self.save() |
|||
|
|||
def get_webhook_secret(self): |
|||
return self.get_password(fieldname="webhook_secret", raise_exception=False) |
|||
|
|||
@frappe.whitelist() |
|||
def get_plans_for_membership(*args, **kwargs): |
|||
controller = get_payment_gateway_controller("Razorpay") |
|||
plans = controller.get_plans() |
|||
return [plan.get("item") for plan in plans.get("items")] |
@ -0,0 +1,273 @@ |
|||
{ |
|||
"actions": [], |
|||
"creation": "2020-03-29 12:57:03.005120", |
|||
"doctype": "DocType", |
|||
"editable_grid": 1, |
|||
"engine": "InnoDB", |
|||
"field_order": [ |
|||
"enable_razorpay_for_memberships", |
|||
"razorpay_settings_section", |
|||
"billing_cycle", |
|||
"billing_frequency", |
|||
"membership_webhook_secret", |
|||
"column_break_6", |
|||
"allow_invoicing", |
|||
"automate_membership_invoicing", |
|||
"automate_membership_payment_entries", |
|||
"company", |
|||
"membership_debit_account", |
|||
"membership_payment_account", |
|||
"column_break_9", |
|||
"send_email", |
|||
"send_invoice", |
|||
"membership_print_format", |
|||
"inv_print_format", |
|||
"email_template", |
|||
"donation_settings_section", |
|||
"donation_company", |
|||
"default_donor_type", |
|||
"donation_webhook_secret", |
|||
"column_break_22", |
|||
"automate_donation_payment_entries", |
|||
"donation_debit_account", |
|||
"donation_payment_account", |
|||
"section_break_27", |
|||
"creation_user" |
|||
], |
|||
"fields": [ |
|||
{ |
|||
"fieldname": "billing_cycle", |
|||
"fieldtype": "Select", |
|||
"label": "Billing Cycle", |
|||
"options": "Monthly\nYearly" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.enable_razorpay_for_memberships", |
|||
"fieldname": "razorpay_settings_section", |
|||
"fieldtype": "Section Break", |
|||
"label": "RazorPay Settings for Memberships" |
|||
}, |
|||
{ |
|||
"description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.", |
|||
"fieldname": "billing_frequency", |
|||
"fieldtype": "Int", |
|||
"label": "Billing Frequency" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_6", |
|||
"fieldtype": "Section Break", |
|||
"label": "Membership Invoicing" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_9", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"description": "This company will be set for the Memberships created via webhook.", |
|||
"fieldname": "company", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Company", |
|||
"options": "Company", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"depends_on": "eval:doc.allow_invoicing && doc.send_email", |
|||
"fieldname": "send_invoice", |
|||
"fieldtype": "Check", |
|||
"label": "Send Invoice with Email" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "send_email", |
|||
"fieldtype": "Check", |
|||
"label": "Send Membership Acknowledgement" |
|||
}, |
|||
{ |
|||
"depends_on": "eval: doc.send_invoice", |
|||
"fieldname": "inv_print_format", |
|||
"fieldtype": "Link", |
|||
"label": "Invoice Print Format", |
|||
"mandatory_depends_on": "eval: doc.send_invoice", |
|||
"options": "Print Format" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.send_email", |
|||
"fieldname": "membership_print_format", |
|||
"fieldtype": "Link", |
|||
"label": "Membership Print Format", |
|||
"options": "Print Format" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.send_email", |
|||
"fieldname": "email_template", |
|||
"fieldtype": "Link", |
|||
"label": "Email Template", |
|||
"mandatory_depends_on": "eval:doc.send_email", |
|||
"options": "Email Template" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "allow_invoicing", |
|||
"fieldtype": "Check", |
|||
"label": "Allow Invoicing for Memberships", |
|||
"mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"depends_on": "eval:doc.allow_invoicing", |
|||
"description": "Automatically create an invoice when payment is authorized from a web form entry", |
|||
"fieldname": "automate_membership_invoicing", |
|||
"fieldtype": "Check", |
|||
"label": "Automate Invoicing for Web Forms" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"depends_on": "eval:doc.allow_invoicing", |
|||
"description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.", |
|||
"fieldname": "automate_membership_payment_entries", |
|||
"fieldtype": "Check", |
|||
"label": "Automate Payment Entry Creation" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"fieldname": "enable_razorpay_for_memberships", |
|||
"fieldtype": "Check", |
|||
"label": "Enable RazorPay For Memberships" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.automate_membership_payment_entries", |
|||
"description": "Account for accepting membership payments", |
|||
"fieldname": "membership_payment_account", |
|||
"fieldtype": "Link", |
|||
"label": "Membership Payment To", |
|||
"mandatory_depends_on": "eval:doc.automate_membership_payment_entries", |
|||
"options": "Account" |
|||
}, |
|||
{ |
|||
"fieldname": "membership_webhook_secret", |
|||
"fieldtype": "Password", |
|||
"label": "Membership Webhook Secret", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "donation_webhook_secret", |
|||
"fieldtype": "Password", |
|||
"label": "Donation Webhook Secret", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "automate_donation_payment_entries", |
|||
"description": "Account for accepting donation payments", |
|||
"fieldname": "donation_payment_account", |
|||
"fieldtype": "Link", |
|||
"label": "Donation Payment To", |
|||
"mandatory_depends_on": "automate_donation_payment_entries", |
|||
"options": "Account" |
|||
}, |
|||
{ |
|||
"default": "0", |
|||
"description": "Auto creates Payment Entry for Donations created from web forms.", |
|||
"fieldname": "automate_donation_payment_entries", |
|||
"fieldtype": "Check", |
|||
"label": "Automate Donation Payment Entries" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.allow_invoicing", |
|||
"fieldname": "membership_debit_account", |
|||
"fieldtype": "Link", |
|||
"label": "Debit Account", |
|||
"mandatory_depends_on": "eval:doc.allow_invoicing", |
|||
"options": "Account" |
|||
}, |
|||
{ |
|||
"depends_on": "automate_donation_payment_entries", |
|||
"fieldname": "donation_debit_account", |
|||
"fieldtype": "Link", |
|||
"label": "Debit Account", |
|||
"mandatory_depends_on": "automate_donation_payment_entries", |
|||
"options": "Account" |
|||
}, |
|||
{ |
|||
"description": "This company will be set for the Donations created via webhook.", |
|||
"fieldname": "donation_company", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Company", |
|||
"options": "Company", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "donation_settings_section", |
|||
"fieldtype": "Section Break", |
|||
"label": "Donation Settings" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_22", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"description": "This Donor Type will be set for the Donor created via Donation web form entry.", |
|||
"fieldname": "default_donor_type", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Default Donor Type", |
|||
"options": "Donor Type", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "section_break_27", |
|||
"fieldtype": "Section Break" |
|||
}, |
|||
{ |
|||
"description": "The user that will be used to create Donations, Memberships, Invoices, and Payment Entries. This user should have the relevant permissions.", |
|||
"fieldname": "creation_user", |
|||
"fieldtype": "Link", |
|||
"label": "Creation User", |
|||
"options": "User", |
|||
"reqd": 1 |
|||
} |
|||
], |
|||
"index_web_pages_for_search": 1, |
|||
"issingle": 1, |
|||
"links": [], |
|||
"modified": "2021-03-11 10:43:38.124240", |
|||
"modified_by": "Administrator", |
|||
"module": "Non Profit", |
|||
"name": "Non Profit Settings", |
|||
"owner": "Administrator", |
|||
"permissions": [ |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "System Manager", |
|||
"share": 1, |
|||
"write": 1 |
|||
}, |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "Non Profit Manager", |
|||
"share": 1, |
|||
"write": 1 |
|||
}, |
|||
{ |
|||
"email": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"role": "Non Profit Member", |
|||
"share": 1 |
|||
} |
|||
], |
|||
"quick_entry": 1, |
|||
"sort_field": "modified", |
|||
"sort_order": "DESC", |
|||
"track_changes": 1 |
|||
} |
@ -0,0 +1,36 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors |
|||
# For license information, please see license.txt |
|||
|
|||
from __future__ import unicode_literals |
|||
import frappe |
|||
from frappe import _ |
|||
from frappe.integrations.utils import get_payment_gateway_controller |
|||
from frappe.model.document import Document |
|||
|
|||
class NonProfitSettings(Document): |
|||
def generate_webhook_secret(self, field="membership_webhook_secret"): |
|||
key = frappe.generate_hash(length=20) |
|||
self.set(field, key) |
|||
self.save() |
|||
|
|||
secret_for = "Membership" if field == "membership_webhook_secret" else "Donation" |
|||
|
|||
frappe.msgprint( |
|||
_("Here is your webhook secret for {0} API, this will be shown to you only once.").format(secret_for) + "<br><br>" + key, |
|||
_("Webhook Secret") |
|||
) |
|||
|
|||
def revoke_key(self, key): |
|||
self.set(key, None) |
|||
self.save() |
|||
|
|||
def get_webhook_secret(self, endpoint="Membership"): |
|||
fieldname = "membership_webhook_secret" if endpoint == "Membership" else "donation_webhook_secret" |
|||
return self.get_password(fieldname=fieldname, raise_exception=False) |
|||
|
|||
@frappe.whitelist() |
|||
def get_plans_for_membership(*args, **kwargs): |
|||
controller = get_payment_gateway_controller("Razorpay") |
|||
plans = controller.get_plans() |
|||
return [plan.get("item") for plan in plans.get("items")] |
@ -0,0 +1,251 @@ |
|||
{ |
|||
"category": "Domains", |
|||
"charts": [], |
|||
"creation": "2020-03-02 17:23:47.811421", |
|||
"developer_mode_only": 0, |
|||
"disable_user_customization": 0, |
|||
"docstatus": 0, |
|||
"doctype": "Workspace", |
|||
"extends_another_page": 0, |
|||
"hide_custom": 0, |
|||
"icon": "non-profit", |
|||
"idx": 0, |
|||
"is_default": 0, |
|||
"is_standard": 1, |
|||
"label": "Non Profit", |
|||
"links": [ |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Loan Management", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Loan Type", |
|||
"link_to": "Loan Type", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Loan Application", |
|||
"link_to": "Loan Application", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Loan", |
|||
"link_to": "Loan", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Grant Application", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Grant Application", |
|||
"link_to": "Grant Application", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Membership", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Member", |
|||
"link_to": "Member", |
|||
"link_type": "DocType", |
|||
"onboard": 1, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Membership", |
|||
"link_to": "Membership", |
|||
"link_type": "DocType", |
|||
"onboard": 1, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Membership Type", |
|||
"link_to": "Membership Type", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Membership Settings", |
|||
"link_to": "Non Profit Settings", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Volunteer", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Volunteer", |
|||
"link_to": "Volunteer", |
|||
"link_type": "DocType", |
|||
"onboard": 1, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Volunteer Type", |
|||
"link_to": "Volunteer Type", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Chapter", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Chapter", |
|||
"link_to": "Chapter", |
|||
"link_type": "DocType", |
|||
"onboard": 1, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Donation", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Donor", |
|||
"link_to": "Donor", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"dependencies": "", |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Donor Type", |
|||
"link_to": "Donor Type", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Donation", |
|||
"link_to": "Donation", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Tax Exemption Certification (India)", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Card Break" |
|||
}, |
|||
{ |
|||
"hidden": 0, |
|||
"is_query_report": 0, |
|||
"label": "Tax Exemption 80G Certificate", |
|||
"link_to": "Tax Exemption 80G Certificate", |
|||
"link_type": "DocType", |
|||
"onboard": 0, |
|||
"type": "Link" |
|||
} |
|||
], |
|||
"modified": "2021-03-11 11:38:09.140655", |
|||
"modified_by": "Administrator", |
|||
"module": "Non Profit", |
|||
"name": "Non Profit", |
|||
"owner": "Administrator", |
|||
"pin_to_bottom": 0, |
|||
"pin_to_top": 0, |
|||
"restrict_to_domain": "Non Profit", |
|||
"shortcuts": [ |
|||
{ |
|||
"label": "Member", |
|||
"link_to": "Member", |
|||
"type": "DocType" |
|||
}, |
|||
{ |
|||
"label": "Non Profit Settings", |
|||
"link_to": "Non Profit Settings", |
|||
"type": "DocType" |
|||
}, |
|||
{ |
|||
"label": "Membership", |
|||
"link_to": "Membership", |
|||
"type": "DocType" |
|||
}, |
|||
{ |
|||
"label": "Chapter", |
|||
"link_to": "Chapter", |
|||
"type": "DocType" |
|||
}, |
|||
{ |
|||
"label": "Chapter Member", |
|||
"link_to": "Chapter Member", |
|||
"type": "DocType" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,22 @@ |
|||
from __future__ import unicode_literals |
|||
import frappe |
|||
from frappe.model.utils.rename_field import rename_field |
|||
|
|||
def execute(): |
|||
if frappe.db.table_exists("Membership Settings"): |
|||
frappe.rename_doc("DocType", "Membership Settings", "Non Profit Settings") |
|||
frappe.reload_doctype("Non Profit Settings", force=True) |
|||
|
|||
if frappe.db.table_exists("Non Profit Settings"): |
|||
rename_fields_map = { |
|||
"enable_invoicing": "allow_invoicing", |
|||
"create_for_web_forms": "automate_membership_invoicing", |
|||
"make_payment_entry": "automate_membership_payment_entries", |
|||
"enable_razorpay": "enable_razorpay_for_memberships", |
|||
"debit_account": "membership_debit_account", |
|||
"payment_account": "membership_payment_account", |
|||
"webhook_secret": "membership_webhook_secret" |
|||
} |
|||
|
|||
for old_name, new_name in rename_fields_map.items(): |
|||
rename_field("Non Profit Settings", old_name, new_name) |
@ -0,0 +1,16 @@ |
|||
import frappe |
|||
from erpnext.regional.india.setup import make_custom_fields |
|||
|
|||
def execute(): |
|||
company = frappe.get_all('Company', filters = {'country': 'India'}) |
|||
if not company: |
|||
return |
|||
|
|||
make_custom_fields() |
|||
|
|||
if not frappe.db.exists('Party Type', 'Donor'): |
|||
frappe.get_doc({ |
|||
'doctype': 'Party Type', |
|||
'party_type': 'Donor', |
|||
'account_type': 'Receivable' |
|||
}).insert(ignore_permissions=True) |
@ -0,0 +1,67 @@ |
|||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
|||
// For license information, please see license.txt
|
|||
|
|||
frappe.ui.form.on('Tax Exemption 80G Certificate', { |
|||
refresh: function(frm) { |
|||
if (frm.doc.donor) { |
|||
frm.set_query('donation', function() { |
|||
return { |
|||
filters: { |
|||
docstatus: 1, |
|||
donor: frm.doc.donor |
|||
} |
|||
}; |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
recipient: function(frm) { |
|||
if (frm.doc.recipient === 'Donor') { |
|||
frm.set_value({ |
|||
'member': '', |
|||
'member_name': '', |
|||
'member_email': '', |
|||
'member_pan_number': '', |
|||
'fiscal_year': '', |
|||
'total': 0, |
|||
'payments': [] |
|||
}); |
|||
} else { |
|||
frm.set_value({ |
|||
'donor': '', |
|||
'donor_name': '', |
|||
'donor_email': '', |
|||
'donor_pan_number': '', |
|||
'donation': '', |
|||
'date_of_donation': '', |
|||
'amount': 0, |
|||
'mode_of_payment': '', |
|||
'razorpay_payment_id': '' |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
get_payments: function(frm) { |
|||
frm.call({ |
|||
doc: frm.doc, |
|||
method: 'get_payments', |
|||
freeze: true |
|||
}); |
|||
}, |
|||
|
|||
company: function(frm) { |
|||
if ((frm.doc.member || frm.doc.donor) && frm.doc.company) { |
|||
frm.call({ |
|||
doc: frm.doc, |
|||
method: 'set_company_address', |
|||
freeze: true |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
donation: function(frm) { |
|||
if (frm.doc.recipient === 'Donor' && !frm.doc.donor) { |
|||
frappe.msgprint(__('Please select donor first')); |
|||
} |
|||
} |
|||
}); |
@ -0,0 +1,297 @@ |
|||
{ |
|||
"actions": [], |
|||
"autoname": "naming_series:", |
|||
"creation": "2021-02-15 12:37:21.577042", |
|||
"doctype": "DocType", |
|||
"editable_grid": 1, |
|||
"engine": "InnoDB", |
|||
"field_order": [ |
|||
"naming_series", |
|||
"recipient", |
|||
"member", |
|||
"member_name", |
|||
"member_email", |
|||
"member_pan_number", |
|||
"donor", |
|||
"donor_name", |
|||
"donor_email", |
|||
"donor_pan_number", |
|||
"column_break_4", |
|||
"date", |
|||
"fiscal_year", |
|||
"section_break_11", |
|||
"company", |
|||
"company_address", |
|||
"company_address_display", |
|||
"column_break_14", |
|||
"company_pan_number", |
|||
"company_80g_number", |
|||
"company_80g_wef", |
|||
"title", |
|||
"section_break_6", |
|||
"get_payments", |
|||
"payments", |
|||
"total", |
|||
"donation_details_section", |
|||
"donation", |
|||
"date_of_donation", |
|||
"amount", |
|||
"column_break_27", |
|||
"mode_of_payment", |
|||
"razorpay_payment_id" |
|||
], |
|||
"fields": [ |
|||
{ |
|||
"fieldname": "recipient", |
|||
"fieldtype": "Select", |
|||
"in_list_view": 1, |
|||
"label": "Certificate Recipient", |
|||
"options": "Member\nDonor", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fieldname": "member", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Member", |
|||
"mandatory_depends_on": "eval:doc.recipient === \"Member\";", |
|||
"options": "Member" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fetch_from": "member.member_name", |
|||
"fieldname": "member_name", |
|||
"fieldtype": "Data", |
|||
"label": "Member Name", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"fieldname": "donor", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Donor", |
|||
"mandatory_depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"options": "Donor" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_4", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"fieldname": "date", |
|||
"fieldtype": "Date", |
|||
"label": "Date", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fieldname": "section_break_6", |
|||
"fieldtype": "Section Break" |
|||
}, |
|||
{ |
|||
"fieldname": "payments", |
|||
"fieldtype": "Table", |
|||
"label": "Payments", |
|||
"options": "Tax Exemption 80G Certificate Detail" |
|||
}, |
|||
{ |
|||
"fieldname": "total", |
|||
"fieldtype": "Currency", |
|||
"in_list_view": 1, |
|||
"label": "Total", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fieldname": "fiscal_year", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Fiscal Year", |
|||
"options": "Fiscal Year" |
|||
}, |
|||
{ |
|||
"fieldname": "company", |
|||
"fieldtype": "Link", |
|||
"label": "Company", |
|||
"options": "Company", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "get_payments", |
|||
"fieldtype": "Button", |
|||
"label": "Get Memberships" |
|||
}, |
|||
{ |
|||
"fieldname": "naming_series", |
|||
"fieldtype": "Select", |
|||
"label": "Naming Series", |
|||
"options": "NPO-80G-.YYYY.-" |
|||
}, |
|||
{ |
|||
"fieldname": "section_break_11", |
|||
"fieldtype": "Section Break", |
|||
"label": "Company Details" |
|||
}, |
|||
{ |
|||
"fieldname": "company_address", |
|||
"fieldtype": "Link", |
|||
"label": "Company Address", |
|||
"options": "Address" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_14", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"fetch_from": "company.pan_details", |
|||
"fieldname": "company_pan_number", |
|||
"fieldtype": "Data", |
|||
"label": "PAN Number", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "company_address_display", |
|||
"fieldtype": "Small Text", |
|||
"hidden": 1, |
|||
"label": "Company Address Display", |
|||
"print_hide": 1, |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "company.company_80g_number", |
|||
"fieldname": "company_80g_number", |
|||
"fieldtype": "Data", |
|||
"label": "80G Number", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "company.with_effect_from", |
|||
"fieldname": "company_80g_wef", |
|||
"fieldtype": "Date", |
|||
"label": "80G With Effect From", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"fieldname": "donation_details_section", |
|||
"fieldtype": "Section Break", |
|||
"label": "Donation Details" |
|||
}, |
|||
{ |
|||
"fieldname": "donation", |
|||
"fieldtype": "Link", |
|||
"label": "Donation", |
|||
"mandatory_depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"options": "Donation" |
|||
}, |
|||
{ |
|||
"fetch_from": "donation.amount", |
|||
"fieldname": "amount", |
|||
"fieldtype": "Currency", |
|||
"label": "Amount", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "donation.mode_of_payment", |
|||
"fieldname": "mode_of_payment", |
|||
"fieldtype": "Link", |
|||
"label": "Mode of Payment", |
|||
"options": "Mode of Payment", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "donation.razorpay_payment_id", |
|||
"fieldname": "razorpay_payment_id", |
|||
"fieldtype": "Data", |
|||
"label": "RazorPay Payment ID", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fetch_from": "donation.date", |
|||
"fieldname": "date_of_donation", |
|||
"fieldtype": "Date", |
|||
"label": "Date of Donation", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_27", |
|||
"fieldtype": "Column Break" |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"fetch_from": "donor.donor_name", |
|||
"fieldname": "donor_name", |
|||
"fieldtype": "Data", |
|||
"label": "Donor Name", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"fetch_from": "donor.email", |
|||
"fieldname": "donor_email", |
|||
"fieldtype": "Data", |
|||
"label": "Email", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fetch_from": "member.email_id", |
|||
"fieldname": "member_email", |
|||
"fieldtype": "Data", |
|||
"in_list_view": 1, |
|||
"label": "Email", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Member\";", |
|||
"fetch_from": "member.pan_number", |
|||
"fieldname": "member_pan_number", |
|||
"fieldtype": "Data", |
|||
"label": "PAN Details", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"depends_on": "eval:doc.recipient === \"Donor\";", |
|||
"fetch_from": "donor.pan_number", |
|||
"fieldname": "donor_pan_number", |
|||
"fieldtype": "Data", |
|||
"label": "PAN Details", |
|||
"read_only": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "title", |
|||
"fieldtype": "Data", |
|||
"hidden": 1, |
|||
"label": "Title", |
|||
"print_hide": 1 |
|||
} |
|||
], |
|||
"index_web_pages_for_search": 1, |
|||
"links": [], |
|||
"modified": "2021-02-22 00:03:34.215633", |
|||
"modified_by": "Administrator", |
|||
"module": "Regional", |
|||
"name": "Tax Exemption 80G Certificate", |
|||
"owner": "Administrator", |
|||
"permissions": [ |
|||
{ |
|||
"create": 1, |
|||
"delete": 1, |
|||
"email": 1, |
|||
"export": 1, |
|||
"print": 1, |
|||
"read": 1, |
|||
"report": 1, |
|||
"role": "System Manager", |
|||
"share": 1, |
|||
"write": 1 |
|||
} |
|||
], |
|||
"search_fields": "member, member_name", |
|||
"sort_field": "modified", |
|||
"sort_order": "DESC", |
|||
"title_field": "title", |
|||
"track_changes": 1 |
|||
} |
@ -0,0 +1,89 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors |
|||
# For license information, please see license.txt |
|||
|
|||
from __future__ import unicode_literals |
|||
import frappe |
|||
from frappe import _ |
|||
from frappe.model.document import Document |
|||
from frappe.utils import getdate, flt, get_link_to_form |
|||
from erpnext.accounts.utils import get_fiscal_year |
|||
from frappe.contacts.doctype.address.address import get_company_address |
|||
|
|||
class TaxExemption80GCertificate(Document): |
|||
def validate(self): |
|||
self.validate_date() |
|||
self.validate_duplicates() |
|||
self.validate_company_details() |
|||
self.set_company_address() |
|||
self.set_title() |
|||
|
|||
def validate_date(self): |
|||
if self.recipient == 'Member': |
|||
if getdate(self.date): |
|||
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True) |
|||
|
|||
if not (fiscal_year.year_start_date <= getdate(self.date) \ |
|||
<= fiscal_year.year_end_date): |
|||
frappe.throw(_('The Certificate Date is not in the Fiscal Year {0}').format(frappe.bold(self.fiscal_year))) |
|||
|
|||
def validate_duplicates(self): |
|||
if self.recipient == 'Donor': |
|||
certificate = frappe.db.exists(self.doctype, {'donation': self.donation}) |
|||
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) |
|||
), title=_('Duplicate Certificate')) |
|||
|
|||
def validate_company_details(self): |
|||
fields = ['company_80g_number', 'with_effect_from', 'pan_details'] |
|||
company_details = frappe.db.get_value('Company', self.company, fields, as_dict=True) |
|||
if not company_details.company_80g_number: |
|||
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('80G Number'), |
|||
get_link_to_form('Company', self.company))) |
|||
|
|||
if not company_details.pan_details: |
|||
frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'), |
|||
get_link_to_form('Company', self.company))) |
|||
|
|||
def set_company_address(self): |
|||
address = get_company_address(self.company) |
|||
self.company_address = address.company_address |
|||
self.company_address_display = address.company_address_display |
|||
|
|||
def set_title(self): |
|||
if self.recipient == "Member": |
|||
self.title = self.member_name |
|||
else: |
|||
self.title = self.donor_name |
|||
|
|||
def get_payments(self): |
|||
if not self.member: |
|||
frappe.throw(_('Please select a Member first.')) |
|||
|
|||
fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True) |
|||
|
|||
memberships = frappe.db.get_all('Membership', { |
|||
'member': self.member, |
|||
'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']) |
|||
|
|||
if not memberships: |
|||
frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member)) |
|||
|
|||
total = 0 |
|||
self.payments = [] |
|||
|
|||
for doc in memberships: |
|||
self.append('payments', { |
|||
'date': doc.from_date, |
|||
'amount': doc.amount, |
|||
'invoice_id': doc.invoice, |
|||
'razorpay_payment_id': doc.payment_id, |
|||
'membership': doc.name |
|||
}) |
|||
total += flt(doc.amount) |
|||
|
|||
self.total = total |
@ -0,0 +1,101 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors |
|||
# See license.txt |
|||
from __future__ import unicode_literals |
|||
|
|||
import frappe |
|||
import unittest |
|||
from frappe.utils import getdate |
|||
from erpnext.accounts.utils import get_fiscal_year |
|||
from erpnext.non_profit.doctype.donation.test_donation import create_donor, create_mode_of_payment, create_donor_type |
|||
from erpnext.non_profit.doctype.donation.donation import create_donation |
|||
from erpnext.non_profit.doctype.membership.test_membership import setup_membership, make_membership |
|||
from erpnext.non_profit.doctype.member.member import create_member |
|||
|
|||
class TestTaxExemption80GCertificate(unittest.TestCase): |
|||
def setUp(self): |
|||
frappe.db.sql('delete from `tabTax Exemption 80G Certificate`') |
|||
frappe.db.sql('delete from `tabMembership`') |
|||
create_donor_type() |
|||
settings = frappe.get_doc('Non Profit Settings') |
|||
settings.company = '_Test Company' |
|||
settings.donation_company = '_Test Company' |
|||
settings.default_donor_type = '_Test Donor' |
|||
settings.creation_user = 'Administrator' |
|||
settings.save() |
|||
|
|||
company = frappe.get_doc('Company', '_Test Company') |
|||
company.pan_details = 'BBBTI3374C' |
|||
company.company_80g_number = 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087' |
|||
company.with_effect_from = getdate() |
|||
company.save() |
|||
|
|||
def test_duplicate_donation_certificate(self): |
|||
donor = create_donor() |
|||
create_mode_of_payment() |
|||
payment = frappe._dict({ |
|||
'amount': 100, |
|||
'method': 'Debit Card', |
|||
'id': 'pay_MeXAmsgeKOhq7O' |
|||
}) |
|||
donation = create_donation(donor, payment) |
|||
|
|||
args = frappe._dict({ |
|||
'recipient': 'Donor', |
|||
'donor': donor.name, |
|||
'donation': donation.name |
|||
}) |
|||
certificate = create_80g_certificate(args) |
|||
certificate.insert() |
|||
|
|||
# check company details |
|||
self.assertEquals(certificate.company_pan_number, 'BBBTI3374C') |
|||
self.assertEquals(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087') |
|||
|
|||
# check donation details |
|||
self.assertEquals(certificate.amount, donation.amount) |
|||
|
|||
duplicate_certificate = create_80g_certificate(args) |
|||
# duplicate validation |
|||
self.assertRaises(frappe.ValidationError, duplicate_certificate.insert) |
|||
|
|||
def test_membership_80g_certificate(self): |
|||
plan = setup_membership() |
|||
|
|||
# make test member |
|||
member_doc = create_member(frappe._dict({ |
|||
'fullname': "_Test_Member", |
|||
'email': "_test_member_erpnext@example.com", |
|||
'plan_id': plan.name |
|||
})) |
|||
member_doc.make_customer_and_link() |
|||
member = member_doc.name |
|||
|
|||
membership = make_membership(member, { "from_date": getdate() }) |
|||
invoice = membership.generate_invoice(save=True) |
|||
|
|||
args = frappe._dict({ |
|||
'recipient': 'Member', |
|||
'member': member, |
|||
'fiscal_year': get_fiscal_year(getdate(), as_dict=True).get('name') |
|||
}) |
|||
certificate = create_80g_certificate(args) |
|||
certificate.get_payments() |
|||
certificate.insert() |
|||
|
|||
self.assertEquals(len(certificate.payments), 1) |
|||
self.assertEquals(certificate.payments[0].amount, membership.amount) |
|||
self.assertEquals(certificate.payments[0].invoice_id, invoice.name) |
|||
|
|||
|
|||
def create_80g_certificate(args): |
|||
certificate = frappe.get_doc({ |
|||
'doctype': 'Tax Exemption 80G Certificate', |
|||
'recipient': args.recipient, |
|||
'date': getdate(), |
|||
'company': '_Test Company' |
|||
}) |
|||
|
|||
certificate.update(args) |
|||
|
|||
return certificate |
@ -0,0 +1,66 @@ |
|||
{ |
|||
"actions": [], |
|||
"creation": "2021-02-15 12:43:52.754124", |
|||
"doctype": "DocType", |
|||
"editable_grid": 1, |
|||
"engine": "InnoDB", |
|||
"field_order": [ |
|||
"date", |
|||
"amount", |
|||
"invoice_id", |
|||
"column_break_4", |
|||
"razorpay_payment_id", |
|||
"membership" |
|||
], |
|||
"fields": [ |
|||
{ |
|||
"fieldname": "date", |
|||
"fieldtype": "Date", |
|||
"in_list_view": 1, |
|||
"label": "Date", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "amount", |
|||
"fieldtype": "Currency", |
|||
"in_list_view": 1, |
|||
"label": "Amount", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "invoice_id", |
|||
"fieldtype": "Link", |
|||
"in_list_view": 1, |
|||
"label": "Invoice ID", |
|||
"options": "Sales Invoice", |
|||
"reqd": 1 |
|||
}, |
|||
{ |
|||
"fieldname": "razorpay_payment_id", |
|||
"fieldtype": "Data", |
|||
"label": "Razorpay Payment ID" |
|||
}, |
|||
{ |
|||
"fieldname": "membership", |
|||
"fieldtype": "Link", |
|||
"label": "Membership", |
|||
"options": "Membership" |
|||
}, |
|||
{ |
|||
"fieldname": "column_break_4", |
|||
"fieldtype": "Column Break" |
|||
} |
|||
], |
|||
"index_web_pages_for_search": 1, |
|||
"istable": 1, |
|||
"links": [], |
|||
"modified": "2021-02-15 16:35:10.777587", |
|||
"modified_by": "Administrator", |
|||
"module": "Regional", |
|||
"name": "Tax Exemption 80G Certificate Detail", |
|||
"owner": "Administrator", |
|||
"permissions": [], |
|||
"sort_field": "modified", |
|||
"sort_order": "DESC", |
|||
"track_changes": 1 |
|||
} |
@ -0,0 +1,10 @@ |
|||
# -*- coding: utf-8 -*- |
|||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors |
|||
# For license information, please see license.txt |
|||
|
|||
from __future__ import unicode_literals |
|||
# import frappe |
|||
from frappe.model.document import Document |
|||
|
|||
class TaxExemption80GCertificateDetail(Document): |
|||
pass |
@ -0,0 +1,26 @@ |
|||
{ |
|||
"absolute_value": 0, |
|||
"align_labels_right": 0, |
|||
"creation": "2021-02-22 00:17:33.878581", |
|||
"css": ".details {\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n line-height: 150%;\n}\n\n.certificate-footer {\n font-size: 15px;\n font-family: Tahoma, sans-serif;\n line-height: 140%;\n margin-top: 120px;\n}\n\n.company-address {\n color: #666666;\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n}", |
|||
"custom_format": 1, |
|||
"default_print_language": "en", |
|||
"disabled": 0, |
|||
"doc_type": "Tax Exemption 80G Certificate", |
|||
"docstatus": 0, |
|||
"doctype": "Print Format", |
|||
"font": "Default", |
|||
"html": "{% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n{%- endif %}\n\n<div>\n <h3 class=\"text-center\">{{ doc.company }} 80G Donor Certificate</h3>\n</div>\n<br><br>\n\n<div class=\"details\">\n <p> <b>{{ _(\"Certificate No. : \") }}</b> {{ doc.name }} </p>\n <p>\n \t<b>{{ _(\"Date\") }} :</b> {{ doc.get_formatted(\"date\") }}<br>\n </p>\n <br><br>\n \n <div>\n\n This is to confirm that the {{ doc.company }} received an amount of <b>{{doc.get_formatted(\"amount\")}}</b>\n from <b>{{ doc.donor_name }}</b>\n {% if doc.pan_number -%}\n bearing PAN Number {{ doc.member_pan_number }}\n {%- endif %}\n\n via the Mode of Payment {{doc.mode_of_payment}}\n\n {% if doc.razorpay_payment_id -%}\n bearing RazorPay Payment ID {{ doc.razorpay_payment_id }}\n {%- endif %}\n\n on {{ doc.get_formatted(\"date_of_donation\") }}\n <br><br>\n \n <p>\n We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n </p>\n\n </div>\n</div>\n\n<br><br>\n<p class=\"company-address text-left\"> {{doc.company_address_display }}</p>\n\n<div class=\"certificate-footer text-center\">\n <p><i>Computer generated receipt - Does not require signature</i></p><br>\n \n {% if doc.company_pan_number %}\n <p>\n <b>{{ doc.company }}'s PAN Account No :</b> {{ doc.company_pan_number }}\n <p><br>\n {% endif %}\n \n <p>\n <b>80G Number : </b> {{ doc.company_80g_number }}\n {% if doc.company_80g_wef %}\n ( w.e.f. {{ doc.get_formatted('company_80g_wef') }} )\n {% endif %}\n </p><br>\n</div>", |
|||
"idx": 0, |
|||
"line_breaks": 0, |
|||
"modified": "2021-02-22 00:20:08.516600", |
|||
"modified_by": "Administrator", |
|||
"module": "Regional", |
|||
"name": "80G Certificate for Donation", |
|||
"owner": "Administrator", |
|||
"print_format_builder": 0, |
|||
"print_format_type": "Jinja", |
|||
"raw_printing": 0, |
|||
"show_section_headings": 0, |
|||
"standard": "Yes" |
|||
} |
@ -0,0 +1,26 @@ |
|||
{ |
|||
"absolute_value": 0, |
|||
"align_labels_right": 0, |
|||
"creation": "2021-02-15 16:53:55.026611", |
|||
"css": ".details {\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n line-height: 150%;\n}\n\n.certificate-footer {\n font-size: 15px;\n font-family: Tahoma, sans-serif;\n line-height: 140%;\n margin-top: 120px;\n}\n\n.company-address {\n color: #666666;\n font-size: 15px;\n font-family: Tahoma, sans-serif;;\n}", |
|||
"custom_format": 1, |
|||
"default_print_language": "en", |
|||
"disabled": 0, |
|||
"doc_type": "Tax Exemption 80G Certificate", |
|||
"docstatus": 0, |
|||
"doctype": "Print Format", |
|||
"font": "Default", |
|||
"html": "{% if letter_head and not no_letterhead -%}\n <div class=\"letter-head\">{{ letter_head }}</div>\n{%- endif %}\n\n<div>\n <h3 class=\"text-center\">{{ doc.company }} Members 80G Donor Certificate</h3>\n <h3 class=\"text-center\">Financial Cycle {{ doc.fiscal_year }}</h3>\n</div>\n<br><br>\n\n<div class=\"details\">\n <p> <b>{{ _(\"Certificate No. : \") }}</b> {{ doc.name }} </p>\n <p>\n \t<b>{{ _(\"Date\") }} :</b> {{ doc.get_formatted(\"date\") }}<br>\n </p>\n <br><br>\n \n <div>\n This is to confirm that the {{ doc.company }} received a total amount of <b>{{doc.get_formatted(\"total\")}}</b>\n from <b>{{ doc.member_name }}</b>\n {% if doc.pan_number -%}\n bearing PAN Number {{ doc.member_pan_number }}\n {%- endif %}\n as per the payment details given below:\n \n <br><br>\n <table class=\"table table-bordered table-condensed\">\n \t<thead>\n \t\t<tr>\n \t\t\t<th >{{ _(\"Date\") }}</th>\n \t\t\t<th class=\"text-right\">{{ _(\"Amount\") }}</th>\n \t\t\t<th class=\"text-right\">{{ _(\"Invoice ID\") }}</th>\n \t\t</tr>\n \t</thead>\n \t<tbody>\n \t\t{%- for payment in doc.payments -%}\n \t\t<tr>\n \t\t\t<td> {{ payment.date }} </td>\n \t\t\t<td class=\"text-right\">{{ payment.get_formatted(\"amount\") }}</td>\n \t\t\t<td class=\"text-right\">{{ payment.invoice_id }}</td>\n \t\t</tr>\n \t\t{%- endfor -%}\n \t</tbody>\n </table>\n \n <br>\n \n <p>\n We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n </p>\n\n </div>\n</div>\n\n<br><br>\n<p class=\"company-address text-left\"> {{doc.company_address_display }}</p>\n\n<div class=\"certificate-footer text-center\">\n <p><i>Computer generated receipt - Does not require signature</i></p><br>\n \n {% if doc.company_pan_number %}\n <p>\n <b>{{ doc.company }}'s PAN Account No :</b> {{ doc.company_pan_number }}\n <p><br>\n {% endif %}\n \n <p>\n <b>80G Number : </b> {{ doc.company_80g_number }}\n {% if doc.company_80g_wef %}\n ( w.e.f. {{ doc.get_formatted('company_80g_wef') }} )\n {% endif %}\n </p><br>\n</div>", |
|||
"idx": 0, |
|||
"line_breaks": 0, |
|||
"modified": "2021-02-21 23:29:00.778973", |
|||
"modified_by": "Administrator", |
|||
"module": "Regional", |
|||
"name": "80G Certificate for Membership", |
|||
"owner": "Administrator", |
|||
"print_format_builder": 0, |
|||
"print_format_type": "Jinja", |
|||
"raw_printing": 0, |
|||
"show_section_headings": 0, |
|||
"standard": "Yes" |
|||
} |
Loading…
Reference in new issue