Merge branch 'develop' into bank_reco_multicurrency_fix

This commit is contained in:
Deepesh Garg 2022-03-06 18:09:30 +05:30 committed by GitHub
commit 629a296fb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
284 changed files with 2035 additions and 8346 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -114,8 +114,6 @@ frappe.ui.form.on('Payment Entry', {
var doctypes = ["Expense Claim", "Journal Entry"]; var doctypes = ["Expense Claim", "Journal Entry"];
} else if (frm.doc.party_type == "Student") { } else if (frm.doc.party_type == "Student") {
var doctypes = ["Fees"]; var doctypes = ["Fees"];
} else if (frm.doc.party_type == "Donor") {
var doctypes = ["Donation"];
} else { } else {
var doctypes = ["Journal Entry"]; var doctypes = ["Journal Entry"];
} }
@ -144,7 +142,7 @@ frappe.ui.form.on('Payment Entry', {
const child = locals[cdt][cdn]; const child = locals[cdt][cdn];
const filters = {"docstatus": 1, "company": doc.company}; const filters = {"docstatus": 1, "company": doc.company};
const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice', const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation']; 'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
if (in_list(party_type_doctypes, child.reference_doctype)) { if (in_list(party_type_doctypes, child.reference_doctype)) {
filters[doc.party_type.toLowerCase()] = doc.party; filters[doc.party_type.toLowerCase()] = doc.party;
@ -196,8 +194,14 @@ frappe.ui.form.on('Payment Entry', {
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency)); 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_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.toggle_display("base_received_amount", (
frm.doc.paid_to_account_currency != company_currency 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: ""; 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", 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(["paid_amount"], frm.doc.paid_from_account_currency);
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_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; frm.set_party_account_based_on_party = true;
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
return frappe.call({ return frappe.call({
method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details", method: "erpnext.accounts.doctype.payment_entry.payment_entry.get_party_details",
args: { args: {
@ -374,7 +381,11 @@ frappe.ui.form.on('Payment Entry', {
if (r.message.bank_account) { if (r.message.bank_account) {
frm.set_value("bank_account", 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) { paid_from_account_currency: function(frm) {
if(!frm.doc.paid_from_account_currency) return; if(!frm.doc.paid_from_account_currency || !frm.doc.company) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
if (frm.doc.paid_from_account_currency == company_currency) { if (frm.doc.paid_from_account_currency == company_currency) {
frm.set_value("source_exchange_rate", 1); frm.set_value("source_exchange_rate", 1);
} else if (frm.doc.paid_from){ } else if (frm.doc.paid_from){
if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) { 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({ frappe.call({
method: "erpnext.setup.utils.get_exchange_rate", method: "erpnext.setup.utils.get_exchange_rate",
args: { args: {
@ -505,8 +516,8 @@ frappe.ui.form.on('Payment Entry', {
}, },
paid_to_account_currency: function(frm) { paid_to_account_currency: function(frm) {
if(!frm.doc.paid_to_account_currency) return; if(!frm.doc.paid_to_account_currency || !frm.doc.company) return;
var company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency; let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
frm.events.set_current_exchange_rate(frm, "target_exchange_rate", frm.events.set_current_exchange_rate(frm, "target_exchange_rate",
frm.doc.paid_to_account_currency, company_currency); 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=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") || (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
) { ) {
if(total_positive_outstanding > total_negative_outstanding) if(total_positive_outstanding > total_negative_outstanding)
if (!frm.doc.paid_amount) if (!frm.doc.paid_amount)
@ -791,8 +801,7 @@ frappe.ui.form.on('Payment Entry', {
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") || (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") || (frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") || (frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
) { ) {
if(total_positive_outstanding_including_order > paid_amount) { if(total_positive_outstanding_including_order > paid_amount) {
var remaining_outstanding = total_positive_outstanding_including_order - paid_amount; var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
@ -951,12 +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])); frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
return false; return false;
} }
if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
return false;
}
} }
if (row) { if (row) {

View File

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

View File

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

View File

@ -633,6 +633,45 @@ class TestPaymentEntry(unittest.TestCase):
self.assertEqual(flt(expected_party_balance), party_balance) self.assertEqual(flt(expected_party_balance), party_balance)
self.assertEqual(flt(expected_party_account_balance), party_account_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(): def create_payment_terms_template():
create_payment_term('Basic Amount Receivable') create_payment_term('Basic Amount Receivable')

View File

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

View File

@ -73,7 +73,7 @@ def get_report_pdf(doc, consolidated=True):
'to_date': doc.to_date, 'to_date': doc.to_date,
'company': doc.company, 'company': doc.company,
'finance_book': doc.finance_book if doc.finance_book else None, '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_type': 'Customer',
'party': [entry.customer], 'party': [entry.customer],
'presentation_currency': presentation_currency, 'presentation_currency': presentation_currency,

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][1], gle.debit)
self.assertEqual(expected_values[gle.account][2], gle.credit) 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): def test_sales_invoice_with_shipping_rule(self):
from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule 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): 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 = copy.deepcopy(si.get('items')[0])
item.update({ item.update({
"qty": 1, "qty": 1,
"rate": 500, "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 # Test valid values
for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)): for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):

View File

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

View File

@ -46,7 +46,7 @@ def valdiate_taxes_and_charges_template(doc):
for tax in doc.get("taxes"): for tax in doc.get("taxes"):
validate_taxes_and_charges(tax) validate_taxes_and_charges(tax)
validate_account_head(tax, doc) validate_account_head(tax.idx, tax.account_head, doc.company)
validate_cost_center(tax, doc) validate_cost_center(tax, doc)
validate_inclusive_tax(tax, doc) validate_inclusive_tax(tax, doc)
@ -55,5 +55,8 @@ def validate_disabled(doc):
frappe.throw(_("Disabled template must not be default template")) frappe.throw(_("Disabled template must not be default template"))
def validate_for_tax_category(doc): 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]}): 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))) 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) debit_credit_diff += flt(d.credit)
round_off_account_exists = True 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) gl_map.remove(round_off_gle)
return return

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -417,11 +417,12 @@ class Asset(AccountsController):
def validate_asset_finance_books(self, row): def validate_asset_finance_books(self, row):
if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount") frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
.format(row.idx)) .format(row.idx), title=_("Invalid Schedule"))
if not row.depreciation_start_date: if not row.depreciation_start_date:
if not self.available_for_use_date: if not self.available_for_use_date:
frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx)) frappe.throw(_("Row {0}: Depreciation Start Date is required")
.format(row.idx), title=_("Invalid Schedule"))
row.depreciation_start_date = get_last_day(self.available_for_use_date) row.depreciation_start_date = get_last_day(self.available_for_use_date)
if not self.is_existing_asset: if not self.is_existing_asset:
@ -439,8 +440,9 @@ class Asset(AccountsController):
else: else:
self.number_of_depreciations_booked = 0 self.number_of_depreciations_booked = 0
if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations): if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked):
frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) frappe.throw(_("Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked")
.format(row.idx), title=_("Invalid Schedule"))
if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date") frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date")

View File

@ -873,8 +873,9 @@ class TestDepreciationBasics(AssetSetup):
self.assertRaises(frappe.ValidationError, asset.save) self.assertRaises(frappe.ValidationError, asset.save)
def test_number_of_depreciations(self): def test_number_of_depreciations(self):
"""Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations.""" """Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations."""
# number_of_depreciations_booked > total_number_of_depreciations
asset = create_asset( asset = create_asset(
item_code = "Macbook Pro", item_code = "Macbook Pro",
calculate_depreciation = 1, calculate_depreciation = 1,
@ -889,6 +890,21 @@ class TestDepreciationBasics(AssetSetup):
self.assertRaises(frappe.ValidationError, asset.save) self.assertRaises(frappe.ValidationError, asset.save)
# number_of_depreciations_booked = total_number_of_depreciations
asset_2 = create_asset(
item_code = "Macbook Pro",
calculate_depreciation = 1,
available_for_use_date = "2019-12-31",
total_number_of_depreciations = 5,
expected_value_after_useful_life = 10000,
depreciation_start_date = "2020-07-01",
opening_accumulated_depreciation = 10000,
number_of_depreciations_booked = 5,
do_not_save = 1
)
self.assertRaises(frappe.ValidationError, asset_2.save)
def test_depreciation_start_date_is_before_purchase_date(self): def test_depreciation_start_date_is_before_purchase_date(self):
asset = create_asset( asset = create_asset(
item_code = "Macbook Pro", item_code = "Macbook Pro",
@ -1264,7 +1280,7 @@ def create_asset(**args):
if not args.do_not_save: if not args.do_not_save:
try: try:
asset.save() asset.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
@ -1305,7 +1321,7 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
"is_grouped_asset": is_grouped_asset, "is_grouped_asset": is_grouped_asset,
"asset_naming_series": naming_series "asset_naming_series": naming_series
}) })
item.insert() item.insert(ignore_if_duplicate=True)
except frappe.DuplicateEntryError: except frappe.DuplicateEntryError:
pass pass
return item return item

View File

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

View File

@ -316,6 +316,16 @@ class PurchaseOrder(BuyingController):
'target_ref_field': 'stock_qty', 'target_ref_field': 'stock_qty',
'source_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): def update_delivered_qty_in_sales_order(self):
"""Update delivered qty in Sales Order for drop ship""" """Update delivered qty in Sales Order for drop ship"""

View File

@ -3,9 +3,9 @@
import json import json
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_days, flt, getdate, nowdate from frappe.utils import add_days, flt, getdate, nowdate
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry 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 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): def test_make_purchase_receipt(self):
po = create_purchase_order(do_not_submit=True) po = create_purchase_order(do_not_submit=True)
self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name) self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)

View File

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

View File

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

View File

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

View File

@ -3,12 +3,12 @@
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
class TestPurchaseOrder(unittest.TestCase): class TestPurchaseOrder(FrappeTestCase):
def test_make_purchase_order(self): def test_make_purchase_order(self):
from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order 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 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import unittest
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
class TestSupplierScorecard(unittest.TestCase): class TestSupplierScorecard(FrappeTestCase):
def test_create_scorecard(self): def test_create_scorecard(self):
doc = make_supplier_scorecard().insert() doc = make_supplier_scorecard().insert()

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@
# Compiled at: 2019-05-06 09:51:46 # Compiled at: 2019-05-06 09:51:46
# Decompiled by https://python-decompiler.com # Decompiled by https://python-decompiler.com
import unittest
import frappe 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.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@ -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 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): def test_pending_and_received_qty(self):
po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes') po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')

View File

@ -4,9 +4,9 @@
# Decompiled by https://python-decompiler.com # Decompiled by https://python-decompiler.com
import json import json
import unittest
import frappe 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.purchase_order import make_rm_stock_entry
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@ -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 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): 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") po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")

View File

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

View File

@ -104,11 +104,11 @@ class EmployeeBoardingController(Document):
def get_task_dates(self, activity, holiday_list): def get_task_dates(self, activity, holiday_list):
start_date = end_date = None start_date = end_date = None
if activity.begin_on: if activity.begin_on is not None:
start_date = add_days(self.boarding_begins_on, activity.begin_on) start_date = add_days(self.boarding_begins_on, activity.begin_on)
start_date = self.update_if_holiday(start_date, holiday_list) start_date = self.update_if_holiday(start_date, holiday_list)
if activity.duration: if activity.duration is not None:
end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration) end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
end_date = self.update_if_holiday(end_date, holiday_list) end_date = self.update_if_holiday(end_date, holiday_list)

View File

@ -507,13 +507,41 @@ class StockController(AccountsController):
"voucher_no": self.name, "voucher_no": self.name,
"company": self.company "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")) item_based_reposting = cint(frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting"))
if item_based_reposting: if item_based_reposting:
create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name) create_item_wise_repost_entries(voucher_type=self.doctype, voucher_no=self.name)
else: else:
create_repost_item_valuation_entry(args) 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() @frappe.whitelist()
def make_quality_inspections(doctype, docname, items): def make_quality_inspections(doctype, docname, items):

View File

@ -363,8 +363,6 @@ class Subcontracting():
return return
for row in self.get(self.raw_material_table): 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) key = (row.rm_item_code, row.main_item_code, row.purchase_order)
if not self.__transferred_items or not self.__transferred_items.get(key): if not self.__transferred_items or not self.__transferred_items.get(key):
return return
@ -372,12 +370,6 @@ class Subcontracting():
self.__validate_batch_no(row, key) self.__validate_batch_no(row, key)
self.__validate_serial_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): 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'): 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) link = get_link_to_form('Purchase Order', row.purchase_order)

View File

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

View File

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

View File

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

View File

@ -5,6 +5,7 @@
import unittest import unittest
import frappe import frappe
from frappe.tests.utils import change_settings
from frappe.utils import add_months, nowdate from frappe.utils import add_months, nowdate
from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
@ -15,7 +16,7 @@ from erpnext.e_commerce.shopping_cart.cart import (
get_party, get_party,
update_cart, 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'] # test_dependencies = ['Payment Terms Template']
@ -57,13 +58,19 @@ class TestShoppingCart(unittest.TestCase):
return quotation return quotation
def test_get_cart_customer(self): def test_get_cart_customer(self):
self.login_as_customer() def validate_quotation():
# test if quotation with customer is fetched
quotation = _get_cart_quotation()
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer")
self.assertEqual(quotation.contact_email, frappe.session.user)
return quotation
# test if quotation with customer is fetched self.login_as_customer("test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer")
quotation = _get_cart_quotation() validate_quotation()
self.assertEqual(quotation.quotation_to, "Customer")
self.assertEqual(quotation.party_name, "_Test Customer") self.login_as_customer()
self.assertEqual(quotation.contact_email, frappe.session.user) quotation = validate_quotation()
return quotation return quotation
@ -175,7 +182,7 @@ class TestShoppingCart(unittest.TestCase):
def create_tax_rule(self): def create_tax_rule(self):
tax_rule = frappe.get_test_records("Tax Rule")[0] tax_rule = frappe.get_test_records("Tax Rule")[0]
try: try:
frappe.get_doc(tax_rule).insert() frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True)
except (frappe.DuplicateEntryError, ConflictingTaxRule): except (frappe.DuplicateEntryError, ConflictingTaxRule):
pass pass
@ -254,10 +261,9 @@ class TestShoppingCart(unittest.TestCase):
self.create_user_if_not_exists("test_cart_user@example.com") self.create_user_if_not_exists("test_cart_user@example.com")
frappe.set_user("test_cart_user@example.com") frappe.set_user("test_cart_user@example.com")
def login_as_customer(self): def login_as_customer(self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"):
self.create_user_if_not_exists("test_contact_customer@example.com", self.create_user_if_not_exists(email, name)
"_Test Contact For _Test Customer") frappe.set_user(email)
frappe.set_user("test_contact_customer@example.com")
def clear_existing_quotations(self): def clear_existing_quotations(self):
quotations = frappe.get_all("Quotation", filters={ quotations = frappe.get_all("Quotation", filters={

View File

@ -1,4 +1,5 @@
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from erpnext.controllers.item_variant import create_variant from erpnext.controllers.item_variant import create_variant
from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import ( 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.doctype.website_item.website_item import make_website_item
from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values 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.stock.doctype.item.test_item import make_item
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Item"] test_dependencies = ["Item"]
class TestVariantSelector(ERPNextTestCase): class TestVariantSelector(FrappeTestCase):
@classmethod @classmethod
def setUpClass(cls): 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(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["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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"autoname": "naming_series:", "autoname": "naming_series:",
"creation": "2017-10-09 14:26:29.612365", "creation": "2022-01-17 18:36:51.450395",
"doctype": "DocType", "doctype": "DocType",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
@ -121,7 +121,7 @@
"fieldtype": "Select", "fieldtype": "Select",
"label": "Status", "label": "Status",
"no_copy": 1, "no_copy": 1,
"options": "Draft\nPaid\nUnpaid\nClaimed\nCancelled", "options": "Draft\nPaid\nUnpaid\nClaimed\nReturned\nPartly Claimed and Returned\nCancelled",
"read_only": 1 "read_only": 1
}, },
{ {
@ -200,7 +200,7 @@
], ],
"is_submittable": 1, "is_submittable": 1,
"links": [], "links": [],
"modified": "2021-09-11 18:38:38.617478", "modified": "2022-01-17 19:33:52.345823",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "HR", "module": "HR",
"name": "Employee Advance", "name": "Employee Advance",
@ -237,5 +237,41 @@
"search_fields": "employee,employee_name", "search_fields": "employee,employee_name",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "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 "track_changes": 1
} }

View File

@ -27,19 +27,33 @@ class EmployeeAdvance(Document):
def on_cancel(self): def on_cancel(self):
self.ignore_linked_doctypes = ('GL Entry') 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: if self.docstatus == 0:
self.status = "Draft" status = "Draft"
if self.docstatus == 1: elif self.docstatus == 1:
if self.claimed_amount and flt(self.claimed_amount) == flt(self.paid_amount): if flt(self.claimed_amount) > 0 and flt(self.claimed_amount, precision) == flt(self.paid_amount, precision):
self.status = "Claimed" status = "Claimed"
elif self.paid_amount and self.advance_amount == flt(self.paid_amount): elif flt(self.return_amount) > 0 and flt(self.return_amount, precision) == flt(self.paid_amount, precision):
self.status = "Paid" 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: else:
self.status = "Unpaid" status = "Unpaid"
elif self.docstatus == 2: 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): def set_total_advance_paid(self):
gle = frappe.qb.DocType("GL Entry") gle = frappe.qb.DocType("GL Entry")
@ -85,9 +99,7 @@ class EmployeeAdvance(Document):
self.db_set("paid_amount", paid_amount) self.db_set("paid_amount", paid_amount)
self.db_set("return_amount", return_amount) self.db_set("return_amount", return_amount)
self.set_status() self.set_status(update=True)
frappe.db.set_value("Employee Advance", self.name , "status", self.status)
def update_claimed_amount(self): def update_claimed_amount(self):
claimed_amount = frappe.db.sql(""" 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)) frappe.db.set_value("Employee Advance", self.name, "claimed_amount", flt(claimed_amount))
self.reload() self.reload()
self.set_status() self.set_status(update=True)
frappe.db.set_value("Employee Advance", self.name, "status", self.status)
@frappe.whitelist() @frappe.whitelist()
def get_pending_amount(employee, posting_date): 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, 'reference_name': employee_advance_name,
'party_type': 'Employee', 'party_type': 'Employee',
'party': 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 \ 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, "debit_in_account_currency": bank_amount,
"account_currency": bank_cash_account.account_currency, "account_currency": bank_cash_account.account_currency,
"account_type": bank_cash_account.account_type, "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() return je.as_dict()

View File

@ -4,7 +4,7 @@
import unittest import unittest
import frappe import frappe
from frappe.utils import nowdate from frappe.utils import flt, nowdate
import erpnext import erpnext
from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.employee.test_employee import make_employee
@ -12,12 +12,21 @@ from erpnext.hr.doctype.employee_advance.employee_advance import (
EmployeeAdvanceOverPayment, EmployeeAdvanceOverPayment,
create_return_through_additional_salary, create_return_through_additional_salary,
make_bank_entry, 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_component.test_salary_component import create_salary_component
from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
class TestEmployeeAdvance(unittest.TestCase): class TestEmployeeAdvance(unittest.TestCase):
def setUp(self):
frappe.db.delete("Employee Advance")
def test_paid_amount_and_status(self): def test_paid_amount_and_status(self):
employee_name = make_employee("_T@employe.advance") employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name) advance = make_employee_advance(employee_name)
@ -52,9 +61,102 @@ class TestEmployeeAdvance(unittest.TestCase):
self.assertEqual(advance.paid_amount, 0) self.assertEqual(advance.paid_amount, 0)
self.assertEqual(advance.status, "Unpaid") 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): def test_repay_unclaimed_amount_from_salary(self):
employee_name = make_employee("_T@employe.advance") employee_name = make_employee("_T@employe.advance")
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1}) advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
pe = make_payment_entry(advance)
pe.submit()
args = {"type": "Deduction"} args = {"type": "Deduction"}
create_salary_component("Advance Salary - Deduction", **args) create_salary_component("Advance Salary - Deduction", **args)
@ -82,11 +184,13 @@ class TestEmployeeAdvance(unittest.TestCase):
advance.reload() advance.reload()
self.assertEqual(advance.return_amount, 1000) self.assertEqual(advance.return_amount, 1000)
self.assertEqual(advance.status, "Returned")
# update advance return amount on additional salary cancellation # update advance return amount on additional salary cancellation
additional_salary.cancel() additional_salary.cancel()
advance.reload() advance.reload()
self.assertEqual(advance.return_amount, 700) self.assertEqual(advance.return_amount, 700)
self.assertEqual(advance.status, "Paid")
def tearDown(self): def tearDown(self):
frappe.db.rollback() frappe.db.rollback()
@ -118,3 +222,24 @@ def make_employee_advance(employee_name, args=None):
doc.submit() doc.submit()
return doc 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

@ -4,7 +4,7 @@
import unittest import unittest
import frappe import frappe
from frappe.utils import getdate from frappe.utils import add_days, getdate
from erpnext.hr.doctype.employee_onboarding.employee_onboarding import ( from erpnext.hr.doctype.employee_onboarding.employee_onboarding import (
IncompleteTaskError, IncompleteTaskError,
@ -35,6 +35,15 @@ class TestEmployeeOnboarding(unittest.TestCase):
# boarding status # boarding status
self.assertEqual(onboarding.boarding_status, 'Pending') self.assertEqual(onboarding.boarding_status, 'Pending')
# start and end dates
start_date, end_date = frappe.db.get_value('Task', onboarding.activities[0].task, ['exp_start_date', 'exp_end_date'])
self.assertEqual(getdate(start_date), getdate(onboarding.boarding_begins_on))
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[0].duration))
start_date, end_date = frappe.db.get_value('Task', onboarding.activities[1].task, ['exp_start_date', 'exp_end_date'])
self.assertEqual(getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration))
self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[1].duration))
# complete the task # complete the task
project = frappe.get_doc('Project', onboarding.project) project = frappe.get_doc('Project', onboarding.project)
for task in frappe.get_all('Task', dict(project=project.name)): for task in frappe.get_all('Task', dict(project=project.name)):
@ -57,10 +66,7 @@ class TestEmployeeOnboarding(unittest.TestCase):
self.assertEqual(employee.employee_name, 'Test Researcher') self.assertEqual(employee.employee_name, 'Test Researcher')
def tearDown(self): def tearDown(self):
for entry in frappe.get_all('Employee Onboarding'): frappe.db.rollback()
doc = frappe.get_doc('Employee Onboarding', entry.name)
doc.cancel()
doc.delete()
def get_job_applicant(): def get_job_applicant():
@ -87,23 +93,31 @@ def get_job_offer(applicant_name):
def create_employee_onboarding(): def create_employee_onboarding():
applicant = get_job_applicant() applicant = get_job_applicant()
job_offer = get_job_offer(applicant.name) job_offer = get_job_offer(applicant.name)
holiday_list = make_holiday_list()
holiday_list = make_holiday_list('_Test Employee Boarding')
holiday_list = frappe.get_doc('Holiday List', holiday_list)
holiday_list.holidays = []
holiday_list.save()
onboarding = frappe.new_doc('Employee Onboarding') onboarding = frappe.new_doc('Employee Onboarding')
onboarding.job_applicant = applicant.name onboarding.job_applicant = applicant.name
onboarding.job_offer = job_offer.name onboarding.job_offer = job_offer.name
onboarding.date_of_joining = onboarding.boarding_begins_on = getdate() onboarding.date_of_joining = onboarding.boarding_begins_on = getdate()
onboarding.company = '_Test Company' onboarding.company = '_Test Company'
onboarding.holiday_list = holiday_list onboarding.holiday_list = holiday_list.name
onboarding.designation = 'Researcher' onboarding.designation = 'Researcher'
onboarding.append('activities', { onboarding.append('activities', {
'activity_name': 'Assign ID Card', 'activity_name': 'Assign ID Card',
'role': 'HR User', 'role': 'HR User',
'required_for_employee_creation': 1 'required_for_employee_creation': 1,
'begin_on': 0,
'duration': 1
}) })
onboarding.append('activities', { onboarding.append('activities', {
'activity_name': 'Assign a laptop', 'activity_name': 'Assign a laptop',
'role': 'HR User' 'role': 'HR User',
'begin_on': 1,
'duration': 1
}) })
onboarding.status = 'Pending' onboarding.status = 'Pending'
onboarding.insert() onboarding.insert()

View File

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

View File

@ -171,7 +171,7 @@ frappe.ui.form.on("Expense Claim", {
['docstatus', '=', 1], ['docstatus', '=', 1],
['employee', '=', frm.doc.employee], ['employee', '=', frm.doc.employee],
['paid_amount', '>', 0], ['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): def validate(self):
validate_active_employee(self.employee) validate_active_employee(self.employee)
self.validate_advances() set_employee_name(self)
self.validate_sanctioned_amount() self.validate_sanctioned_amount()
self.calculate_total_amount() self.calculate_total_amount()
set_employee_name(self) self.validate_advances()
self.set_expense_account(validate=True) self.set_expense_account(validate=True)
self.set_payable_account() self.set_payable_account()
self.set_cost_center() self.set_cost_center()
@ -341,18 +341,27 @@ def get_expense_claim_account(expense_claim_type, company):
@frappe.whitelist() @frappe.whitelist()
def get_advances(employee, advance_id=None): def get_advances(employee, advance_id=None):
if not advance_id: advance = frappe.qb.DocType("Employee Advance")
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))
return frappe.db.sql(""" query = (
select frappe.qb.from_(advance)
name, posting_date, paid_amount, claimed_amount, advance_account .select(
from advance.name, advance.posting_date, advance.paid_amount,
`tabEmployee Advance` advance.claimed_amount, advance.advance_account
where {0} )
""".format(condition), as_dict=1) )
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() @frappe.whitelist()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,15 @@
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_months, today from frappe.utils import add_months, today
from erpnext import get_company_currency from erpnext import get_company_currency
from erpnext.tests.utils import ERPNextTestCase
from .blanket_order import make_order from .blanket_order import make_order
class TestBlanketOrder(ERPNextTestCase): class TestBlanketOrder(FrappeTestCase):
def setUp(self): def setUp(self):
frappe.flags.args = frappe._dict() 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.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
@frappe.whitelist() @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": if not parent or parent=="BOM":
frappe.msgprint(_('Please select a BOM')) frappe.msgprint(_('Please select a BOM'))
return return

View File

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

View File

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

View File

@ -2,6 +2,7 @@
# See license.txt # See license.txt
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import random_string from frappe.utils import random_string
from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError 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.work_order.test_work_order import make_wo_order_test_record
from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation 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.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): def setUp(self):
make_bom_for_jc_tests() make_bom_for_jc_tests()

View File

@ -7,7 +7,7 @@ def get_data():
'transactions': [ 'transactions': [
{ {
'label': _('Manufacture'), '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) { if (d.item_code) {
return { return {
query: "erpnext.controllers.queries.bom", 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")); } else frappe.msgprint(__("Please enter Item first"));
} }
@ -232,7 +232,7 @@ frappe.ui.form.on('Production Plan', {
}); });
}, },
combine_items: function (frm) { combine_items: function (frm) {
frm.clear_table('prod_plan_references'); frm.clear_table("prod_plan_references");
frappe.call({ frappe.call({
method: "get_items", 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) { get_sub_assembly_items: function(frm) {
frm.dirty(); frm.dirty();

View File

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

View File

@ -21,7 +21,8 @@ from frappe.utils import (
) )
from frappe.utils.csvutils import build_csv_response 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.manufacturing.doctype.work_order.work_order import get_item_details
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
@ -570,17 +571,28 @@ class ProductionPlan(Document):
@frappe.whitelist() @frappe.whitelist()
def get_sub_assembly_items(self, manufacturing_type=None): def get_sub_assembly_items(self, manufacturing_type=None):
"Fetch sub assembly items and optionally combine them."
self.sub_assembly_items = [] self.sub_assembly_items = []
sub_assembly_items_store = [] # temporary store to process all subassembly items
for row in self.po_items: for row in self.po_items:
bom_data = [] bom_data = []
get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty) 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) 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) if self.combine_sub_items:
for idx, row in enumerate(self.sub_assembly_items, start=1): # Combine subassembly items
row.idx = idx 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): 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: for data in bom_data:
data.qty = data.stock_qty data.qty = data.stock_qty
data.production_plan_item = row.name 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 data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
else "In House") 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): def all_items_completed(self):
all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001 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): 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: for d in data:
if d.expandable: if d.expandable:
parent_item_code = frappe.get_cached_value("BOM", bom_no, "item") 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 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt # See license.txt
import frappe import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import add_to_date, flt, now_datetime, nowdate from frappe.utils import add_to_date, flt, now_datetime, nowdate
from erpnext.controllers.item_variant import create_variant from erpnext.controllers.item_variant import create_variant
@ -9,16 +10,16 @@ from erpnext.manufacturing.doctype.production_plan.production_plan import (
get_sales_orders, get_sales_orders,
get_warehouse_list, get_warehouse_list,
) )
from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
from erpnext.stock.doctype.item.test_item import create_item from erpnext.stock.doctype.item.test_item import create_item
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import ( from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
create_stock_reconciliation, create_stock_reconciliation,
) )
from erpnext.tests.utils import ERPNextTestCase
class TestProductionPlan(ERPNextTestCase): class TestProductionPlan(FrappeTestCase):
def setUp(self): def setUp(self):
for item in ['Test Production Item 1', 'Subassembly Item 1', for item in ['Test Production Item 1', 'Subassembly Item 1',
'Raw Material Item 1', 'Raw Material Item 2']: 'Raw Material Item 1', 'Raw Material Item 2']:
@ -37,6 +38,9 @@ class TestProductionPlan(ERPNextTestCase):
if not frappe.db.get_value('BOM', {'item': item}): if not frappe.db.get_value('BOM', {'item': item}):
make_bom(item = item, raw_materials = raw_materials) make_bom(item = item, raw_materials = raw_materials)
def tearDown(self) -> None:
frappe.db.rollback()
def test_production_plan_mr_creation(self): def test_production_plan_mr_creation(self):
"Test if MRs are created for unavailable raw materials." "Test if MRs are created for unavailable raw materials."
pln = create_production_plan(item_code='Test Production Item 1') pln = create_production_plan(item_code='Test Production Item 1')
@ -109,7 +113,7 @@ class TestProductionPlan(ERPNextTestCase):
item_code='Test Production Item 1', item_code='Test Production Item 1',
ignore_existing_ordered_qty=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) self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
sr1.cancel() sr1.cancel()
@ -150,7 +154,7 @@ class TestProductionPlan(ERPNextTestCase):
use_multi_level_bom=0, use_multi_level_bom=0,
ignore_existing_ordered_qty=0 ignore_existing_ordered_qty=0
) )
self.assertTrue(len(pln.mr_items), 0) self.assertFalse(len(pln.mr_items))
sr1.cancel() sr1.cancel()
sr2.cancel() sr2.cancel()
@ -257,6 +261,51 @@ class TestProductionPlan(ERPNextTestCase):
pln.reload() pln.reload()
pln.cancel() 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): def test_pp_to_mr_customer_provided(self):
" Test Material Request from Production Plan for Customer Provided Item." " 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) create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
@ -466,26 +515,29 @@ class TestProductionPlan(ERPNextTestCase):
bom = make_bom(item=item, raw_materials=raw_materials) bom = make_bom(item=item, raw_materials=raw_materials)
# Create Production Plan # Create Production Plan
pln = create_production_plan(item_code=bom.item, planned_qty=10) pln = create_production_plan(item_code=bom.item, planned_qty=5)
# All the created Work Orders # All the created Work Orders
wo_list = [] wo_list = []
# Create and Submit 1st Work Order for 5 qty # Create and Submit 1st Work Order for 3 qty
create_work_order(item, pln, 5) create_work_order(item, pln, 3)
pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 3)
# Create and Submit 2nd Work Order for 2 qty
create_work_order(item, pln, 2)
pln.reload() pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 5) self.assertEqual(pln.po_items[0].ordered_qty, 5)
# Create and Submit 2nd Work Order for 3 qty # Overproduction
create_work_order(item, pln, 3) self.assertRaises(OverProductionError, create_work_order, item=item, pln=pln, qty=2)
pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 8)
# Cancel 1st Work Order # Cancel 1st Work Order
wo1 = frappe.get_doc("Work Order", wo_list[0]) wo1 = frappe.get_doc("Work Order", wo_list[0])
wo1.cancel() wo1.cancel()
pln.reload() pln.reload()
self.assertEqual(pln.po_items[0].ordered_qty, 3) self.assertEqual(pln.po_items[0].ordered_qty, 2)
# Cancel 2nd Work Order # Cancel 2nd Work Order
wo2 = frappe.get_doc("Work Order", wo_list[1]) wo2 = frappe.get_doc("Work Order", wo_list[1])
@ -528,6 +580,7 @@ class TestProductionPlan(ERPNextTestCase):
wip_warehouse='Work In Progress - _TC', wip_warehouse='Work In Progress - _TC',
fg_warehouse='Finished Goods - _TC', fg_warehouse='Finished Goods - _TC',
skip_transfer=1, skip_transfer=1,
use_multi_level_bom=1,
do_not_submit=True do_not_submit=True
) )
wo.production_plan = pln.name wo.production_plan = pln.name
@ -572,6 +625,7 @@ class TestProductionPlan(ERPNextTestCase):
wip_warehouse='Work In Progress - _TC', wip_warehouse='Work In Progress - _TC',
fg_warehouse='Finished Goods - _TC', fg_warehouse='Finished Goods - _TC',
skip_transfer=1, skip_transfer=1,
use_multi_level_bom=1,
do_not_submit=True do_not_submit=True
) )
wo.production_plan = pln.name wo.production_plan = pln.name

View File

@ -17,7 +17,7 @@ frappe.ui.form.on('Routing', {
}, },
calculate_operating_cost: function(frm, child) { 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); 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: for operation in self.operations:
if not operation.hour_rate: if not operation.hour_rate:
operation.hour_rate = frappe.db.get_value("Workstation", operation.workstation, '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): def set_routing_id(self):
sequence_id = 0 sequence_id = 0

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
# See license.txt # See license.txt
import frappe import frappe
from frappe.test_runner import make_test_records 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.operation.test_operation import make_operation
from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom
@ -10,13 +11,12 @@ from erpnext.manufacturing.doctype.workstation.workstation import (
WorkstationHolidayError, WorkstationHolidayError,
check_if_within_operating_hours, check_if_within_operating_hours,
) )
from erpnext.tests.utils import ERPNextTestCase
test_dependencies = ["Warehouse"] test_dependencies = ["Warehouse"]
test_records = frappe.get_test_records('Workstation') test_records = frappe.get_test_records('Workstation')
make_test_records('Workstation') make_test_records('Workstation')
class TestWorkstation(ERPNextTestCase): class TestWorkstation(FrappeTestCase):
def test_validate_timings(self): 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 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") 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'), 'label': _('Transaction'),
'items': ['Work Order', 'Job Card', 'Timesheet'] 'items': ['Work Order', 'Job Card',]
} }
], ],
'disable_create_buttons': ['BOM', 'Routing', 'Operation', 'disable_create_buttons': ['BOM', 'Routing', 'Operation',
'Work Order', 'Job Card', 'Timesheet'] 'Work Order', 'Job Card',]
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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