Merge branch 'develop' into ordered-qty-for-packed-items

This commit is contained in:
Saqib Ansari 2022-02-28 11:35:42 +05:30 committed by GitHub
commit cde711151e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
189 changed files with 869 additions and 7944 deletions

View File

@ -131,11 +131,3 @@ def allow_regional(fn):
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs) return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
return caller return caller
def get_last_membership(member):
'''Returns last membership if exists'''
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
dict(member=member, paid=1), order_by='to_date desc', limit=1)
if last_membership:
return last_membership[0]

View File

@ -7,6 +7,7 @@ import json
import frappe import frappe
from frappe import _ from frappe import _
from frappe.model.document import Document from frappe.model.document import Document
from frappe.query_builder.custom import ConstantColumn
from frappe.utils import flt from frappe.utils import flt
from erpnext import get_company_currency from erpnext import get_company_currency
@ -275,6 +276,10 @@ def check_matching(bank_account, company, transaction, document_types):
} }
matching_vouchers = [] matching_vouchers = []
matching_vouchers.extend(get_loan_vouchers(bank_account, transaction,
document_types, filters))
for query in subquery: for query in subquery:
matching_vouchers.extend( matching_vouchers.extend(
frappe.db.sql(query, filters,) frappe.db.sql(query, filters,)
@ -311,6 +316,114 @@ def get_queries(bank_account, company, transaction, document_types):
return queries return queries
def get_loan_vouchers(bank_account, transaction, document_types, filters):
vouchers = []
amount_condition = True if "exact_match" in document_types else False
if transaction.withdrawal > 0 and "loan_disbursement" in document_types:
vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters))
if transaction.deposit > 0 and "loan_repayment" in document_types:
vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters))
return vouchers
def get_ld_matching_query(bank_account, amount_condition, filters):
loan_disbursement = frappe.qb.DocType("Loan Disbursement")
matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
matching_party = loan_disbursement.applicant_type == filters.get("party_type") and \
loan_disbursement.applicant == filters.get("party")
rank = (
frappe.qb.terms.Case()
.when(matching_reference, 1)
.else_(0)
)
rank1 = (
frappe.qb.terms.Case()
.when(matching_party, 1)
.else_(0)
)
query = frappe.qb.from_(loan_disbursement).select(
rank + rank1 + 1,
ConstantColumn("Loan Disbursement").as_("doctype"),
loan_disbursement.name,
loan_disbursement.disbursed_amount,
loan_disbursement.reference_number,
loan_disbursement.reference_date,
loan_disbursement.applicant_type,
loan_disbursement.disbursement_date
).where(
loan_disbursement.docstatus == 1
).where(
loan_disbursement.clearance_date.isnull()
).where(
loan_disbursement.disbursement_account == bank_account
)
if amount_condition:
query.where(
loan_disbursement.disbursed_amount == filters.get('amount')
)
else:
query.where(
loan_disbursement.disbursed_amount <= filters.get('amount')
)
vouchers = query.run(as_list=True)
return vouchers
def get_lr_matching_query(bank_account, amount_condition, filters):
loan_repayment = frappe.qb.DocType("Loan Repayment")
matching_reference = loan_repayment.reference_number == filters.get("reference_number")
matching_party = loan_repayment.applicant_type == filters.get("party_type") and \
loan_repayment.applicant == filters.get("party")
rank = (
frappe.qb.terms.Case()
.when(matching_reference, 1)
.else_(0)
)
rank1 = (
frappe.qb.terms.Case()
.when(matching_party, 1)
.else_(0)
)
query = frappe.qb.from_(loan_repayment).select(
rank + rank1 + 1,
ConstantColumn("Loan Repayment").as_("doctype"),
loan_repayment.name,
loan_repayment.amount_paid,
loan_repayment.reference_number,
loan_repayment.reference_date,
loan_repayment.applicant_type,
loan_repayment.posting_date
).where(
loan_repayment.docstatus == 1
).where(
loan_repayment.clearance_date.isnull()
).where(
loan_repayment.payment_account == bank_account
)
if amount_condition:
query.where(
loan_repayment.amount_paid == filters.get('amount')
)
else:
query.where(
loan_repayment.amount_paid <= filters.get('amount')
)
vouchers = query.run()
return vouchers
def get_pe_matching_query(amount_condition, account_from_to, transaction): def get_pe_matching_query(amount_condition, account_from_to, transaction):
# get matching payment entries query # get matching payment entries query
if transaction.deposit > 0: if transaction.deposit > 0:
@ -348,7 +461,6 @@ def get_je_matching_query(amount_condition, transaction):
# We have mapping at the bank level # We have mapping at the bank level
# So one bank could have both types of bank accounts like asset and liability # So one bank could have both types of bank accounts like asset and liability
# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type # So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit" cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
return f""" return f"""

View File

@ -49,7 +49,8 @@ class BankTransaction(StatusUpdater):
def clear_linked_payment_entries(self, for_cancel=False): def clear_linked_payment_entries(self, for_cancel=False):
for payment_entry in self.payment_entries: for payment_entry in self.payment_entries:
if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim", "Loan Repayment",
"Loan Disbursement"]:
self.clear_simple_entry(payment_entry, for_cancel=for_cancel) self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
elif payment_entry.payment_document == "Sales Invoice": elif payment_entry.payment_document == "Sales Invoice":
@ -116,11 +117,18 @@ def get_paid_amount(payment_entry, currency, bank_account):
payment_entry.payment_entry, paid_amount_field) payment_entry.payment_entry, paid_amount_field)
elif payment_entry.payment_document == "Journal Entry": elif payment_entry.payment_document == "Journal Entry":
return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account}, "sum(credit_in_account_currency)") return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account},
"sum(credit_in_account_currency)")
elif payment_entry.payment_document == "Expense Claim": elif payment_entry.payment_document == "Expense Claim":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed") return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
elif payment_entry.payment_document == "Loan Disbursement":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount")
elif payment_entry.payment_document == "Loan Repayment":
return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
else: else:
frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry)) frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry))

View File

@ -109,7 +109,7 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
frappe.get_doc({ frappe.get_doc({
"doctype": "Bank", "doctype": "Bank",
"bank_name":bank_name, "bank_name":bank_name,
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -119,7 +119,7 @@ def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
"account_name":"Checking Account", "account_name":"Checking Account",
"bank": bank_name, "bank": bank_name,
"account": account_name "account": account_name
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -184,7 +184,7 @@ def add_vouchers():
"supplier_group":"All Supplier Groups", "supplier_group":"All Supplier Groups",
"supplier_type": "Company", "supplier_type": "Company",
"supplier_name": "Conrad Electronic" "supplier_name": "Conrad Electronic"
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -203,7 +203,7 @@ def add_vouchers():
"supplier_group":"All Supplier Groups", "supplier_group":"All Supplier Groups",
"supplier_type": "Company", "supplier_type": "Company",
"supplier_name": "Mr G" "supplier_name": "Mr G"
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -227,7 +227,7 @@ def add_vouchers():
"supplier_group":"All Supplier Groups", "supplier_group":"All Supplier Groups",
"supplier_type": "Company", "supplier_type": "Company",
"supplier_name": "Poore Simon's" "supplier_name": "Poore Simon's"
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -237,7 +237,7 @@ def add_vouchers():
"customer_group":"All Customer Groups", "customer_group":"All Customer Groups",
"customer_type": "Company", "customer_type": "Company",
"customer_name": "Poore Simon's" "customer_name": "Poore Simon's"
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -266,7 +266,7 @@ def add_vouchers():
"customer_group":"All Customer Groups", "customer_group":"All Customer Groups",
"customer_type": "Company", "customer_type": "Company",
"customer_name": "Fayva" "customer_name": "Fayva"
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass

View File

@ -166,7 +166,7 @@ class OpeningInvoiceCreationTool(Document):
frappe.scrub(row.party_type): row.party, frappe.scrub(row.party_type): row.party,
"is_pos": 0, "is_pos": 0,
"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice", "doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
"update_stock": 0, "update_stock": 0, # important: https://github.com/frappe/erpnext/pull/23559
"invoice_number": row.invoice_number, "invoice_number": row.invoice_number,
"disable_rounded_total": 1 "disable_rounded_total": 1
}) })

View File

@ -1,11 +1,7 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe.cache_manager import clear_doctype_cache
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import ( from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension, create_dimension,
@ -14,14 +10,17 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import ( from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account, get_temporary_opening_account,
) )
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"] test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
class TestOpeningInvoiceCreationTool(unittest.TestCase): class TestOpeningInvoiceCreationTool(ERPNextTestCase):
def setUp(self): @classmethod
def setUpClass(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"): if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
make_company() make_company()
create_dimension() create_dimension()
return super().setUpClass()
def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None): def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
doc = frappe.get_single("Opening Invoice Creation Tool") doc = frappe.get_single("Opening Invoice Creation Tool")
@ -31,26 +30,20 @@ class TestOpeningInvoiceCreationTool(unittest.TestCase):
return doc.make_invoices() return doc.make_invoices()
def test_opening_sales_invoice_creation(self): def test_opening_sales_invoice_creation(self):
property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check") invoices = self.make_invoices(company="_Test Opening Invoice Company")
try:
invoices = self.make_invoices(company="_Test Opening Invoice Company")
self.assertEqual(len(invoices), 2) self.assertEqual(len(invoices), 2)
expected_value = { expected_value = {
"keys": ["customer", "outstanding_amount", "status"], "keys": ["customer", "outstanding_amount", "status"],
0: ["_Test Customer", 300, "Overdue"], 0: ["_Test Customer", 300, "Overdue"],
1: ["_Test Customer 1", 250, "Overdue"], 1: ["_Test Customer 1", 250, "Overdue"],
} }
self.check_expected_values(invoices, expected_value) self.check_expected_values(invoices, expected_value)
si = frappe.get_doc("Sales Invoice", invoices[0]) si = frappe.get_doc("Sales Invoice", invoices[0])
# Check if update stock is not enabled # Check if update stock is not enabled
self.assertEqual(si.update_stock, 0) self.assertEqual(si.update_stock, 0)
finally:
property_setter.delete()
clear_doctype_cache("Sales Invoice")
def check_expected_values(self, invoices, expected_value, invoice_type="Sales"): def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice" doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"

View File

@ -114,8 +114,6 @@ frappe.ui.form.on('Payment Entry', {
var doctypes = ["Expense Claim", "Journal Entry"]; var doctypes = ["Expense Claim", "Journal Entry"];
} else if (frm.doc.party_type == "Student") { } else if (frm.doc.party_type == "Student") {
var doctypes = ["Fees"]; var doctypes = ["Fees"];
} else if (frm.doc.party_type == "Donor") {
var doctypes = ["Donation"];
} else { } else {
var doctypes = ["Journal Entry"]; var doctypes = ["Journal Entry"];
} }
@ -144,7 +142,7 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn]; const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company}; const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice', const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation']; 'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) { if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party; filters[doc.party_type.toLowerCase()] = doc.party;
@ -747,8 +745,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (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=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || (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(total_positive_outstanding > total_negative_outstanding)
if (!frm.doc.paid_amount) if (!frm.doc.paid_amount)
@ -791,8 +788,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (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=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || (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) { if(total_positive_outstanding_including_order > paid_amount) {
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount; var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
@ -951,12 +947,6 @@ frappe.ui.form.on('Payment Entry', {
frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx])); frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
return false; 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) { if (row) {

View File

@ -91,7 +91,6 @@ class PaymentEntry(AccountsController):
self.update_expense_claim() self.update_expense_claim()
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
self.update_donation()
self.update_payment_schedule() self.update_payment_schedule()
self.set_status() self.set_status()
@ -101,7 +100,6 @@ class PaymentEntry(AccountsController):
self.update_expense_claim() self.update_expense_claim()
self.update_outstanding_amounts() self.update_outstanding_amounts()
self.update_advance_paid() self.update_advance_paid()
self.update_donation(cancel=1)
self.delink_advance_entry_references() self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1) self.update_payment_schedule(cancel=1)
self.set_payment_req_status() self.set_payment_req_status()
@ -284,8 +282,6 @@ class PaymentEntry(AccountsController):
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity") valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder": elif self.party_type == "Shareholder":
valid_reference_doctypes = ("Journal Entry") valid_reference_doctypes = ("Journal Entry")
elif self.party_type == "Donor":
valid_reference_doctypes = ("Donation")
for d in self.get("references"): for d in self.get("references"):
if not d.allocated_amount: if not d.allocated_amount:
@ -843,13 +839,6 @@ class PaymentEntry(AccountsController):
else: else:
update_reimbursed_amount(doc, d.allocated_amount) update_reimbursed_amount(doc, d.allocated_amount)
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): def on_recurring(self, reference_doc, auto_repeat_doc):
self.reference_no = reference_doc.name self.reference_no = reference_doc.name
self.reference_date = nowdate() self.reference_date = nowdate()
@ -1077,7 +1066,7 @@ def get_outstanding_reference_documents(args):
if d.voucher_type in ("Purchase Invoice"): if d.voucher_type in ("Purchase Invoice"):
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no") d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
# Get all SO / PO which are not fully billed or aginst which full advance not paid # Get all SO / PO which are not fully billed or against which full advance not paid
orders_to_be_billed = [] orders_to_be_billed = []
if (args.get("party_type") != "Student"): if (args.get("party_type") != "Student"):
orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"), orders_to_be_billed = get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
@ -1337,10 +1326,6 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
total_amount = ref_doc.get("grand_total") total_amount = ref_doc.get("grand_total")
exchange_rate = 1 exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount") outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Donation":
total_amount = ref_doc.get("amount")
outstanding_amount = total_amount
exchange_rate = 1
elif reference_doctype == "Dunning": elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount") total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1 exchange_rate = 1
@ -1611,8 +1596,6 @@ def set_party_type(dt):
party_type = "Employee" party_type = "Employee"
elif dt == "Fees": elif dt == "Fees":
party_type = "Student" party_type = "Student"
elif dt == "Donation":
party_type = "Donor"
return party_type return party_type
def set_party_account(dt, dn, doc, party_type): def set_party_account(dt, dn, doc, party_type):
@ -1640,7 +1623,7 @@ def set_party_account_currency(dt, party_account, doc):
return party_account_currency return party_account_currency
def set_payment_type(dt, doc): def set_payment_type(dt, doc):
if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \ if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
or (dt=="Purchase Invoice" and doc.outstanding_amount < 0): or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
payment_type = "Receive" payment_type = "Receive"
else: else:
@ -1673,9 +1656,6 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
elif dt == "Dunning": elif dt == "Dunning":
grand_total = doc.grand_total grand_total = doc.grand_total
outstanding_amount = doc.grand_total outstanding_amount = doc.grand_total
elif dt == "Donation":
grand_total = doc.amount
outstanding_amount = doc.amount
elif dt == "Gratuity": elif dt == "Gratuity":
grand_total = doc.amount grand_total = doc.amount
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount) outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)

View File

@ -439,7 +439,6 @@ class POSInvoice(SalesInvoice):
self.paid_amount = 0 self.paid_amount = 0
def set_account_for_mode_of_payment(self): def set_account_for_mode_of_payment(self):
self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
for pay in self.payments: for pay in self.payments:
if not pay.account: if not pay.account:
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account") pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")

View File

@ -46,7 +46,7 @@ def valdiate_taxes_and_charges_template(doc):
for tax in doc.get("taxes"): for tax in doc.get("taxes"):
validate_taxes_and_charges(tax) validate_taxes_and_charges(tax)
validate_account_head(tax, doc) validate_account_head(tax.idx, tax.account_head, doc.company)
validate_cost_center(tax, doc) validate_cost_center(tax, doc)
validate_inclusive_tax(tax, doc) validate_inclusive_tax(tax, doc)

View File

@ -307,7 +307,7 @@ def validate_party_gle_currency(party_type, party, company, party_account_curren
.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency) .format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
def validate_party_accounts(doc): def validate_party_accounts(doc):
from erpnext.controllers.accounts_controller import validate_account_head
companies = [] companies = []
for account in doc.get("accounts"): for account in doc.get("accounts"):
@ -330,6 +330,9 @@ def validate_party_accounts(doc):
if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency: if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
frappe.throw(_("Billing currency must be equal to either default company's currency or party account currency")) frappe.throw(_("Billing currency must be equal to either default company's currency or party account currency"))
# validate if account is mapped for same company
validate_account_head(account.idx, account.account, account.company)
@frappe.whitelist() @frappe.whitelist()
def get_due_date(posting_date, party_type, party, company=None, bill_date=None): def get_due_date(posting_date, party_type, party, company=None, bill_date=None):

View File

@ -4,7 +4,12 @@
import frappe import frappe
from frappe import _ from frappe import _
from frappe.utils import flt, getdate, nowdate from frappe.query_builder.custom import ConstantColumn
from frappe.query_builder.functions import Sum
from frappe.utils import flt, getdate
from pypika import CustomFunction
from erpnext.accounts.utils import get_balance_on
def execute(filters=None): def execute(filters=None):
@ -18,7 +23,6 @@ def execute(filters=None):
data = get_entries(filters) data = get_entries(filters)
from erpnext.accounts.utils import get_balance_on
balance_as_per_system = get_balance_on(filters["account"], filters["report_date"]) balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
total_debit, total_credit = 0,0 total_debit, total_credit = 0,0
@ -118,7 +122,21 @@ def get_columns():
] ]
def get_entries(filters): def get_entries(filters):
journal_entries = frappe.db.sql(""" journal_entries = get_journal_entries(filters)
payment_entries = get_payment_entries(filters)
loan_entries = get_loan_entries(filters)
pos_entries = []
if filters.include_pos_transactions:
pos_entries = get_pos_entries(filters)
return sorted(list(payment_entries)+list(journal_entries+list(pos_entries) + list(loan_entries)),
key=lambda k: getdate(k['posting_date']))
def get_journal_entries(filters):
return frappe.db.sql("""
select "Journal Entry" as payment_document, jv.posting_date, select "Journal Entry" as payment_document, jv.posting_date,
jv.name as payment_entry, jvd.debit_in_account_currency as debit, jv.name as payment_entry, jvd.debit_in_account_currency as debit,
jvd.credit_in_account_currency as credit, jvd.against_account, jvd.credit_in_account_currency as credit, jvd.against_account,
@ -130,7 +148,8 @@ def get_entries(filters):
and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1) and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
payment_entries = frappe.db.sql(""" def get_payment_entries(filters):
return frappe.db.sql("""
select select
"Payment Entry" as payment_document, name as payment_entry, "Payment Entry" as payment_document, name as payment_entry,
reference_no, reference_date as ref_date, reference_no, reference_date as ref_date,
@ -145,9 +164,8 @@ def get_entries(filters):
and ifnull(clearance_date, '4000-01-01') > %(report_date)s and ifnull(clearance_date, '4000-01-01') > %(report_date)s
""", filters, as_dict=1) """, filters, as_dict=1)
pos_entries = [] def get_pos_entries(filters):
if filters.include_pos_transactions: return frappe.db.sql("""
pos_entries = frappe.db.sql("""
select select
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit, "Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
si.posting_date, si.debit_to as against_account, sip.clearance_date, si.posting_date, si.debit_to as against_account, sip.clearance_date,
@ -161,8 +179,42 @@ def get_entries(filters):
si.posting_date ASC, si.name DESC si.posting_date ASC, si.name DESC
""", filters, as_dict=1) """, filters, as_dict=1)
return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)), def get_loan_entries(filters):
key=lambda k: k['posting_date'] or getdate(nowdate())) loan_docs = []
for doctype in ["Loan Disbursement", "Loan Repayment"]:
loan_doc = frappe.qb.DocType(doctype)
ifnull = CustomFunction('IFNULL', ['value', 'default'])
if doctype == "Loan Disbursement":
amount_field = (loan_doc.disbursed_amount).as_("credit")
posting_date = (loan_doc.disbursement_date).as_("posting_date")
account = loan_doc.disbursement_account
else:
amount_field = (loan_doc.amount_paid).as_("debit")
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
entries = frappe.qb.from_(loan_doc).select(
ConstantColumn(doctype).as_("payment_document"),
(loan_doc.name).as_("payment_entry"),
(loan_doc.reference_number).as_("reference_no"),
(loan_doc.reference_date).as_("ref_date"),
amount_field,
posting_date,
).where(
loan_doc.docstatus == 1
).where(
account == filters.get('account')
).where(
posting_date <= getdate(filters.get('report_date'))
).where(
ifnull(loan_doc.clearance_date, '4000-01-01') > getdate(filters.get('report_date'))
).run(as_dict=1)
loan_docs.extend(entries)
return loan_docs
def get_amounts_not_reflected_in_system(filters): def get_amounts_not_reflected_in_system(filters):
je_amount = frappe.db.sql(""" je_amount = frappe.db.sql("""
@ -182,7 +234,40 @@ def get_amounts_not_reflected_in_system(filters):
pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0 pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
return je_amount + pe_amount loan_amount = get_loan_amount(filters)
return je_amount + pe_amount + loan_amount
def get_loan_amount(filters):
total_amount = 0
for doctype in ["Loan Disbursement", "Loan Repayment"]:
loan_doc = frappe.qb.DocType(doctype)
ifnull = CustomFunction('IFNULL', ['value', 'default'])
if doctype == "Loan Disbursement":
amount_field = Sum(loan_doc.disbursed_amount)
posting_date = (loan_doc.disbursement_date).as_("posting_date")
account = loan_doc.disbursement_account
else:
amount_field = Sum(loan_doc.amount_paid)
posting_date = (loan_doc.posting_date).as_("posting_date")
account = loan_doc.payment_account
amount = frappe.qb.from_(loan_doc).select(
amount_field
).where(
loan_doc.docstatus == 1
).where(
account == filters.get('account')
).where(
posting_date > getdate(filters.get('report_date'))
).where(
ifnull(loan_doc.clearance_date, '4000-01-01') <= getdate(filters.get('report_date'))
).run()[0][0]
total_amount += flt(amount)
return amount
def get_balance_row(label, amount, account_currency): def get_balance_row(label, amount, account_currency):
if amount > 0: if amount > 0:

View File

@ -61,7 +61,7 @@ class TestTaxDetail(unittest.TestCase):
# Create GL Entries: # Create GL Entries:
db_doc.submit() db_doc.submit()
else: else:
db_doc.insert() db_doc.insert(ignore_if_duplicate=True)
except frappe.exceptions.DuplicateEntryError: except frappe.exceptions.DuplicateEntryError:
pass pass

View File

@ -39,10 +39,11 @@ class TestReports(unittest.TestCase):
def test_execute_all_accounts_reports(self): def test_execute_all_accounts_reports(self):
"""Test that all script report in stock modules are executable with supported filters""" """Test that all script report in stock modules are executable with supported filters"""
for report, filter in REPORT_FILTER_TEST_CASES: for report, filter in REPORT_FILTER_TEST_CASES:
execute_script_report( with self.subTest(report=report):
report_name=report, execute_script_report(
module="Accounts", report_name=report,
filters=filter, module="Accounts",
default_filters=DEFAULT_FILTERS, filters=filter,
optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, default_filters=DEFAULT_FILTERS,
) optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
)

View File

@ -847,7 +847,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
"payment_account": bank_account.name, "payment_account": bank_account.name,
"currency": bank_account.account_currency, "currency": bank_account.account_currency,
"payment_channel": payment_channel "payment_channel": payment_channel
}).insert(ignore_permissions=True) }).insert(ignore_permissions=True, ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
# already exists, due to a reinstall? # already exists, due to a reinstall?

View File

@ -1280,7 +1280,7 @@ def create_asset(**args):
if not args.do_not_save: if not args.do_not_save:
try: try:
asset.save() asset.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -1321,7 +1321,7 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
"is_grouped_asset": is_grouped_asset, "is_grouped_asset": is_grouped_asset,
"asset_naming_series": naming_series "asset_naming_series": naming_series
}) })
item.insert() item.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return item return item

View File

@ -23,7 +23,7 @@ class TestAssetCategory(unittest.TestCase):
}) })
try: try:
asset_category.insert() asset_category.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass

View File

@ -14,151 +14,150 @@ test_records = frappe.get_test_records('Supplier')
class TestSupplier(unittest.TestCase): class TestSupplier(unittest.TestCase):
def test_get_supplier_group_details(self): def test_get_supplier_group_details(self):
doc = frappe.new_doc("Supplier Group") doc = frappe.new_doc("Supplier Group")
doc.supplier_group_name = "_Testing Supplier Group" doc.supplier_group_name = "_Testing Supplier Group"
doc.payment_terms = "_Test Payment Term Template 3" doc.payment_terms = "_Test Payment Term Template 3"
doc.accounts = [] doc.accounts = []
test_account_details = { test_account_details = {
"company": "_Test Company", "company": "_Test Company",
"account": "Creditors - _TC", "account": "Creditors - _TC",
} }
doc.append("accounts", test_account_details) doc.append("accounts", test_account_details)
doc.save() doc.save()
s_doc = frappe.new_doc("Supplier") s_doc = frappe.new_doc("Supplier")
s_doc.supplier_name = "Testing Supplier" s_doc.supplier_name = "Testing Supplier"
s_doc.supplier_group = "_Testing Supplier Group" s_doc.supplier_group = "_Testing Supplier Group"
s_doc.payment_terms = "" s_doc.payment_terms = ""
s_doc.accounts = [] s_doc.accounts = []
s_doc.insert() s_doc.insert()
s_doc.get_supplier_group_details() s_doc.get_supplier_group_details()
self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3") self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
self.assertEqual(s_doc.accounts[0].company, "_Test Company") self.assertEqual(s_doc.accounts[0].company, "_Test Company")
self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC") self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
s_doc.delete() s_doc.delete()
doc.delete() doc.delete()
def test_supplier_default_payment_terms(self): def test_supplier_default_payment_terms(self):
# Payment Term based on Days after invoice date # Payment Term based on Days after invoice date
frappe.db.set_value( frappe.db.set_value(
"Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3") "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3")
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-21") self.assertEqual(due_date, "2016-02-21")
due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2017-02-21") self.assertEqual(due_date, "2017-02-21")
# Payment Term based on last day of month # Payment Term based on last day of month
frappe.db.set_value( frappe.db.set_value(
"Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1") "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1")
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-29") self.assertEqual(due_date, "2016-02-29")
due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2017-02-28") self.assertEqual(due_date, "2017-02-28")
frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "") frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "")
# Set credit limit for the supplier group instead of supplier and evaluate the due date # Set credit limit for the supplier group instead of supplier and evaluate the due date
frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3") frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3")
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-21") self.assertEqual(due_date, "2016-02-21")
# Payment terms for Supplier Group instead of supplier and evaluate the due date # Payment terms for Supplier Group instead of supplier and evaluate the due date
frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1") frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1")
# Leap year # Leap year
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2016-02-29") self.assertEqual(due_date, "2016-02-29")
# # Non Leap year # # Non Leap year
due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1") due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
self.assertEqual(due_date, "2017-02-28") self.assertEqual(due_date, "2017-02-28")
# Supplier with no default Payment Terms Template # Supplier with no default Payment Terms Template
frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "") frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "")
frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "") frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "")
due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier")
self.assertEqual(due_date, "2016-01-22") self.assertEqual(due_date, "2016-01-22")
# # Non Leap year # # Non Leap year
due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier") due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier")
self.assertEqual(due_date, "2017-01-22") self.assertEqual(due_date, "2017-01-22")
def test_supplier_disabled(self): def test_supplier_disabled(self):
make_test_records("Item") make_test_records("Item")
frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1) frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1)
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
po = create_purchase_order(do_not_save=True) po = create_purchase_order(do_not_save=True)
self.assertRaises(PartyDisabled, po.save) self.assertRaises(PartyDisabled, po.save)
frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0) frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0)
po.save() po.save()
def test_supplier_country(self): def test_supplier_country(self):
# Test that country field exists in Supplier DocType # Test that country field exists in Supplier DocType
supplier = frappe.get_doc('Supplier', '_Test Supplier with Country') supplier = frappe.get_doc('Supplier', '_Test Supplier with Country')
self.assertTrue('country' in supplier.as_dict()) self.assertTrue('country' in supplier.as_dict())
# Test if test supplier field record is 'Greece' # Test if test supplier field record is 'Greece'
self.assertEqual(supplier.country, "Greece") self.assertEqual(supplier.country, "Greece")
# Test update Supplier instance country value # Test update Supplier instance country value
supplier = frappe.get_doc('Supplier', '_Test Supplier') supplier = frappe.get_doc('Supplier', '_Test Supplier')
supplier.country = 'Greece' supplier.country = 'Greece'
supplier.save() supplier.save()
self.assertEqual(supplier.country, "Greece") self.assertEqual(supplier.country, "Greece")
def test_party_details_tax_category(self): def test_party_details_tax_category(self):
from erpnext.accounts.party import get_party_details from erpnext.accounts.party import get_party_details
frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing") frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing")
# Tax Category without Address # Tax Category without Address
details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier") details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
self.assertEqual(details.tax_category, "_Test Tax Category 1") self.assertEqual(details.tax_category, "_Test Tax Category 1")
address = frappe.get_doc(dict( address = frappe.get_doc(dict(
doctype='Address', doctype='Address',
address_title='_Test Address With Tax Category', address_title='_Test Address With Tax Category',
tax_category='_Test Tax Category 2', tax_category='_Test Tax Category 2',
address_type='Billing', address_type='Billing',
address_line1='Station Road', address_line1='Station Road',
city='_Test City', city='_Test City',
country='India', country='India',
links=[dict( links=[dict(
link_doctype='Supplier', link_doctype='Supplier',
link_name='_Test Supplier With Tax Category' link_name='_Test Supplier With Tax Category'
)] )]
)).insert() )).insert()
# Tax Category with Address # Tax Category with Address
details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier") details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
self.assertEqual(details.tax_category, "_Test Tax Category 2") self.assertEqual(details.tax_category, "_Test Tax Category 2")
# Rollback # Rollback
address.delete() address.delete()
def create_supplier(**args): def create_supplier(**args):
args = frappe._dict(args) args = frappe._dict(args)
try: if frappe.db.exists("Supplier", args.supplier_name):
doc = frappe.get_doc({ return frappe.get_doc("Supplier", args.supplier_name)
"doctype": "Supplier",
"supplier_name": args.supplier_name,
"supplier_group": args.supplier_group or "Services",
"supplier_type": args.supplier_type or "Company",
"tax_withholding_category": args.tax_withholding_category
}).insert()
return doc doc = frappe.get_doc({
"doctype": "Supplier",
"supplier_name": args.supplier_name,
"supplier_group": args.supplier_group or "Services",
"supplier_type": args.supplier_type or "Company",
"tax_withholding_category": args.tax_withholding_category
}).insert()
except frappe.DuplicateEntryError: return doc
return frappe.get_doc("Supplier", args.supplier_name)

View File

@ -1566,13 +1566,12 @@ def validate_taxes_and_charges(tax):
tax.rate = None tax.rate = None
def validate_account_head(tax, doc): def validate_account_head(idx, account, company):
company = frappe.get_cached_value('Account', account_company = frappe.get_cached_value('Account', account, 'company')
tax.account_head, 'company')
if company != doc.company: if account_company != company:
frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}') frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
.format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account')) .format(idx, frappe.bold(account), frappe.bold(company)), title=_('Invalid Account'))
def validate_cost_center(tax, doc): def validate_cost_center(tax, doc):

View File

@ -1,22 +0,0 @@
data = {
'desktop_icons': [
'Non Profit',
'Member',
'Donor',
'Volunteer',
'Grant Application',
'Accounts',
'Buying',
'HR',
'ToDo'
],
'restricted_roles': [
'Non Profit Manager',
'Non Profit Member',
'Non Profit Portal User'
],
'modules': [
'Non Profit'
],
'default_portal_role': 'Non Profit Manager'
}

View File

@ -264,7 +264,7 @@ class ProductQuery:
customer = get_customer(silent=True) customer = get_customer(silent=True)
if customer: if customer:
quotation = frappe.get_all("Quotation", fields=["name"], filters= quotation = frappe.get_all("Quotation", fields=["name"], filters=
{"party_name": customer, "order_type": "Shopping Cart", "docstatus": 0}, {"party_name": customer, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
order_by="modified desc", limit_page_length=1) order_by="modified desc", limit_page_length=1)
if quotation: if quotation:
items = frappe.get_all( items = frappe.get_all(
@ -298,4 +298,4 @@ class ProductQuery:
# slice results manually # slice results manually
result[:self.page_length] result[:self.page_length]
return result return result

View File

@ -310,7 +310,7 @@ def _get_cart_quotation(party=None):
party = get_party() party = get_party()
quotation = frappe.get_all("Quotation", fields=["name"], filters= quotation = frappe.get_all("Quotation", fields=["name"], filters=
{"party_name": party.name, "order_type": "Shopping Cart", "docstatus": 0}, {"party_name": party.name, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
order_by="modified desc", limit_page_length=1) order_by="modified desc", limit_page_length=1)
if quotation: if quotation:

View File

@ -57,13 +57,19 @@ class TestShoppingCart(unittest.TestCase):
return quotation return quotation
def test_get_cart_customer(self): def test_get_cart_customer(self):
self.login_as_customer() def validate_quotation():
# test if quotation with customer is fetched
quotation = _get_cart_quotation()
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer")
self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation
# test if quotation with customer is fetched self.login_as_customer("test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer")
quotation = _get_cart_quotation() validate_quotation()
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer") self.login_as_customer()
self.assertEqual(quotation.contact_email, frappe.session.user) quotation = validate_quotation()
return quotation return quotation
@ -175,7 +181,7 @@ class TestShoppingCart(unittest.TestCase):
def create_tax_rule(self): def create_tax_rule(self):
tax_rule = frappe.get_test_records("Tax Rule")[0] tax_rule = frappe.get_test_records("Tax Rule")[0]
try: try:
frappe.get_doc(tax_rule).insert() frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True)
except (frappe.DuplicateEntryError, ConflictingTaxRule): except (frappe.DuplicateEntryError, ConflictingTaxRule):
pass pass
@ -254,10 +260,9 @@ class TestShoppingCart(unittest.TestCase):
self.create_user_if_not_exists("test_cart_user@example.com") self.create_user_if_not_exists("test_cart_user@example.com")
frappe.set_user("test_cart_user@example.com") frappe.set_user("test_cart_user@example.com")
def login_as_customer(self): def login_as_customer(self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"):
self.create_user_if_not_exists("test_contact_customer@example.com", self.create_user_if_not_exists(email, name)
"_Test Contact For _Test Customer") frappe.set_user(email)
frappe.set_user("test_contact_customer@example.com")
def clear_existing_quotations(self): def clear_existing_quotations(self):
quotations = frappe.get_all("Quotation", filters={ quotations = frappe.get_all("Quotation", filters={

View File

@ -3,6 +3,10 @@
frappe.provide("education"); frappe.provide("education");
frappe.ui.form.on('Student Attendance Tool', { frappe.ui.form.on('Student Attendance Tool', {
setup: (frm) => {
frm.students_area = $('<div>')
.appendTo(frm.fields_dict.students_html.wrapper);
},
onload: function(frm) { onload: function(frm) {
frm.set_query("student_group", function() { frm.set_query("student_group", function() {
return { return {
@ -34,6 +38,7 @@ frappe.ui.form.on('Student Attendance Tool', {
student_group: function(frm) { student_group: function(frm) {
if ((frm.doc.student_group && frm.doc.date) || frm.doc.course_schedule) { if ((frm.doc.student_group && frm.doc.date) || frm.doc.course_schedule) {
frm.students_area.find('.student-attendance-checks').html(`<div style='padding: 2rem 0'>Fetching...</div>`);
var method = "erpnext.education.doctype.student_attendance_tool.student_attendance_tool.get_student_attendance_records"; var method = "erpnext.education.doctype.student_attendance_tool.student_attendance_tool.get_student_attendance_records";
frappe.call({ frappe.call({
@ -62,10 +67,6 @@ frappe.ui.form.on('Student Attendance Tool', {
}, },
get_students: function(frm, students) { get_students: function(frm, students) {
if (!frm.students_area) {
frm.students_area = $('<div>')
.appendTo(frm.fields_dict.students_html.wrapper);
}
students = students || []; students = students || [];
frm.students_editor = new education.StudentsEditor(frm, frm.students_area, students); frm.students_editor = new education.StudentsEditor(frm, frm.students_area, students);
} }
@ -163,16 +164,26 @@ education.StudentsEditor = class StudentsEditor {
); );
}); });
var htmls = students.map(function(student) { // make html grid of students
return frappe.render_template("student_button", { let student_html = '';
student: student.student, for (let student of students) {
student_name: student.student_name, student_html += `<div class="col-sm-3">
group_roll_number: student.group_roll_number, <div class="checkbox">
status: student.status <label>
}) <input
}); type="checkbox"
data-group_roll_number="${student.group_roll_number}"
data-student="${student.student}"
data-student-name="${student.student_name}"
class="students-check"
${student.status==='Present' ? 'checked' : ''}>
${student.group_roll_number} - ${student.student_name}
</label>
</div>
</div>`;
}
$(htmls.join("")).appendTo(me.wrapper); $(`<div class='student-attendance-checks'>${student_html}</div>`).appendTo(me.wrapper);
} }
show_empty_state() { show_empty_state() {

View File

@ -24,24 +24,24 @@ def get_student_attendance_records(based_on, date=None, student_group=None, cour
student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"], student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
filters={"parent": student_group, "active": 1}, order_by= "group_roll_number") filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
table = frappe.qb.DocType("Student Attendance") StudentAttendance = frappe.qb.DocType("Student Attendance")
if course_schedule: if course_schedule:
student_attendance_list = ( student_attendance_list = (
frappe.qb.from_(table) frappe.qb.from_(StudentAttendance)
.select(table.student, table.status) .select(StudentAttendance.student, StudentAttendance.status)
.where( .where(
(table.course_schedule == course_schedule) (StudentAttendance.course_schedule == course_schedule)
) )
).run(as_dict=True) ).run(as_dict=True)
else: else:
student_attendance_list = ( student_attendance_list = (
frappe.qb.from_(table) frappe.qb.from_(StudentAttendance)
.select(table.student, table.status) .select(StudentAttendance.student, StudentAttendance.status)
.where( .where(
(table.student_group == student_group) (StudentAttendance.student_group == student_group)
& (table.date == date) & (StudentAttendance.date == date)
& (table.course_schedule == "") | (table.course_schedule.isnull()) & ((StudentAttendance.course_schedule == "") | (StudentAttendance.course_schedule.isnull()))
) )
).run(as_dict=True) ).run(as_dict=True)

View File

@ -82,7 +82,7 @@ class TallyMigration(Document):
"is_private": True "is_private": True
}) })
try: try:
f.insert() f.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
setattr(self, key, f.file_url) setattr(self, key, f.file_url)

View File

@ -8,10 +8,6 @@ from frappe.utils import cint, flt
from erpnext import get_default_company, get_region from erpnext import get_default_company, get_region
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI", SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO", "FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
"SE", "SI", "SK", "US"] "SE", "SI", "SK", "US"]
@ -35,12 +31,14 @@ def get_client():
if api_key and api_url: if api_key and api_url:
client = taxjar.Client(api_key=api_key, api_url=api_url) client = taxjar.Client(api_key=api_key, api_url=api_url)
client.set_api_config('headers', { client.set_api_config('headers', {
'x-api-version': '2020-08-07' 'x-api-version': '2022-01-24'
}) })
return client return client
def create_transaction(doc, method): def create_transaction(doc, method):
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
"""Create an order transaction in TaxJar""" """Create an order transaction in TaxJar"""
if not TAXJAR_CREATE_TRANSACTIONS: if not TAXJAR_CREATE_TRANSACTIONS:
@ -51,6 +49,7 @@ def create_transaction(doc, method):
if not client: if not client:
return return
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD]) sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD])
if not sales_tax: if not sales_tax:
@ -79,6 +78,7 @@ def create_transaction(doc, method):
def delete_transaction(doc, method): def delete_transaction(doc, method):
"""Delete an existing TaxJar order transaction""" """Delete an existing TaxJar order transaction"""
TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
if not TAXJAR_CREATE_TRANSACTIONS: if not TAXJAR_CREATE_TRANSACTIONS:
return return
@ -92,6 +92,8 @@ def delete_transaction(doc, method):
def get_tax_data(doc): def get_tax_data(doc):
SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
from_address = get_company_address_details(doc) from_address = get_company_address_details(doc)
from_shipping_state = from_address.get("state") from_shipping_state = from_address.get("state")
from_country_code = frappe.db.get_value("Country", from_address.country, "code") from_country_code = frappe.db.get_value("Country", from_address.country, "code")
@ -113,20 +115,20 @@ def get_tax_data(doc):
to_shipping_state = get_state_code(to_address, 'Shipping') to_shipping_state = get_state_code(to_address, 'Shipping')
tax_dict = { tax_dict = {
'from_country': from_country_code, "from_country": from_country_code,
'from_zip': from_address.pincode, "from_zip": from_address.pincode,
'from_state': from_shipping_state, "from_state": from_shipping_state,
'from_city': from_address.city, "from_city": from_address.city,
'from_street': from_address.address_line1, "from_street": from_address.address_line1,
'to_country': to_country_code, "to_country": to_country_code,
'to_zip': to_address.pincode, "to_zip": to_address.pincode,
'to_city': to_address.city, "to_city": to_address.city,
'to_street': to_address.address_line1, "to_street": to_address.address_line1,
'to_state': to_shipping_state, "to_state": to_shipping_state,
'shipping': shipping, "shipping": shipping,
'amount': doc.net_total, "amount": doc.net_total,
'plugin': 'erpnext', "plugin": "erpnext",
'line_items': line_items "line_items": line_items
} }
return tax_dict return tax_dict
@ -156,6 +158,9 @@ def get_line_item_dict(item, docstatus):
return tax_dict return tax_dict
def set_sales_tax(doc, method): def set_sales_tax(doc, method):
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
if not TAXJAR_CALCULATE_TAX: if not TAXJAR_CALCULATE_TAX:
return return
@ -206,6 +211,7 @@ def set_sales_tax(doc, method):
doc.run_method("calculate_taxes_and_totals") doc.run_method("calculate_taxes_and_totals")
def check_for_nexus(doc, tax_dict): def check_for_nexus(doc, tax_dict):
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
if not frappe.db.get_value('TaxJar Nexus', {'region_code': tax_dict["to_state"]}): if not frappe.db.get_value('TaxJar Nexus', {'region_code': tax_dict["to_state"]}):
for item in doc.get("items"): for item in doc.get("items"):
item.tax_collectable = flt(0) item.tax_collectable = flt(0)
@ -218,6 +224,8 @@ def check_for_nexus(doc, tax_dict):
def check_sales_tax_exemption(doc): def check_sales_tax_exemption(doc):
# if the party is exempt from sales tax, then set all tax account heads to zero # if the party is exempt from sales tax, then set all tax account heads to zero
TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \ sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
or frappe.db.has_column("Customer", "exempt_from_sales_tax") \ or frappe.db.has_column("Customer", "exempt_from_sales_tax") \
and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")

View File

@ -68,7 +68,6 @@ domains = {
'Distribution': 'erpnext.domains.distribution', 'Distribution': 'erpnext.domains.distribution',
'Education': 'erpnext.domains.education', 'Education': 'erpnext.domains.education',
'Manufacturing': 'erpnext.domains.manufacturing', 'Manufacturing': 'erpnext.domains.manufacturing',
'Non Profit': 'erpnext.domains.non_profit',
'Retail': 'erpnext.domains.retail', 'Retail': 'erpnext.domains.retail',
'Services': 'erpnext.domains.services', 'Services': 'erpnext.domains.services',
} }
@ -175,7 +174,6 @@ standard_portal_menu_items = [
{"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"}, {"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"},
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"}, {"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"}, {"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"},
{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"},
{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"}, {"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
{"title": _("Appointment Booking"), "route": "/book_appointment"}, {"title": _("Appointment Booking"), "route": "/book_appointment"},
] ]
@ -369,7 +367,6 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.quotation.set_expired_status", "erpnext.selling.doctype.quotation.quotation.set_expired_status",
"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status", "erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email", "erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
"erpnext.non_profit.doctype.membership.membership.set_expired_status",
"erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder" "erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder"
], ],
"daily_long": [ "daily_long": [
@ -563,19 +560,6 @@ global_search_doctypes = {
{'doctype': 'Assessment Code', 'index': 39}, {'doctype': 'Assessment Code', 'index': 39},
{'doctype': 'Discussion', 'index': 40}, {'doctype': 'Discussion', 'index': 40},
], ],
"Non Profit": [
{'doctype': 'Certified Consultant', 'index': 1},
{'doctype': 'Certification Application', 'index': 2},
{'doctype': 'Volunteer', 'index': 3},
{'doctype': 'Membership', 'index': 4},
{'doctype': 'Member', 'index': 5},
{'doctype': 'Donor', 'index': 6},
{'doctype': 'Chapter', 'index': 7},
{'doctype': 'Grant Application', 'index': 8},
{'doctype': 'Volunteer Type', 'index': 9},
{'doctype': 'Donor Type', 'index': 10},
{'doctype': 'Membership Type', 'index': 11}
],
} }
additional_timeline_content = { additional_timeline_content = {

View File

@ -32,7 +32,7 @@ class Department(NestedSet):
return new return new
def on_update(self): def on_update(self):
if not frappe.local.flags.ignore_update_nsm: if not (frappe.local.flags.ignore_update_nsm or frappe.flags.in_setup_wizard):
super(Department, self).on_update() super(Department, self).on_update()
def on_trash(self): def on_trash(self):

View File

@ -1,4 +1,4 @@
[ [
{"doctype":"Department", "department_name":"_Test Department", "company": "_Test Company"}, {"doctype":"Department", "department_name":"_Test Department", "company": "_Test Company", "parent_department": "All Departments"},
{"doctype":"Department", "department_name":"_Test Department 1", "company": "_Test Company"} {"doctype":"Department", "department_name":"_Test Department 1", "company": "_Test Company", "parent_department": "All Departments"}
] ]

View File

@ -142,7 +142,7 @@ class Employee(NestedSet):
"file_url": self.image, "file_url": self.image,
"attached_to_doctype": "User", "attached_to_doctype": "User",
"attached_to_name": self.user_id "attached_to_name": self.user_id
}).insert() }).insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
# already exists # already exists
pass pass

View File

@ -128,4 +128,4 @@ def show_email_summary(email_success, email_failure):
message += _('{0} due to missing email information for employee(s): {1}').format( message += _('{0} due to missing email information for employee(s): {1}').format(
frappe.bold('Sending Failed'), ', '.join(email_failure)) frappe.bold('Sending Failed'), ', '.join(email_failure))
frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True) frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)

View File

@ -82,7 +82,7 @@ def get_vehicle(employee_id):
"vehicle_value": flt(500000) "vehicle_value": flt(500000)
}) })
try: try:
vehicle.insert() vehicle.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return license_plate return license_plate

View File

@ -14,11 +14,15 @@
"applicant", "applicant",
"section_break_7", "section_break_7",
"disbursement_date", "disbursement_date",
"clearance_date",
"column_break_8", "column_break_8",
"disbursed_amount", "disbursed_amount",
"accounting_dimensions_section", "accounting_dimensions_section",
"cost_center", "cost_center",
"customer_details_section", "accounting_details",
"disbursement_account",
"column_break_16",
"loan_account",
"bank_account", "bank_account",
"disbursement_references_section", "disbursement_references_section",
"reference_date", "reference_date",
@ -106,11 +110,6 @@
"fieldtype": "Section Break", "fieldtype": "Section Break",
"label": "Disbursement Details" "label": "Disbursement Details"
}, },
{
"fieldname": "customer_details_section",
"fieldtype": "Section Break",
"label": "Customer Details"
},
{ {
"fetch_from": "against_loan.applicant_type", "fetch_from": "against_loan.applicant_type",
"fieldname": "applicant_type", "fieldname": "applicant_type",
@ -149,15 +148,48 @@
"fieldname": "reference_number", "fieldname": "reference_number",
"fieldtype": "Data", "fieldtype": "Data",
"label": "Reference Number" "label": "Reference Number"
},
{
"fieldname": "clearance_date",
"fieldtype": "Date",
"label": "Clearance Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "accounting_details",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fetch_from": "against_loan.disbursement_account",
"fieldname": "disbursement_account",
"fieldtype": "Link",
"label": "Disbursement Account",
"options": "Account",
"read_only": 1
},
{
"fieldname": "column_break_16",
"fieldtype": "Column Break"
},
{
"fetch_from": "against_loan.loan_account",
"fieldname": "loan_account",
"fieldtype": "Link",
"label": "Loan Account",
"options": "Account",
"read_only": 1
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-04-19 18:09:32.175355", "modified": "2022-02-17 18:23:44.157598",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Disbursement", "name": "Loan Disbursement",
"naming_rule": "Expression (old style)",
"owner": "Administrator", "owner": "Administrator",
"permissions": [ "permissions": [
{ {
@ -194,5 +226,6 @@
"quick_entry": 1, "quick_entry": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"track_changes": 1 "track_changes": 1
} }

View File

@ -42,9 +42,6 @@ class LoanDisbursement(AccountsController):
if not self.posting_date: if not self.posting_date:
self.posting_date = self.disbursement_date or nowdate() self.posting_date = self.disbursement_date or nowdate()
if not self.bank_account and self.applicant_type == "Customer":
self.bank_account = frappe.db.get_value("Customer", self.applicant, "default_bank_account")
def validate_disbursal_amount(self): def validate_disbursal_amount(self):
possible_disbursal_amount = get_disbursal_amount(self.against_loan) possible_disbursal_amount = get_disbursal_amount(self.against_loan)
@ -117,12 +114,11 @@ class LoanDisbursement(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = [] gle_map = []
loan_details = frappe.get_doc("Loan", self.against_loan)
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": loan_details.loan_account, "account": self.loan_account,
"against": loan_details.disbursement_account, "against": self.disbursement_account,
"debit": self.disbursed_amount, "debit": self.disbursed_amount,
"debit_in_account_currency": self.disbursed_amount, "debit_in_account_currency": self.disbursed_amount,
"against_voucher_type": "Loan", "against_voucher_type": "Loan",
@ -137,8 +133,8 @@ class LoanDisbursement(AccountsController):
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": loan_details.disbursement_account, "account": self.disbursement_account,
"against": loan_details.loan_account, "against": self.loan_account,
"credit": self.disbursed_amount, "credit": self.disbursed_amount,
"credit_in_account_currency": self.disbursed_amount, "credit_in_account_currency": self.disbursed_amount,
"against_voucher_type": "Loan", "against_voucher_type": "Loan",

View File

@ -1,7 +1,7 @@
{ {
"actions": [], "actions": [],
"autoname": "LM-REP-.####", "autoname": "LM-REP-.####",
"creation": "2019-09-03 14:44:39.977266", "creation": "2022-01-25 10:30:02.767941",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
@ -13,6 +13,7 @@
"column_break_3", "column_break_3",
"company", "company",
"posting_date", "posting_date",
"clearance_date",
"rate_of_interest", "rate_of_interest",
"payroll_payable_account", "payroll_payable_account",
"is_term_loan", "is_term_loan",
@ -37,7 +38,12 @@
"total_penalty_paid", "total_penalty_paid",
"total_interest_paid", "total_interest_paid",
"repayment_details", "repayment_details",
"amended_from" "amended_from",
"accounting_details_section",
"payment_account",
"penalty_income_account",
"column_break_36",
"loan_account"
], ],
"fields": [ "fields": [
{ {
@ -260,12 +266,52 @@
"fieldname": "repay_from_salary", "fieldname": "repay_from_salary",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Repay From Salary" "label": "Repay From Salary"
},
{
"fieldname": "clearance_date",
"fieldtype": "Date",
"label": "Clearance Date",
"no_copy": 1,
"read_only": 1
},
{
"fieldname": "accounting_details_section",
"fieldtype": "Section Break",
"label": "Accounting Details"
},
{
"fetch_from": "against_loan.payment_account",
"fieldname": "payment_account",
"fieldtype": "Link",
"label": "Repayment Account",
"options": "Account",
"read_only": 1
},
{
"fieldname": "column_break_36",
"fieldtype": "Column Break"
},
{
"fetch_from": "against_loan.loan_account",
"fieldname": "loan_account",
"fieldtype": "Link",
"label": "Loan Account",
"options": "Account",
"read_only": 1
},
{
"fetch_from": "against_loan.penalty_income_account",
"fieldname": "penalty_income_account",
"fieldtype": "Link",
"hidden": 1,
"label": "Penalty Income Account",
"options": "Account"
} }
], ],
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2022-01-06 01:51:06.707782", "modified": "2022-02-18 19:10:07.742298",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Loan Management", "module": "Loan Management",
"name": "Loan Repayment", "name": "Loan Repayment",

View File

@ -310,7 +310,6 @@ class LoanRepayment(AccountsController):
def make_gl_entries(self, cancel=0, adv_adj=0): def make_gl_entries(self, cancel=0, adv_adj=0):
gle_map = [] gle_map = []
loan_details = frappe.get_doc("Loan", self.against_loan)
if self.shortfall_amount and self.amount_paid > self.shortfall_amount: if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount, remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
@ -323,13 +322,13 @@ class LoanRepayment(AccountsController):
if self.repay_from_salary: if self.repay_from_salary:
payment_account = self.payroll_payable_account payment_account = self.payroll_payable_account
else: else:
payment_account = loan_details.payment_account payment_account = self.payment_account
if self.total_penalty_paid: if self.total_penalty_paid:
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": loan_details.loan_account, "account": self.loan_account,
"against": loan_details.payment_account, "against": payment_account,
"debit": self.total_penalty_paid, "debit": self.total_penalty_paid,
"debit_in_account_currency": self.total_penalty_paid, "debit_in_account_currency": self.total_penalty_paid,
"against_voucher_type": "Loan", "against_voucher_type": "Loan",
@ -344,8 +343,8 @@ class LoanRepayment(AccountsController):
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": loan_details.penalty_income_account, "account": self.penalty_income_account,
"against": loan_details.loan_account, "against": self.loan_account,
"credit": self.total_penalty_paid, "credit": self.total_penalty_paid,
"credit_in_account_currency": self.total_penalty_paid, "credit_in_account_currency": self.total_penalty_paid,
"against_voucher_type": "Loan", "against_voucher_type": "Loan",
@ -359,8 +358,7 @@ class LoanRepayment(AccountsController):
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": payment_account, "account": payment_account,
"against": loan_details.loan_account + ", " + loan_details.interest_income_account "against": self.loan_account + ", " + self.penalty_income_account,
+ ", " + loan_details.penalty_income_account,
"debit": self.amount_paid, "debit": self.amount_paid,
"debit_in_account_currency": self.amount_paid, "debit_in_account_currency": self.amount_paid,
"against_voucher_type": "Loan", "against_voucher_type": "Loan",
@ -368,16 +366,16 @@ class LoanRepayment(AccountsController):
"remarks": remarks, "remarks": remarks,
"cost_center": self.cost_center, "cost_center": self.cost_center,
"posting_date": getdate(self.posting_date), "posting_date": getdate(self.posting_date),
"party_type": loan_details.applicant_type if self.repay_from_salary else '', "party_type": self.applicant_type if self.repay_from_salary else '',
"party": loan_details.applicant if self.repay_from_salary else '' "party": self.applicant if self.repay_from_salary else ''
}) })
) )
gle_map.append( gle_map.append(
self.get_gl_dict({ self.get_gl_dict({
"account": loan_details.loan_account, "account": self.loan_account,
"party_type": loan_details.applicant_type, "party_type": self.applicant_type,
"party": loan_details.applicant, "party": self.applicant,
"against": payment_account, "against": payment_account,
"credit": self.amount_paid, "credit": self.amount_paid,
"credit_in_account_currency": self.amount_paid, "credit_in_account_currency": self.amount_paid,

View File

@ -9,6 +9,7 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_sales_orders, get_sales_orders,
get_warehouse_list, get_warehouse_list,
) )
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
@ -466,26 +467,29 @@ class TestProductionPlan(ERPNextTestCase):
bom = make_bom(item=item, raw_materials=raw_materials) bom = make_bom(item=item, raw_materials=raw_materials)
# Create Production Plan # Create Production Plan
pln = create_production_plan(item_code=bom.item, planned_qty=10) pln = create_production_plan(item_code=bom.item, planned_qty=5)
# All the created Work Orders # All the created Work Orders
wo_list = [] wo_list = []
# Create and Submit 1st Work Order for 5 qty # Create and Submit 1st Work Order for 3 qty
create_work_order(item, pln, 5) create_work_order(item, pln, 3)
pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 3)
# Create and Submit 2nd Work Order for 2 qty
create_work_order(item, pln, 2)
pln.reload() pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 5) self.assertEqual(pln.po_items[0].ordered_qty, 5)
# Create and Submit 2nd Work Order for 3 qty # Overproduction
create_work_order(item, pln, 3) self.assertRaises(OverProductionError, create_work_order, item=item, pln=pln, qty=2)
pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 8)
# Cancel 1st Work Order # Cancel 1st Work Order
wo1 = frappe.get_doc("Work Order", wo_list[0]) wo1 = frappe.get_doc("Work Order", wo_list[0])
wo1.cancel() wo1.cancel()
pln.reload() pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 3) self.assertEqual(pln.po_items[0].ordered_qty, 2)
# Cancel 2nd Work Order # Cancel 2nd Work Order
wo2 = frappe.get_doc("Work Order", wo_list[1]) wo2 = frappe.get_doc("Work Order", wo_list[1])

View File

@ -636,6 +636,21 @@ class WorkOrder(Document):
if not self.qty > 0: if not self.qty > 0:
frappe.throw(_("Quantity to Manufacture must be greater than 0.")) frappe.throw(_("Quantity to Manufacture must be greater than 0."))
if self.production_plan and self.production_plan_item:
qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
allowance_qty =flt(frappe.db.get_single_value("Manufacturing Settings",
"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
if max_qty < 1:
frappe.throw(_("Cannot produce more item for {0}")
.format(self.production_item), OverProductionError)
elif self.qty > max_qty:
frappe.throw(_("Cannot produce more than {0} items for {1}")
.format(max_qty, self.production_item), OverProductionError)
def validate_transfer_against(self): def validate_transfer_against(self):
if not self.docstatus == 1: if not self.docstatus == 1:
# let user configure operations until they're ready to submit # let user configure operations until they're ready to submit

View File

@ -55,10 +55,11 @@ class TestManufacturingReports(unittest.TestCase):
def test_execute_all_manufacturing_reports(self): def test_execute_all_manufacturing_reports(self):
"""Test that all script report in manufacturing modules are executable with supported filters""" """Test that all script report in manufacturing modules are executable with supported filters"""
for report, filter in REPORT_FILTER_TEST_CASES: for report, filter in REPORT_FILTER_TEST_CASES:
execute_script_report( with self.subTest(report=report):
report_name=report, execute_script_report(
module="Manufacturing", report_name=report,
filters=filter, module="Manufacturing",
default_filters=DEFAULT_FILTERS, filters=filter,
optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None, default_filters=DEFAULT_FILTERS,
) optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
)

View File

@ -15,7 +15,6 @@ Maintenance
Education Education
Regional Regional
ERPNext Integrations ERPNext Integrations
Non Profit
Quality Management Quality Management
Communication Communication
Loan Management Loan Management

View File

@ -1,8 +0,0 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Certification Application', {
refresh: function(frm) {
}
});

View File

@ -1,323 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "NPO-CAPP-.YYYY.-.#####",
"beta": 0,
"creation": "2018-06-08 16:12:42.091729",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "name_of_applicant",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name of Applicant",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "certification_status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Certification Status",
"length": 0,
"no_copy": 0,
"options": "Yet to appear\nCertified\nNot Certified",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "payment_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Payment Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "paid",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Paid",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "currency",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Currency",
"length": 0,
"no_copy": 0,
"options": "USD\nINR",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-04 03:36:35.337403",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Certification Application",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,9 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class CertificationApplication(Document):
pass

View File

@ -1,8 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestCertificationApplication(unittest.TestCase):
pass

View File

@ -1,8 +0,0 @@
// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Certified Consultant', {
refresh: function(frm) {
}
});

View File

@ -1,724 +0,0 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "NPO-CONS-.YYYY.-.#####",
"beta": 0,
"creation": "2018-06-13 17:27:19.838334",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "name_of_consultant",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name of Consultant",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "country",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Country",
"length": 0,
"no_copy": 0,
"options": "Country",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "phone",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Phone",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "website_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Website",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break1",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Image",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "certification_application",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Certification Application",
"length": 0,
"no_copy": 0,
"options": "Certification Application",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break1",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Certification Validity",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "from_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "From",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_beak2",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "to_date",
"fieldtype": "Date",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "To",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break2",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "introduction",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Introduction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "details",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Details",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break3",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "discuss_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Discuss ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "github_id",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "GitHub ID",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "show_in_website",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Show in Website",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2018-11-04 03:36:47.386618",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Certified Consultant",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}

View File

@ -1,9 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class CertifiedConsultant(Document):
pass

View File

@ -1,8 +0,0 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestCertifiedConsultant(unittest.TestCase):
pass

View File

@ -1,8 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Chapter', {
refresh: function() {
}
});

View File

@ -1,397 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 1,
"allow_import": 0,
"allow_rename": 1,
"autoname": "prompt",
"beta": 0,
"creation": "2017-09-14 13:36:03.904702",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "chapter_head",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Chapter Head",
"length": 0,
"no_copy": 0,
"options": "Member",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "region",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Region",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_5",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "introduction",
"fieldtype": "Text Editor",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Introduction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "meetup_embed_html",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Meetup Embed HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address",
"fieldtype": "Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"description": "chapters/chapter_name\nleave blank automatically set after saving chapter.",
"fieldname": "route",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Route",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "published",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Published",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 1,
"columns": 0,
"fieldname": "chapter_members",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Chapter Members",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "members",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Members",
"length": 0,
"no_copy": 0,
"options": "Chapter Member",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_published_field": "published",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-14 12:59:31.424240",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Chapter",
"name_case": "Title Case",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"route": "chapters",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,49 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe.website.website_generator import WebsiteGenerator
class Chapter(WebsiteGenerator):
_website = frappe._dict(
condition_field = "published",
)
def get_context(self, context):
context.no_cache = True
context.show_sidebar = True
context.parents = [dict(label='View All Chapters',
route='chapters', title='View Chapters')]
def validate(self):
if not self.route: #pylint: disable=E0203
self.route = 'chapters/' + self.scrub(self.name)
def enable(self):
chapter = frappe.get_doc('Chapter', frappe.form_dict.name)
chapter.append('members', dict(enable=self.value))
chapter.save(ignore_permissions=1)
frappe.db.commit()
def get_list_context(context):
context.allow_guest = True
context.no_cache = True
context.show_sidebar = True
context.title = 'All Chapters'
context.no_breadcrumbs = True
context.order_by = 'creation desc'
@frappe.whitelist()
def leave(title, user_id, leave_reason):
chapter = frappe.get_doc("Chapter", title)
for member in chapter.members:
if member.user == user_id:
member.enabled = 0
member.leave_reason = leave_reason
chapter.save(ignore_permissions=1)
frappe.db.commit()
return "Thank you for Feedback"

View File

@ -1,79 +0,0 @@
{% extends "templates/web.html" %}
{% block page_content %}
<h1>{{ title }}</h1>
<p>{{ introduction }}</p>
{% if meetup_embed_html %}
{{ meetup_embed_html }}
{% endif %}
<h3>Member Details</h3>
{% if members %}
<table class="table" style="max-width: 600px;">
{% set index = [1] %}
{% for user in members %}
{% if user.enabled == 1 %}
<tr>
<td>
<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-6">
<div class="pull-left">
<b>{{ index|length }}. {{ frappe.db.get_value('User', user.user, 'full_name') }}</b></div>
</div>
<div class="pull-right">
{% if user.website_url %}
<a href="{{ user.website_url }}">{{ user.website_url | truncate (50) or '' }}</a>
{% endif %}
</div>
<div class="clearfix"></div>
</div>
<br><br>
<div class="col-lg-12">
{% if user.introduction %}
{{ user.introduction }}
{% endif %}
</div>
</div>
</div>
</td>
</tr>
{% set __ = index.append(1) %}
{% endif %}
{% endfor %}
</table>
{% else %}
<p>No member yet.</p>
{% endif %}
<h3>Chapter Head</h3>
<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
<table class="table table-bordered small" style="max-width: 600px;">
{% set doc = frappe.get_doc('Member',chapter_head) %}
<tr>
<td style='width: 15%'>Name</td>
<td><b>{{ doc.member_name }}<b></td>
</tr>
<tr>
<td>Email</td>
<td>{{ frappe.db.get_value('User', doc.email, 'email') or '' }}</td>
</tr>
<tr>
<td>Phone</td>
<td>{{ frappe.db.get_value('User', doc.email, 'phone') or '' }}</td>
</tr>
</table>
</div>
{% if address %}
<h3>Address</h3>
<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
<p>{{ address or ''}}</p>
</div>
{% endif %}
<p style="margin: 20px 0 30px;"><a href="/non_profit/join-chapter?name={{ name }}" class='btn btn-primary'>Join this Chapter</a></p>
<p style="margin: 20px 0 30px;"><a href="/non_profit/leave-chapter?name={{ name }}" class=''>Leave this Chapter</a></p>
{% endblock %}

View File

@ -1,25 +0,0 @@
{% if doc.published %}
<div style="margin-bottom: 30px; max-width: 600px" class="with-border clickable">
<a href={{ doc.route }}>
<h3>{{ doc.name }}</h3>
<p>
<span class="label"> Chapter Head : {{ frappe.db.get_value('User', chapter_head, 'full_name') }} </span>
<span class="label">
{% if members %}
{% set index = [] %}
{% for user in members %}
{% if user.enabled == 1 %}
{% set __ = index.append(1) %}
{% endif %}
{% endfor %}
Members: {{ index|length }}
{% else %}
Members: 0
{% endif %}
</span>
<!-- Assignment of value to global variable not working in jinja -->
</p>
<p>{{ html2text(doc.introduction) | truncate (200) }}</p>
</a>
</div>
{% endif %}

View File

@ -1,8 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestChapter(unittest.TestCase):
pass

View File

@ -1,199 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2017-09-14 13:38:04.296375",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "User",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "introduction",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Introduction",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "website_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Website URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 2,
"default": "1",
"fieldname": "enabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Enabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "leave_reason",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Leave Reason",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1,
"max_attachments": 0,
"modified": "2018-03-07 05:36:51.664816",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Chapter Member",
"name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,9 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class ChapterMember(Document):
pass

View File

@ -1,26 +0,0 @@
// 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);
}
});
},
});

View File

@ -1,156 +0,0 @@
{
"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
}

View File

@ -1,220 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
import frappe
from frappe import _
from frappe.email import sendmail_to_system_managers
from frappe.model.document import Document
from frappe.utils import flt, get_link_to_form, getdate
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, date=None):
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.posting_date = date or getdate()
pe.reference_no = self.name
pe.reference_date = date or 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, str):
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
# to avoid capturing subscription payments as donations
if payment.description and 'subscription' in str(payment.description).lower():
return
donor = get_donor(payment.email)
if not donor:
donor = create_donor(payment)
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) / 100, # Convert to rupees from paise
'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.non_profit.utils 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

View File

@ -1,16 +0,0 @@
from frappe import _
def get_data():
return {
'fieldname': 'donation',
'non_standard_fieldnames': {
'Payment Entry': 'reference_name'
},
'transactions': [
{
'label': _('Payment'),
'items': ['Payment Entry']
}
]
}

View File

@ -1,77 +0,0 @@
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
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.assertEqual(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()

View File

@ -1,17 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Donor', {
refresh: function(frm) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Donor'};
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
} else {
frappe.contacts.clear_address_and_contact(frm);
}
}
});

View File

@ -1,110 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "field:email",
"creation": "2017-09-19 16:20:27.510196",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"donor_name",
"column_break_5",
"donor_type",
"email",
"image",
"address_contacts",
"address_html",
"column_break_9",
"contact_html"
],
"fields": [
{
"fieldname": "donor_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Donor Name",
"reqd": 1
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "donor_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Donor Type",
"options": "Donor Type",
"reqd": 1
},
{
"fieldname": "email",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Email",
"reqd": 1,
"unique": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1,
"print_hide": 1
},
{
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
"options": "fa fa-map-marker"
},
{
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML"
}
],
"image_field": "image",
"links": [
{
"link_doctype": "Donation",
"link_fieldname": "donor"
}
],
"modified": "2021-02-17 16:36:33.470731",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Donor",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"restrict_to_domain": "Non Profit",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "donor_name",
"track_changes": 1
}

View File

@ -1,17 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.model.document import Document
class Donor(Document):
def onload(self):
"""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)

View File

@ -1,3 +0,0 @@
frappe.listview_settings['Donor'] = {
add_fields: ["donor_name", "donor_type", "image"],
};

View File

@ -1,8 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestDonor(unittest.TestCase):
pass

View File

@ -1,8 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Donor Type', {
refresh: function() {
}
});

View File

@ -1,94 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:donor_type",
"beta": 0,
"creation": "2017-09-19 16:19:16.639635",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "donor_type",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Donor Type",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-05 07:04:36.757595",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Donor Type",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,9 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
from frappe.model.document import Document
class DonorType(Document):
pass

View File

@ -1,8 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestDonorType(unittest.TestCase):
pass

View File

@ -1,27 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Grant Application', {
refresh: function(frm) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Grant Application'};
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
} else {
frappe.contacts.clear_address_and_contact(frm);
}
if(frm.doc.status == 'Received' && !frm.doc.email_notification_sent){
frm.add_custom_button(__("Send Grant Review Email"), function() {
frappe.call({
method: "erpnext.non_profit.doctype.grant_application.grant_application.send_grant_review_emails",
args: {
grant_application: frm.doc.name
}
});
});
}
}
});

View File

@ -1,851 +0,0 @@
{
"allow_copy": 0,
"allow_guest_to_view": 1,
"allow_import": 0,
"allow_rename": 0,
"autoname": "",
"beta": 0,
"creation": "2017-09-21 12:02:01.206913",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "applicant_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Applicant Type",
"length": 0,
"no_copy": 0,
"options": "Individual\nOrganization",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "applicant_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.applicant_type=='Organization'",
"fieldname": "contact_person",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact Person",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 0,
"label": "Email",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_5",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"default": "Open",
"fieldname": "status",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Status",
"length": 0,
"no_copy": 0,
"options": "Open\nReceived\nIn Progress\nApproved\nRejected\nExpired\nWithdrawn",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "website_url",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Website URL",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address and Contact",
"length": 0,
"no_copy": 0,
"options": "fa fa-map-marker",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "address_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Address HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_9",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "contact_html",
"fieldtype": "HTML",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Contact HTML",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grant_application_details",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Grant Application Details ",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "grant_description",
"fieldtype": "Long Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Grant Description",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "section_break_15",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "amount",
"fieldtype": "Currency",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Requested Amount",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "has_any_past_grant_record",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Has any past Grant Record",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_17",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "route",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Route",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "published",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Show on Website",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_result",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Assessment Result",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_mark",
"fieldtype": "Float",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Assessment Mark (Out of 10)",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "note",
"fieldtype": "Small Text",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Note",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_24",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "assessment_manager",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Assessment Manager",
"length": 0,
"no_copy": 0,
"options": "User",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "email_notification_sent",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Email Notification Sent",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
}
],
"has_web_view": 1,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_field": "",
"image_view": 0,
"in_create": 0,
"is_published_field": "published",
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2017-12-06 12:39:57.677899",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Grant Application",
"name_case": "",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
}
],
"quick_entry": 0,
"read_only": 0,
"read_only_onload": 0,
"restrict_to_domain": "Non Profit",
"route": "grant-application",
"show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "applicant_name",
"track_changes": 1,
"track_seen": 0
}

View File

@ -1,58 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.utils import get_url
from frappe.website.website_generator import WebsiteGenerator
class GrantApplication(WebsiteGenerator):
_website = frappe._dict(
condition_field = "published",
)
def validate(self):
if not self.route: #pylint: disable=E0203
self.route = 'grant-application/' + self.scrub(self.name)
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self)
def get_context(self, context):
context.no_cache = True
context.show_sidebar = True
context.parents = [dict(label='View All Grant Applications',
route='grant-application', title='View Grants')]
def get_list_context(context):
context.allow_guest = True
context.no_cache = True
context.no_breadcrumbs = True
context.show_sidebar = True
context.order_by = 'creation desc'
context.introduction ='''<a class="btn btn-primary" href="/my-grant?new=1">
Apply for new Grant Application</a>'''
@frappe.whitelist()
def send_grant_review_emails(grant_application):
grant = frappe.get_doc("Grant Application", grant_application)
url = get_url('grant-application/{0}'.format(grant_application))
frappe.sendmail(
recipients= grant.assessment_manager,
sender=frappe.session.user,
subject='Grant Application for {0}'.format(grant.applicant_name),
message='<p> Please Review this grant application</p><br>' + url,
reference_doctype=grant.doctype,
reference_name=grant.name
)
grant.status = 'In Progress'
grant.email_notification_sent = 1
grant.save()
frappe.db.commit()
frappe.msgprint(_("Review Invitation Sent"))

View File

@ -1,68 +0,0 @@
{% extends "templates/web.html" %}
{% block page_content %}
<h1>{{ applicant_name }}</h1>
{% if frappe.user == owner %}
<p><a class='btn btn-primary btn-sm' href="/my-grant?name={{ name }}">Edit Grant</a></p>
{% endif %}
<br>
<table class='table table-bordered small' style='max-width: 400px; margin-bottom: 0px;'>
<tr>
<td style='width: 38.2%'>Organization/Indvidual</td>
<td>{{ applicant_type }}</td>
</tr>
<tr>
<td>Grant Applicant Name</td>
<td>{{ applicant_name}}</td>
</tr>
<tr>
<td>Date</td>
<td>{{ frappe.format_date(creation) }}</td>
</tr>
<tr>
<td>Status</td>
<td>{{ status }}</td>
</tr>
<tr>
<td>Email</td>
<td>{{ email }}</td>
</tr>
</table>
<h2>Q. Please outline your current situation and why you are applying for a grant?</h2>
<p> {{ grant_description }}</p>
<h2>Q. Requested grant amount</h2>
<p>{{ amount }}</p>
<h2>Q. Have you recevied grant from us before?</h2>
<p>{{ has_any_past_grant_record }}</p>
<h3>Contact</h3>
{% if frappe.user != 'Guest' %}
<table class='table table-bordered small' style='max-width: 400px; margin-bottom: 0px;'>
{% if contact_person %}
<tr>
<td style='width: 38.2%'>Contact Person</td>
<td>{{ contact_person }}</td>
</tr>
{% endif %}
<tr>
<td style='width: 38.2%'>Email</td>
<td>{{ email }}</td>
</tr>
</table>
{% else %}
<p><a href="/login">You must register and login to view contact details</a></p>
{% endif %}
<br>
{% if frappe.session.user == assessment_manager %}
{% if assessment_scale %}
<p> Assessment Review done </p>
{% endif %}
{% else %}
<p><br><a href="/my-grant?new=1" class='btn btn-primary'>Post a New Grant</a></p>
{% endif %}
{% endblock %}
{% block style %}
<link type="text/css" rel="stylesheet" href="/assets/css/non-profits.css">
<style>
{% if style is defined %}{{ style }}{% endif %}
</style>
{% endblock %}

View File

@ -1,11 +0,0 @@
{% if doc.published %}
<div style='margin-bottom: 30px; max-width: 600px;'
class='with-border clickable'>
<a href="/{{ doc.route }}">
<h3 style='margin-top: 0px;'>{{ doc.name }}</h3>
<p>
<span class='label'>{{ frappe.format_date(doc.creation) }}</span>
</p>
</a>
</div>
{% endif %}

View File

@ -1,8 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestGrantApplication(unittest.TestCase):
pass

View File

@ -1,64 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Member', {
setup: function(frm) {
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);
}
})
},
refresh: function(frm) {
frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'};
frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
if(!frm.doc.__islocal) {
frappe.contacts.render_address_and_contact(frm);
// custom buttons
frm.add_custom_button(__('Accounting Ledger'), function() {
frappe.set_route('query-report', 'General Ledger',
{party_type:'Member', party:frm.doc.name});
});
frm.add_custom_button(__('Accounts Receivable'), function() {
frappe.set_route('query-report', 'Accounts Receivable', {member:frm.doc.name});
});
if (!frm.doc.customer) {
frm.add_custom_button(__('Create Customer'), () => {
frm.call('make_customer_and_link').then(() => {
frm.reload_doc();
});
});
}
// indicator
erpnext.utils.set_party_dashboard_indicators(frm);
} else {
frappe.contacts.clear_address_and_contact(frm);
}
frappe.call({
method:"frappe.client.get_value",
args:{
'doctype':"Membership",
'filters':{'member': frm.doc.name},
'fieldname':[
'to_date'
]
},
callback: function (data) {
if(data.message) {
frappe.model.set_value(frm.doctype,frm.docname,
"membership_expiry_date", data.message.to_date);
}
}
});
}
});

View File

@ -1,210 +0,0 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "naming_series:",
"creation": "2017-09-11 09:24:52.898356",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"naming_series",
"member_name",
"membership_expiry_date",
"column_break_5",
"membership_type",
"email_id",
"image",
"customer_section",
"customer",
"customer_name",
"supplier_section",
"supplier",
"address_contacts",
"address_html",
"column_break_9",
"contact_html",
"razorpay_details_section",
"subscription_id",
"customer_id",
"subscription_status",
"column_break_21",
"subscription_start",
"subscription_end"
],
"fields": [
{
"fieldname": "naming_series",
"fieldtype": "Select",
"label": "Series",
"options": "NPO-MEM-.YYYY.-",
"reqd": 1
},
{
"fieldname": "member_name",
"fieldtype": "Data",
"in_list_view": 1,
"label": "Member Name",
"reqd": 1
},
{
"fieldname": "membership_expiry_date",
"fieldtype": "Date",
"label": "Membership Expiry Date"
},
{
"fieldname": "column_break_5",
"fieldtype": "Column Break"
},
{
"fieldname": "membership_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Membership Type",
"options": "Membership Type",
"reqd": 1
},
{
"fieldname": "image",
"fieldtype": "Attach Image",
"hidden": 1,
"label": "Image",
"no_copy": 1,
"print_hide": 1
},
{
"collapsible": 1,
"fieldname": "customer_section",
"fieldtype": "Section Break",
"label": "Customer"
},
{
"fieldname": "customer",
"fieldtype": "Link",
"label": "Customer",
"options": "Customer"
},
{
"fetch_from": "customer.customer_name",
"fieldname": "customer_name",
"fieldtype": "Data",
"label": "Customer Name",
"read_only": 1
},
{
"collapsible": 1,
"fieldname": "supplier_section",
"fieldtype": "Section Break",
"label": "Supplier"
},
{
"fieldname": "supplier",
"fieldtype": "Link",
"label": "Supplier",
"options": "Supplier"
},
{
"depends_on": "eval:!doc.__islocal;",
"fieldname": "address_contacts",
"fieldtype": "Section Break",
"label": "Address and Contact",
"options": "fa fa-map-marker"
},
{
"fieldname": "address_html",
"fieldtype": "HTML",
"label": "Address HTML"
},
{
"fieldname": "column_break_9",
"fieldtype": "Column Break"
},
{
"fieldname": "contact_html",
"fieldtype": "HTML",
"label": "Contact HTML"
},
{
"fieldname": "email_id",
"fieldtype": "Data",
"label": "Email Address",
"options": "Email"
},
{
"fieldname": "subscription_id",
"fieldtype": "Data",
"label": "Subscription ID",
"read_only": 1
},
{
"fieldname": "customer_id",
"fieldtype": "Data",
"label": "Customer ID",
"read_only": 1
},
{
"fieldname": "razorpay_details_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Razorpay Details"
},
{
"fieldname": "column_break_21",
"fieldtype": "Column Break"
},
{
"fieldname": "subscription_start",
"fieldtype": "Date",
"label": "Subscription Start "
},
{
"fieldname": "subscription_end",
"fieldtype": "Date",
"label": "Subscription End"
},
{
"fieldname": "subscription_status",
"fieldtype": "Select",
"label": "Subscription Status",
"options": "\nActive\nHalted"
}
],
"image_field": "image",
"links": [],
"modified": "2021-07-11 14:27:26.368039",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Member",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Member",
"share": 1,
"write": 1
}
],
"quick_entry": 1,
"restrict_to_domain": "Non Profit",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "member_name",
"track_changes": 1
}

View File

@ -1,185 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import frappe
from frappe import _
from frappe.contacts.address_and_contact import load_address_and_contact
from frappe.integrations.utils import get_payment_gateway_controller
from frappe.model.document import Document
from frappe.utils import cint, get_link_to_form
from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
class Member(Document):
def onload(self):
"""Load address and contacts in `__onload`"""
load_address_and_contact(self)
def validate(self):
if self.email_id:
self.validate_email_type(self.email_id)
def validate_email_type(self, email):
from frappe.utils import validate_email_address
validate_email_address(email.strip(), True)
def setup_subscription(self):
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({})
plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id")
if not plan_id:
frappe.throw(_("Please setup Razorpay Plan ID"))
subscription_details = {
"plan_id": plan_id,
"billing_frequency": cint(non_profit_settings.billing_frequency),
"customer_notify": 1
}
args = {
'subscription_details': subscription_details
}
subscription = controller.setup_subscription(settings, **args)
return subscription
@frappe.whitelist()
def make_customer_and_link(self):
if self.customer:
frappe.msgprint(_("A customer is already linked to this Member"))
customer = create_customer(frappe._dict({
'fullname': self.member_name,
'email': self.email_id,
'phone': None
}))
self.customer = customer
self.save()
frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
def get_or_create_member(user_details):
member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id})
if member_list and member_list[0]:
return member_list[0]['name']
else:
return create_member(user_details)
def create_member(user_details):
user_details = frappe._dict(user_details)
member = frappe.new_doc("Member")
member.update({
"member_name": user_details.fullname,
"email_id": user_details.email,
"pan_number": user_details.pan or None,
"membership_type": user_details.plan_id,
"customer_id": user_details.customer_id or None,
"subscription_id": user_details.subscription_id or None,
"subscription_status": user_details.subscription_status or ""
})
member.insert(ignore_permissions=True)
member.customer = create_customer(user_details, member.name)
member.save(ignore_permissions=True)
return member
def create_customer(user_details, member=None):
customer = frappe.new_doc("Customer")
customer.customer_name = user_details.fullname
customer.customer_type = "Individual"
customer.flags.ignore_mandatory = True
customer.insert(ignore_permissions=True)
try:
contact = frappe.new_doc("Contact")
contact.first_name = user_details.fullname
if user_details.mobile:
contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1)
if user_details.email:
contact.add_email(user_details.email, is_primary=1)
contact.insert(ignore_permissions=True)
contact.append("links", {
"link_doctype": "Customer",
"link_name": customer.name
})
if member:
contact.append("links", {
"link_doctype": "Member",
"link_name": member
})
contact.save(ignore_permissions=True)
except frappe.DuplicateEntryError:
return customer.name
except Exception as e:
frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
pass
return customer.name
@frappe.whitelist(allow_guest=True)
def create_member_subscription_order(user_details):
"""Create Member subscription and order for payment
Args:
user_details (TYPE): Description
Returns:
Dictionary: Dictionary with subscription details
{
'subscription_details': {
'plan_id': 'plan_EXwyxDYDCj3X4v',
'billing_frequency': 24,
'customer_notify': 1
},
'subscription_id': 'sub_EZycCvXFvqnC6p'
}
"""
user_details = frappe._dict(user_details)
member = get_or_create_member(user_details)
subscription = member.setup_subscription()
member.subscription_id = subscription.get('subscription_id')
member.save(ignore_permissions=True)
return subscription
@frappe.whitelist()
def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, mobile=None):
plan = get_membership_type(rzpay_plan_id)
if not plan:
raise frappe.DoesNotExistError
member = frappe.db.exists("Member", {'email': email, 'subscription_id': subscription_id })
if member:
return member
else:
member = create_member(dict(
fullname=fullname,
email=email,
plan_id=plan,
subscription_id=subscription_id,
pan=pan,
mobile=mobile
))
return member.name

View File

@ -1,22 +0,0 @@
from frappe import _
def get_data():
return {
'heatmap': True,
'heatmap_message': _('Member Activity'),
'fieldname': 'member',
'non_standard_fieldnames': {
'Bank Account': 'party'
},
'transactions': [
{
'label': _('Membership Details'),
'items': ['Membership']
},
{
'label': _('Fee'),
'items': ['Bank Account']
}
]
}

View File

@ -1,3 +0,0 @@
frappe.listview_settings['Member'] = {
add_fields: ["member_name", "membership_type", "image"],
};

View File

@ -1,8 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
class TestMember(unittest.TestCase):
pass

View File

@ -1,41 +0,0 @@
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
frappe.ui.form.on('Membership', {
setup: function(frm) {
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);
})
},
refresh: function(frm) {
if (frm.doc.__islocal)
return;
!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
frm.call({
doc: frm.doc,
method: "generate_invoice",
args: {save: true},
freeze: true,
freeze_message: __("Creating Membership Invoice"),
callback: function(r) {
if (r.invoice)
frm.reload_doc();
}
});
});
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();
});
});
})
},
onload: function(frm) {
frm.add_fetch("membership_type", "amount", "amount");
}
});

View File

@ -1,184 +0,0 @@
{
"actions": [],
"autoname": "NPO-MSH-.YYYY.-.#####",
"creation": "2017-09-11 11:39:18.492184",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"member",
"member_name",
"membership_type",
"column_break_3",
"company",
"membership_status",
"membership_validity_section",
"from_date",
"to_date",
"column_break_8",
"member_since_date",
"payment_details",
"paid",
"currency",
"amount",
"invoice",
"razorpay_details_section",
"subscription_id",
"payment_id"
],
"fields": [
{
"fieldname": "member",
"fieldtype": "Link",
"label": "Member",
"options": "Member"
},
{
"fieldname": "membership_type",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Membership Type",
"options": "Membership Type",
"reqd": 1
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "membership_status",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Membership Status",
"options": "New\nCurrent\nExpired\nPending\nCancelled"
},
{
"fieldname": "membership_validity_section",
"fieldtype": "Section Break",
"label": "Validity"
},
{
"fieldname": "from_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "From",
"reqd": 1
},
{
"fieldname": "to_date",
"fieldtype": "Date",
"in_list_view": 1,
"label": "To",
"reqd": 1
},
{
"fieldname": "column_break_8",
"fieldtype": "Column Break"
},
{
"fieldname": "member_since_date",
"fieldtype": "Date",
"label": "Member Since"
},
{
"fieldname": "payment_details",
"fieldtype": "Section Break",
"label": "Payment Details"
},
{
"default": "0",
"fieldname": "paid",
"fieldtype": "Check",
"label": "Paid"
},
{
"fieldname": "currency",
"fieldtype": "Link",
"label": "Currency",
"options": "Currency"
},
{
"fieldname": "amount",
"fieldtype": "Float",
"label": "Amount"
},
{
"fieldname": "razorpay_details_section",
"fieldtype": "Section Break",
"hidden": 1,
"label": "Razorpay Details"
},
{
"fieldname": "subscription_id",
"fieldtype": "Data",
"label": "Subscription ID",
"read_only": 1
},
{
"fieldname": "payment_id",
"fieldtype": "Data",
"label": "Payment ID",
"read_only": 1
},
{
"fieldname": "invoice",
"fieldtype": "Link",
"label": "Invoice",
"options": "Sales Invoice"
},
{
"fetch_from": "member.member_name",
"fieldname": "member_name",
"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-02-19 14:33:44.925122",
"modified_by": "Administrator",
"module": "Non Profit",
"name": "Membership",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Non Profit Member",
"share": 1,
"write": 1
}
],
"restrict_to_domain": "Non Profit",
"search_fields": "member, member_name",
"sort_field": "modified",
"sort_order": "DESC",
"title_field": "member_name",
"track_changes": 1
}

View File

@ -1,415 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt
import json
from datetime import datetime
import frappe
from frappe import _
from frappe.email import sendmail_to_system_managers
from frappe.model.document import Document
from frappe.utils import add_days, add_months, add_years, get_link_to_form, getdate, nowdate
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
class Membership(Document):
def validate(self):
if not self.member or not frappe.db.exists("Member", self.member):
# for web forms
user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
if user_type == "Website User":
self.create_member_from_website_user()
else:
frappe.throw(_("Please select a Member"))
self.validate_membership_period()
def create_member_from_website_user(self):
member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
if not member_name:
user = frappe.get_doc("User", frappe.session.user)
member = frappe.get_doc(dict(
doctype="Member",
email_id=frappe.session.user,
membership_type=self.membership_type,
member_name=user.get_fullname()
)).insert(ignore_permissions=True)
member_name = member.name
if self.get("__islocal"):
self.member = member_name
def validate_membership_period(self):
# get last membership (if active)
last_membership = erpnext.get_last_membership(self.member)
# if person applied for offline membership
if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator":
# if last membership does not expire in 30 days, then do not allow to renew
if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
frappe.throw(_("You can only renew if your membership expires within 30 days"))
self.from_date = add_days(last_membership.to_date, 1)
elif frappe.session.user == "Administrator":
self.from_date = self.from_date
else:
self.from_date = nowdate()
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)
def on_payment_authorized(self, status_changed_to=None):
if status_changed_to not in ("Completed", "Authorized"):
return
self.load_from_db()
self.db_set("paid", 1)
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)
@frappe.whitelist()
def generate_invoice(self, save=True, with_payment_entry=False):
if not (self.paid or self.currency or self.amount):
frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
if self.invoice:
frappe.throw(_("An invoice is already linked to this document"))
member = frappe.get_doc("Member", self.member)
if not member.customer:
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("Non Profit Settings")
self.validate_membership_type_and_settings(plan, settings)
invoice = make_invoice(self, member, plan, settings)
self.reload()
self.invoice = invoice.name
if with_payment_entry:
self.make_payment_entry(settings, invoice)
if save:
self.save()
return invoice
def validate_membership_type_and_settings(self, plan, settings):
settings_link = get_link_to_form("Membership Type", self.membership_type)
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:
frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
if not plan.linked_item:
frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
get_link_to_form("Membership Type", self.membership_type)))
def make_payment_entry(self, settings, invoice):
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.membership_payment_account
pe.reference_no = self.name
pe.reference_date = getdate()
pe.flags.ignore_mandatory = True
pe.save()
pe.submit()
@frappe.whitelist()
def send_acknowlement(self):
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("Non Profit Settings", "Non Profit Settings")))
member = frappe.get_doc("Member", self.member)
if not member.email_id:
frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
plan = frappe.get_doc("Membership Type", self.membership_type)
email = member.email_id
attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
if self.invoice and settings.send_invoice:
attachments.append(frappe.attach_print("Sales Invoice", self.invoice, print_format=settings.inv_print_format))
email_template = frappe.get_doc("Email Template", settings.email_template)
context = { "doc": self, "member": member}
email_args = {
"recipients": [email],
"message": frappe.render_template(email_template.get("response"), context),
"subject": frappe.render_template(email_template.get("subject"), context),
"attachments": attachments,
"reference_doctype": self.doctype,
"reference_name": self.name
}
if not frappe.flags.in_test:
frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
else:
frappe.sendmail(**email_args)
def generate_and_send_invoice(self):
self.generate_invoice(save=False)
self.send_acknowlement()
def make_invoice(membership, member, plan, settings):
invoice = frappe.get_doc({
"doctype": "Sales Invoice",
"customer": member.customer,
"debit_to": settings.membership_debit_account,
"currency": membership.currency,
"company": settings.company,
"is_pos": 0,
"items": [
{
"item_code": plan.linked_item,
"rate": membership.amount,
"qty": 1
}
]
})
invoice.set_missing_values()
invoice.insert()
invoice.submit()
frappe.msgprint(_("Sales Invoice created successfully"))
return invoice
def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
filters = {"subscription_id": subscription_id}
if email:
filters.update({"email_id": email})
if customer_id:
filters.update({"customer_id": customer_id})
members = frappe.get_all("Member", filters=filters, order_by="creation desc")
try:
return frappe.get_doc("Member", members[0]["name"])
except Exception:
return None
def verify_signature(data, endpoint="Membership"):
signature = frappe.request.headers.get("X-Razorpay-Signature")
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)
def trigger_razorpay_subscription(*args, **kwargs):
data = frappe.request.get_data(as_text=True)
data = process_request_data(data)
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
payment = data.payload.get("payment", {}).get("entity", {})
payment = frappe._dict(payment)
try:
if not data.event == "subscription.charged":
return
member = get_member_based_on_subscription(subscription.id, payment.email)
if not member:
member = create_member(frappe._dict({
"fullname": payment.email,
"email": payment.email,
"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
}))
member.subscription_id = subscription.id
member.customer_id = payment.customer_id
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,
"currency": "INR",
"paid": 1,
"payment_id": payment.id,
"from_date": datetime.fromtimestamp(subscription.current_start),
"to_date": datetime.fromtimestamp(subscription.current_end),
"amount": payment.amount / 100 # Convert to rupees from paise
})
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_status = "Active"
member.flags.ignore_mandatory = True
member.save()
settings = frappe.get_doc("Non Profit Settings")
if settings.allow_invoicing and settings.automate_membership_invoicing:
membership.reload()
membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
except Exception as e:
message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
notify_failure(log)
return {"status": "Failed", "reason": e}
return {"status": "Success"}
@frappe.whitelist(allow_guest=True)
def update_halted_razorpay_subscription(*args, **kwargs):
"""
When all retries have been exhausted, Razorpay moves the subscription to the halted state.
The customer has to manually retry the charge or change the card linked to the subscription,
for the subscription to move back to the active state.
"""
if frappe.request:
data = frappe.request.get_data(as_text=True)
data = process_request_data(data)
elif frappe.flags.in_test:
data = kwargs.get("data")
data = frappe._dict(data)
else:
return
if not data.event == "subscription.halted":
return
subscription = data.payload.get("subscription", {}).get("entity", {})
subscription = frappe._dict(subscription)
try:
member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
if not member:
frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
member.subscription_status = "Halted"
member.flags.ignore_mandatory = True
member.save()
if subscription.get("notes"):
member = get_additional_notes(member, subscription)
except Exception as e:
message = "{0}\n\n{1}".format(e, frappe.get_traceback())
log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
notify_failure(log)
return {"status": "Failed", "reason": e}
return {"status": "Success"}
def process_request_data(data):
try:
verify_signature(data)
except Exception as e:
log = frappe.log_error(e, "Membership Webhook Verification Error")
notify_failure(log)
return {"status": "Failed", "reason": e}
if isinstance(data, str):
data = json.loads(data)
data = frappe._dict(data)
return data
def get_company_for_memberships():
company = frappe.db.get_single_value("Non Profit Settings", "company")
if not company:
from erpnext.non_profit.utils 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 = """
Dear System Manager,
Razorpay webhook for creating renewing membership subscription failed due to some reason.
Please check the following 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 membership webhook failed , please check.", content)
except Exception:
pass
def get_plan_from_razorpay_id(plan_id):
plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
try:
return plan[0]["name"]
except Exception:
return None
def set_expired_status():
frappe.db.sql("""
UPDATE
`tabMembership` SET `membership_status` = 'Expired'
WHERE
`membership_status` not in ('Cancelled') AND `to_date` < %s
""", (nowdate()))

View File

@ -1,15 +0,0 @@
frappe.listview_settings['Membership'] = {
get_indicator: function(doc) {
if (doc.membership_status == 'New') {
return [__('New'), 'blue', 'membership_status,=,New'];
} else if (doc.membership_status === 'Current') {
return [__('Current'), 'green', 'membership_status,=,Current'];
} else if (doc.membership_status === 'Pending') {
return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
} else if (doc.membership_status === 'Expired') {
return [__('Expired'), 'grey', 'membership_status,=,Expired'];
} else {
return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
}
}
};

View File

@ -1,164 +0,0 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import add_months, nowdate
import erpnext
from erpnext.non_profit.doctype.member.member import create_member
from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
class TestMembership(unittest.TestCase):
def setUp(self):
plan = setup_membership()
# make test member
self.member_doc = create_member(
frappe._dict({
"fullname": "_Test_Member",
"email": "_test_member_erpnext@example.com",
"plan_id": plan.name,
"subscription_id": "sub_DEX6xcJ1HSW4CR",
"customer_id": "cust_C0WlbKhp3aLA7W",
"subscription_status": "Active"
})
)
self.member_doc.make_customer_and_link()
self.member = self.member_doc.name
def test_auto_generate_invoice_and_payment_entry(self):
entry = make_membership(self.member)
# Naive test to see if at all invoice was generated and attached to member
# In any case if details were missing, the invoicing would throw an error
invoice = entry.generate_invoice(save=True)
self.assertEqual(invoice.name, entry.invoice)
def test_renew_within_30_days(self):
# create a membership for two months
# Should work fine
make_membership(self.member, { "from_date": nowdate() })
make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
from frappe.utils.user import add_role
add_role("test@example.com", "Non Profit Manager")
frappe.set_user("test@example.com")
# create next membership with expiry not within 30 days
self.assertRaises(frappe.ValidationError, make_membership, self.member, {
"from_date": add_months(nowdate(), 2),
})
frappe.set_user("Administrator")
# create the same membership but as administrator
make_membership(self.member, {
"from_date": add_months(nowdate(), 2),
"to_date": add_months(nowdate(), 3),
})
def test_halted_memberships(self):
make_membership(self.member, {
"from_date": add_months(nowdate(), 2),
"to_date": add_months(nowdate(), 3)
})
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
payload = get_subscription_payload()
update_halted_razorpay_subscription(data=payload)
self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
def tearDown(self):
frappe.db.rollback()
def set_config(key, value):
frappe.db.set_value("Non Profit Settings", None, key, value)
def make_membership(member, payload={}):
data = {
"doctype": "Membership",
"member": member,
"membership_status": "Current",
"membership_type": "_rzpy_test_milythm",
"currency": "INR",
"paid": 1,
"from_date": nowdate(),
"amount": 100
}
data.update(payload)
membership = frappe.get_doc(data)
membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
return membership
def create_item(item_code):
if not frappe.db.exists("Item", item_code):
item = frappe.new_doc("Item")
item.item_code = item_code
item.item_name = item_code
item.stock_uom = "Nos"
item.description = item_code
item.item_group = "All Item Groups"
item.is_stock_item = 0
item.save()
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
def get_subscription_payload():
return {
"entity": "event",
"account_id": "acc_BFQ7uQEaa7j2z7",
"event": "subscription.halted",
"contains": [
"subscription"
],
"payload": {
"subscription": {
"entity": {
"id": "sub_DEX6xcJ1HSW4CR",
"entity": "subscription",
"plan_id": "_rzpy_test_milythm",
"customer_id": "cust_C0WlbKhp3aLA7W",
"status": "halted",
"notes": {
"Important": "Notes for Internal Reference"
},
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More