feat(Non Profit): 80G Certificates and Donations (#24657)
* feat: 80G Certificates * feat: add non-profit custom fields for India * feat: 80G Certificate print format for memberships * feat: Donation doctype and API endpoint to capture donations via razorpay * chore: Rename Membership Settings to Non Profit Settings * chore: clean up Non Profit Settings - Rename fields, better labels - patch for renaming * feat: Webhook secret generation for Razorpay donations in Non-Profit Settings * feat: Payment Entry for donations - added Donor as Party Type - setting for automating payment entries for donations created via web form * fix: linter and sider issues * fix: translation syntax * feat: PAN Details custom field for Indian donors * feat: 80G certificates for Donations with Print Format * fix: sider * feat: validations for donor and donation - create donor for website user from donations - validate donor email * feat: extract member name from subscription notes * test: Donation * test: Tax Exemption 80G Certificate * chore: styling fixes * fix: tests * fix: sider * feat: extract PAN number from additional subscription notes * feat: Add creation user field in Non Profit Settings fix: Payment Entry not generating for memberships and donations * feat: update desk page * fix: tests
This commit is contained in:
parent
63eb6bdb3c
commit
be2c1fca7b
@ -92,14 +92,16 @@ frappe.ui.form.on('Payment Entry', {
|
||||
});
|
||||
|
||||
frm.set_query("reference_doctype", "references", function() {
|
||||
if (frm.doc.party_type=="Customer") {
|
||||
if (frm.doc.party_type == "Customer") {
|
||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||
} else if (frm.doc.party_type=="Supplier") {
|
||||
} else if (frm.doc.party_type == "Supplier") {
|
||||
var doctypes = ["Purchase Order", "Purchase Invoice", "Journal Entry"];
|
||||
} else if (frm.doc.party_type=="Employee") {
|
||||
} else if (frm.doc.party_type == "Employee") {
|
||||
var doctypes = ["Expense Claim", "Journal Entry"];
|
||||
} else if (frm.doc.party_type=="Student") {
|
||||
} else if (frm.doc.party_type == "Student") {
|
||||
var doctypes = ["Fees"];
|
||||
} else if (frm.doc.party_type == "Donor") {
|
||||
var doctypes = ["Donation"];
|
||||
} else {
|
||||
var doctypes = ["Journal Entry"];
|
||||
}
|
||||
@ -128,7 +130,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
const child = locals[cdt][cdn];
|
||||
const filters = {"docstatus": 1, "company": doc.company};
|
||||
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
|
||||
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
|
||||
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
|
||||
|
||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||
@ -281,7 +283,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
let party_types = Object.keys(frappe.boot.party_account_types);
|
||||
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
||||
frm.set_value("party_type", "");
|
||||
frappe.throw(__("Party can only be one of "+ party_types.join(", ")));
|
||||
frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")]));
|
||||
}
|
||||
|
||||
frm.set_query("party", function() {
|
||||
@ -705,7 +707,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||
) {
|
||||
if(total_positive_outstanding > total_negative_outstanding)
|
||||
if (!frm.doc.paid_amount)
|
||||
@ -748,7 +751,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
|
||||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
|
||||
) {
|
||||
if(total_positive_outstanding_including_order > paid_amount) {
|
||||
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
|
||||
@ -905,6 +909,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
|
||||
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
|
||||
frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (row) {
|
||||
|
@ -72,6 +72,7 @@ class PaymentEntry(AccountsController):
|
||||
self.update_outstanding_amounts()
|
||||
self.update_advance_paid()
|
||||
self.update_expense_claim()
|
||||
self.update_donation()
|
||||
self.update_payment_schedule()
|
||||
self.set_status()
|
||||
|
||||
@ -82,6 +83,7 @@ class PaymentEntry(AccountsController):
|
||||
self.update_outstanding_amounts()
|
||||
self.update_advance_paid()
|
||||
self.update_expense_claim()
|
||||
self.update_donation(cancel=1)
|
||||
self.delink_advance_entry_references()
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.set_payment_req_status()
|
||||
@ -245,6 +247,8 @@ class PaymentEntry(AccountsController):
|
||||
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance")
|
||||
elif self.party_type == "Shareholder":
|
||||
valid_reference_doctypes = ("Journal Entry")
|
||||
elif self.party_type == "Donor":
|
||||
valid_reference_doctypes = ("Donation")
|
||||
|
||||
for d in self.get("references"):
|
||||
if not d.allocated_amount:
|
||||
@ -618,6 +622,13 @@ class PaymentEntry(AccountsController):
|
||||
doc = frappe.get_doc("Expense Claim", d.reference_name)
|
||||
update_reimbursed_amount(doc, self.name)
|
||||
|
||||
def update_donation(self, cancel=0):
|
||||
if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:
|
||||
for d in self.get("references"):
|
||||
if d.reference_doctype=="Donation" and d.reference_name:
|
||||
is_paid = 0 if cancel else 1
|
||||
frappe.db.set_value("Donation", d.reference_name, "paid", is_paid)
|
||||
|
||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||
self.reference_no = reference_doc.name
|
||||
self.reference_date = nowdate()
|
||||
@ -917,6 +928,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
exchange_rate = 1
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
elif reference_doctype == "Donation":
|
||||
total_amount = ref_doc.get("amount")
|
||||
exchange_rate = 1
|
||||
elif reference_doctype == "Dunning":
|
||||
total_amount = ref_doc.get("dunning_amount")
|
||||
exchange_rate = 1
|
||||
@ -1166,8 +1180,10 @@ def set_party_type(dt):
|
||||
party_type = "Supplier"
|
||||
elif dt in ("Expense Claim", "Employee Advance"):
|
||||
party_type = "Employee"
|
||||
elif dt in ("Fees"):
|
||||
elif dt == "Fees":
|
||||
party_type = "Student"
|
||||
elif dt == "Donation":
|
||||
party_type = "Donor"
|
||||
return party_type
|
||||
|
||||
def set_party_account(dt, dn, doc, party_type):
|
||||
@ -1193,7 +1209,7 @@ def set_party_account_currency(dt, party_account, doc):
|
||||
return party_account_currency
|
||||
|
||||
def set_payment_type(dt, doc):
|
||||
if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||
if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
|
||||
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
@ -1226,6 +1242,9 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
||||
elif dt == "Dunning":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.grand_total
|
||||
elif dt == "Donation":
|
||||
grand_total = doc.amount
|
||||
outstanding_amount = doc.amount
|
||||
else:
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||
|
26
erpnext/non_profit/doctype/donation/donation.js
Normal file
26
erpnext/non_profit/doctype/donation/donation.js
Normal file
@ -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);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
156
erpnext/non_profit/doctype/donation/donation.json
Normal file
156
erpnext/non_profit/doctype/donation/donation.json
Normal file
@ -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
|
||||
}
|
215
erpnext/non_profit/doctype/donation/donation.py
Normal file
215
erpnext/non_profit/doctype/donation/donation.py
Normal file
@ -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
|
||||
|
16
erpnext/non_profit/doctype/donation/donation_dashboard.py
Normal file
16
erpnext/non_profit/doctype/donation/donation_dashboard.py
Normal file
@ -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']
|
||||
}
|
||||
]
|
||||
}
|
76
erpnext/non_profit/doctype/donation/test_donation.py
Normal file
76
erpnext/non_profit/doctype/donation/test_donation.py
Normal file
@ -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()
|
@ -76,8 +76,13 @@
|
||||
}
|
||||
],
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2020-09-16 23:46:04.083274",
|
||||
"links": [
|
||||
{
|
||||
"link_doctype": "Donation",
|
||||
"link_fieldname": "donor"
|
||||
}
|
||||
],
|
||||
"modified": "2021-02-17 16:36:33.470731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Donor",
|
||||
|
@ -11,3 +11,8 @@ class Donor(Document):
|
||||
"""Load address and contacts in `__onload`"""
|
||||
load_address_and_contact(self)
|
||||
|
||||
def validate(self):
|
||||
from frappe.utils import validate_email_address
|
||||
if self.email:
|
||||
validate_email_address(self.email.strip(), True)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('Member', {
|
||||
setup: function(frm) {
|
||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
||||
frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
|
||||
if (val && (frm.doc.subscription_id || frm.doc.customer_id)) {
|
||||
frm.set_df_property('razorpay_details_section', 'hidden', false);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.contacts.address_and_contact import load_address_and_contact
|
||||
from frappe.utils import cint
|
||||
from frappe.utils import cint, get_link_to_form
|
||||
from frappe.integrations.utils import get_payment_gateway_controller
|
||||
from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
|
||||
|
||||
@ -26,9 +26,10 @@ class Member(Document):
|
||||
validate_email_address(email.strip(), True)
|
||||
|
||||
def setup_subscription(self):
|
||||
membership_settings = frappe.get_doc("Membership Settings")
|
||||
if not membership_settings.enable_razorpay:
|
||||
frappe.throw("Please enable Razorpay to setup subscription")
|
||||
non_profit_settings = frappe.get_doc('Non Profit Settings')
|
||||
if not non_profit_settings.enable_razorpay_for_memberships:
|
||||
frappe.throw('Please check Enable Razorpay for Memberships in {0} to setup subscription').format(
|
||||
get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
|
||||
|
||||
controller = get_payment_gateway_controller("Razorpay")
|
||||
settings = controller.get_settings({})
|
||||
@ -40,7 +41,7 @@ class Member(Document):
|
||||
|
||||
subscription_details = {
|
||||
"plan_id": plan_id,
|
||||
"billing_frequency": cint(membership_settings.billing_frequency),
|
||||
"billing_frequency": cint(non_profit_settings.billing_frequency),
|
||||
"customer_notify": 1
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
frappe.ui.form.on('Membership', {
|
||||
setup: function(frm) {
|
||||
frappe.db.get_single_value("Membership Settings", "enable_razorpay").then(val => {
|
||||
frappe.db.get_single_value("Non Profit Settings", "enable_razorpay_for_memberships").then(val => {
|
||||
if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
|
||||
})
|
||||
},
|
||||
@ -26,7 +26,7 @@ frappe.ui.form.on('Membership', {
|
||||
});
|
||||
});
|
||||
|
||||
frappe.db.get_single_value("Membership Settings", "send_email").then(val => {
|
||||
frappe.db.get_single_value("Non Profit Settings", "send_email").then(val => {
|
||||
if (val) frm.add_custom_button("Send Acknowledgement", () => {
|
||||
frm.call("send_acknowlement").then(() => {
|
||||
frm.reload_doc();
|
||||
|
@ -10,6 +10,7 @@
|
||||
"member_name",
|
||||
"membership_type",
|
||||
"column_break_3",
|
||||
"company",
|
||||
"membership_status",
|
||||
"membership_validity_section",
|
||||
"from_date",
|
||||
@ -132,11 +133,18 @@
|
||||
"fieldtype": "Data",
|
||||
"label": "Member Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-21 16:31:20.032656",
|
||||
"modified": "2021-02-19 14:33:44.925122",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Membership",
|
||||
|
@ -6,6 +6,7 @@ from __future__ import unicode_literals
|
||||
import json
|
||||
import frappe
|
||||
import six
|
||||
import os
|
||||
from datetime import datetime
|
||||
from frappe.model.document import Document
|
||||
from frappe.email import sendmail_to_system_managers
|
||||
@ -58,7 +59,7 @@ class Membership(Document):
|
||||
else:
|
||||
self.from_date = nowdate()
|
||||
|
||||
if frappe.db.get_single_value("Membership Settings", "billing_cycle") == "Yearly":
|
||||
if frappe.db.get_single_value("Non Profit Settings", "billing_cycle") == "Yearly":
|
||||
self.to_date = add_years(self.from_date, 1)
|
||||
else:
|
||||
self.to_date = add_months(self.from_date, 1)
|
||||
@ -68,9 +69,9 @@ class Membership(Document):
|
||||
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)
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
if settings.allow_invoicing and settings.automate_membership_invoicing:
|
||||
self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
|
||||
|
||||
|
||||
def generate_invoice(self, save=True, with_payment_entry=False):
|
||||
@ -85,7 +86,7 @@ class Membership(Document):
|
||||
frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
|
||||
|
||||
plan = frappe.get_doc("Membership Type", self.membership_type)
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
self.validate_membership_type_and_settings(plan, settings)
|
||||
|
||||
invoice = make_invoice(self, member, plan, settings)
|
||||
@ -102,7 +103,7 @@ class Membership(Document):
|
||||
def validate_membership_type_and_settings(self, plan, settings):
|
||||
settings_link = get_link_to_form("Membership Type", self.membership_type)
|
||||
|
||||
if not settings.debit_account:
|
||||
if not settings.membership_debit_account:
|
||||
frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
|
||||
|
||||
if not settings.company:
|
||||
@ -113,25 +114,26 @@ class Membership(Document):
|
||||
get_link_to_form("Membership Type", self.membership_type)))
|
||||
|
||||
def make_payment_entry(self, settings, invoice):
|
||||
if not settings.payment_account:
|
||||
frappe.throw(_("You need to set <b>Payment Account</b> in {0}").format(
|
||||
get_link_to_form("Membership Type", self.membership_type)))
|
||||
if not settings.membership_payment_account:
|
||||
frappe.throw(_("You need to set <b>Payment Account</b> for Membership 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="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
|
||||
frappe.flags.ignore_account_permission=False
|
||||
pe.paid_to = settings.payment_account
|
||||
pe.paid_to = settings.membership_payment_account
|
||||
pe.reference_no = self.name
|
||||
pe.reference_date = getdate()
|
||||
pe.save(ignore_permissions=True)
|
||||
pe.flags.ignore_mandatory = True
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
def send_acknowlement(self):
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
if not settings.send_email:
|
||||
frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
|
||||
get_link_to_form("Membership Settings", "Membership Settings")))
|
||||
get_link_to_form("Non Profit Settings", "Non Profit Settings")))
|
||||
|
||||
member = frappe.get_doc("Member", self.member)
|
||||
if not member.email_id:
|
||||
@ -170,7 +172,7 @@ def make_invoice(membership, member, plan, settings):
|
||||
invoice = frappe.get_doc({
|
||||
"doctype": "Sales Invoice",
|
||||
"customer": member.customer,
|
||||
"debit_to": settings.debit_account,
|
||||
"debit_to": settings.membership_debit_account,
|
||||
"currency": membership.currency,
|
||||
"company": settings.company,
|
||||
"is_pos": 0,
|
||||
@ -183,7 +185,7 @@ def make_invoice(membership, member, plan, settings):
|
||||
]
|
||||
})
|
||||
invoice.set_missing_values()
|
||||
invoice.insert(ignore_permissions=True)
|
||||
invoice.insert()
|
||||
invoice.submit()
|
||||
|
||||
frappe.msgprint(_("Sales Invoice created successfully"))
|
||||
@ -203,17 +205,18 @@ def get_member_based_on_subscription(subscription_id, email):
|
||||
return None
|
||||
|
||||
|
||||
def verify_signature(data):
|
||||
if frappe.flags.in_test:
|
||||
def verify_signature(data, endpoint="Membership"):
|
||||
if frappe.flags.in_test or os.environ.get("CI"):
|
||||
return True
|
||||
signature = frappe.request.headers.get("X-Razorpay-Signature")
|
||||
|
||||
settings = frappe.get_doc("Membership Settings")
|
||||
key = settings.get_webhook_secret()
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
key = settings.get_webhook_secret(endpoint)
|
||||
|
||||
controller = frappe.get_doc("Razorpay Settings")
|
||||
|
||||
controller.verify_signature(data, signature, key)
|
||||
frappe.set_user(settings.creation_user)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@ -222,7 +225,7 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
try:
|
||||
verify_signature(data)
|
||||
except Exception as e:
|
||||
log = frappe.log_error(e, "Webhook Verification Error")
|
||||
log = frappe.log_error(e, "Membership Webhook Verification Error")
|
||||
notify_failure(log)
|
||||
return { "status": "Failed", "reason": e}
|
||||
|
||||
@ -250,16 +253,15 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
|
||||
member.subscription_id = subscription.id
|
||||
member.customer_id = payment.customer_id
|
||||
if subscription.notes and type(subscription.notes) == dict:
|
||||
notes = "\n".join("{}: {}".format(k, v) for k, v in subscription.notes.items())
|
||||
member.add_comment("Comment", notes)
|
||||
elif subscription.notes and type(subscription.notes) == str:
|
||||
member.add_comment("Comment", subscription.notes)
|
||||
|
||||
if subscription.get("notes"):
|
||||
member = get_additional_notes(member, subscription)
|
||||
|
||||
company = get_company_for_memberships()
|
||||
# Update Membership
|
||||
membership = frappe.new_doc("Membership")
|
||||
membership.update({
|
||||
"company": company,
|
||||
"member": member.name,
|
||||
"membership_status": "Current",
|
||||
"membership_type": member.membership_type,
|
||||
@ -270,13 +272,19 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
"to_date": datetime.fromtimestamp(subscription.current_end),
|
||||
"amount": payment.amount / 100 # Convert to rupees from paise
|
||||
})
|
||||
membership.insert(ignore_permissions=True)
|
||||
membership.flags.ignore_mandatory = True
|
||||
membership.insert()
|
||||
|
||||
# Update membership values
|
||||
member.subscription_start = datetime.fromtimestamp(subscription.start_at)
|
||||
member.subscription_end = datetime.fromtimestamp(subscription.end_at)
|
||||
member.subscription_activated = 1
|
||||
member.save(ignore_permissions=True)
|
||||
member.flags.ignore_mandatory = True
|
||||
member.save()
|
||||
|
||||
automate_payment = frappe.db.get_single_value("Membership Settings", "automate_membership_payment_entries")
|
||||
membership.generate_invoice(with_payment_entry=automate_payment, save=True)
|
||||
|
||||
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 membership entry for {0}").format(member.name))
|
||||
@ -286,6 +294,39 @@ def trigger_razorpay_subscription(*args, **kwargs):
|
||||
return { "status": "Success" }
|
||||
|
||||
|
||||
def get_company_for_memberships():
|
||||
company = frappe.db.get_single_value("Non Profit Settings", "company")
|
||||
if not company:
|
||||
from erpnext.healthcare.setup import get_company
|
||||
company = get_company()
|
||||
return company
|
||||
|
||||
|
||||
def get_additional_notes(member, subscription):
|
||||
if type(subscription.notes) == dict:
|
||||
for k, v in subscription.notes.items():
|
||||
notes = "\n".join("{}: {}".format(k, v))
|
||||
|
||||
# extract member name from notes
|
||||
if "name" in k.lower():
|
||||
member.update({
|
||||
"member_name": subscription.notes.get(k)
|
||||
})
|
||||
|
||||
# extract pan number from notes
|
||||
if "pan" in k.lower():
|
||||
member.update({
|
||||
"pan_number": subscription.notes.get(k)
|
||||
})
|
||||
|
||||
member.add_comment("Comment", notes)
|
||||
|
||||
elif type(subscription.notes) == str:
|
||||
member.add_comment("Comment", subscription.notes)
|
||||
|
||||
return member
|
||||
|
||||
|
||||
def notify_failure(log):
|
||||
try:
|
||||
content = """
|
||||
|
@ -10,33 +10,7 @@ from frappe.utils import nowdate, add_months
|
||||
|
||||
class TestMembership(unittest.TestCase):
|
||||
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
|
||||
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
||||
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()
|
||||
else:
|
||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||
plan = setup_membership()
|
||||
|
||||
# make test member
|
||||
self.member_doc = create_member(frappe._dict({
|
||||
@ -78,7 +52,7 @@ class TestMembership(unittest.TestCase):
|
||||
})
|
||||
|
||||
def set_config(key, value):
|
||||
frappe.db.set_value("Membership Settings", None, key, value)
|
||||
frappe.db.set_value("Non Profit Settings", None, key, value)
|
||||
|
||||
def make_membership(member, payload={}):
|
||||
data = {
|
||||
@ -109,3 +83,36 @@ def create_item(item_code):
|
||||
else:
|
||||
item = frappe.get_doc("Item", item_code)
|
||||
return item
|
||||
|
||||
def setup_membership():
|
||||
# Get default company
|
||||
company = frappe.get_doc("Company", erpnext.get_default_company())
|
||||
|
||||
# update non profit settings
|
||||
settings = frappe.get_doc("Non Profit Settings")
|
||||
# Enable razorpay
|
||||
settings.enable_razorpay_for_memberships = 1
|
||||
settings.billing_cycle = "Monthly"
|
||||
settings.billing_frequency = 24
|
||||
# Enable invoicing
|
||||
settings.allow_invoicing = 1
|
||||
settings.automate_membership_payment_entries = 1
|
||||
settings.company = company.name
|
||||
settings.donation_company = company.name
|
||||
settings.membership_payment_account = company.default_cash_account
|
||||
settings.membership_debit_account = company.default_receivable_account
|
||||
settings.flags.ignore_mandatory = True
|
||||
settings.save()
|
||||
|
||||
# make test plan
|
||||
if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
|
||||
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()
|
||||
else:
|
||||
plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
|
||||
|
||||
return plan
|
@ -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
|
||||
}
|
@ -3,11 +3,11 @@
|
||||
|
||||
frappe.ui.form.on('Membership Type', {
|
||||
refresh: function (frm) {
|
||||
frappe.db.get_single_value('Membership Settings', 'enable_razorpay').then(val => {
|
||||
frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
|
||||
if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
|
||||
});
|
||||
|
||||
frappe.db.get_single_value('Membership Settings', 'enable_invoicing').then(val => {
|
||||
frappe.db.get_single_value('Non Profit Settings', 'allow_invoicing').then(val => {
|
||||
if (val) frm.set_df_property('linked_item', 'hidden', false);
|
||||
});
|
||||
|
||||
|
@ -1,16 +1,8 @@
|
||||
// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Membership Settings", {
|
||||
frappe.ui.form.on("Non Profit Settings", {
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.webhook_secret) {
|
||||
frm.add_custom_button(__("Revoke <Key></Key>"), () => {
|
||||
frm.call("revoke_key").then(() => {
|
||||
frm.refresh();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query("inv_print_format", function() {
|
||||
return {
|
||||
filters: {
|
||||
@ -37,7 +29,7 @@ frappe.ui.form.on("Membership Settings", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("payment_account", function () {
|
||||
frm.set_query("membership_payment_account", function () {
|
||||
var account_types = ["Bank", "Cash"];
|
||||
return {
|
||||
filters: {
|
||||
@ -51,31 +43,70 @@ frappe.ui.form.on("Membership Settings", {
|
||||
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. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
|
||||
|
||||
frm.trigger("add_generate_button");
|
||||
frm.trigger("add_copy_buttonn");
|
||||
frm.trigger("setup_buttons_for_membership");
|
||||
frm.trigger("setup_buttons_for_donation");
|
||||
},
|
||||
|
||||
add_generate_button: function(frm) {
|
||||
setup_buttons_for_membership: function(frm) {
|
||||
let label;
|
||||
|
||||
if (frm.doc.webhook_secret) {
|
||||
if (frm.doc.membership_webhook_secret) {
|
||||
|
||||
frm.add_custom_button(__("Copy Webhook URL"), () => {
|
||||
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
|
||||
}, __("Memberships"));
|
||||
|
||||
frm.add_custom_button(__("Revoke Key"), () => {
|
||||
frm.call("revoke_key", {
|
||||
key: "membership_webhook_secret"
|
||||
}).then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
}, __("Memberships"));
|
||||
|
||||
label = __("Regenerate Webhook Secret");
|
||||
|
||||
} else {
|
||||
label = __("Generate Webhook Secret");
|
||||
}
|
||||
|
||||
frm.add_custom_button(label, () => {
|
||||
frm.call("generate_webhook_key").then(() => {
|
||||
frm.call("generate_webhook_secret", {
|
||||
field: "membership_webhook_secret"
|
||||
}).then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
});
|
||||
}, __("Memberships"));
|
||||
},
|
||||
|
||||
add_copy_buttonn: function(frm) {
|
||||
if (frm.doc.webhook_secret) {
|
||||
setup_buttons_for_donation: function(frm) {
|
||||
let label;
|
||||
|
||||
if (frm.doc.donation_webhook_secret) {
|
||||
label = __("Regenerate Webhook Secret");
|
||||
|
||||
frm.add_custom_button(__("Copy Webhook URL"), () => {
|
||||
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
|
||||
});
|
||||
frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.donation.donation.capture_razorpay_donations`);
|
||||
}, __("Donations"));
|
||||
|
||||
frm.add_custom_button(__("Revoke Key"), () => {
|
||||
frm.call("revoke_key", {
|
||||
key: "donation_webhook_secret"
|
||||
}).then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
}, __("Donations"));
|
||||
|
||||
} else {
|
||||
label = __("Generate Webhook Secret");
|
||||
}
|
||||
|
||||
frm.add_custom_button(label, () => {
|
||||
frm.call("generate_webhook_secret", {
|
||||
field: "donation_webhook_secret"
|
||||
}).then(() => {
|
||||
frm.refresh();
|
||||
});
|
||||
}, __("Donations"));
|
||||
}
|
||||
});
|
@ -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
|
||||
}
|
@ -8,23 +8,26 @@ 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):
|
||||
class NonProfitSettings(Document):
|
||||
def generate_webhook_secret(self, field):
|
||||
key = frappe.generate_hash(length=20)
|
||||
self.webhook_secret = key
|
||||
self.set(field, key)
|
||||
self.save()
|
||||
|
||||
secret_for = "Membership" if field == "membership_webhook_secret" else "Donation"
|
||||
|
||||
frappe.msgprint(
|
||||
_("Here is your webhook secret, this will be shown to you only once.") + "<br><br>" + key,
|
||||
_("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):
|
||||
self.webhook_secret = None;
|
||||
def revoke_key(self, key):
|
||||
self.set(key, None)
|
||||
self.save()
|
||||
|
||||
def get_webhook_secret(self):
|
||||
return self.get_password(fieldname="webhook_secret", raise_exception=False)
|
||||
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):
|
@ -6,5 +6,5 @@ from __future__ import unicode_literals
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestMembershipSettings(unittest.TestCase):
|
||||
class TestNonProfitSettings(unittest.TestCase):
|
||||
pass
|
@ -10,6 +10,7 @@
|
||||
"hide_custom": 0,
|
||||
"icon": "non-profit",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"label": "Non Profit",
|
||||
"links": [
|
||||
@ -109,7 +110,7 @@
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Membership Settings",
|
||||
"link_to": "Membership Settings",
|
||||
"link_to": "Non Profit Settings",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
@ -161,7 +162,7 @@
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Donor",
|
||||
"label": "Donation",
|
||||
"onboard": 0,
|
||||
"type": "Card Break"
|
||||
},
|
||||
@ -184,9 +185,35 @@
|
||||
"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": "2020-12-01 13:38:38.351409",
|
||||
"modified": "2021-03-11 11:38:09.140655",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Non Profit",
|
||||
"name": "Non Profit",
|
||||
@ -201,8 +228,8 @@
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
"label": "Membership Settings",
|
||||
"link_to": "Membership Settings",
|
||||
"label": "Non Profit Settings",
|
||||
"link_to": "Non Profit Settings",
|
||||
"type": "DocType"
|
||||
},
|
||||
{
|
||||
|
@ -756,3 +756,5 @@ erpnext.patches.v12_0.add_state_code_for_ladakh
|
||||
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
|
||||
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
||||
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
@ -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
|
@ -399,9 +399,9 @@ def make_custom_fields(update=True):
|
||||
si_einvoice_fields = [
|
||||
dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
|
||||
depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
|
||||
|
||||
|
||||
dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
|
||||
|
||||
|
||||
dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
|
||||
|
||||
dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
|
||||
@ -499,6 +499,14 @@ def make_custom_fields(update=True):
|
||||
fieldtype='Link', options='Salary Component', insert_after='basic_component'),
|
||||
dict(fieldname='arrear_component', label='Arrear Component',
|
||||
fieldtype='Link', options='Salary Component', insert_after='hra_component'),
|
||||
dict(fieldname='non_profit_section', label='Non Profit Settings',
|
||||
fieldtype='Section Break', insert_after='asset_received_but_not_billed', collapsible=1),
|
||||
dict(fieldname='company_80g_number', label='80G Number',
|
||||
fieldtype='Data', insert_after='non_profit_section'),
|
||||
dict(fieldname='with_effect_from', label='80G With Effect From',
|
||||
fieldtype='Date', insert_after='company_80g_number'),
|
||||
dict(fieldname='pan_details', label='PAN Number',
|
||||
fieldtype='Data', insert_after='with_effect_from')
|
||||
],
|
||||
'Employee Tax Exemption Declaration':[
|
||||
dict(fieldname='hra_section', label='HRA Exemption',
|
||||
@ -581,7 +589,15 @@ def make_custom_fields(update=True):
|
||||
'options': '\nWith Payment of Tax\nWithout Payment of Tax'
|
||||
}
|
||||
],
|
||||
"Member": [
|
||||
'Member': [
|
||||
{
|
||||
'fieldname': 'pan_number',
|
||||
'label': 'PAN Details',
|
||||
'fieldtype': 'Data',
|
||||
'insert_after': 'email_id'
|
||||
}
|
||||
],
|
||||
'Donor': [
|
||||
{
|
||||
'fieldname': 'pan_number',
|
||||
'label': 'PAN Details',
|
||||
@ -643,7 +659,7 @@ def set_tax_withholding_category(company):
|
||||
pass
|
||||
|
||||
docs = get_tds_details(accounts, fiscal_year)
|
||||
|
||||
|
||||
for d in docs:
|
||||
try:
|
||||
doc = frappe.get_doc(d)
|
||||
@ -661,7 +677,7 @@ def set_tax_withholding_category(company):
|
||||
fy_exist = [k for k in doc.get('rates') if k.get('fiscal_year')==fiscal_year]
|
||||
if not fy_exist:
|
||||
doc.append("rates", d.get('rates')[0])
|
||||
|
||||
|
||||
doc.flags.ignore_permissions = True
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.save()
|
||||
|
@ -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"
|
||||
}
|
@ -195,6 +195,7 @@ def install(country=None):
|
||||
{'doctype': "Party Type", "party_type": "Member", "account_type": "Receivable"},
|
||||
{'doctype': "Party Type", "party_type": "Shareholder", "account_type": "Payable"},
|
||||
{'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"},
|
||||
{'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"},
|
||||
|
||||
{'doctype': "Opportunity Type", "name": "Hub"},
|
||||
{'doctype': "Opportunity Type", "name": _("Sales")},
|
||||
|
Loading…
x
Reference in New Issue
Block a user