Merge branch 'develop' into leave-opening-balance

This commit is contained in:
Rucha Mahabal 2022-03-09 16:40:26 +05:30 committed by GitHub
commit 9b152e93cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
255 changed files with 1659 additions and 8152 deletions

View File

@ -131,11 +131,3 @@ def allow_regional(fn):
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
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

@ -120,6 +120,7 @@ def get_booking_dates(doc, item, posting_date=None):
prev_gl_entry = frappe.db.sql('''
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
order by posting_date desc limit 1
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
@ -227,6 +228,7 @@ def get_already_booked_amount(doc, item):
gl_entries_details = frappe.db.sql('''
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
and is_cancelled = 0
group by voucher_detail_no
'''.format(total_credit_debit, total_credit_debit_currency),
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
@ -282,7 +284,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
return
# check if books nor frozen till endate:
if getdate(end_date) >= getdate(accounts_frozen_upto):
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
if via_journal_entry:

View File

@ -2,6 +2,7 @@
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
create_dimension,
@ -10,11 +11,10 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
get_temporary_opening_account,
)
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
class TestOpeningInvoiceCreationTool(ERPNextTestCase):
class TestOpeningInvoiceCreationTool(FrappeTestCase):
@classmethod
def setUpClass(self):
if not frappe.db.exists("Company", "_Test Opening Invoice Company"):

View File

@ -114,8 +114,6 @@ frappe.ui.form.on('Payment Entry', {
var doctypes = ["Expense Claim", "Journal Entry"];
} else if (frm.doc.party_type == "Student") {
var doctypes = ["Fees"];
} else if (frm.doc.party_type == "Donor") {
var doctypes = ["Donation"];
} else {
var doctypes = ["Journal Entry"];
}
@ -144,7 +142,7 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party;
@ -196,8 +194,14 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_from_account_currency != company_currency));
if (frm.doc.payment_type == "Pay") {
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_to_account_currency != company_currency));
} else {
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
(frm.doc.paid_from_account_currency != company_currency));
}
frm.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency
@ -232,7 +236,8 @@ frappe.ui.form.on('Payment Entry', {
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax",
"base_total_taxes_and_charges"], company_currency);
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@ -341,6 +346,8 @@ frappe.ui.form.on('Payment Entry', {
}
frm.set_party_account_based_on_party = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
return frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details",
args: {
@ -374,7 +381,11 @@ frappe.ui.form.on('Payment Entry', {
if (r.message.bank_account) {
frm.set_value("bank_account", r.message.bank_account);
}
}
},
() => frm.events.set_current_exchange_rate(frm, "source_exchange_rate",
frm.doc.paid_from_account_currency, company_currency),
() => frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
frm.doc.paid_to_account_currency, company_currency)
]);
}
}
@ -478,14 +489,14 @@ frappe.ui.form.on('Payment Entry', {
},
paid_from_account_currency: function(frm) {
if(!frm.doc.paid_from_account_currency) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if(!frm.doc.paid_from_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from){
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frappe.call({
method: "erpnext.setup.utils.get_exchange_rate",
args: {
@ -505,8 +516,8 @@ frappe.ui.form.on('Payment Entry', {
},
paid_to_account_currency: function(frm) {
if(!frm.doc.paid_to_account_currency) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if(!frm.doc.paid_to_account_currency || !frm.doc.company) return;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
frm.doc.paid_to_account_currency, company_currency);
@ -747,8 +758,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
) {
if(total_positive_outstanding > total_negative_outstanding)
if (!frm.doc.paid_amount)
@ -791,8 +801,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
) {
if(total_positive_outstanding_including_order > paid_amount) {
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
@ -951,12 +960,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]));
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) {

View File

@ -66,7 +66,9 @@
"tax_withholding_category",
"section_break_56",
"taxes",
"section_break_60",
"base_total_taxes_and_charges",
"column_break_61",
"total_taxes_and_charges",
"deductions_or_loss_section",
"deductions",
@ -715,12 +717,21 @@
"fieldtype": "Data",
"hidden": 1,
"label": "Paid To Account Type"
},
{
"fieldname": "column_break_61",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_60",
"fieldtype": "Section Break",
"hide_border": 1
}
],
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-11-24 18:58:24.919764",
"modified": "2022-02-23 20:08:39.559814",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Payment Entry",
@ -763,6 +774,7 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}

View File

@ -91,7 +91,6 @@ class PaymentEntry(AccountsController):
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_donation()
self.update_payment_schedule()
self.set_status()
@ -101,7 +100,6 @@ class PaymentEntry(AccountsController):
self.update_expense_claim()
self.update_outstanding_amounts()
self.update_advance_paid()
self.update_donation(cancel=1)
self.delink_advance_entry_references()
self.update_payment_schedule(cancel=1)
self.set_payment_req_status()
@ -284,8 +282,6 @@ class PaymentEntry(AccountsController):
valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
elif self.party_type == "Shareholder":
valid_reference_doctypes = ("Journal Entry")
elif self.party_type == "Donor":
valid_reference_doctypes = ("Donation")
for d in self.get("references"):
if not d.allocated_amount:
@ -843,13 +839,6 @@ class PaymentEntry(AccountsController):
else:
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):
self.reference_no = reference_doc.name
self.reference_date = nowdate()
@ -945,8 +934,12 @@ class PaymentEntry(AccountsController):
tax.base_total = tax.total * self.source_exchange_rate
self.total_taxes_and_charges += current_tax_amount
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
if self.payment_type == 'Pay':
self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
else:
self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
if self.get('taxes'):
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
@ -1337,10 +1330,6 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
total_amount = ref_doc.get("grand_total")
exchange_rate = 1
outstanding_amount = ref_doc.get("outstanding_amount")
elif reference_doctype == "Donation":
total_amount = ref_doc.get("amount")
outstanding_amount = total_amount
exchange_rate = 1
elif reference_doctype == "Dunning":
total_amount = ref_doc.get("dunning_amount")
exchange_rate = 1
@ -1611,8 +1600,6 @@ def set_party_type(dt):
party_type = "Employee"
elif dt == "Fees":
party_type = "Student"
elif dt == "Donation":
party_type = "Donor"
return party_type
def set_party_account(dt, dn, doc, party_type):
@ -1640,7 +1627,7 @@ def set_party_account_currency(dt, party_account, doc):
return party_account_currency
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):
payment_type = "Receive"
else:
@ -1673,9 +1660,6 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
elif dt == "Dunning":
grand_total = doc.grand_total
outstanding_amount = doc.grand_total
elif dt == "Donation":
grand_total = doc.amount
outstanding_amount = doc.amount
elif dt == "Gratuity":
grand_total = doc.amount
outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)

View File

@ -633,6 +633,45 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(flt(expected_party_balance), party_balance)
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
def test_multi_currency_payment_entry_with_taxes(self):
payment_entry = create_payment_entry(party='_Test Supplier USD', paid_to = '_Test Payable USD - _TC',
save=True)
payment_entry.append('taxes', {
'account_head': '_Test Account Service Tax - _TC',
'charge_type': 'Actual',
'tax_amount': 10,
'add_deduct_tax': 'Add',
'description': 'Test'
})
payment_entry.save()
self.assertEqual(payment_entry.base_total_taxes_and_charges, 10)
self.assertEqual(flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2))
def create_payment_entry(**args):
payment_entry = frappe.new_doc('Payment Entry')
payment_entry.company = args.get('company') or '_Test Company'
payment_entry.payment_type = args.get('payment_type') or 'Pay'
payment_entry.party_type = args.get('party_type') or 'Supplier'
payment_entry.party = args.get('party') or '_Test Supplier'
payment_entry.paid_from = args.get('paid_from') or '_Test Bank - _TC'
payment_entry.paid_to = args.get('paid_to') or 'Creditors - _TC'
payment_entry.paid_amount = args.get('paid_amount') or 1000
payment_entry.setup_party_account_field()
payment_entry.set_missing_values()
payment_entry.set_exchange_rate()
payment_entry.received_amount = payment_entry.paid_amount / payment_entry.target_exchange_rate
payment_entry.reference_no = 'Test001'
payment_entry.reference_date = nowdate()
if args.get('save'):
payment_entry.save()
if args.get('submit'):
payment_entry.submit()
return payment_entry
def create_payment_terms_template():
create_payment_term('Basic Amount Receivable')

View File

@ -84,20 +84,12 @@ class POSInvoiceMergeLog(Document):
sales_invoice.set_posting_time = 1
sales_invoice.posting_date = getdate(self.posting_date)
sales_invoice.save()
self.write_off_fractional_amount(sales_invoice, data)
sales_invoice.submit()
self.consolidated_invoice = sales_invoice.name
return sales_invoice.name
def write_off_fractional_amount(self, invoice, data):
pos_invoice_grand_total = sum(d.grand_total for d in data)
if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
invoice.save()
def process_merging_into_credit_note(self, data):
credit_note = self.get_new_sales_invoice()
credit_note.is_return = 1
@ -110,7 +102,6 @@ class POSInvoiceMergeLog(Document):
# TODO: return could be against multiple sales invoice which could also have been consolidated?
# credit_note.return_against = self.consolidated_invoice
credit_note.save()
self.write_off_fractional_amount(credit_note, data)
credit_note.submit()
self.consolidated_credit_note = credit_note.name

View File

@ -5,6 +5,7 @@ import json
import unittest
import frappe
from frappe.tests.utils import change_settings
from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
@ -280,3 +281,100 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
@change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3})
def test_consolidation_round_off_error_3(self):
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
item_rates = [69, 59, 29]
for i in [1, 2]:
inv = create_pos_invoice(is_return=1, do_not_save=1)
inv.items = []
for rate in item_rates:
inv.append("items", {
"item_code": "_Test Item",
"warehouse": "_Test Warehouse - _TC",
"qty": -1,
"rate": rate,
"income_account": "Sales - _TC",
"expense_account": "Cost of Goods Sold - _TC",
"cost_center": "_Test Cost Center - _TC",
})
inv.append("taxes", {
"account_head": "_Test Account VAT - _TC",
"charge_type": "On Net Total",
"cost_center": "_Test Cost Center - _TC",
"description": "VAT",
"doctype": "Sales Taxes and Charges",
"rate": 15,
"included_in_print_rate": 1
})
inv.payments = []
inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157
})
inv.paid_amount = -157
inv.save()
inv.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.status, 'Return')
self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")
def test_consolidation_rounding_adjustment(self):
'''
Test if the rounding adjustment is calculated correctly
'''
frappe.db.sql("delete from `tabPOS Invoice`")
try:
make_stock_entry(
to_warehouse="_Test Warehouse - _TC",
item_code="_Test Item",
rate=8000,
qty=10,
)
init_user_and_profile()
inv = create_pos_invoice(qty=1, rate=69.5, do_not_save=True)
inv.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70
})
inv.insert()
inv.submit()
inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True)
inv2.append('payments', {
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60
})
inv2.insert()
inv2.submit()
consolidate_pos_invoices()
inv.load_from_db()
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
self.assertEqual(consolidated_invoice.rounding_adjustment, 1)
finally:
frappe.set_user("Administrator")
frappe.db.sql("delete from `tabPOS Profile`")
frappe.db.sql("delete from `tabPOS Invoice`")

View File

@ -73,7 +73,7 @@ def get_report_pdf(doc, consolidated=True):
'to_date': doc.to_date,
'company': doc.company,
'finance_book': doc.finance_book if doc.finance_book else None,
'account': doc.account if doc.account else None,
'account': [doc.account] if doc.account else None,
'party_type': 'Customer',
'party': [entry.customer],
'presentation_currency': presentation_currency,

View File

@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2013-05-24 19:29:05",
"creation": "2022-01-25 10:29:57.771398",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
@ -651,7 +651,6 @@
"hide_seconds": 1,
"label": "Ignore Pricing Rule",
"no_copy": 1,
"permlevel": 0,
"print_hide": 1
},
{
@ -1974,9 +1973,10 @@
},
{
"default": "0",
"description": "Issue a debit note with 0 qty against an existing Sales Invoice",
"fieldname": "is_debit_note",
"fieldtype": "Check",
"label": "Is Debit Note"
"label": "Is Rate Adjustment Entry (Debit Note)"
},
{
"default": "0",
@ -2038,7 +2038,7 @@
"link_fieldname": "consolidated_invoice"
}
],
"modified": "2021-12-23 20:19:38.667508",
"modified": "2022-03-08 16:08:53.517903",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice",
@ -2089,8 +2089,9 @@
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"timeline_field": "customer",
"title_field": "title",
"track_changes": 1,
"track_seen": 1
}
}

View File

@ -263,6 +263,9 @@ class SalesInvoice(SellingController):
self.process_common_party_accounting()
def validate_pos_return(self):
if self.is_consolidated:
# pos return is already validated in pos invoice
return
if self.is_pos and self.is_return:
total_amount_in_payments = 0

View File

@ -1595,6 +1595,56 @@ class TestSalesInvoice(unittest.TestCase):
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
def test_rounding_adjustment_3(self):
si = create_sales_invoice(do_not_save=True)
si.items = []
for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
si.append("items", {
"item_code": "_Test Item",
"gst_hsn_code": "999800",
"warehouse": "_Test Warehouse - _TC",
"qty": d[1],
"rate": d[0],
"income_account": "Sales - _TC",
"cost_center": "_Test Cost Center - _TC"
})
for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": tax_account,
"description": tax_account,
"rate": 6,
"cost_center": "_Test Cost Center - _TC",
"included_in_print_rate": 1
})
si.save()
si.submit()
self.assertEqual(si.net_total, 4007.16)
self.assertEqual(si.grand_total, 4488.02)
self.assertEqual(si.total_taxes_and_charges, 480.86)
self.assertEqual(si.rounding_adjustment, -0.02)
expected_values = dict((d[0], d) for d in [
[si.debit_to, 4488.0, 0.0],
["_Test Account Service Tax - _TC", 0.0, 240.43],
["_Test Account VAT - _TC", 0.0, 240.43],
["Sales - _TC", 0.0, 4007.15],
["Round Off - _TC", 0.01, 0]
])
gl_entries = frappe.db.sql("""select account, debit, credit
from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
order by account asc""", si.name, as_dict=1)
debit_credit_diff = 0
for gle in gl_entries:
self.assertEqual(expected_values[gle.account][0], gle.account)
self.assertEqual(expected_values[gle.account][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit)
debit_credit_diff += (gle.debit - gle.credit)
self.assertEqual(debit_credit_diff, 0)
def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
@ -2429,14 +2479,22 @@ class TestSalesInvoice(unittest.TestCase):
def test_sales_commission(self):
si = frappe.copy_doc(test_records[0])
si = frappe.copy_doc(test_records[2])
frappe.db.set_value('Item', si.get('items')[0].item_code, 'grant_commission', 1)
frappe.db.set_value('Item', si.get('items')[1].item_code, 'grant_commission', 0)
item = copy.deepcopy(si.get('items')[0])
item.update({
"qty": 1,
"rate": 500,
"grant_commission": 1
})
si.append("items", item)
item = copy.deepcopy(si.get('items')[1])
item.update({
"qty": 1,
"rate": 500,
})
# Test valid values
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):

View File

@ -832,6 +832,7 @@
},
{
"default": "0",
"fetch_from": "item_code.grant_commission",
"fieldname": "grant_commission",
"fieldtype": "Check",
"label": "Grant Commission",
@ -841,7 +842,7 @@
"idx": 1,
"istable": 1,
"links": [],
"modified": "2021-10-05 12:24:54.968907",
"modified": "2022-02-24 14:41:36.392560",
"modified_by": "Administrator",
"module": "Accounts",
"name": "Sales Invoice Item",
@ -849,5 +850,6 @@
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC"
"sort_order": "DESC",
"states": []
}

View File

@ -55,5 +55,8 @@ def validate_disabled(doc):
frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc):
if not doc.tax_category:
return
if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))

View File

@ -274,7 +274,7 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
debit_credit_diff += flt(d.credit)
round_off_account_exists = True
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
gl_map.remove(round_off_gle)
return

View File

@ -1,3 +1,5 @@
from frappe import _
def get_data():
return {
'non_standard_fieldnames': {
@ -5,7 +7,7 @@ def get_data():
},
'transactions': [
{
'label': ['Movement'],
'label': _('Movement'),
'items': ['Asset Movement']
}
]

View File

@ -48,7 +48,7 @@ frappe.ui.form.on('Asset Maintenance', {
<div class='col-sm-3 small'>
<a onclick="frappe.set_route('List', 'Asset Maintenance Log',
{'asset_name': '${d.asset_name}','maintenance_status': '${d.maintenance_status}' });">
${d.maintenance_status} <span class="badge">${d.count}</span>
${__(d.maintenance_status)} <span class="badge">${d.count}</span>
</a>
</div>
</div>`).appendTo(rows);

View File

@ -68,6 +68,28 @@ frappe.ui.form.on('Asset Repair', {
});
frappe.ui.form.on('Asset Repair Consumed Item', {
item_code: function(frm, cdt, cdn) {
var item = locals[cdt][cdn];
let item_args = {
'item_code': item.item_code,
'warehouse': frm.doc.warehouse,
'qty': item.consumed_quantity,
'serial_no': item.serial_no,
'company': frm.doc.company
};
frappe.call({
method: 'erpnext.stock.utils.get_incoming_rate',
args: {
args: item_args
},
callback: function(r) {
frappe.model.set_value(cdt, cdn, 'valuation_rate', r.message);
}
});
},
consumed_quantity: function(frm, cdt, cdn) {
var row = locals[cdt][cdn];
frappe.model.set_value(cdt, cdn, 'total_value', row.consumed_quantity * row.valuation_rate);

View File

@ -13,12 +13,10 @@
],
"fields": [
{
"fetch_from": "item.valuation_rate",
"fieldname": "valuation_rate",
"fieldtype": "Currency",
"in_list_view": 1,
"label": "Valuation Rate",
"read_only": 1
"label": "Valuation Rate"
},
{
"fieldname": "consumed_quantity",
@ -49,7 +47,7 @@
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-11 18:23:00.492483",
"modified": "2022-02-08 17:37:20.028290",
"modified_by": "Administrator",
"module": "Assets",
"name": "Asset Repair Consumed Item",

View File

@ -3,15 +3,11 @@
frappe.ui.form.on('Bulk Transaction Log', {
before_load: function(frm) {
query(frm);
},
refresh: function(frm) {
frm.disable_save();
frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
query(frm);
query(frm, 1);
}
);
});
@ -25,8 +21,8 @@ function query(frm) {
log_date: frm.doc.log_date
}
}).then((r) => {
if (r.message) {
frm.remove_custom_button("Retry Failed Transactions");
if (r.message === "No Failed Records") {
frappe.show_alert(__(r.message), 5);
} else {
frappe.show_alert(__("Retrying Failed Transactions"), 5);
}

View File

@ -15,6 +15,8 @@ class BulkTransactionLog(Document):
@frappe.whitelist()
def retry_failing_transaction(log_date=None):
if not log_date:
log_date = str(date.today())
btp = frappe.qb.DocType("Bulk Transaction Log Detail")
data = (
frappe.qb.from_(btp)
@ -25,9 +27,7 @@ def retry_failing_transaction(log_date=None):
.where(btp.date == log_date)
).run(as_dict=True)
if data:
if not log_date:
log_date = str(date.today())
if data :
if len(data) > 10:
frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
else:

View File

@ -316,6 +316,16 @@ class PurchaseOrder(BuyingController):
'target_ref_field': 'stock_qty',
'source_field': 'stock_qty'
})
self.status_updater.append({
'source_dt': 'Purchase Order Item',
'target_dt': 'Packed Item',
'target_field': 'ordered_qty',
'target_parent_dt': 'Sales Order',
'target_parent_field': '',
'join_field': 'sales_order_packed_item',
'target_ref_field': 'qty',
'source_field': 'stock_qty'
})
def update_delivered_qty_in_sales_order(self):
"""Update delivered qty in Sales Order for drop ship"""

View File

@ -3,9 +3,9 @@
import json
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@ -27,7 +27,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestPurchaseOrder(unittest.TestCase):
class TestPurchaseOrder(FrappeTestCase):
def test_make_purchase_receipt(self):
po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)

View File

@ -63,6 +63,7 @@
"material_request_item",
"sales_order",
"sales_order_item",
"sales_order_packed_item",
"supplier_quotation",
"supplier_quotation_item",
"col_break5",
@ -837,21 +838,30 @@
"label": "Product Bundle",
"options": "Product Bundle",
"read_only": 1
},
{
"fieldname": "sales_order_packed_item",
"fieldtype": "Data",
"label": "Sales Order Packed Item",
"no_copy": 1,
"print_hide": 1
}
],
"idx": 1,
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-08-30 20:06:26.712097",
"modified": "2022-02-02 13:10:18.398976",
"modified_by": "Administrator",
"module": "Buying",
"name": "Purchase Order Item",
"naming_rule": "Random",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
"search_fields": "item_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}

View File

@ -1,9 +1,9 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import nowdate
from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
@ -16,7 +16,7 @@ from erpnext.stock.doctype.item.test_item import make_item
from erpnext.templates.pages.rfq import check_supplier_has_docname_access
class TestRequestforQuotation(unittest.TestCase):
class TestRequestforQuotation(FrappeTestCase):
def test_quote_status(self):
rfq = make_request_for_quotation()

View File

@ -1,7 +1,6 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
import unittest
import frappe
from frappe.test_runner import make_test_records
@ -12,8 +11,10 @@ from erpnext.exceptions import PartyDisabled
test_dependencies = ['Payment Term', 'Payment Terms Template']
test_records = frappe.get_test_records('Supplier')
from frappe.tests.utils import FrappeTestCase
class TestSupplier(unittest.TestCase):
class TestSupplier(FrappeTestCase):
def test_get_supplier_group_details(self):
doc = frappe.new_doc("Supplier Group")
doc.supplier_group_name = "_Testing Supplier Group"

View File

@ -3,12 +3,12 @@
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
class TestPurchaseOrder(unittest.TestCase):
class TestPurchaseOrder(FrappeTestCase):
def test_make_purchase_order(self):
from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order

View File

@ -1,12 +1,12 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
class TestSupplierScorecard(unittest.TestCase):
class TestSupplierScorecard(FrappeTestCase):
def test_create_scorecard(self):
doc = make_supplier_scorecard().insert()

View File

@ -1,12 +1,12 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
class TestSupplierScorecardCriteria(unittest.TestCase):
class TestSupplierScorecardCriteria(FrappeTestCase):
def test_variables_exist(self):
delete_test_scorecards()
for d in test_good_criteria:

View File

@ -1,16 +1,16 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable import (
VariablePathNotFound,
)
class TestSupplierScorecardVariable(unittest.TestCase):
class TestSupplierScorecardVariable(FrappeTestCase):
def test_variable_exist(self):
for d in test_existing_variables:
my_doc = frappe.get_doc("Supplier Scorecard Variable", d.get("name"))

View File

@ -2,10 +2,10 @@
# For license information, please see license.txt
import unittest
from datetime import datetime
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.report.procurement_tracker.procurement_tracker import execute
@ -14,7 +14,7 @@ from erpnext.stock.doctype.material_request.test_material_request import make_ma
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
class TestProcurementTracker(unittest.TestCase):
class TestProcurementTracker(FrappeTestCase):
def test_result_for_procurement_tracker(self):
filters = {
'company': '_Test Procurement Company',

View File

@ -6,6 +6,7 @@ import copy
import frappe
from frappe import _
from frappe.query_builder.functions import Coalesce, Sum
from frappe.utils import date_diff, flt, getdate
@ -16,12 +17,9 @@ def execute(filters=None):
validate_filters(filters)
columns = get_columns(filters)
conditions = get_conditions(filters)
data = get_data(filters)
#get queried data
data = get_data(filters, conditions)
#prepare data for report and chart views
# prepare data for report and chart views
data, chart_data = prepare_data(data, filters)
return columns, data, None, chart_data
@ -34,53 +32,70 @@ def validate_filters(filters):
elif date_diff(to_date, from_date) < 0:
frappe.throw(_("To Date cannot be before From Date."))
def get_conditions(filters):
conditions = ''
def get_data(filters):
mr = frappe.qb.DocType("Material Request")
mr_item = frappe.qb.DocType("Material Request Item")
query = (
frappe.qb.from_(mr)
.join(mr_item).on(mr_item.parent == mr.name)
.select(
mr.name.as_("material_request"),
mr.transaction_date.as_("date"),
mr_item.schedule_date.as_("required_date"),
mr_item.item_code.as_("item_code"),
Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
Coalesce(mr_item.stock_uom, '').as_("uom"),
Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
(
Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))
).as_("qty_to_receive"),
Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
(
Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.ordered_qty, 0))
).as_("qty_to_order"),
mr_item.item_name,
mr_item.description,
mr.company
).where(
(mr.material_request_type == "Purchase")
& (mr.docstatus == 1)
& (mr.status != "Stopped")
& (mr.per_received < 100)
)
)
query = get_conditions(filters, query, mr, mr_item) # add conditional conditions
query = (
query.groupby(
mr.name, mr_item.item_code
).orderby(
mr.transaction_date, mr.schedule_date
)
)
data = query.run(as_dict=True)
return data
def get_conditions(filters, query, mr, mr_item):
if filters.get("from_date") and filters.get("to_date"):
conditions += " and mr.transaction_date between '{0}' and '{1}'".format(filters.get("from_date"),filters.get("to_date"))
query = (
query.where(
(mr.transaction_date >= filters.get("from_date"))
& (mr.transaction_date <= filters.get("to_date"))
)
)
if filters.get("company"):
conditions += " and mr.company = '{0}'".format(filters.get("company"))
query = query.where(mr.company == filters.get("company"))
if filters.get("material_request"):
conditions += " and mr.name = '{0}'".format(filters.get("material_request"))
query = query.where(mr.name == filters.get("material_request"))
if filters.get("item_code"):
conditions += " and mr_item.item_code = '{0}'".format(filters.get("item_code"))
query = query.where(mr_item.item_code == filters.get("item_code"))
return conditions
def get_data(filters, conditions):
data = frappe.db.sql("""
select
mr.name as material_request,
mr.transaction_date as date,
mr_item.schedule_date as required_date,
mr_item.item_code as item_code,
sum(ifnull(mr_item.stock_qty, 0)) as qty,
ifnull(mr_item.stock_uom, '') as uom,
sum(ifnull(mr_item.ordered_qty, 0)) as ordered_qty,
sum(ifnull(mr_item.received_qty, 0)) as received_qty,
(sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.received_qty, 0))) as qty_to_receive,
(sum(ifnull(mr_item.stock_qty, 0)) - sum(ifnull(mr_item.ordered_qty, 0))) as qty_to_order,
mr_item.item_name as item_name,
mr_item.description as "description",
mr.company as company
from
`tabMaterial Request` mr, `tabMaterial Request Item` mr_item
where
mr_item.parent = mr.name
and mr.material_request_type = "Purchase"
and mr.docstatus = 1
and mr.status != "Stopped"
{conditions}
group by mr.name, mr_item.item_code
having
sum(ifnull(mr_item.ordered_qty, 0)) < sum(ifnull(mr_item.stock_qty, 0))
order by mr.transaction_date, mr.schedule_date""".format(conditions=conditions), as_dict=1)
return data
return query
def update_qty_columns(row_to_update, data_row):
fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]

View File

@ -0,0 +1,69 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, today
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.report.requested_items_to_order_and_receive.requested_items_to_order_and_receive import (
get_data,
)
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.material_request.material_request import make_purchase_order
class TestRequestedItemsToOrderAndReceive(FrappeTestCase):
def setUp(self) -> None:
create_item("Test MR Report Item")
self.setup_material_request() # to order and receive
self.setup_material_request(order=True) # to receive (ordered)
self.setup_material_request(order=True, receive=True) # complete (ordered & received)
self.filters = frappe._dict(
company="_Test Company", from_date=today(), to_date=add_days(today(), 30),
item_code="Test MR Report Item"
)
def tearDown(self) -> None:
frappe.db.rollback()
def test_date_range(self):
data = get_data(self.filters)
self.assertEqual(len(data), 2) # MRs today should be fetched
self.filters.from_date = add_days(today(), 1)
data = get_data(self.filters)
self.assertEqual(len(data), 0) # MRs today should not be fetched as from date is tomorrow
def test_ordered_received_material_requests(self):
data = get_data(self.filters)
# from the 3 MRs made, only 2 (to receive) should be fetched
self.assertEqual(len(data), 2)
self.assertEqual(data[0].ordered_qty, 0.0)
self.assertEqual(data[1].ordered_qty, 57.0)
def setup_material_request(self, order=False, receive=False):
po = None
test_records = frappe.get_test_records('Material Request')
mr = frappe.copy_doc(test_records[0])
mr.transaction_date = today()
mr.schedule_date = add_days(today(), 1)
for row in mr.items:
row.item_code = "Test MR Report Item"
row.item_name = "Test MR Report Item"
row.description = "Test MR Report Item"
row.uom = "Nos"
row.schedule_date = add_days(today(), 1)
mr.submit()
if order or receive:
po = make_purchase_order(mr.name)
po.supplier = "_Test Supplier"
po.submit()
if receive:
pr = make_purchase_receipt(po.name)
pr.submit()

View File

@ -3,9 +3,9 @@
# Compiled at: 2019-05-06 09:51:46
# Decompiled by https://python-decompiler.com
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@ -15,7 +15,7 @@ from erpnext.buying.report.subcontracted_item_to_be_received.subcontracted_item_
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestSubcontractedItemToBeReceived(unittest.TestCase):
class TestSubcontractedItemToBeReceived(FrappeTestCase):
def test_pending_and_received_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')

View File

@ -4,9 +4,9 @@
# Decompiled by https://python-decompiler.com
import json
import unittest
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@ -16,7 +16,7 @@ from erpnext.buying.report.subcontracted_raw_materials_to_be_transferred.subcont
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
class TestSubcontractedItemToBeTransferred(unittest.TestCase):
class TestSubcontractedItemToBeTransferred(FrappeTestCase):
def test_pending_and_transferred_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")

View File

@ -507,13 +507,41 @@ class StockController(AccountsController):
"voucher_no": self.name,
"company": self.company
})
if future_sle_exists(args):
if future_sle_exists(args) or repost_required_for_queue(self):
item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
if item_based_reposting:
create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
else:
create_repost_item_valuation_entry(args)
def repost_required_for_queue(doc: StockController) -> bool:
"""check if stock document contains repeated item-warehouse with queue based valuation.
if queue exists for repeated items then SLEs need to reprocessed in background again.
"""
consuming_sles = frappe.db.get_all("Stock Ledger Entry",
filters={
"voucher_type": doc.doctype,
"voucher_no": doc.name,
"actual_qty": ("<", 0),
"is_cancelled": 0
},
fields=["item_code", "warehouse", "stock_queue"]
)
item_warehouses = [(sle.item_code, sle.warehouse) for sle in consuming_sles]
unique_item_warehouses = set(item_warehouses)
if len(unique_item_warehouses) == len(item_warehouses):
return False
for sle in consuming_sles:
if sle.stock_queue != "[]": # using FIFO/LIFO valuation
return True
return False
@frappe.whitelist()
def make_quality_inspections(doctype, docname, items):

View File

@ -363,8 +363,6 @@ class Subcontracting():
return
for row in self.get(self.raw_material_table):
self.__validate_consumed_qty(row)
key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if not self.__transferred_items or not self.__transferred_items.get(key):
return
@ -372,12 +370,6 @@ class Subcontracting():
self.__validate_batch_no(row, key)
self.__validate_serial_no(row, key)
def __validate_consumed_qty(self, row):
if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
def __validate_batch_no(self, row, key):
if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
link = get_link_to_form('Purchase Order', row.purchase_order)

View File

@ -113,17 +113,24 @@ class calculate_taxes_and_totals(object):
for item in self.doc.get("items"):
self.doc.round_floats_in(item)
if not item.rate:
item.rate = item.price_list_rate
if item.discount_percentage == 100:
item.rate = 0.0
elif item.price_list_rate:
if not item.rate or (item.pricing_rules and item.discount_percentage > 0):
if item.pricing_rules or abs(item.discount_percentage) > 0:
item.rate = flt(item.price_list_rate *
(1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount and item.pricing_rules:
if abs(item.discount_percentage) > 0:
item.discount_amount = item.price_list_rate * (item.discount_percentage / 100.0)
elif item.discount_amount or item.pricing_rules:
item.rate = item.price_list_rate - item.discount_amount
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item', 'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item',
'POS Invoice Item', 'Purchase Invoice Item', 'Purchase Order Item', 'Purchase Receipt Item']:
item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
if flt(item.rate_with_margin) > 0:
item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
@ -270,7 +277,8 @@ class calculate_taxes_and_totals(object):
shipping_rule.apply(self.doc)
def calculate_taxes(self):
if not self.doc.get('is_consolidated'):
rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment')
if not rounding_adjustment_computed:
self.doc.rounding_adjustment = 0
# maintain actual tax rate based on idx
@ -326,7 +334,7 @@ class calculate_taxes_and_totals(object):
if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \
and self.doc.discount_amount \
and self.doc.apply_discount_on == "Grand Total" \
and not self.doc.get('is_consolidated'):
and not rounding_adjustment_computed:
self.doc.rounding_adjustment = flt(self.doc.grand_total
- flt(self.doc.discount_amount) - tax.total,
self.doc.precision("rounding_adjustment"))
@ -465,20 +473,22 @@ class calculate_taxes_and_totals(object):
self.doc.total_net_weight += d.total_weight
def set_rounded_total(self):
if not self.doc.get('is_consolidated'):
if self.doc.meta.get_field("rounded_total"):
if self.doc.is_rounded_total_disabled():
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'):
return
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
self.doc.currency, self.doc.precision("rounded_total"))
if self.doc.meta.get_field("rounded_total"):
if self.doc.is_rounded_total_disabled():
self.doc.rounded_total = self.doc.base_rounded_total = 0
return
#if print_in_rate is set, we would have already calculated rounding adjustment
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
self.doc.precision("rounding_adjustment"))
self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total,
self.doc.currency, self.doc.precision("rounded_total"))
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
#if print_in_rate is set, we would have already calculated rounding adjustment
self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total,
self.doc.precision("rounding_adjustment"))
self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
def _cleanup(self):
if not self.doc.get('is_consolidated'):

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

@ -5,6 +5,7 @@
import unittest
import frappe
from frappe.tests.utils import change_settings
from frappe.utils import add_months, nowdate
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
@ -15,7 +16,7 @@ from erpnext.e_commerce.shopping_cart.cart import (
get_party,
update_cart,
)
from erpnext.tests.utils import change_settings, create_test_contact_and_address
from erpnext.tests.utils import create_test_contact_and_address
# test_dependencies = ['Payment Terms Template']

View File

@ -1,4 +1,5 @@
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.controllers.item_variant import create_variant
from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
@ -7,11 +8,10 @@ from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings imp
from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Item"]
class TestVariantSelector(ERPNextTestCase):
class TestVariantSelector(FrappeTestCase):
@classmethod
def setUpClass(cls):
@ -116,4 +116,4 @@ class TestVariantSelector(ERPNextTestCase):
self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
self.assertEqual(price_info["price_list_rate"], 100.0)
self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")

View File

@ -68,7 +68,6 @@ domains = {
'Distribution': 'erpnext.domains.distribution',
'Education': 'erpnext.domains.education',
'Manufacturing': 'erpnext.domains.manufacturing',
'Non Profit': 'erpnext.domains.non_profit',
'Retail': 'erpnext.domains.retail',
'Services': 'erpnext.domains.services',
}
@ -175,7 +174,6 @@ standard_portal_menu_items = [
{"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"},
{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
{"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": _("Appointment Booking"), "route": "/book_appointment"},
]
@ -369,7 +367,6 @@ scheduler_events = {
"erpnext.selling.doctype.quotation.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.non_profit.doctype.membership.membership.set_expired_status",
"erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder"
],
"daily_long": [
@ -563,19 +560,6 @@ global_search_doctypes = {
{'doctype': 'Assessment Code', 'index': 39},
{'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 = {

View File

@ -174,16 +174,22 @@ def get_month_map():
def get_unmarked_days(employee, month, exclude_holidays=0):
import calendar
month_map = get_month_map()
today = get_datetime()
dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(1, calendar.monthrange(today.year, month_map[month])[1] + 1)]
joining_date, relieving_date = frappe.get_cached_value("Employee", employee, ["date_of_joining", "relieving_date"])
start_day = 1
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
length = len(dates_of_month)
month_start, month_end = dates_of_month[0], dates_of_month[length-1]
if joining_date and joining_date.month == month_map[month]:
start_day = joining_date.day
if relieving_date and relieving_date.month == month_map[month]:
end_day = relieving_date.day + 1
records = frappe.get_all("Attendance", fields = ['attendance_date', 'employee'] , filters = [
dates_of_month = ['{}-{}-{}'.format(today.year, month_map[month], r) for r in range(start_day, end_day)]
month_start, month_end = dates_of_month[0], dates_of_month[-1]
records = frappe.get_all("Attendance", fields=['attendance_date', 'employee'], filters=[
["attendance_date", ">=", month_start],
["attendance_date", "<=", month_end],
["employee", "=", employee],
@ -200,7 +206,7 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
for date in dates_of_month:
date_time = get_datetime(date)
if today.day == date_time.day and today.month == date_time.month:
if today.day <= date_time.day and today.month <= date_time.month:
break
if date_time not in marked_days:
unmarked_days.append(date)

View File

@ -1,20 +1,108 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
# See license.txt
import unittest
import frappe
from frappe.utils import nowdate
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, get_first_day, getdate, now_datetime, nowdate
from erpnext.hr.doctype.attendance.attendance import (
get_month_map,
get_unmarked_days,
mark_attendance,
)
from erpnext.hr.doctype.employee.test_employee import make_employee
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
test_records = frappe.get_test_records('Attendance')
class TestAttendance(unittest.TestCase):
class TestAttendance(FrappeTestCase):
def test_mark_absent(self):
from erpnext.hr.doctype.employee.test_employee import make_employee
employee = make_employee("test_mark_absent@example.com")
date = nowdate()
frappe.db.delete('Attendance', {'employee':employee, 'attendance_date':date})
from erpnext.hr.doctype.attendance.attendance import mark_attendance
attendance = mark_attendance(employee, date, 'Absent')
fetch_attendance = frappe.get_value('Attendance', {'employee':employee, 'attendance_date':date, 'status':'Absent'})
self.assertEqual(attendance, fetch_attendance)
def test_unmarked_days(self):
first_day = get_first_day(getdate())
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
frappe.db.delete('Attendance', {'employee': employee})
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
holiday_list = make_holiday_list()
frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
first_sunday = get_first_sunday(holiday_list)
mark_attendance(employee, first_day, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(first_day, unmarked_days)
# attendance unmarked
self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
# holiday considered in unmarked days
self.assertIn(first_sunday, unmarked_days)
def test_unmarked_days_excluding_holidays(self):
first_day = get_first_day(getdate())
employee = make_employee('test_unmarked_days@example.com', date_of_joining=add_days(first_day, -1))
frappe.db.delete('Attendance', {'employee': employee})
from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
holiday_list = make_holiday_list()
frappe.db.set_value('Employee', employee, 'holiday_list', holiday_list)
first_sunday = get_first_sunday(holiday_list)
mark_attendance(employee, first_day, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name, exclude_holidays=True)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(first_day, unmarked_days)
# attendance unmarked
self.assertIn(getdate(add_days(first_day, 1)), unmarked_days)
# holidays not considered in unmarked days
self.assertNotIn(first_sunday, unmarked_days)
def test_unmarked_days_as_per_joining_and_relieving_dates(self):
now = now_datetime()
previous_month = now.month - 1
first_day = now.replace(day=1).replace(month=previous_month).date()
doj = add_days(first_day, 1)
relieving_date = add_days(first_day, 5)
employee = make_employee('test_unmarked_days_as_per_doj@example.com', date_of_joining=doj,
relieving_date=relieving_date)
frappe.db.delete('Attendance', {'employee': employee})
attendance_date = add_days(first_day, 2)
mark_attendance(employee, attendance_date, 'Present')
month_name = get_month_name(first_day)
unmarked_days = get_unmarked_days(employee, month_name)
unmarked_days = [getdate(date) for date in unmarked_days]
# attendance already marked for the day
self.assertNotIn(attendance_date, unmarked_days)
# date before doj not in unmarked days
self.assertNotIn(add_days(doj, -1), unmarked_days)
# date after relieving not in unmarked days
self.assertNotIn(add_days(relieving_date, 1), unmarked_days)
def tearDown(self):
frappe.db.rollback()
def get_month_name(date):
month_number = date.month
for month, number in get_month_map().items():
if number == month_number:
return month

View File

@ -2,7 +2,7 @@
"actions": [],
"allow_import": 1,
"autoname": "naming_series:",
"creation": "2017-10-09 14:26:29.612365",
"creation": "2022-01-17 18:36:51.450395",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
@ -121,7 +121,7 @@
"fieldtype": "Select",
"label": "Status",
"no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled",
"options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled",
"read_only": 1
},
{
@ -200,7 +200,7 @@
],
"is_submittable": 1,
"links": [],
"modified": "2021-09-11 18:38:38.617478",
"modified": "2022-01-17 19:33:52.345823",
"modified_by": "Administrator",
"module": "HR",
"name": "Employee Advance",
@ -237,5 +237,41 @@
"search_fields": "employee,employee_name",
"sort_field": "modified",
"sort_order": "DESC",
"states": [
{
"color": "Red",
"custom": 1,
"title": "Draft"
},
{
"color": "Green",
"custom": 1,
"title": "Paid"
},
{
"color": "Orange",
"custom": 1,
"title": "Unpaid"
},
{
"color": "Blue",
"custom": 1,
"title": "Claimed"
},
{
"color": "Gray",
"title": "Returned"
},
{
"color": "Yellow",
"title": "Partly Claimed and Returned"
},
{
"color": "Red",
"custom": 1,
"title": "Cancelled"
}
],
"title_field": "employee_name",
"track_changes": 1
}

View File

@ -27,19 +27,33 @@ class EmployeeAdvance(Document):
def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry')
self.set_status(update=True)
def set_status(self, update=False):
precision = self.precision("paid_amount")
total_amount = flt(flt(self.claimed_amount) + flt(self.return_amount), precision)
status = None
def set_status(self):
if self.docstatus == 0:
self.status = "Draft"
if self.docstatus == 1:
if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount):
self.status = "Claimed"
elif self.paid_amount and self.advance_amount == flt(self.paid_amount):
self.status = "Paid"
status = "Draft"
elif self.docstatus == 1:
if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
status = "Claimed"
elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
status = "Returned"
elif flt(self.claimed_amount) > 0 and (flt(self.return_amount) > 0) and total_amount == flt(self.paid_amount, precision):
status = "Partly Claimed and Returned"
elif flt(self.paid_amount) > 0 and flt(self.advance_amount, precision) == flt(self.paid_amount, precision):
status = "Paid"
else:
self.status = "Unpaid"
status = "Unpaid"
elif self.docstatus == 2:
self.status = "Cancelled"
status = "Cancelled"
if update:
self.db_set("status", status)
else:
self.status = status
def set_total_advance_paid(self):
gle = frappe.qb.DocType("GL Entry")
@ -85,9 +99,7 @@ class EmployeeAdvance(Document):
self.db_set("paid_amount", paid_amount)
self.db_set("return_amount", return_amount)
self.set_status()
frappe.db.set_value("Employee Advance", self.name , "status", self.status)
self.set_status(update=True)
def update_claimed_amount(self):
claimed_amount = frappe.db.sql("""
@ -103,8 +115,8 @@ class EmployeeAdvance(Document):
frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload()
self.set_status()
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
self.set_status(update=True)
@frappe.whitelist()
def get_pending_amount(employee, posting_date):
@ -222,7 +234,8 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
'reference_name': employee_advance_name,
'party_type': 'Employee',
'party': employee,
'is_advance': 'Yes'
'is_advance': 'Yes',
'cost_center': erpnext.get_default_cost_center(company)
})
bank_amount = flt(return_amount) if bank_cash_account.account_currency==currency \
@ -233,7 +246,8 @@ def make_return_entry(employee, company, employee_advance_name, return_amount,
"debit_in_account_currency": bank_amount,
"account_currency": bank_cash_account.account_currency,
"account_type": bank_cash_account.account_type,
"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1
"exchange_rate": flt(exchange_rate) if bank_cash_account.account_currency == currency else 1,
"cost_center": erpnext.get_default_cost_center(company)
})
return je.as_dict()

View File

@ -4,7 +4,7 @@
import unittest
import frappe
from frappe.utils import nowdate
from frappe.utils import flt, nowdate
import erpnext
from erpnext.hr.doctype.employee.test_employee import make_employee
@ -12,12 +12,21 @@ from erpnext.hr.doctype.employee_advance.employee_advance import (
EmployeeAdvanceOverPayment,
create_return_through_additional_salary,
make_bank_entry,
make_return_entry,
)
from erpnext.hr.doctype.expense_claim.expense_claim import get_advances
from erpnext.hr.doctype.expense_claim.test_expense_claim import (
get_payable_account,
make_expense_claim,
)
from erpnext.payroll.doctype.salary_component.test_salary_component import create_salary_component
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestEmployeeAdvance(unittest.TestCase):
def setUp(self):
frappe.db.delete("Employee Advance")
def test_paid_amount_and_status(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name)
@ -52,9 +61,102 @@ class TestEmployeeAdvance(unittest.TestCase):
self.assertEqual(advance.paid_amount, 0)
self.assertEqual(advance.status, "Unpaid")
advance.cancel()
advance.reload()
self.assertEqual(advance.status, "Cancelled")
def test_claimed_status(self):
# CLAIMED Status check, full amount claimed
payable_account = get_payable_account("_Test Company")
claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
claim = get_advances_for_claim(claim, advance.name)
claim.save()
claim.submit()
advance.reload()
self.assertEqual(advance.claimed_amount, 1000)
self.assertEqual(advance.status, "Claimed")
# advance should not be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name not in advances)
# cancel claim; status should be Paid
claim.cancel()
advance.reload()
self.assertEqual(advance.claimed_amount, 0)
self.assertEqual(advance.status, "Paid")
def test_partly_claimed_and_returned_status(self):
payable_account = get_payable_account("_Test Company")
claim = make_expense_claim(payable_account, 1000, 1000, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
# PARTLY CLAIMED AND RETURNED status check
# 500 Claimed, 500 Returned
claim = make_expense_claim(payable_account, 500, 500, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
advance = make_employee_advance(claim.employee)
pe = make_payment_entry(advance)
pe.submit()
claim = get_advances_for_claim(claim, advance.name, amount=500)
claim.save()
claim.submit()
advance.reload()
self.assertEqual(advance.claimed_amount, 500)
self.assertEqual(advance.status, "Paid")
entry = make_return_entry(
employee=advance.employee,
company=advance.company,
employee_advance_name=advance.name,
return_amount=flt(advance.paid_amount - advance.claimed_amount),
advance_account=advance.advance_account,
mode_of_payment=advance.mode_of_payment,
currency=advance.currency,
exchange_rate=advance.exchange_rate
)
entry = frappe.get_doc(entry)
entry.insert()
entry.submit()
advance.reload()
self.assertEqual(advance.return_amount, 500)
self.assertEqual(advance.status, "Partly Claimed and Returned")
# advance should not be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name not in advances)
# Cancel return entry; status should change to PAID
entry.cancel()
advance.reload()
self.assertEqual(advance.return_amount, 0)
self.assertEqual(advance.status, "Paid")
# advance should be shown in claims
advances = get_advances(claim.employee)
advances = [entry.name for entry in advances]
self.assertTrue(advance.name in advances)
def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
pe = make_payment_entry(advance)
pe.submit()
args = {"type": "Deduction"}
create_salary_component("Advance Salary - Deduction", **args)
@ -82,11 +184,13 @@ class TestEmployeeAdvance(unittest.TestCase):
advance.reload()
self.assertEqual(advance.return_amount, 1000)
self.assertEqual(advance.status, "Returned")
# update advance return amount on additional salary cancellation
additional_salary.cancel()
advance.reload()
self.assertEqual(advance.return_amount, 700)
self.assertEqual(advance.status, "Paid")
def tearDown(self):
frappe.db.rollback()
@ -118,3 +222,24 @@ def make_employee_advance(employee_name, args=None):
doc.submit()
return doc
def get_advances_for_claim(claim, advance_name, amount=None):
advances = get_advances(claim.employee, advance_name)
for entry in advances:
if amount:
allocated_amount = amount
else:
allocated_amount = flt(entry.paid_amount) - flt(entry.claimed_amount)
claim.append("advances", {
"employee_advance": entry.name,
"posting_date": entry.posting_date,
"advance_account": entry.advance_account,
"advance_paid": entry.paid_amount,
"unclaimed_amount": allocated_amount,
"allocated_amount": allocated_amount
})
return claim

View File

@ -171,7 +171,7 @@ frappe.ui.form.on("Expense Claim", {
['docstatus', '=', 1],
['employee', '=', frm.doc.employee],
['paid_amount', '>', 0],
['status', '!=', 'Claimed']
['status', 'not in', ['Claimed', 'Returned', 'Partly Claimed and Returned']]
]
};
});

View File

@ -23,10 +23,10 @@ class ExpenseClaim(AccountsController):
def validate(self):
validate_active_employee(self.employee)
self.validate_advances()
set_employee_name(self)
self.validate_sanctioned_amount()
self.calculate_total_amount()
set_employee_name(self)
self.validate_advances()
self.set_expense_account(validate=True)
self.set_payable_account()
self.set_cost_center()
@ -341,18 +341,27 @@ def get_expense_claim_account(expense_claim_type, company):
@frappe.whitelist()
def get_advances(employee, advance_id=None):
if not advance_id:
condition = 'docstatus=1 and employee={0} and paid_amount > 0 and paid_amount > claimed_amount + return_amount'.format(frappe.db.escape(employee))
else:
condition = 'name={0}'.format(frappe.db.escape(advance_id))
advance = frappe.qb.DocType("Employee Advance")
return frappe.db.sql("""
select
name, posting_date, paid_amount, claimed_amount, advance_account
from
`tabEmployee Advance`
where {0}
""".format(condition), as_dict=1)
query = (
frappe.qb.from_(advance)
.select(
advance.name, advance.posting_date, advance.paid_amount,
advance.claimed_amount, advance.advance_account
)
)
if not advance_id:
query = query.where(
(advance.docstatus == 1)
& (advance.employee == employee)
& (advance.paid_amount > 0)
& (advance.status.notin(["Claimed", "Returned", "Partly Claimed and Returned"]))
)
else:
query = query.where(advance.name == advance_id)
return query.run(as_dict=True)
@frappe.whitelist()

View File

@ -5,6 +5,7 @@ from typing import Dict
import frappe
from frappe import _
from frappe.query_builder.functions import Max, Min, Sum
from frappe.utils import (
add_days,
cint,
@ -674,28 +675,39 @@ def get_leave_balance_on(employee, leave_type, date, to_date=None,
def get_leave_allocation_records(employee, date, leave_type=None):
''' returns the total allocated leaves and carry forwarded leaves based on ledger entries '''
"""Returns the total allocated leaves and carry forwarded leaves based on ledger entries"""
Ledger = frappe.qb.DocType("Leave Ledger Entry")
conditions = ("and leave_type='%s'" % leave_type) if leave_type else ""
allocation_details = frappe.db.sql("""
SELECT
SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves,
SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves,
MIN(from_date) as from_date,
MAX(to_date) as to_date,
leave_type
FROM `tabLeave Ledger Entry`
WHERE
from_date <= %(date)s
AND to_date >= %(date)s
AND docstatus=1
AND transaction_type="Leave Allocation"
AND employee=%(employee)s
AND is_expired=0
AND is_lwp=0
{0}
GROUP BY employee, leave_type
""".format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec
cf_leave_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "1", Ledger.leaves).else_(0)
sum_cf_leaves = Sum(cf_leave_case).as_("cf_leaves")
new_leaves_case = frappe.qb.terms.Case().when(Ledger.is_carry_forward == "0", Ledger.leaves).else_(0)
sum_new_leaves = Sum(new_leaves_case).as_("new_leaves")
query = (
frappe.qb.from_(Ledger)
.select(
sum_cf_leaves,
sum_new_leaves,
Min(Ledger.from_date).as_("from_date"),
Max(Ledger.to_date).as_("to_date"),
Ledger.leave_type
).where(
(Ledger.from_date <= date)
& (Ledger.to_date >= date)
& (Ledger.docstatus == 1)
& (Ledger.transaction_type == "Leave Allocation")
& (Ledger.employee == employee)
& (Ledger.is_expired == 0)
& (Ledger.is_lwp == 0)
)
)
if leave_type:
query = query.where((Ledger.leave_type == leave_type))
query = query.groupby(Ledger.employee, Ledger.leave_type)
allocation_details = query.run(as_dict=True)
allocated_leaves = frappe._dict()
for d in allocation_details:

View File

@ -969,4 +969,4 @@ def get_first_sunday(holiday_list, for_date=None):
order by holiday_date
""", (holiday_list, month_start_date, month_end_date))[0][0]
return first_sunday
return first_sunday

View File

@ -1,15 +1,15 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, today
from erpnext import get_company_currency
from erpnext.tests.utils import ERPNextTestCase
from .blanket_order import make_order
class TestBlanketOrder(ERPNextTestCase):
class TestBlanketOrder(FrappeTestCase):
def setUp(self):
frappe.flags.args = frappe._dict()

View File

@ -918,7 +918,7 @@ def validate_bom_no(item, bom_no):
frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
@frappe.whitelist()
def get_children(doctype, parent=None, is_root=False, **filters):
def get_children(parent=None, is_root=False, **filters):
if not parent or parent=="BOM":
frappe.msgprint(_('Please select a BOM'))
return

View File

@ -7,6 +7,7 @@ from functools import partial
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
from frappe.utils import cstr, flt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@ -17,11 +18,10 @@ from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import
create_stock_reconciliation,
)
from erpnext.tests.test_subcontracting import set_backflush_based_on
from erpnext.tests.utils import ERPNextTestCase
test_records = frappe.get_test_records('BOM')
class TestBOM(ERPNextTestCase):
class TestBOM(FrappeTestCase):
def setUp(self):
if not frappe.get_value('Item', '_Test Item'):
make_test_records('Item')

View File

@ -2,15 +2,15 @@
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import create_item
from erpnext.tests.utils import ERPNextTestCase
test_records = frappe.get_test_records('BOM')
class TestBOMUpdateTool(ERPNextTestCase):
class TestBOMUpdateTool(FrappeTestCase):
def test_replace_bom(self):
current_bom = "BOM-_Test Item Home Desktop Manufactured-001"

View File

@ -2,6 +2,7 @@
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import random_string
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError
@ -11,10 +12,9 @@ from erpnext.manufacturing.doctype.job_card.job_card import (
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
from erpnext.tests.utils import ERPNextTestCase
class TestJobCard(ERPNextTestCase):
class TestJobCard(FrappeTestCase):
def setUp(self):
make_bom_for_jc_tests()

View File

@ -7,7 +7,7 @@ def get_data():
'transactions': [
{
'label': _('Manufacture'),
'items': ['BOM', 'Work Order', 'Job Card', 'Timesheet']
'items': ['BOM', 'Work Order', 'Job Card']
}
]
}

View File

@ -49,7 +49,7 @@ frappe.ui.form.on('Production Plan', {
if (d.item_code) {
return {
query: "erpnext.controllers.queries.bom",
filters:{'item': cstr(d.item_code)}
filters:{'item': cstr(d.item_code), 'docstatus': 1}
}
} else frappe.msgprint(__("Please enter Item first"));
}
@ -232,7 +232,7 @@ frappe.ui.form.on('Production Plan', {
});
},
combine_items: function (frm) {
frm.clear_table('prod_plan_references');
frm.clear_table("prod_plan_references");
frappe.call({
method: "get_items",
@ -247,6 +247,13 @@ frappe.ui.form.on('Production Plan', {
});
},
combine_sub_items: (frm) => {
if (frm.doc.sub_assembly_items.length > 0) {
frm.clear_table("sub_assembly_items");
frm.trigger("get_sub_assembly_items");
}
},
get_sub_assembly_items: function(frm) {
frm.dirty();

View File

@ -36,6 +36,7 @@
"prod_plan_references",
"section_break_24",
"get_sub_assembly_items",
"combine_sub_items",
"sub_assembly_items",
"material_request_planning",
"include_non_stock_items",
@ -340,7 +341,6 @@
{
"fieldname": "prod_plan_references",
"fieldtype": "Table",
"hidden": 1,
"label": "Production Plan Item Reference",
"options": "Production Plan Item Reference"
},
@ -370,16 +370,23 @@
"fieldname": "to_delivery_date",
"fieldtype": "Date",
"label": "To Delivery Date"
},
{
"default": "0",
"fieldname": "combine_sub_items",
"fieldtype": "Check",
"label": "Consolidate Sub Assembly Items"
}
],
"icon": "fa fa-calendar",
"index_web_pages_for_search": 1,
"is_submittable": 1,
"links": [],
"modified": "2021-09-06 18:35:59.642232",
"modified": "2022-02-23 17:16:10.629378",
"modified_by": "Administrator",
"module": "Manufacturing",
"name": "Production Plan",
"naming_rule": "By \"Naming Series\" field",
"owner": "Administrator",
"permissions": [
{

View File

@ -21,7 +21,8 @@ from frappe.utils import (
)
from frappe.utils.csvutils import build_csv_response
from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no
from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_children
from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@ -570,17 +571,28 @@ class ProductionPlan(Document):
@frappe.whitelist()
def get_sub_assembly_items(self, manufacturing_type=None):
"Fetch sub assembly items and optionally combine them."
self.sub_assembly_items = []
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items:
bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
sub_assembly_items_store.extend(bom_data)
self.sub_assembly_items.sort(key= lambda d: d.bom_level, reverse=True)
for idx, row in enumerate(self.sub_assembly_items, start=1):
row.idx = idx
if self.combine_sub_items:
# Combine subassembly items
sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
sub_assembly_items_store.sort(key= lambda d: d.bom_level, reverse=True) # sort by bom level
for idx, row in enumerate(sub_assembly_items_store):
row.idx = idx + 1
self.append("sub_assembly_items", row)
def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
"Modify bom_data, set additional details."
for data in bom_data:
data.qty = data.stock_qty
data.production_plan_item = row.name
@ -589,7 +601,32 @@ class ProductionPlan(Document):
data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
else "In House")
self.append("sub_assembly_items", data)
def combine_subassembly_items(self, sub_assembly_items_store):
"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
key_wise_data = {}
for row in sub_assembly_items_store:
key = (
row.get("production_item"), row.get("fg_warehouse"),
row.get("bom_no"), row.get("type_of_manufacturing")
)
if key not in key_wise_data:
# intialise (item, wh, bom no, man.g type) wise dict
key_wise_data[key] = row
continue
existing_row = key_wise_data[key]
if existing_row:
# if row with same (item, wh, bom no, man.g type) key, merge
existing_row.qty += flt(row.qty)
existing_row.stock_qty += flt(row.stock_qty)
existing_row.bom_level = max(existing_row.bom_level, row.bom_level)
continue
else:
# add row with key
key_wise_data[key] = row
sub_assembly_items_store = [key_wise_data[key] for key in key_wise_data] # unpack into single level list
return sub_assembly_items_store
def all_items_completed(self):
all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001
@ -1031,7 +1068,7 @@ def get_item_data(item_code):
}
def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
data = get_children('BOM', parent = bom_no)
data = get_bom_children(parent=bom_no)
for d in data:
if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")

View File

@ -1,6 +1,7 @@
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_to_date, flt, now_datetime, nowdate
from erpnext.controllers.item_variant import create_variant
@ -16,10 +17,9 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation,
)
from erpnext.tests.utils import ERPNextTestCase
class TestProductionPlan(ERPNextTestCase):
class TestProductionPlan(FrappeTestCase):
def setUp(self):
for item in ['Test Production Item 1', 'Subassembly Item 1',
'Raw Material Item 1', 'Raw Material Item 2']:
@ -38,6 +38,9 @@ class TestProductionPlan(ERPNextTestCase):
if not frappe.db.get_value('BOM', {'item': item}):
make_bom(item = item, raw_materials = raw_materials)
def tearDown(self) -> None:
frappe.db.rollback()
def test_production_plan_mr_creation(self):
"Test if MRs are created for unavailable raw materials."
pln = create_production_plan(item_code='Test Production Item 1')
@ -110,7 +113,7 @@ class TestProductionPlan(ERPNextTestCase):
item_code='Test Production Item 1',
ignore_existing_ordered_qty=1
)
self.assertTrue(len(pln.mr_items), 1)
self.assertTrue(len(pln.mr_items))
self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
sr1.cancel()
@ -151,7 +154,7 @@ class TestProductionPlan(ERPNextTestCase):
use_multi_level_bom=0,
ignore_existing_ordered_qty=0
)
self.assertTrue(len(pln.mr_items), 0)
self.assertFalse(len(pln.mr_items))
sr1.cancel()
sr2.cancel()
@ -258,6 +261,51 @@ class TestProductionPlan(ERPNextTestCase):
pln.reload()
pln.cancel()
def test_production_plan_combine_subassembly(self):
"""
Test combining Sub assembly items belonging to the same BOM in Prod Plan.
1) Red-Car -> Wheel (sub assembly) > BOM-WHEEL-001
2) Green-Car -> Wheel (sub assembly) > BOM-WHEEL-001
"""
from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
bom_tree_1 = {
"Red-Car": {"Wheel": {"Rubber": {}}}
}
bom_tree_2 = {
"Green-Car": {"Wheel": {"Rubber": {}}}
}
parent_bom_1 = create_nested_bom(bom_tree_1, prefix="")
parent_bom_2 = create_nested_bom(bom_tree_2, prefix="")
# make sure both boms use same subassembly bom
subassembly_bom = parent_bom_1.items[0].bom_no
frappe.db.set_value("BOM Item", parent_bom_2.items[0].name, "bom_no", subassembly_bom)
plan = create_production_plan(item_code="Red-Car", use_multi_level_bom=1, do_not_save=True)
plan.append("po_items", { # Add Green-Car to Prod Plan
'use_multi_level_bom': 1,
'item_code': "Green-Car",
'bom_no': frappe.db.get_value('Item', "Green-Car", 'default_bom'),
'planned_qty': 1,
'planned_start_date': now_datetime()
})
plan.get_sub_assembly_items()
self.assertTrue(len(plan.sub_assembly_items), 2)
plan.combine_sub_items = 1
plan.get_sub_assembly_items()
self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
self.assertEqual(plan.sub_assembly_items[0].qty, 2.0)
self.assertEqual(plan.sub_assembly_items[0].stock_qty, 2.0)
# change warehouse in one row, sub-assemblies should not merge
plan.po_items[0].warehouse = "Finished Goods - _TC"
plan.get_sub_assembly_items()
self.assertTrue(len(plan.sub_assembly_items), 2)
def test_pp_to_mr_customer_provided(self):
" Test Material Request from Production Plan for Customer Provided Item."
create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
@ -532,6 +580,7 @@ class TestProductionPlan(ERPNextTestCase):
wip_warehouse='Work In Progress - _TC',
fg_warehouse='Finished Goods - _TC',
skip_transfer=1,
use_multi_level_bom=1,
do_not_submit=True
)
wo.production_plan = pln.name
@ -576,6 +625,7 @@ class TestProductionPlan(ERPNextTestCase):
wip_warehouse='Work In Progress - _TC',
fg_warehouse='Finished Goods - _TC',
skip_transfer=1,
use_multi_level_bom=1,
do_not_submit=True
)
wo.production_plan = pln.name

View File

@ -17,7 +17,7 @@ frappe.ui.form.on('Routing', {
},
calculate_operating_cost: function(frm, child) {
const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, 2);
const operating_cost = flt(flt(child.hour_rate) * flt(child.time_in_mins) / 60, precision("operating_cost", child));
frappe.model.set_value(child.doctype, child.name, "operating_cost", operating_cost);
}
});

View File

@ -20,7 +20,8 @@ class Routing(Document):
for operation in self.operations:
if not operation.hour_rate:
operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, 'hour_rate')
operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60, 2)
operation.operating_cost = flt(flt(operation.hour_rate) * flt(operation.time_in_mins) / 60,
operation.precision("operating_cost"))
def set_routing_id(self):
sequence_id = 0

View File

@ -2,14 +2,14 @@
# See license.txt
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestCase
class TestRouting(ERPNextTestCase):
class TestRouting(FrappeTestCase):
@classmethod
def setUpClass(cls):
cls.item_code = "Test Routing Item - A"

View File

@ -2,6 +2,7 @@
# License: GNU General Public License v3. See license.txt
import frappe
from frappe.tests.utils import FrappeTestCase, timeout
from frappe.utils import add_days, add_months, cint, flt, now, today
from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
@ -21,10 +22,9 @@ from erpnext.stock.doctype.item.test_item import create_item, make_item
from erpnext.stock.doctype.stock_entry import test_stock_entry
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
from erpnext.stock.utils import get_bin
from erpnext.tests.utils import ERPNextTestCase, timeout
class TestWorkOrder(ERPNextTestCase):
class TestWorkOrder(FrappeTestCase):
def setUp(self):
self.warehouse = '_Test Warehouse 2 - _TC'
self.item = '_Test Item'
@ -1040,7 +1040,7 @@ def make_wo_order_test_record(**args):
wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
wo_order.company = args.company or "_Test Company"
wo_order.stock_uom = args.stock_uom or "_Test UOM"
wo_order.use_multi_level_bom=0
wo_order.use_multi_level_bom= args.use_multi_level_bom or 0
wo_order.skip_transfer=args.skip_transfer or 0
wo_order.get_items_and_operations_from_bom()
wo_order.sales_order = args.sales_order or None

View File

@ -854,7 +854,7 @@ def get_item_details(item, project = None, skip_bom_info=False):
res = res[0]
if skip_bom_info: return res
filters = {"item": item, "is_default": 1}
filters = {"item": item, "is_default": 1, "docstatus": 1}
if project:
filters = {"item": item, "project": project}

View File

@ -2,6 +2,7 @@
# See license.txt
import frappe
from frappe.test_runner import make_test_records
from frappe.tests.utils import FrappeTestCase
from erpnext.manufacturing.doctype.operation.test_operation import make_operation
from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom
@ -10,13 +11,12 @@ from erpnext.manufacturing.doctype.workstation.workstation import (
WorkstationHolidayError,
check_if_within_operating_hours,
)
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Warehouse"]
test_records = frappe.get_test_records('Workstation')
make_test_records('Workstation')
class TestWorkstation(ERPNextTestCase):
class TestWorkstation(FrappeTestCase):
def test_validate_timings(self):
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")

View File

@ -11,9 +11,9 @@ def get_data():
},
{
'label': _('Transaction'),
'items': ['Work Order', 'Job Card', 'Timesheet']
'items': ['Work Order', 'Job Card',]
}
],
'disable_create_buttons': ['BOM', 'Routing', 'Operation',
'Work Order', 'Job Card', 'Timesheet']
'Work Order', 'Job Card',]
}

View File

@ -15,7 +15,6 @@ Maintenance
Education
Regional
ERPNext Integrations
Non Profit
Quality Management
Communication
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);
}
}
});

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