mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-18 22:35:10 +00:00
fix: merge conflict
This commit is contained in:
@@ -159,7 +159,7 @@ def book_deferred_income_or_expense(doc, posting_date=None):
|
||||
total_days, total_booking_days, account_currency)
|
||||
|
||||
make_gl_entries(doc, credit_account, debit_account, against,
|
||||
amount, base_amount, end_date, project, account_currency, item.cost_center, item.name)
|
||||
amount, base_amount, end_date, project, account_currency, item.cost_center, item)
|
||||
|
||||
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
|
||||
_book_deferred_revenue_or_expense(item)
|
||||
@@ -170,7 +170,7 @@ def book_deferred_income_or_expense(doc, posting_date=None):
|
||||
_book_deferred_revenue_or_expense(item)
|
||||
|
||||
def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
amount, base_amount, posting_date, project, account_currency, cost_center, voucher_detail_no):
|
||||
amount, base_amount, posting_date, project, account_currency, cost_center, item):
|
||||
# GL Entry for crediting the amount in the deferred expense
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
@@ -184,10 +184,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
"credit": base_amount,
|
||||
"credit_in_account_currency": amount,
|
||||
"cost_center": cost_center,
|
||||
"voucher_detail_no": voucher_detail_no,
|
||||
"voucher_detail_no": item.name,
|
||||
'posting_date': posting_date,
|
||||
'project': project
|
||||
}, account_currency)
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
# GL Entry to debit the amount from the expense
|
||||
gl_entries.append(
|
||||
@@ -197,10 +197,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
"debit": base_amount,
|
||||
"debit_in_account_currency": amount,
|
||||
"cost_center": cost_center,
|
||||
"voucher_detail_no": voucher_detail_no,
|
||||
"voucher_detail_no": item.name,
|
||||
'posting_date': posting_date,
|
||||
'project': project
|
||||
}, account_currency)
|
||||
}, account_currency, item=item)
|
||||
)
|
||||
|
||||
if gl_entries:
|
||||
|
||||
@@ -89,7 +89,7 @@ class Account(NestedSet):
|
||||
throw(_("Root cannot be edited."), RootNotEditable)
|
||||
|
||||
if not self.parent_account and not self.is_group:
|
||||
frappe.throw(_("Root Account must be a group"))
|
||||
frappe.throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
|
||||
|
||||
def validate_root_company_and_sync_account_to_children(self):
|
||||
# ignore validation while creating new compnay or while syncing to child companies
|
||||
|
||||
@@ -69,6 +69,7 @@ class TestAccount(unittest.TestCase):
|
||||
acc.account_name = "Accumulated Depreciation"
|
||||
acc.parent_account = "Fixed Assets - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.account_type = "Accumulated Depreciation"
|
||||
acc.insert()
|
||||
|
||||
doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
|
||||
@@ -149,7 +150,7 @@ def _make_test_records(verbose):
|
||||
|
||||
# fixed asset depreciation
|
||||
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
||||
["_Test Accumulated Depreciations", "Current Assets", 0, None, None],
|
||||
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
||||
["_Test Depreciations", "Expenses", 0, None, None],
|
||||
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
||||
|
||||
|
||||
@@ -162,9 +162,9 @@ def toggle_disabling(doc):
|
||||
|
||||
def get_doctypes_with_dimensions():
|
||||
doclist = ["GL Entry", "Sales Invoice", "Purchase Invoice", "Payment Entry", "Asset",
|
||||
"Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Sales Invoice Item", "Purchase Invoice Item",
|
||||
"Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item", "Purchase Receipt Item",
|
||||
"Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
||||
"Expense Claim", "Expense Claim Detail", "Expense Taxes and Charges", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note",
|
||||
"Sales Invoice Item", "Purchase Invoice Item", "Purchase Order Item", "Journal Entry Account", "Material Request Item", "Delivery Note Item",
|
||||
"Purchase Receipt Item", "Stock Entry Detail", "Payment Entry Deduction", "Sales Taxes and Charges", "Purchase Taxes and Charges", "Shipping Rule",
|
||||
"Landed Cost Item", "Asset Value Adjustment", "Loyalty Program", "Fee Schedule", "Fee Structure", "Stock Reconciliation",
|
||||
"Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", "Opening Invoice Creation Tool Item", "Subscription",
|
||||
"Subscription Plan"]
|
||||
@@ -206,12 +206,13 @@ def get_dimension_filters():
|
||||
WHERE disabled = 0
|
||||
""", as_dict=1)
|
||||
|
||||
default_dimensions = frappe.db.sql("""SELECT parent, company, default_dimension
|
||||
FROM `tabAccounting Dimension Detail`""", as_dict=1)
|
||||
default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
|
||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||
WHERE c.parent = p.name""", as_dict=1)
|
||||
|
||||
default_dimensions_map = {}
|
||||
for dimension in default_dimensions:
|
||||
default_dimensions_map.setdefault(dimension['company'], {})
|
||||
default_dimensions_map[dimension['company']][dimension['parent']] = dimension['default_dimension']
|
||||
default_dimensions_map.setdefault(dimension.company, {})
|
||||
default_dimensions_map[dimension.company][dimension.fieldname] = dimension.default_dimension
|
||||
|
||||
return dimension_filters, default_dimensions_map
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe.utils import flt, getdate, add_months, get_last_day, fmt_money, nowd
|
||||
from frappe.model.naming import make_autoname
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from frappe.model.document import Document
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
|
||||
class BudgetError(frappe.ValidationError): pass
|
||||
class DuplicateBudgetError(frappe.ValidationError): pass
|
||||
@@ -98,30 +99,32 @@ def validate_expense_against_budget(args):
|
||||
if not (args.get('account') and args.get('cost_center')) and args.item_code:
|
||||
args.cost_center, args.account = get_item_details(args)
|
||||
|
||||
if not (args.cost_center or args.project) and not args.account:
|
||||
if not args.account:
|
||||
return
|
||||
|
||||
for budget_against in ['project', 'cost_center']:
|
||||
for budget_against in ['project', 'cost_center'] + get_accounting_dimensions():
|
||||
if (args.get(budget_against) and args.account
|
||||
and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})):
|
||||
|
||||
if args.project and budget_against == 'project':
|
||||
condition = "and b.project=%s" % frappe.db.escape(args.project)
|
||||
args.budget_against_field = "Project"
|
||||
doctype = frappe.unscrub(budget_against)
|
||||
|
||||
elif args.cost_center and budget_against == 'cost_center':
|
||||
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", args.cost_center, ["lft", "rgt"])
|
||||
condition = """and exists(select name from `tabCost Center`
|
||||
where lft<=%s and rgt>=%s and name=b.cost_center)""" % (cc_lft, cc_rgt)
|
||||
args.budget_against_field = "Cost Center"
|
||||
if frappe.get_cached_value('DocType', doctype, 'is_tree'):
|
||||
lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
|
||||
condition = """and exists(select name from `tab%s`
|
||||
where lft<=%s and rgt>=%s and name=b.%s)""" % (doctype, lft, rgt, budget_against) #nosec
|
||||
args.is_tree = True
|
||||
else:
|
||||
condition = "and b.%s=%s" % (budget_against, frappe.db.escape(args.get(budget_against)))
|
||||
args.is_tree = False
|
||||
|
||||
args.budget_against = args.get(budget_against)
|
||||
args.budget_against_field = budget_against
|
||||
args.budget_against_doctype = doctype
|
||||
|
||||
budget_records = frappe.db.sql("""
|
||||
select
|
||||
b.{budget_against_field} as budget_against, ba.budget_amount, b.monthly_distribution,
|
||||
ifnull(b.applicable_on_material_request, 0) as for_material_request,
|
||||
ifnull(applicable_on_purchase_order,0) as for_purchase_order,
|
||||
ifnull(applicable_on_purchase_order, 0) as for_purchase_order,
|
||||
ifnull(applicable_on_booking_actual_expenses,0) as for_actual_expenses,
|
||||
b.action_if_annual_budget_exceeded, b.action_if_accumulated_monthly_budget_exceeded,
|
||||
b.action_if_annual_budget_exceeded_on_mr, b.action_if_accumulated_monthly_budget_exceeded_on_mr,
|
||||
@@ -132,9 +135,7 @@ def validate_expense_against_budget(args):
|
||||
b.name=ba.parent and b.fiscal_year=%s
|
||||
and ba.account=%s and b.docstatus=1
|
||||
{condition}
|
||||
""".format(condition=condition,
|
||||
budget_against_field=frappe.scrub(args.get("budget_against_field"))),
|
||||
(args.fiscal_year, args.account), as_dict=True)
|
||||
""".format(condition=condition, budget_against_field=budget_against), (args.fiscal_year, args.account), as_dict=True) #nosec
|
||||
|
||||
if budget_records:
|
||||
validate_budget_records(args, budget_records)
|
||||
@@ -230,10 +231,10 @@ def get_ordered_amount(args, budget):
|
||||
|
||||
def get_other_condition(args, budget, for_doc):
|
||||
condition = "expense_account = '%s'" % (args.expense_account)
|
||||
budget_against_field = frappe.scrub(args.get("budget_against_field"))
|
||||
budget_against_field = args.get("budget_against_field")
|
||||
|
||||
if budget_against_field and args.get(budget_against_field):
|
||||
condition += " and child.%s = '%s'" %(budget_against_field, args.get(budget_against_field))
|
||||
condition += " and child.%s = '%s'" % (budget_against_field, args.get(budget_against_field))
|
||||
|
||||
if args.get('fiscal_year'):
|
||||
date_field = 'schedule_date' if for_doc == 'Material Request' else 'transaction_date'
|
||||
@@ -246,19 +247,30 @@ def get_other_condition(args, budget, for_doc):
|
||||
return condition
|
||||
|
||||
def get_actual_expense(args):
|
||||
if not args.budget_against_doctype:
|
||||
args.budget_against_doctype = frappe.unscrub(args.budget_against_field)
|
||||
|
||||
budget_against_field = args.get('budget_against_field')
|
||||
condition1 = " and gle.posting_date <= %(month_end_date)s" \
|
||||
if args.get("month_end_date") else ""
|
||||
if args.budget_against_field == "Cost Center":
|
||||
lft_rgt = frappe.db.get_value(args.budget_against_field,
|
||||
args.budget_against, ["lft", "rgt"], as_dict=1)
|
||||
|
||||
if args.is_tree:
|
||||
lft_rgt = frappe.db.get_value(args.budget_against_doctype,
|
||||
args.get(budget_against_field), ["lft", "rgt"], as_dict=1)
|
||||
|
||||
args.update(lft_rgt)
|
||||
condition2 = """and exists(select name from `tabCost Center`
|
||||
where lft>=%(lft)s and rgt<=%(rgt)s and name=gle.cost_center)"""
|
||||
|
||||
elif args.budget_against_field == "Project":
|
||||
condition2 = "and exists(select name from `tabProject` where name=gle.project and gle.project = %(budget_against)s)"
|
||||
condition2 = """and exists(select name from `tab{doctype}`
|
||||
where lft>=%(lft)s and rgt<=%(rgt)s
|
||||
and name=gle.{budget_against_field})""".format(doctype=args.budget_against_doctype, #nosec
|
||||
budget_against_field=budget_against_field)
|
||||
else:
|
||||
condition2 = """and exists(select name from `tab{doctype}`
|
||||
where name=gle.{budget_against} and
|
||||
gle.{budget_against} = %({budget_against})s)""".format(doctype=args.budget_against_doctype,
|
||||
budget_against = budget_against_field)
|
||||
|
||||
return flt(frappe.db.sql("""
|
||||
amount = flt(frappe.db.sql("""
|
||||
select sum(gle.debit) - sum(gle.credit)
|
||||
from `tabGL Entry` gle
|
||||
where gle.account=%(account)s
|
||||
@@ -267,7 +279,9 @@ def get_actual_expense(args):
|
||||
and gle.company=%(company)s
|
||||
and gle.docstatus=1
|
||||
{condition2}
|
||||
""".format(condition1=condition1, condition2=condition2), (args))[0][0])
|
||||
""".format(condition1=condition1, condition2=condition2), (args))[0][0]) #nosec
|
||||
|
||||
return amount
|
||||
|
||||
def get_accumulated_monthly_budget(monthly_distribution, posting_date, fiscal_year, annual_budget):
|
||||
distribution = {}
|
||||
|
||||
@@ -13,7 +13,7 @@ from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journ
|
||||
|
||||
class TestBudget(unittest.TestCase):
|
||||
def test_monthly_budget_crossed_ignore(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_crossed_stop1(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
@@ -41,7 +41,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_exception_approver_role(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
@@ -114,7 +114,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_crossed_stop2(self):
|
||||
set_total_expense_zero("2013-02-28", "Project")
|
||||
set_total_expense_zero("2013-02-28", "project")
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
|
||||
@@ -129,7 +129,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_yearly_budget_crossed_stop1(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
@@ -141,7 +141,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_yearly_budget_crossed_stop2(self):
|
||||
set_total_expense_zero("2013-02-28", "Project")
|
||||
set_total_expense_zero("2013-02-28", "project")
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
|
||||
@@ -153,7 +153,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_on_cancellation1(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center")
|
||||
|
||||
@@ -177,7 +177,7 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_on_cancellation2(self):
|
||||
set_total_expense_zero("2013-02-28", "Project")
|
||||
set_total_expense_zero("2013-02-28", "project")
|
||||
|
||||
budget = make_budget(budget_against="Project")
|
||||
|
||||
@@ -201,8 +201,8 @@ class TestBudget(unittest.TestCase):
|
||||
budget.cancel()
|
||||
|
||||
def test_monthly_budget_against_group_cost_center(self):
|
||||
set_total_expense_zero("2013-02-28", "Cost Center")
|
||||
set_total_expense_zero("2013-02-28", "Cost Center", "_Test Cost Center 2 - _TC")
|
||||
set_total_expense_zero("2013-02-28", "cost_center")
|
||||
set_total_expense_zero("2013-02-28", "cost_center", "_Test Cost Center 2 - _TC")
|
||||
|
||||
budget = make_budget(budget_against="Cost Center", cost_center="_Test Company - _TC")
|
||||
frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop")
|
||||
@@ -241,25 +241,30 @@ class TestBudget(unittest.TestCase):
|
||||
|
||||
|
||||
def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None):
|
||||
if budget_against_field == "Project":
|
||||
if budget_against_field == "project":
|
||||
budget_against = "_Test Project"
|
||||
else:
|
||||
budget_against = budget_against_CC or "_Test Cost Center - _TC"
|
||||
existing_expense = get_actual_expense(frappe._dict({
|
||||
|
||||
args = frappe._dict({
|
||||
"account": "_Test Account Cost for Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"monthly_end_date": posting_date,
|
||||
"company": "_Test Company",
|
||||
"fiscal_year": "_Test Fiscal Year 2013",
|
||||
"budget_against_field": budget_against_field,
|
||||
"budget_against": budget_against
|
||||
}))
|
||||
})
|
||||
|
||||
if not args.get(budget_against_field):
|
||||
args[budget_against_field] = budget_against
|
||||
|
||||
existing_expense = get_actual_expense(args)
|
||||
|
||||
if existing_expense:
|
||||
if budget_against_field == "Cost Center":
|
||||
if budget_against_field == "cost_center":
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True)
|
||||
elif budget_against_field == "Project":
|
||||
elif budget_against_field == "project":
|
||||
make_journal_entry("_Test Account Cost for Goods Sold - _TC",
|
||||
"_Test Bank - _TC", -existing_expense, "_Test Cost Center - _TC", submit=True, project="_Test Project", posting_date="2013-02-28")
|
||||
|
||||
|
||||
@@ -17,17 +17,60 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
if (frm.page && frm.page.show_import_button) {
|
||||
create_import_button(frm);
|
||||
}
|
||||
},
|
||||
|
||||
// show download template button when company is properly selected
|
||||
if(frm.doc.company) {
|
||||
// download the csv template file
|
||||
frm.add_custom_button(__("Download template"), function () {
|
||||
let get_template_url = 'erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template';
|
||||
open_url_post(frappe.request.url, { cmd: get_template_url, doctype: frm.doc.doctype });
|
||||
});
|
||||
} else {
|
||||
frm.set_value("import_file", "");
|
||||
}
|
||||
download_template: function(frm) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __("Download Template"),
|
||||
fields: [
|
||||
{
|
||||
label : "File Type",
|
||||
fieldname: "file_type",
|
||||
fieldtype: "Select",
|
||||
reqd: 1,
|
||||
options: ["Excel", "CSV"]
|
||||
},
|
||||
{
|
||||
label: "Template Type",
|
||||
fieldname: "template_type",
|
||||
fieldtype: "Select",
|
||||
reqd: 1,
|
||||
options: ["Sample Template", "Blank Template"],
|
||||
change: () => {
|
||||
let template_type = d.get_value('template_type');
|
||||
|
||||
if (template_type === "Sample Template") {
|
||||
d.set_df_property('template_type', 'description',
|
||||
`The Sample Template contains all the required accounts pre filled in the template.
|
||||
You can add more accounts or change existing accounts in the template as per your choice.`);
|
||||
} else {
|
||||
d.set_df_property('template_type', 'description',
|
||||
`The Blank Template contains just the account type and root type required to build the Chart
|
||||
of Accounts. Please enter the account names and add more rows as per your requirement.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
|
||||
if (!data.template_type) {
|
||||
frappe.throw(__('Please select <b>Template Type</b> to download template'));
|
||||
}
|
||||
|
||||
open_url_post(
|
||||
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
|
||||
{
|
||||
file_type: data.file_type,
|
||||
template_type: data.template_type
|
||||
}
|
||||
);
|
||||
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Download')
|
||||
});
|
||||
d.show();
|
||||
},
|
||||
|
||||
import_file: function (frm) {
|
||||
@@ -41,21 +84,24 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
},
|
||||
|
||||
company: function (frm) {
|
||||
// validate that no Gl Entry record for the company exists.
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
|
||||
args: {
|
||||
company: frm.doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message===false) {
|
||||
frm.set_value("company", "");
|
||||
frappe.throw(__("Transactions against the company already exist! "));
|
||||
} else {
|
||||
frm.trigger("refresh");
|
||||
if (frm.doc.company) {
|
||||
// validate that no Gl Entry record for the company exists.
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.validate_company",
|
||||
args: {
|
||||
company: frm.doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message===false) {
|
||||
frm.set_value("company", "");
|
||||
frappe.throw(__(`Transactions against the company already exist!
|
||||
Chart Of accounts can be imported for company with no transactions`));
|
||||
} else {
|
||||
frm.trigger("refresh");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,7 +123,7 @@ var validate_csv_data = function(frm) {
|
||||
};
|
||||
|
||||
var create_import_button = function(frm) {
|
||||
frm.page.set_primary_action(__("Start Import"), function () {
|
||||
frm.page.set_primary_action(__("Import"), function () {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.import_coa",
|
||||
args: {
|
||||
@@ -118,7 +164,8 @@ var generate_tree_preview = function(frm) {
|
||||
args: {
|
||||
file_name: frm.doc.import_file,
|
||||
parent: parent,
|
||||
doctype: 'Chart of Accounts Importer'
|
||||
doctype: 'Chart of Accounts Importer',
|
||||
file_type: frm.doc.file_type
|
||||
},
|
||||
onclick: function(node) {
|
||||
parent = node.value;
|
||||
|
||||
@@ -1,226 +1,71 @@
|
||||
{
|
||||
"allow_copy": 1,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2019-02-01 12:24:34.761380",
|
||||
"custom": 0,
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"creation": "2019-02-01 12:24:34.761380",
|
||||
"description": "Import Chart of Accounts from a csv file",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"download_template",
|
||||
"import_file",
|
||||
"chart_preview",
|
||||
"chart_tree"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "import_file_section",
|
||||
"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,
|
||||
"depends_on": "",
|
||||
"depends_on": "company",
|
||||
"fieldname": "import_file",
|
||||
"fieldtype": "Attach",
|
||||
"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": "Attach custom Chart of Accounts file",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"label": "Attach custom Chart of Accounts file"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 1,
|
||||
"columns": 0,
|
||||
"fieldname": "chart_preview",
|
||||
"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": "Chart Preview",
|
||||
"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
|
||||
},
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Chart Preview"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "chart_tree",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Chart Tree",
|
||||
"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
|
||||
"label": "Chart Tree"
|
||||
},
|
||||
{
|
||||
"depends_on": "company",
|
||||
"fieldname": "download_template",
|
||||
"fieldtype": "Button",
|
||||
"label": "Download Template"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 1,
|
||||
"hide_toolbar": 1,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 1,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-04 23:10:30.136807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Chart of Accounts Importer",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"in_create": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-02-28 08:49:11.422846",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Chart of Accounts Importer",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"read": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 1,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -4,18 +4,28 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from functools import reduce
|
||||
import frappe, csv
|
||||
import frappe, csv, os
|
||||
from frappe import _
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils import cstr, cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts, build_tree_from_json
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
|
||||
|
||||
class ChartofAccountsImporter(Document):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
parent_company, allow_account_creation_against_child_company = frappe.db.get_value('Company',
|
||||
{'name': company}, ['parent_company',
|
||||
'allow_account_creation_against_child_company'])
|
||||
|
||||
if parent_company and (not allow_account_creation_against_child_company):
|
||||
frappe.throw(_("""{0} is a child company. Please import accounts against parent company
|
||||
or enable {1} in company master""").format(frappe.bold(company),
|
||||
frappe.bold('Allow Account Creation Against Child Company')), title='Wrong Company')
|
||||
|
||||
if frappe.db.get_all('GL Entry', {"company": company}, "name", limit=1):
|
||||
return False
|
||||
|
||||
@@ -25,42 +35,85 @@ def import_coa(file_name, company):
|
||||
unset_existing_data(company)
|
||||
|
||||
# create accounts
|
||||
forest = build_forest(generate_data_from_csv(file_name))
|
||||
file_doc, extension = get_file(file_name)
|
||||
|
||||
if extension == 'csv':
|
||||
data = generate_data_from_csv(file_doc)
|
||||
else:
|
||||
data = generate_data_from_excel(file_doc, extension)
|
||||
|
||||
forest = build_forest(data)
|
||||
create_charts(company, custom_chart=forest)
|
||||
|
||||
# trigger on_update for company to reset default accounts
|
||||
set_default_accounts(company)
|
||||
|
||||
def generate_data_from_csv(file_name, as_dict=False):
|
||||
''' read csv file and return the generated nested tree '''
|
||||
if not file_name.endswith('.csv'):
|
||||
frappe.throw("Only CSV files can be used to for importing data. Please check the file format you are trying to upload")
|
||||
def get_file(file_name):
|
||||
file_doc = frappe.get_doc("File", {"file_url": file_name})
|
||||
parts = file_doc.get_extension()
|
||||
extension = parts[1]
|
||||
extension = extension.lstrip(".")
|
||||
|
||||
if extension not in ('csv', 'xlsx', 'xls'):
|
||||
frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
|
||||
|
||||
return file_doc, extension
|
||||
|
||||
def generate_data_from_csv(file_doc, as_dict=False):
|
||||
''' read csv file and return the generated nested tree '''
|
||||
|
||||
file_doc = frappe.get_doc('File', {"file_url": file_name})
|
||||
file_path = file_doc.get_full_path()
|
||||
|
||||
data = []
|
||||
with open(file_path, 'r') as in_file:
|
||||
csv_reader = list(csv.reader(in_file))
|
||||
headers = csv_reader[1][1:]
|
||||
del csv_reader[0:2] # delete top row and headers row
|
||||
headers = csv_reader[0]
|
||||
del csv_reader[0] # delete top row and headers row
|
||||
|
||||
for row in csv_reader:
|
||||
if as_dict:
|
||||
data.append({frappe.scrub(header): row[index+1] for index, header in enumerate(headers)})
|
||||
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
|
||||
else:
|
||||
if not row[2]: row[2] = row[1]
|
||||
data.append(row[1:])
|
||||
if not row[1]: row[1] = row[0]
|
||||
data.append(row)
|
||||
|
||||
# convert csv data
|
||||
return data
|
||||
|
||||
def generate_data_from_excel(file_doc, extension, as_dict=False):
|
||||
content = file_doc.get_content()
|
||||
|
||||
if extension == "xlsx":
|
||||
rows = read_xlsx_file_from_attached_file(fcontent=content)
|
||||
elif extension == "xls":
|
||||
rows = read_xls_file_from_attached_file(content)
|
||||
|
||||
data = []
|
||||
headers = rows[0]
|
||||
del rows[0]
|
||||
|
||||
for row in rows:
|
||||
if as_dict:
|
||||
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
|
||||
else:
|
||||
if not row[1]: row[1] = row[0]
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(doctype, parent, is_root=False, file_name=None):
|
||||
''' called by tree view (to fetch node's children) '''
|
||||
|
||||
file_doc, extension = get_file(file_name)
|
||||
parent = None if parent==_('All Accounts') else parent
|
||||
forest = build_forest(generate_data_from_csv(file_name))
|
||||
|
||||
if extension == 'csv':
|
||||
data = generate_data_from_csv(file_doc)
|
||||
else:
|
||||
data = generate_data_from_excel(file_doc, extension)
|
||||
|
||||
forest = build_forest(data)
|
||||
accounts = build_tree_from_json("", chart_data=forest) # returns alist of dict in a tree render-able form
|
||||
|
||||
# filter out to show data for the selected node only
|
||||
@@ -91,15 +144,18 @@ def build_forest(data):
|
||||
|
||||
# returns the path of any node in list format
|
||||
def return_parent(data, child):
|
||||
from frappe import _
|
||||
|
||||
for row in data:
|
||||
account_name, parent_account = row[0:2]
|
||||
if parent_account == account_name == child:
|
||||
return [parent_account]
|
||||
elif account_name == child:
|
||||
parent_account_list = return_parent(data, parent_account)
|
||||
if not parent_account_list and parent_account:
|
||||
frappe.throw(_("The parent account {0} does not exists")
|
||||
.format(parent_account))
|
||||
if not parent_account_list:
|
||||
frappe.throw(_("The parent account {0} does not exists in the uploaded template").format(
|
||||
frappe.bold(parent_account)))
|
||||
|
||||
return [child] + parent_account_list
|
||||
|
||||
charts_map, paths = {}, []
|
||||
@@ -114,7 +170,7 @@ def build_forest(data):
|
||||
error_messages.append("Row {0}: Please enter Account Name".format(line_no))
|
||||
|
||||
charts_map[account_name] = {}
|
||||
if is_group == 1: charts_map[account_name]["is_group"] = is_group
|
||||
if cint(is_group) == 1: charts_map[account_name]["is_group"] = is_group
|
||||
if account_type: charts_map[account_name]["account_type"] = account_type
|
||||
if root_type: charts_map[account_name]["root_type"] = root_type
|
||||
if account_number: charts_map[account_name]["account_number"] = account_number
|
||||
@@ -132,24 +188,94 @@ def build_forest(data):
|
||||
|
||||
return out
|
||||
|
||||
def build_response_as_excel(writer):
|
||||
filename = frappe.generate_hash("", 10)
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(cstr(writer.getvalue()).encode('utf-8'))
|
||||
f = open(filename)
|
||||
reader = csv.reader(f)
|
||||
|
||||
from frappe.utils.xlsxutils import make_xlsx
|
||||
xlsx_file = make_xlsx(reader, "Chart Of Accounts Importer Template")
|
||||
|
||||
f.close()
|
||||
os.remove(filename)
|
||||
|
||||
# write out response as a xlsx type
|
||||
frappe.response['filename'] = 'coa_importer_template.xlsx'
|
||||
frappe.response['filecontent'] = xlsx_file.getvalue()
|
||||
frappe.response['type'] = 'binary'
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template():
|
||||
def download_template(file_type, template_type):
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
writer = get_template(template_type)
|
||||
|
||||
if file_type == 'CSV':
|
||||
# download csv file
|
||||
frappe.response['result'] = cstr(writer.getvalue())
|
||||
frappe.response['type'] = 'csv'
|
||||
frappe.response['doctype'] = 'Chart of Accounts Importer'
|
||||
else:
|
||||
build_response_as_excel(writer)
|
||||
|
||||
def get_template(template_type):
|
||||
|
||||
fields = ["Account Name", "Parent Account", "Account Number", "Is Group", "Account Type", "Root Type"]
|
||||
writer = UnicodeWriter()
|
||||
writer.writerow(fields)
|
||||
|
||||
writer.writerow([_('Chart of Accounts Template')])
|
||||
writer.writerow([_("Column Labels : ")] + fields)
|
||||
writer.writerow([_("Start entering data from here : ")])
|
||||
if template_type == 'Blank Template':
|
||||
for root_type in get_root_types():
|
||||
writer.writerow(['', '', '', 1, '', root_type])
|
||||
|
||||
for account in get_mandatory_group_accounts():
|
||||
writer.writerow(['', '', '', 1, account, "Asset"])
|
||||
|
||||
for account_type in get_mandatory_account_types():
|
||||
writer.writerow(['', '', '', 0, account_type.get('account_type'), account_type.get('root_type')])
|
||||
else:
|
||||
writer = get_sample_template(writer)
|
||||
|
||||
return writer
|
||||
|
||||
def get_sample_template(writer):
|
||||
template = [
|
||||
["Application Of Funds(Assets)", "", "", 1, "", "Asset"],
|
||||
["Sources Of Funds(Liabilities)", "", "", 1, "", "Liability"],
|
||||
["Equity", "", "", 1, "", "Equity"],
|
||||
["Expenses", "", "", 1, "", "Expense"],
|
||||
["Income", "", "", 1, "", "Income"],
|
||||
["Bank Accounts", "Application Of Funds(Assets)", "", 1, "Bank", "Asset"],
|
||||
["Cash In Hand", "Application Of Funds(Assets)", "", 1, "Cash", "Asset"],
|
||||
["Stock Assets", "Application Of Funds(Assets)", "", 1, "Stock", "Asset"],
|
||||
["Cost Of Goods Sold", "Expenses", "", 0, "Cost of Goods Sold", "Expense"],
|
||||
["Asset Depreciation", "Expenses", "", 0, "Depreciation", "Expense"],
|
||||
["Fixed Assets", "Application Of Funds(Assets)", "", 0, "Fixed Asset", "Asset"],
|
||||
["Accounts Payable", "Sources Of Funds(Liabilities)", "", 0, "Payable", "Liability"],
|
||||
["Accounts Receivable", "Application Of Funds(Assets)", "", 1, "Receivable", "Asset"],
|
||||
["Stock Expenses", "Expenses", "", 0, "Stock Adjustment", "Expense"],
|
||||
["Sample Bank", "Bank Accounts", "", 0, "Bank", "Asset"],
|
||||
["Cash", "Cash In Hand", "", 0, "Cash", "Asset"],
|
||||
["Stores", "Stock Assets", "", 0, "Stock", "Asset"],
|
||||
]
|
||||
|
||||
for row in template:
|
||||
writer.writerow(row)
|
||||
|
||||
return writer
|
||||
|
||||
# download csv file
|
||||
frappe.response['result'] = cstr(writer.getvalue())
|
||||
frappe.response['type'] = 'csv'
|
||||
frappe.response['doctype'] = data.get('doctype')
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_accounts(file_name):
|
||||
accounts = generate_data_from_csv(file_name, as_dict=True)
|
||||
|
||||
file_doc, extension = get_file(file_name)
|
||||
|
||||
if extension == 'csv':
|
||||
accounts = generate_data_from_csv(file_doc, as_dict=True)
|
||||
else:
|
||||
accounts = generate_data_from_excel(file_doc, extension, as_dict=True)
|
||||
|
||||
accounts_dict = {}
|
||||
for account in accounts:
|
||||
@@ -174,12 +300,38 @@ def validate_root(accounts):
|
||||
for account in roots:
|
||||
if not account.get("root_type") and account.get("account_name"):
|
||||
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
|
||||
elif account.get("root_type") not in ("Asset", "Liability", "Expense", "Income", "Equity") and account.get("account_name"):
|
||||
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
|
||||
error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name")))
|
||||
|
||||
if error_messages:
|
||||
return "<br>".join(error_messages)
|
||||
|
||||
def get_root_types():
|
||||
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
|
||||
|
||||
def get_report_type(root_type):
|
||||
if root_type in ('Asset', 'Liability', 'Equity'):
|
||||
return 'Balance Sheet'
|
||||
else:
|
||||
return 'Profit and Loss'
|
||||
|
||||
def get_mandatory_group_accounts():
|
||||
return ('Bank', 'Cash', 'Stock')
|
||||
|
||||
def get_mandatory_account_types():
|
||||
return [
|
||||
{'account_type': 'Cost of Goods Sold', 'root_type': 'Expense'},
|
||||
{'account_type': 'Depreciation', 'root_type': 'Expense'},
|
||||
{'account_type': 'Fixed Asset', 'root_type': 'Asset'},
|
||||
{'account_type': 'Payable', 'root_type': 'Liability'},
|
||||
{'account_type': 'Receivable', 'root_type': 'Asset'},
|
||||
{'account_type': 'Stock Adjustment', 'root_type': 'Expense'},
|
||||
{'account_type': 'Bank', 'root_type': 'Asset'},
|
||||
{'account_type': 'Cash', 'root_type': 'Asset'},
|
||||
{'account_type': 'Stock', 'root_type': 'Asset'}
|
||||
]
|
||||
|
||||
|
||||
def validate_account_types(accounts):
|
||||
account_types_for_ledger = ["Cost of Goods Sold", "Depreciation", "Fixed Asset", "Payable", "Receivable", "Stock Adjustment"]
|
||||
account_types = [accounts[d]["account_type"] for d in accounts if not accounts[d]['is_group'] == 1]
|
||||
|
||||
@@ -18,7 +18,7 @@ frappe.ui.form.on('Cost Center', {
|
||||
},
|
||||
refresh: function(frm) {
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__('Update Cost Center Number'), function () {
|
||||
frm.add_custom_button(__('Update Cost Center Name / Number'), function () {
|
||||
frm.trigger("update_cost_center_number");
|
||||
});
|
||||
}
|
||||
@@ -47,35 +47,45 @@ frappe.ui.form.on('Cost Center', {
|
||||
},
|
||||
update_cost_center_number: function(frm) {
|
||||
var d = new frappe.ui.Dialog({
|
||||
title: __('Update Cost Center Number'),
|
||||
title: __('Update Cost Center Name / Number'),
|
||||
fields: [
|
||||
{
|
||||
"label": 'Cost Center Number',
|
||||
"label": "Cost Center Name",
|
||||
"fieldname": "cost_center_name",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"default": frm.doc.cost_center_name
|
||||
},
|
||||
{
|
||||
"label": "Cost Center Number",
|
||||
"fieldname": "cost_center_number",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
"reqd": 1,
|
||||
"default": frm.doc.cost_center_number
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
if(data.cost_center_number === frm.doc.cost_center_number) {
|
||||
if(data.cost_center_name === frm.doc.cost_center_name && data.cost_center_number === frm.doc.cost_center_number) {
|
||||
d.hide();
|
||||
return;
|
||||
}
|
||||
frappe.dom.freeze();
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.utils.update_number_field",
|
||||
method: "erpnext.accounts.utils.update_cost_center",
|
||||
args: {
|
||||
doctype_name: frm.doc.doctype,
|
||||
name: frm.doc.name,
|
||||
field_name: d.fields[0].fieldname,
|
||||
number_value: data.cost_center_number,
|
||||
docname: frm.doc.name,
|
||||
cost_center_name: data.cost_center_name,
|
||||
cost_center_number: data.cost_center_number,
|
||||
company: frm.doc.company
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.dom.unfreeze();
|
||||
if(!r.exc) {
|
||||
if(r.message) {
|
||||
frappe.set_route("Form", "Cost Center", r.message);
|
||||
} else {
|
||||
me.frm.set_value("cost_center_name", data.cost_center_name);
|
||||
me.frm.set_value("cost_center_number", data.cost_center_number);
|
||||
}
|
||||
d.hide();
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-23 19:57:17",
|
||||
"description": "Track separate Income and Expense for product verticals or divisions.",
|
||||
"doctype": "DocType",
|
||||
@@ -126,7 +125,7 @@
|
||||
"idx": 1,
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-18 18:26:01.540170",
|
||||
"modified": "2020-04-29 16:09:30.025214",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cost Center",
|
||||
|
||||
@@ -115,8 +115,8 @@ class GLEntry(Document):
|
||||
from tabAccount where name=%s""", self.account, as_dict=1)[0]
|
||||
|
||||
if ret.is_group==1:
|
||||
frappe.throw(_("{0} {1}: Account {2} cannot be a Group")
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
frappe.throw(_('''{0} {1}: Account {2} is a Group Account and group accounts cannot be used in
|
||||
transactions''').format(self.voucher_type, self.voucher_no, self.account))
|
||||
|
||||
if ret.docstatus==2:
|
||||
frappe.throw(_("{0} {1}: Account {2} is inactive")
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe import _
|
||||
from frappe.utils import flt, getdate, nowdate, add_days
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
|
||||
|
||||
class InvoiceDiscounting(AccountsController):
|
||||
def validate(self):
|
||||
@@ -81,10 +82,15 @@ class InvoiceDiscounting(AccountsController):
|
||||
def make_gl_entries(self):
|
||||
company_currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||
|
||||
|
||||
gl_entries = []
|
||||
invoice_fields = ["debit_to", "party_account_currency", "conversion_rate", "cost_center"]
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
|
||||
invoice_fields.extend(accounting_dimensions)
|
||||
|
||||
for d in self.invoices:
|
||||
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice,
|
||||
["debit_to", "party_account_currency", "conversion_rate", "cost_center"], as_dict=1)
|
||||
inv = frappe.db.get_value("Sales Invoice", d.sales_invoice, invoice_fields, as_dict=1)
|
||||
|
||||
if d.outstanding_amount:
|
||||
outstanding_in_company_currency = flt(d.outstanding_amount * inv.conversion_rate,
|
||||
@@ -102,7 +108,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
"cost_center": inv.cost_center,
|
||||
"against_voucher": d.sales_invoice,
|
||||
"against_voucher_type": "Sales Invoice"
|
||||
}, inv.party_account_currency))
|
||||
}, inv.party_account_currency, item=inv))
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": self.accounts_receivable_credit,
|
||||
@@ -115,7 +121,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
"cost_center": inv.cost_center,
|
||||
"against_voucher": d.sales_invoice,
|
||||
"against_voucher_type": "Sales Invoice"
|
||||
}, ar_credit_account_currency))
|
||||
}, ar_credit_account_currency, item=inv))
|
||||
|
||||
make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
|
||||
|
||||
|
||||
@@ -561,20 +561,20 @@ class JournalEntry(AccountsController):
|
||||
|
||||
if self.write_off_based_on == 'Accounts Receivable':
|
||||
jd1.party_type = "Customer"
|
||||
jd1.credit = flt(d.outstanding_amount, self.precision("credit", "accounts"))
|
||||
jd1.credit_in_account_currency = flt(d.outstanding_amount, self.precision("credit", "accounts"))
|
||||
jd1.reference_type = "Sales Invoice"
|
||||
jd1.reference_name = cstr(d.name)
|
||||
elif self.write_off_based_on == 'Accounts Payable':
|
||||
jd1.party_type = "Supplier"
|
||||
jd1.debit = flt(d.outstanding_amount, self.precision("debit", "accounts"))
|
||||
jd1.debit_in_account_currency = flt(d.outstanding_amount, self.precision("debit", "accounts"))
|
||||
jd1.reference_type = "Purchase Invoice"
|
||||
jd1.reference_name = cstr(d.name)
|
||||
|
||||
jd2 = self.append('accounts', {})
|
||||
if self.write_off_based_on == 'Accounts Receivable':
|
||||
jd2.debit = total
|
||||
jd2.debit_in_account_currency = total
|
||||
elif self.write_off_based_on == 'Accounts Payable':
|
||||
jd2.credit = total
|
||||
jd2.credit_in_account_currency = total
|
||||
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
|
||||
@@ -104,6 +104,21 @@ frappe.ui.form.on('Payment Entry', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('payment_term', 'references', function(frm, cdt, cdn) {
|
||||
const child = locals[cdt][cdn];
|
||||
if (in_list(['Purchase Invoice', 'Sales Invoice'], child.reference_doctype) && child.reference_name) {
|
||||
let payment_term_list = frappe.get_list('Payment Schedule', {'parent': child.reference_name});
|
||||
|
||||
payment_term_list = payment_term_list.map(pt => pt.payment_term);
|
||||
|
||||
return {
|
||||
filters: {
|
||||
'name': ['in', payment_term_list]
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("reference_name", "references", function(doc, cdt, cdn) {
|
||||
const child = locals[cdt][cdn];
|
||||
const filters = {"docstatus": 1, "company": doc.company};
|
||||
@@ -272,7 +287,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.set_value("contact_email", "");
|
||||
frm.set_value("contact_person", "");
|
||||
}
|
||||
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party) {
|
||||
if(frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
|
||||
if(!frm.doc.posting_date) {
|
||||
frappe.msgprint(__("Please select Posting Date before selecting Party"))
|
||||
frm.set_value("party", "");
|
||||
@@ -1018,4 +1033,4 @@ frappe.ui.form.on('Payment Entry', {
|
||||
});
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -60,6 +60,7 @@ class PaymentEntry(AccountsController):
|
||||
self.set_remarks()
|
||||
self.validate_duplicate_entry()
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_status()
|
||||
|
||||
@@ -71,9 +72,9 @@ class PaymentEntry(AccountsController):
|
||||
self.update_outstanding_amounts()
|
||||
self.update_advance_paid()
|
||||
self.update_expense_claim()
|
||||
self.update_payment_schedule()
|
||||
self.set_status()
|
||||
|
||||
|
||||
def on_cancel(self):
|
||||
self.setup_party_account_field()
|
||||
self.make_gl_entries(cancel=1)
|
||||
@@ -81,9 +82,10 @@ class PaymentEntry(AccountsController):
|
||||
self.update_advance_paid()
|
||||
self.update_expense_claim()
|
||||
self.delink_advance_entry_references()
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.set_payment_req_status()
|
||||
self.set_status()
|
||||
|
||||
|
||||
def set_payment_req_status(self):
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import update_payment_req_status
|
||||
update_payment_req_status(self, None)
|
||||
@@ -94,10 +96,10 @@ class PaymentEntry(AccountsController):
|
||||
def validate_duplicate_entry(self):
|
||||
reference_names = []
|
||||
for d in self.get("references"):
|
||||
if (d.reference_doctype, d.reference_name) in reference_names:
|
||||
if (d.reference_doctype, d.reference_name, d.payment_term) in reference_names:
|
||||
frappe.throw(_("Row #{0}: Duplicate entry in References {1} {2}")
|
||||
.format(d.idx, d.reference_doctype, d.reference_name))
|
||||
reference_names.append((d.reference_doctype, d.reference_name))
|
||||
reference_names.append((d.reference_doctype, d.reference_name, d.payment_term))
|
||||
|
||||
def set_bank_account_data(self):
|
||||
if self.bank_account:
|
||||
@@ -264,6 +266,25 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("{0} {1} must be submitted")
|
||||
.format(d.reference_doctype, d.reference_name))
|
||||
|
||||
def validate_paid_invoices(self):
|
||||
no_oustanding_refs = {}
|
||||
|
||||
for d in self.get("references"):
|
||||
if not d.allocated_amount:
|
||||
continue
|
||||
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
|
||||
outstanding_amount, is_return = frappe.get_cached_value(d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"])
|
||||
if outstanding_amount <= 0 and not is_return:
|
||||
no_oustanding_refs.setdefault(d.reference_doctype, []).append(d)
|
||||
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.<br><br>\
|
||||
If this is undesirable please cancel the corresponding Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount")),
|
||||
title=_("Warning"), indicator="orange")
|
||||
|
||||
|
||||
def validate_journal_entry(self):
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
||||
@@ -285,6 +306,36 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry")
|
||||
.format(d.reference_name, dr_or_cr))
|
||||
|
||||
def update_payment_schedule(self, cancel=0):
|
||||
invoice_payment_amount_map = {}
|
||||
invoice_paid_amount_map = {}
|
||||
|
||||
for reference in self.get('references'):
|
||||
if reference.payment_term and reference.reference_name:
|
||||
key = (reference.payment_term, reference.reference_name)
|
||||
invoice_payment_amount_map.setdefault(key, 0.0)
|
||||
invoice_payment_amount_map[key] += reference.allocated_amount
|
||||
|
||||
if not invoice_paid_amount_map.get(reference.reference_name):
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term'])
|
||||
for term in payment_schedule:
|
||||
invoice_key = (term.payment_term, reference.reference_name)
|
||||
invoice_paid_amount_map.setdefault(invoice_key, {})
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
|
||||
|
||||
for key, amount in iteritems(invoice_payment_amount_map):
|
||||
if cancel:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
else:
|
||||
outstanding = invoice_paid_amount_map.get(key)['outstanding']
|
||||
if amount > outstanding:
|
||||
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
|
||||
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
self.status = 'Cancelled'
|
||||
@@ -397,8 +448,6 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Reference No and Reference Date is mandatory for Bank transaction"))
|
||||
|
||||
def set_remarks(self):
|
||||
if self.remarks: return
|
||||
|
||||
if self.payment_type=="Internal Transfer":
|
||||
remarks = [_("Amount {0} {1} transferred from {2} to {3}")
|
||||
.format(self.paid_from_account_currency, self.paid_amount, self.paid_from, self.paid_to)]
|
||||
@@ -452,7 +501,7 @@ class PaymentEntry(AccountsController):
|
||||
"against": against_account,
|
||||
"account_currency": self.party_account_currency,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
|
||||
dr_or_cr = "credit" if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit"
|
||||
|
||||
@@ -496,7 +545,7 @@ class PaymentEntry(AccountsController):
|
||||
"credit_in_account_currency": self.paid_amount,
|
||||
"credit": self.base_paid_amount,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
if self.payment_type in ("Receive", "Internal Transfer"):
|
||||
gl_entries.append(
|
||||
@@ -507,7 +556,7 @@ class PaymentEntry(AccountsController):
|
||||
"debit_in_account_currency": self.received_amount,
|
||||
"debit": self.base_received_amount,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
def add_deductions_gl_entries(self, gl_entries):
|
||||
@@ -1012,15 +1061,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
|
||||
frappe.msgprint(_('{0} is on hold till {1}'.format(doc.name, doc.release_date)))
|
||||
else:
|
||||
pe.append("references", {
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
"bill_no": doc.get("bill_no"),
|
||||
"due_date": doc.get("due_date"),
|
||||
'total_amount': grand_total,
|
||||
'outstanding_amount': outstanding_amount,
|
||||
'allocated_amount': outstanding_amount
|
||||
})
|
||||
if (doc.doctype in ('Sales Invoice', 'Purchase Invoice')
|
||||
and frappe.get_value('Payment Terms Template',
|
||||
{'name': doc.payment_terms_template}, 'allocate_payment_based_on_payment_terms')):
|
||||
|
||||
for reference in get_reference_as_per_payment_terms(doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
pe.append('references', reference)
|
||||
else:
|
||||
pe.append("references", {
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
"bill_no": doc.get("bill_no"),
|
||||
"due_date": doc.get("due_date"),
|
||||
'total_amount': grand_total,
|
||||
'outstanding_amount': outstanding_amount,
|
||||
'allocated_amount': outstanding_amount
|
||||
})
|
||||
|
||||
pe.setup_party_account_field()
|
||||
pe.set_missing_values()
|
||||
@@ -1029,6 +1085,22 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
pe.set_amounts()
|
||||
return pe
|
||||
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
references.append({
|
||||
'reference_doctype': dt,
|
||||
'reference_name': dn,
|
||||
'bill_no': doc.get('bill_no'),
|
||||
'due_date': doc.get('due_date'),
|
||||
'total_amount': grand_total,
|
||||
'outstanding_amount': outstanding_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': flt(payment_term.payment_amount - payment_term.paid_amount,
|
||||
payment_term.precision('payment_amount'))
|
||||
})
|
||||
|
||||
return references
|
||||
|
||||
def get_paid_amount(dt, dn, party_type, party, account, due_date):
|
||||
if party_type=="Customer":
|
||||
|
||||
12
erpnext/accounts/doctype/payment_entry/payment_entry_list.js
Normal file
12
erpnext/accounts/doctype/payment_entry/payment_entry_list.js
Normal file
@@ -0,0 +1,12 @@
|
||||
frappe.listview_settings['Payment Entry'] = {
|
||||
|
||||
onload: function(listview) {
|
||||
listview.page.fields_dict.party_type.get_query = function() {
|
||||
return {
|
||||
"filters": {
|
||||
"name": ["in", Object.keys(frappe.boot.party_account_types)],
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -149,6 +149,30 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
outstanding_amount = flt(frappe.db.get_value("Sales Invoice", pi.name, "outstanding_amount"))
|
||||
self.assertEqual(outstanding_amount, 0)
|
||||
|
||||
def test_payment_entry_against_payment_terms(self):
|
||||
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
create_payment_terms_template()
|
||||
si.payment_terms_template = 'Test Receivable Template'
|
||||
|
||||
si.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 18
|
||||
})
|
||||
si.save()
|
||||
|
||||
si.submit()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
|
||||
pe.submit()
|
||||
si.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].payment_term, 'Basic Amount Receivable')
|
||||
self.assertEqual(pe.references[1].payment_term, 'Tax Receivable')
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
|
||||
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
|
||||
|
||||
def test_payment_against_sales_invoice_to_check_status(self):
|
||||
si = create_sales_invoice(customer="_Test Customer USD", debit_to="_Test Receivable USD - _TC",
|
||||
@@ -609,4 +633,38 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
self.assertEqual(expected_party_account_balance, party_account_balance)
|
||||
|
||||
accounts_settings.allow_cost_center_in_entry_of_bs_account = 0
|
||||
accounts_settings.save()
|
||||
accounts_settings.save()
|
||||
|
||||
def create_payment_terms_template():
|
||||
|
||||
create_payment_term('Basic Amount Receivable')
|
||||
create_payment_term('Tax Receivable')
|
||||
|
||||
if not frappe.db.exists('Payment Terms Template', 'Test Receivable Template'):
|
||||
payment_term_template = frappe.get_doc({
|
||||
'doctype': 'Payment Terms Template',
|
||||
'template_name': 'Test Receivable Template',
|
||||
'allocate_payment_based_on_payment_terms': 1,
|
||||
'terms': [{
|
||||
'doctype': 'Payment Terms Template Detail',
|
||||
'payment_term': 'Basic Amount Receivable',
|
||||
'invoice_portion': 84.746,
|
||||
'credit_days_based_on': 'Day(s) after invoice date',
|
||||
'credit_days': 1
|
||||
},
|
||||
{
|
||||
'doctype': 'Payment Terms Template Detail',
|
||||
'payment_term': 'Tax Receivable',
|
||||
'invoice_portion': 15.254,
|
||||
'credit_days_based_on': 'Day(s) after invoice date',
|
||||
'credit_days': 2
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
|
||||
def create_payment_term(name):
|
||||
if not frappe.db.exists('Payment Term', name):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Payment Term',
|
||||
'payment_term_name': name
|
||||
}).insert()
|
||||
|
||||
@@ -1,343 +1,107 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"actions": [],
|
||||
"creation": "2016-06-01 16:55:32.196722",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"due_date",
|
||||
"bill_no",
|
||||
"payment_term",
|
||||
"column_break_4",
|
||||
"total_amount",
|
||||
"outstanding_amount",
|
||||
"allocated_amount",
|
||||
"exchange_rate"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "reference_doctype",
|
||||
"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": "Type",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "DocType",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "reference_doctype",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "due_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": "Due Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "bill_no",
|
||||
"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": "Supplier Invoice No",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"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": "Total Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Float",
|
||||
"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": "Outstanding",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Float",
|
||||
"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": "Allocated",
|
||||
"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
|
||||
"label": "Allocated"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:(doc.reference_doctype=='Purchase Invoice')",
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "exchange_rate",
|
||||
"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": "Exchange Rate",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-05-01 13:24:56.586677",
|
||||
"links": [],
|
||||
"modified": "2020-03-13 12:07:19.362539",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -80,7 +80,7 @@ def make_journal_entry(doc, supplier, mode_of_payment=None):
|
||||
paid_amt += d.amount
|
||||
|
||||
je.append('accounts', {
|
||||
'account': doc.references[0].account,
|
||||
'account': doc.account,
|
||||
'credit_in_account_currency': paid_amt
|
||||
})
|
||||
|
||||
|
||||
@@ -326,7 +326,7 @@ def make_payment_request(**args):
|
||||
"reference_doctype": args.dt,
|
||||
"reference_name": args.dn,
|
||||
"party_type": args.get("party_type") or "Customer",
|
||||
"party": args.get("party") or ref_doc.customer,
|
||||
"party": args.get("party") or ref_doc.get("customer"),
|
||||
"bank_account": bank_account
|
||||
})
|
||||
|
||||
@@ -420,7 +420,7 @@ def make_payment_entry(docname):
|
||||
|
||||
def update_payment_req_status(doc, method):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_reference_details
|
||||
|
||||
|
||||
for ref in doc.references:
|
||||
payment_request_name = frappe.db.get_value("Payment Request",
|
||||
{"reference_doctype": ref.reference_doctype, "reference_name": ref.reference_name,
|
||||
@@ -430,7 +430,7 @@ def update_payment_req_status(doc, method):
|
||||
ref_details = get_reference_details(ref.reference_doctype, ref.reference_name, doc.party_account_currency)
|
||||
pay_req_doc = frappe.get_doc('Payment Request', payment_request_name)
|
||||
status = pay_req_doc.status
|
||||
|
||||
|
||||
if status != "Paid" and not ref_details.outstanding_amount:
|
||||
status = 'Paid'
|
||||
elif status != "Partially Paid" and ref_details.outstanding_amount != ref_details.total_amount:
|
||||
|
||||
@@ -1,243 +1,82 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:38:00.080575",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-08-10 15:38:00.080575",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"description",
|
||||
"due_date",
|
||||
"invoice_portion",
|
||||
"payment_amount",
|
||||
"mode_of_payment",
|
||||
"paid_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"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": "Payment Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"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
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_from": "",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"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": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "due_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Due Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "due_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fetch_from": "",
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Percent",
|
||||
"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": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"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
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Percent",
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Portion",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "payment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"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": "Payment Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "currency",
|
||||
"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
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "payment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Amount",
|
||||
"options": "currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"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": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"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
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-09-06 17:35:44.580209",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 17:58:24.729526",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,164 +1,84 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:template_name",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:34:28.058054",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:template_name",
|
||||
"creation": "2017-08-10 15:34:28.058054",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"template_name",
|
||||
"allocate_payment_based_on_payment_terms",
|
||||
"terms"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "template_name",
|
||||
"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": "Template Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "template_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Template Name",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "terms",
|
||||
"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": "Payment Terms",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Terms Template Detail",
|
||||
"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
|
||||
"fieldname": "terms",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Terms",
|
||||
"options": "Payment Terms Template Detail",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If this checkbox is checked, paid amount will be splitted and allocated as per the amounts in payment schedule against each payment term",
|
||||
"fieldname": "allocate_payment_based_on_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allocate Payment Based On Payment Terms"
|
||||
}
|
||||
],
|
||||
"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-01-24 11:13:31.158613",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-04-01 15:35:18.112619",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template",
|
||||
"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": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"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": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"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": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -7,6 +7,8 @@ from frappe.utils import flt
|
||||
from frappe import _
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (get_accounting_dimensions,
|
||||
get_dimension_filters)
|
||||
|
||||
class PeriodClosingVoucher(AccountsController):
|
||||
def validate(self):
|
||||
@@ -49,7 +51,15 @@ class PeriodClosingVoucher(AccountsController):
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
net_pl_balance = 0
|
||||
pl_accounts = self.get_pl_balances()
|
||||
dimension_fields = ['t1.cost_center']
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
for dimension in accounting_dimensions:
|
||||
dimension_fields.append('t1.{0}'.format(dimension))
|
||||
|
||||
dimension_filters, default_dimensions = get_dimension_filters()
|
||||
|
||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
||||
|
||||
for acc in pl_accounts:
|
||||
if flt(acc.balance_in_company_currency):
|
||||
@@ -65,34 +75,41 @@ class PeriodClosingVoucher(AccountsController):
|
||||
if flt(acc.balance_in_account_currency) > 0 else 0,
|
||||
"credit": abs(flt(acc.balance_in_company_currency)) \
|
||||
if flt(acc.balance_in_company_currency) > 0 else 0
|
||||
}))
|
||||
}, item=acc))
|
||||
|
||||
net_pl_balance += flt(acc.balance_in_company_currency)
|
||||
|
||||
if net_pl_balance:
|
||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"cost_center": cost_center
|
||||
}))
|
||||
})
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
gl_entry.update({
|
||||
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
||||
})
|
||||
|
||||
gl_entries.append(gl_entry)
|
||||
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def get_pl_balances(self):
|
||||
def get_pl_balances(self, dimension_fields):
|
||||
"""Get balance for pl accounts"""
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
t1.account, t1.cost_center, t2.account_currency,
|
||||
t1.account, t2.account_currency, {dimension_fields},
|
||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
|
||||
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
|
||||
from `tabGL Entry` t1, `tabAccount` t2
|
||||
where t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||
and t2.docstatus < 2 and t2.company = %s
|
||||
and t1.posting_date between %s and %s
|
||||
group by t1.account, t1.cost_center
|
||||
""", (self.company, self.get("year_start_date"), self.posting_date), as_dict=1)
|
||||
group by t1.account, {dimension_fields}
|
||||
""".format(dimension_fields = ', '.join(dimension_fields)), (self.company, self.get("year_start_date"), self.posting_date), as_dict=1)
|
||||
|
||||
@@ -15,12 +15,12 @@ class TestPOSProfile(unittest.TestCase):
|
||||
pos_profile = get_pos_profile("_Test Company") or {}
|
||||
if pos_profile:
|
||||
doc = frappe.get_doc("POS Profile", pos_profile.get("name"))
|
||||
doc.append('item_groups', {'item_group': '_Test Item Group'})
|
||||
doc.append('customer_groups', {'customer_group': '_Test Customer Group'})
|
||||
doc.set('item_groups', [{'item_group': '_Test Item Group'}])
|
||||
doc.set('customer_groups', [{'customer_group': '_Test Customer Group'}])
|
||||
doc.save()
|
||||
items = get_items_list(doc, doc.company)
|
||||
customers = get_customers_list(doc)
|
||||
|
||||
|
||||
products_count = frappe.db.sql(""" select count(name) from tabItem where item_group = '_Test Item Group'""", as_list=1)
|
||||
customers_count = frappe.db.sql(""" select count(name) from tabCustomer where customer_group = '_Test Customer Group'""")
|
||||
|
||||
|
||||
@@ -103,7 +103,7 @@ class PricingRule(Document):
|
||||
self.same_item = 1
|
||||
|
||||
def validate_max_discount(self):
|
||||
if self.rate_or_discount == "Discount Percentage" and self.items:
|
||||
if self.rate_or_discount == "Discount Percentage" and self.get("items"):
|
||||
for d in self.items:
|
||||
max_discount = frappe.get_cached_value("Item", d.item_code, "max_discount")
|
||||
if max_discount and flt(self.discount_percentage) > flt(max_discount):
|
||||
@@ -241,6 +241,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
||||
item_details.update({
|
||||
'apply_rule_on_other_items': json.dumps(pricing_rule.apply_rule_on_other_items),
|
||||
'price_or_product_discount': pricing_rule.price_or_product_discount,
|
||||
'apply_rule_on': (frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||
if pricing_rule.apply_rule_on_other else frappe.scrub(pricing_rule.get('apply_on')))
|
||||
})
|
||||
|
||||
@@ -4,13 +4,19 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, copy, json
|
||||
from frappe import throw, _
|
||||
|
||||
import copy
|
||||
import json
|
||||
|
||||
from six import string_types
|
||||
from frappe.utils import flt, cint, get_datetime, get_link_to_form, today
|
||||
|
||||
import frappe
|
||||
from erpnext.setup.doctype.item_group.item_group import get_child_item_groups
|
||||
from erpnext.stock.doctype.warehouse.warehouse import get_child_warehouses
|
||||
from erpnext.stock.get_item_details import get_conversion_factor
|
||||
from frappe import _, throw
|
||||
from frappe.utils import cint, flt, get_datetime, get_link_to_form, getdate, today
|
||||
|
||||
|
||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||
|
||||
@@ -330,9 +336,9 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
|
||||
if pr_doc.mixed_conditions:
|
||||
amt = args.get('qty') * args.get("price_list_rate")
|
||||
if args.get("item_code") != row.get("item_code"):
|
||||
amt = row.get('qty') * (row.get("price_list_rate") or args.get("rate"))
|
||||
amt = flt(row.get('qty')) * flt(row.get("price_list_rate") or args.get("rate"))
|
||||
|
||||
sum_qty += row.get("stock_qty") or args.get("stock_qty") or args.get("qty")
|
||||
sum_qty += flt(row.get("stock_qty")) or flt(args.get("stock_qty")) or flt(args.get("qty"))
|
||||
sum_amt += amt
|
||||
|
||||
if pr_doc.is_cumulative:
|
||||
@@ -502,18 +508,16 @@ def get_pricing_rule_items(pr_doc):
|
||||
return list(set(apply_on_data))
|
||||
|
||||
def validate_coupon_code(coupon_name):
|
||||
from frappe.utils import today,getdate
|
||||
coupon=frappe.get_doc("Coupon Code",coupon_name)
|
||||
coupon = frappe.get_doc("Coupon Code", coupon_name)
|
||||
|
||||
if coupon.valid_from:
|
||||
if coupon.valid_from > getdate(today()) :
|
||||
frappe.throw(_("Sorry,coupon code validity has not started"))
|
||||
if coupon.valid_from > getdate(today()):
|
||||
frappe.throw(_("Sorry, this coupon code's validity has not started"))
|
||||
elif coupon.valid_upto:
|
||||
if coupon.valid_upto < getdate(today()) :
|
||||
frappe.throw(_("Sorry,coupon code validity has expired"))
|
||||
elif coupon.used>=coupon.maximum_use:
|
||||
frappe.throw(_("Sorry,coupon code are exhausted"))
|
||||
else:
|
||||
return
|
||||
if coupon.valid_upto < getdate(today()):
|
||||
frappe.throw(_("Sorry, this coupon code's validity has expired"))
|
||||
elif coupon.used >= coupon.maximum_use:
|
||||
frappe.throw(_("Sorry, this coupon code is no longer valid"))
|
||||
|
||||
def update_coupon_code_count(coupon_name,transaction_type):
|
||||
coupon=frappe.get_doc("Coupon Code",coupon_name)
|
||||
|
||||
@@ -261,12 +261,25 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
price_list: this.frm.doc.buying_price_list
|
||||
}, function() {
|
||||
me.apply_pricing_rule();
|
||||
|
||||
me.frm.doc.apply_tds = me.frm.supplier_tds ? 1 : 0;
|
||||
me.frm.doc.tax_withholding_category = me.frm.supplier_tds;
|
||||
me.frm.set_df_property("apply_tds", "read_only", me.frm.supplier_tds ? 0 : 1);
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", me.frm.supplier_tds ? 0 : 1);
|
||||
})
|
||||
},
|
||||
|
||||
apply_tds: function(frm) {
|
||||
var me = this;
|
||||
|
||||
if (!me.frm.doc.apply_tds) {
|
||||
me.frm.set_value("tax_withholding_category", '');
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
|
||||
} else {
|
||||
me.frm.set_value("tax_withholding_category", me.frm.supplier_tds);
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", 0);
|
||||
}
|
||||
},
|
||||
|
||||
credit_to: function() {
|
||||
var me = this;
|
||||
if(this.frm.doc.credit_to) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"is_paid",
|
||||
"is_return",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"column_break1",
|
||||
"company",
|
||||
"posting_date",
|
||||
@@ -73,9 +74,9 @@
|
||||
"base_total",
|
||||
"base_net_total",
|
||||
"column_break_28",
|
||||
"total_net_weight",
|
||||
"total",
|
||||
"net_total",
|
||||
"total_net_weight",
|
||||
"taxes_section",
|
||||
"tax_category",
|
||||
"column_break_49",
|
||||
@@ -1284,13 +1285,21 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"print_hide": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2019-12-30 19:13:49.610538",
|
||||
"modified": "2020-04-18 13:05:25.199832",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -452,7 +452,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_item_gl_entries(self, gl_entries):
|
||||
@@ -802,7 +802,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
@@ -813,7 +813,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"credit_in_account_currency": self.base_paid_amount \
|
||||
if bank_account_currency==self.company_currency else self.paid_amount,
|
||||
"cost_center": self.cost_center
|
||||
}, bank_account_currency)
|
||||
}, bank_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_write_off_gl_entry(self, gl_entries):
|
||||
@@ -834,7 +834,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@@ -844,7 +844,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"credit_in_account_currency": self.base_write_off_amount \
|
||||
if write_off_account_currency==self.company_currency else self.write_off_amount,
|
||||
"cost_center": self.cost_center or self.write_off_cost_center
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
@@ -863,8 +863,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"debit_in_account_currency": self.rounding_adjustment,
|
||||
"debit": self.base_rounding_adjustment,
|
||||
"cost_center": self.cost_center or round_off_cost_center,
|
||||
}
|
||||
))
|
||||
}, item=self))
|
||||
|
||||
def on_cancel(self):
|
||||
super(PurchaseInvoice, self).on_cancel()
|
||||
@@ -959,7 +958,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.apply_tds:
|
||||
return
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(self)
|
||||
tax_withholding_details = get_party_tax_withholding_details(self, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
@@ -981,6 +980,40 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def set_status(self, update=False, status=None, update_modified=True):
|
||||
if self.is_new():
|
||||
if self.get('amended_from'):
|
||||
self.status = 'Draft'
|
||||
return
|
||||
|
||||
precision = self.precision("outstanding_amount")
|
||||
outstanding_amount = flt(self.outstanding_amount, precision)
|
||||
due_date = getdate(self.due_date)
|
||||
nowdate = getdate()
|
||||
|
||||
if not status:
|
||||
if self.docstatus == 2:
|
||||
status = "Cancelled"
|
||||
elif self.docstatus == 1:
|
||||
if outstanding_amount > 0 and due_date < nowdate:
|
||||
self.status = "Overdue"
|
||||
elif outstanding_amount > 0 and due_date >= nowdate:
|
||||
self.status = "Unpaid"
|
||||
#Check if outstanding amount is 0 due to debit note issued against invoice
|
||||
elif outstanding_amount <= 0 and self.is_return == 0 and frappe.db.get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1}):
|
||||
self.status = "Debit Note Issued"
|
||||
elif self.is_return == 1:
|
||||
self.status = "Return"
|
||||
elif outstanding_amount<=0:
|
||||
self.status = "Paid"
|
||||
else:
|
||||
self.status = "Submitted"
|
||||
else:
|
||||
self.status = "Draft"
|
||||
|
||||
if update:
|
||||
self.db_set('status', self.status, update_modified = update_modified)
|
||||
|
||||
def get_list_context(context=None):
|
||||
from erpnext.controllers.website_list_for_contact import get_list_context
|
||||
|
||||
@@ -86,6 +86,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pe.submit()
|
||||
|
||||
pi_doc = frappe.get_doc('Purchase Invoice', pi_doc.name)
|
||||
pi_doc.load_from_db()
|
||||
self.assertTrue(pi_doc.status, "Paid")
|
||||
|
||||
self.assertRaises(frappe.LinkExistsError, pi_doc.cancel)
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
@@ -203,7 +205,9 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertTrue(pi.status, "Unpaid")
|
||||
self.check_gle_for_pi(pi.name)
|
||||
|
||||
def check_gle_for_pi(self, pi):
|
||||
@@ -234,6 +238,9 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi = frappe.copy_doc(test_records[0])
|
||||
pi.insert()
|
||||
pi.load_from_db()
|
||||
|
||||
self.assertTrue(pi.status, "Draft")
|
||||
pi.naming_series = 'TEST-'
|
||||
|
||||
self.assertRaises(frappe.CannotChangeConstantError, pi.save)
|
||||
@@ -248,6 +255,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
pi.get("taxes").pop(1)
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
pi.load_from_db()
|
||||
self.assertTrue(pi.status, "Unpaid")
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
@@ -599,6 +608,11 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
# return entry
|
||||
pi1 = make_purchase_invoice(is_return=1, return_against=pi.name, qty=-2, rate=50, update_stock=1)
|
||||
|
||||
pi.load_from_db()
|
||||
self.assertTrue(pi.status, "Debit Note Issued")
|
||||
pi1.load_from_db()
|
||||
self.assertTrue(pi1.status, "Return")
|
||||
|
||||
actual_qty_2 = get_qty_after_transaction()
|
||||
self.assertEqual(actual_qty_1 - 2, actual_qty_2)
|
||||
|
||||
@@ -771,6 +785,8 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import get_outstanding_amount
|
||||
|
||||
pi = make_purchase_invoice(item_code = "_Test Item", qty = (5 * -1), rate=500, is_return = 1)
|
||||
pi.load_from_db()
|
||||
self.assertTrue(pi.status, "Return")
|
||||
|
||||
outstanding_amount = get_outstanding_amount(pi.doctype,
|
||||
pi.name, "Creditors - _TC", pi.supplier, "Supplier")
|
||||
|
||||
@@ -180,14 +180,16 @@ def get_items_list(pos_profile, company):
|
||||
i.name, i.item_code, i.item_name, i.description, i.item_group, i.has_batch_no,
|
||||
i.has_serial_no, i.is_stock_item, i.brand, i.stock_uom, i.image,
|
||||
id.expense_account, id.selling_cost_center, id.default_warehouse,
|
||||
i.sales_uom, c.conversion_factor
|
||||
i.sales_uom, c.conversion_factor, it.item_tax_template, it.valid_from
|
||||
from
|
||||
`tabItem` i
|
||||
left join `tabItem Default` id on id.parent = i.name and id.company = %s
|
||||
left join `tabItem Tax` it on it.parent = i.name
|
||||
left join `tabUOM Conversion Detail` c on i.name = c.parent and i.sales_uom = c.uom
|
||||
where
|
||||
i.disabled = 0 and i.has_variants = 0 and i.is_sales_item = 1
|
||||
{cond}
|
||||
group by i.item_code
|
||||
""".format(cond=cond), tuple([company] + args_list), as_dict=1)
|
||||
|
||||
|
||||
|
||||
@@ -25,18 +25,26 @@ frappe.ui.form.on("Sales Invoice", {
|
||||
if(frm.doc.docstatus == 1 && !frm.is_dirty()
|
||||
&& !frm.doc.is_return && !frm.doc.ewaybill) {
|
||||
|
||||
frm.add_custom_button('e-Way Bill JSON', () => {
|
||||
var w = window.open(
|
||||
frappe.urllib.get_full_url(
|
||||
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
|
||||
+ "dt=" + encodeURIComponent(frm.doc.doctype)
|
||||
+ "&dn=" + encodeURIComponent(frm.doc.name)
|
||||
)
|
||||
);
|
||||
if (!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||
}
|
||||
}, __("Make"));
|
||||
frm.add_custom_button('E-Way Bill JSON', () => {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': frm.doc.doctype,
|
||||
'dn': [frm.doc.name]
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.utils.download_ewb_json',
|
||||
data: r.message,
|
||||
docname: frm.doc.name
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}, __("Create"));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -16,17 +16,23 @@ frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
|
||||
}
|
||||
}
|
||||
|
||||
var w = window.open(
|
||||
frappe.urllib.get_full_url(
|
||||
"/api/method/erpnext.regional.india.utils.generate_ewb_json?"
|
||||
+ "dt=" + encodeURIComponent(doclist.doctype)
|
||||
+ "&dn=" + encodeURIComponent(docnames)
|
||||
)
|
||||
);
|
||||
if (!w) {
|
||||
frappe.msgprint(__("Please enable pop-ups")); return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.utils.generate_ewb_json',
|
||||
args: {
|
||||
'dt': doclist.doctype,
|
||||
'dn': docnames
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.utils.download_ewb_json',
|
||||
data: r.message,
|
||||
docname: docnames
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
doclist.page.add_actions_menu_item(__('Generate e-Way Bill JSON'), action, false);
|
||||
|
||||
@@ -32,6 +32,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
me.frm.script_manager.trigger("is_pos");
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
erpnext.queries.setup_warehouse_query(this.frm);
|
||||
},
|
||||
|
||||
refresh: function(doc, dt, dn) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-24 19:29:05",
|
||||
@@ -74,9 +75,9 @@
|
||||
"base_total",
|
||||
"base_net_total",
|
||||
"column_break_32",
|
||||
"total_net_weight",
|
||||
"total",
|
||||
"net_total",
|
||||
"total_net_weight",
|
||||
"taxes_section",
|
||||
"taxes_and_charges",
|
||||
"column_break_38",
|
||||
@@ -148,9 +149,9 @@
|
||||
"edit_printing_settings",
|
||||
"letter_head",
|
||||
"group_same_items",
|
||||
"language",
|
||||
"column_break_84",
|
||||
"select_print_heading",
|
||||
"column_break_84",
|
||||
"language",
|
||||
"more_information",
|
||||
"inter_company_invoice_reference",
|
||||
"customer_group",
|
||||
@@ -1568,7 +1569,8 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 181,
|
||||
"is_submittable": 1,
|
||||
"modified": "2020-02-10 04:57:11.221180",
|
||||
"links": [],
|
||||
"modified": "2020-04-29 13:37:09.355300",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -785,7 +785,7 @@ class SalesInvoice(SellingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
@@ -802,7 +802,7 @@ class SalesInvoice(SellingController):
|
||||
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
|
||||
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))),
|
||||
"cost_center": tax.cost_center
|
||||
}, account_currency)
|
||||
}, account_currency, item=tax)
|
||||
)
|
||||
|
||||
def make_item_gl_entries(self, gl_entries):
|
||||
@@ -822,7 +822,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle))
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
asset.set_status("Sold" if self.docstatus==1 else None)
|
||||
@@ -860,7 +860,7 @@ class SalesInvoice(SellingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@@ -869,7 +869,7 @@ class SalesInvoice(SellingController):
|
||||
"against": self.customer,
|
||||
"debit": self.loyalty_amount,
|
||||
"remark": "Loyalty Points redeemed by the customer"
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
def make_pos_gl_entries(self, gl_entries):
|
||||
@@ -890,7 +890,7 @@ class SalesInvoice(SellingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
payment_mode_account_currency = get_account_currency(payment_mode.account)
|
||||
@@ -903,7 +903,7 @@ class SalesInvoice(SellingController):
|
||||
if payment_mode_account_currency==self.company_currency \
|
||||
else payment_mode.amount,
|
||||
"cost_center": self.cost_center
|
||||
}, payment_mode_account_currency)
|
||||
}, payment_mode_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_gle_for_change_amount(self, gl_entries):
|
||||
@@ -921,7 +921,7 @@ class SalesInvoice(SellingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) and self.return_against else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
@@ -930,7 +930,7 @@ class SalesInvoice(SellingController):
|
||||
"against": self.customer,
|
||||
"credit": self.base_change_amount,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
else:
|
||||
frappe.throw(_("Select change amount account"), title="Mandatory Field")
|
||||
@@ -954,7 +954,7 @@ class SalesInvoice(SellingController):
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center
|
||||
}, self.party_account_currency)
|
||||
}, self.party_account_currency, item=self)
|
||||
)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@@ -965,7 +965,7 @@ class SalesInvoice(SellingController):
|
||||
self.precision("base_write_off_amount")) if write_off_account_currency==self.company_currency
|
||||
else flt(self.write_off_amount, self.precision("write_off_amount"))),
|
||||
"cost_center": self.cost_center or self.write_off_cost_center or default_cost_center
|
||||
}, write_off_account_currency)
|
||||
}, write_off_account_currency, item=self)
|
||||
)
|
||||
|
||||
def make_gle_for_rounding_adjustment(self, gl_entries):
|
||||
@@ -982,8 +982,7 @@ class SalesInvoice(SellingController):
|
||||
"credit": flt(self.base_rounding_adjustment,
|
||||
self.precision("base_rounding_adjustment")),
|
||||
"cost_center": self.cost_center or round_off_cost_center,
|
||||
}
|
||||
))
|
||||
}, item=self))
|
||||
|
||||
def update_billing_status_in_dn(self, update_modified=True):
|
||||
updated_delivery_notes = []
|
||||
|
||||
@@ -1834,7 +1834,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
si.submit()
|
||||
|
||||
data = get_ewb_data("Sales Invoice", si.name)
|
||||
data = get_ewb_data("Sales Invoice", [si.name])
|
||||
|
||||
self.assertEqual(data['version'], '1.0.1118')
|
||||
self.assertEqual(data['billLists'][0]['fromGstin'], '27AAECE4835E1ZR')
|
||||
|
||||
@@ -6,23 +6,42 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import flt, getdate
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
pass
|
||||
|
||||
def get_party_tax_withholding_details(ref_doc):
|
||||
tax_withholding_category = frappe.db.get_value('Supplier', ref_doc.supplier, 'tax_withholding_category')
|
||||
def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
|
||||
|
||||
pan_no = ''
|
||||
suppliers = []
|
||||
|
||||
if not tax_withholding_category:
|
||||
tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
|
||||
|
||||
if not tax_withholding_category:
|
||||
return
|
||||
|
||||
if not pan_no:
|
||||
pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
|
||||
|
||||
# Get others suppliers with the same PAN No
|
||||
if pan_no:
|
||||
suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
|
||||
|
||||
if not suppliers:
|
||||
suppliers.append(ref_doc.supplier)
|
||||
|
||||
fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
|
||||
if not tax_details:
|
||||
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
|
||||
.format(tax_withholding_category, ref_doc.company))
|
||||
tds_amount = get_tds_amount(ref_doc, tax_details, fy)
|
||||
|
||||
tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
|
||||
tax_details, fy, ref_doc.posting_date, pan_no)
|
||||
|
||||
tax_row = get_tax_row(tax_details, tds_amount)
|
||||
|
||||
return tax_row
|
||||
@@ -51,6 +70,7 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
|
||||
|
||||
def get_tax_row(tax_details, tds_amount):
|
||||
|
||||
return {
|
||||
"category": "Total",
|
||||
"add_deduct_tax": "Deduct",
|
||||
@@ -60,25 +80,36 @@ def get_tax_row(tax_details, tds_amount):
|
||||
"tax_amount": tds_amount
|
||||
}
|
||||
|
||||
def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
|
||||
def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
|
||||
fiscal_year, year_start_date, year_end_date = fiscal_year_details
|
||||
tds_amount = 0
|
||||
tds_deducted = 0
|
||||
|
||||
def _get_tds(amount):
|
||||
def _get_tds(amount, rate):
|
||||
if amount <= 0:
|
||||
return 0
|
||||
|
||||
return amount * tax_details.rate / 100
|
||||
return amount * rate / 100
|
||||
|
||||
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
|
||||
{
|
||||
'pan_no': pan_no,
|
||||
'fiscal_year': fiscal_year
|
||||
}, 'name')
|
||||
ldc = ''
|
||||
|
||||
if ldc_name:
|
||||
ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
|
||||
|
||||
entries = frappe.db.sql("""
|
||||
select voucher_no, credit
|
||||
from `tabGL Entry`
|
||||
where party=%s and fiscal_year=%s and credit > 0
|
||||
""", (ref_doc.supplier, fiscal_year), as_dict=1)
|
||||
where company = %s and
|
||||
party in %s and fiscal_year=%s and credit > 0
|
||||
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
advance_vouchers = get_advance_vouchers(ref_doc.supplier, fiscal_year)
|
||||
advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
|
||||
|
||||
tds_vouchers = vouchers + advance_vouchers
|
||||
|
||||
@@ -93,7 +124,20 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
|
||||
tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
|
||||
|
||||
if tds_deducted:
|
||||
tds_amount = _get_tds(ref_doc.net_total)
|
||||
if ldc:
|
||||
limit_consumed = frappe.db.get_value('Purchase Invoice',
|
||||
{
|
||||
'supplier': ('in', suppliers),
|
||||
'apply_tds': 1,
|
||||
'docstatus': 1
|
||||
}, 'sum(net_total)')
|
||||
|
||||
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
|
||||
ldc.certificate_limit):
|
||||
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
else:
|
||||
tds_amount = _get_tds(net_total, tax_details.rate)
|
||||
else:
|
||||
supplier_credit_amount = frappe.get_all('Purchase Invoice Item',
|
||||
fields = ['sum(net_amount)'],
|
||||
@@ -106,43 +150,79 @@ def get_tds_amount(ref_doc, tax_details, fiscal_year_details):
|
||||
fields = ['sum(credit_in_account_currency)'],
|
||||
filters = {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
'party': ref_doc.supplier,
|
||||
'party': ('in', suppliers),
|
||||
'reference_type': ('not in', ['Purchase Invoice'])
|
||||
}, as_list=1)
|
||||
|
||||
supplier_credit_amount += (jv_supplier_credit_amt[0][0]
|
||||
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
|
||||
|
||||
supplier_credit_amount += ref_doc.net_total
|
||||
supplier_credit_amount += net_total
|
||||
|
||||
debit_note_amount = get_debit_note_amount(ref_doc.supplier, year_start_date, year_end_date)
|
||||
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
|
||||
supplier_credit_amount -= debit_note_amount
|
||||
|
||||
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
|
||||
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
|
||||
tds_amount = _get_tds(supplier_credit_amount)
|
||||
|
||||
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
|
||||
ldc.certificate_limit):
|
||||
tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
|
||||
tax_details)
|
||||
else:
|
||||
tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_advance_vouchers(supplier, fiscal_year=None, company=None, from_date=None, to_date=None):
|
||||
def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
|
||||
condition = "fiscal_year=%s" % fiscal_year
|
||||
|
||||
if company:
|
||||
condition += "and company =%s" % (company)
|
||||
if from_date and to_date:
|
||||
condition = "company=%s and posting_date between %s and %s" % (company, from_date, to_date)
|
||||
condition += "and posting_date between %s and %s" % (company, from_date, to_date)
|
||||
|
||||
## Appending the same supplier again if length of suppliers list is 1
|
||||
## since tuple of single element list contains None, For example ('Test Supplier 1', )
|
||||
## and the below query fails
|
||||
if len(suppliers) == 1:
|
||||
suppliers.append(suppliers[0])
|
||||
|
||||
return frappe.db.sql_list("""
|
||||
select distinct voucher_no
|
||||
from `tabGL Entry`
|
||||
where party=%s and %s and debit > 0
|
||||
""", (supplier, condition)) or []
|
||||
where party in %s and %s and debit > 0
|
||||
""", (tuple(suppliers), condition)) or []
|
||||
|
||||
def get_debit_note_amount(supplier, year_start_date, year_end_date, company=None):
|
||||
condition = ""
|
||||
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
||||
condition = "and 1=1"
|
||||
if company:
|
||||
condition = " and company=%s " % company
|
||||
|
||||
if len(suppliers) == 1:
|
||||
suppliers.append(suppliers[0])
|
||||
|
||||
return flt(frappe.db.sql("""
|
||||
select abs(sum(net_total))
|
||||
from `tabPurchase Invoice`
|
||||
where supplier=%s %s and is_return=1 and docstatus=1
|
||||
and posting_date between %s and %s
|
||||
""", (supplier, condition, year_start_date, year_end_date)))
|
||||
where supplier in %s and is_return=1 and docstatus=1
|
||||
and posting_date between %s and %s %s
|
||||
""", (tuple(suppliers), year_start_date, year_end_date, condition)))
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if current_amount < (certificate_limit - deducted_amount):
|
||||
return current_amount * rate/100
|
||||
else:
|
||||
ltds_amount = (certificate_limit - deducted_amount)
|
||||
tds_amount = current_amount - ltds_amount
|
||||
|
||||
return ltds_amount * rate/100 + tds_amount * tax_details.rate/100
|
||||
|
||||
def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount, current_amount, certificate_limit):
|
||||
valid = False
|
||||
|
||||
if ((getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and
|
||||
certificate_limit > deducted_amount):
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
@@ -1458,7 +1458,36 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({
|
||||
this.child.batch_no = this.item_batch_no[this.child.item_code];
|
||||
this.child.serial_no = (this.item_serial_no[this.child.item_code]
|
||||
? this.item_serial_no[this.child.item_code][0] : '');
|
||||
this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_code]);
|
||||
|
||||
const tax_template_is_valid = frappe.datetime.get_diff(frappe.datetime.now_date(), this.items[0].valid_from) > 0;
|
||||
|
||||
this.child.item_tax_template = tax_template_is_valid ? this.items[0].item_tax_template : '';
|
||||
this.child.item_tax_rate = JSON.stringify(this.tax_data[this.child.item_tax_template]);
|
||||
|
||||
if (this.child.item_tax_rate) {
|
||||
this.add_taxes_from_item_tax_template(this.child.item_tax_rate);
|
||||
}
|
||||
},
|
||||
|
||||
add_taxes_from_item_tax_template: function(item_tax_map) {
|
||||
let me = this;
|
||||
|
||||
if(item_tax_map && cint(frappe.defaults.get_default("add_taxes_from_item_tax_template"))) {
|
||||
if(typeof (item_tax_map) == "string") {
|
||||
item_tax_map = JSON.parse(item_tax_map);
|
||||
}
|
||||
|
||||
$.each(item_tax_map, function(tax, rate) {
|
||||
let found = (me.frm.doc.taxes || []).find(d => d.account_head === tax);
|
||||
if(!found) {
|
||||
let child = frappe.model.add_child(me.frm.doc, "taxes");
|
||||
child.charge_type = "On Net Total";
|
||||
child.account_head = tax;
|
||||
child.description = String(tax);
|
||||
child.rate = rate;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
update_paid_amount_status: function (update_paid_amount) {
|
||||
|
||||
@@ -163,7 +163,7 @@ def get_default_price_list(party):
|
||||
def set_price_list(party_details, party, party_type, given_price_list, pos=None):
|
||||
# price list
|
||||
price_list = get_permitted_documents('Price List')
|
||||
|
||||
|
||||
# if there is only one permitted document based on user permissions, set it
|
||||
if price_list and len(price_list) == 1:
|
||||
price_list = price_list[0]
|
||||
@@ -468,23 +468,25 @@ def get_timeline_data(doctype, name):
|
||||
from frappe.desk.form.load import get_communication_data
|
||||
|
||||
out = {}
|
||||
fields = 'date(creation), count(name)'
|
||||
fields = 'creation, count(*)'
|
||||
after = add_years(None, -1).strftime('%Y-%m-%d')
|
||||
group_by='group by date(creation)'
|
||||
group_by='group by Date(creation)'
|
||||
|
||||
data = get_communication_data(doctype, name, after=after, group_by='group by date(creation)',
|
||||
fields='date(C.creation) as creation, count(C.name)',as_dict=False)
|
||||
data = get_communication_data(doctype, name, after=after, group_by='group by creation',
|
||||
fields='C.creation as creation, count(C.name)',as_dict=False)
|
||||
|
||||
# fetch and append data from Activity Log
|
||||
data += frappe.db.sql("""select {fields}
|
||||
from `tabActivity Log`
|
||||
where (reference_doctype="{doctype}" and reference_name="{name}")
|
||||
or (timeline_doctype in ("{doctype}") and timeline_name="{name}")
|
||||
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name="{name}")
|
||||
where (reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||
or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
|
||||
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
|
||||
and status!='Success' and creation > {after}
|
||||
{group_by} order by creation desc
|
||||
""".format(doctype=frappe.db.escape(doctype), name=frappe.db.escape(name), fields=fields,
|
||||
group_by=group_by, after=after), as_dict=False)
|
||||
""".format(fields=fields, group_by=group_by, after=after), {
|
||||
"doctype": doctype,
|
||||
"name": name
|
||||
}, as_dict=False)
|
||||
|
||||
timeline_items = dict(data)
|
||||
|
||||
@@ -603,10 +605,12 @@ def get_party_shipping_address(doctype, name):
|
||||
else:
|
||||
return ''
|
||||
|
||||
def get_partywise_advanced_payment_amount(party_type, posting_date = None):
|
||||
def get_partywise_advanced_payment_amount(party_type, posting_date = None, company=None):
|
||||
cond = "1=1"
|
||||
if posting_date:
|
||||
cond = "posting_date <= '{0}'".format(posting_date)
|
||||
if company:
|
||||
cond += "and company = '{0}'".format(company)
|
||||
|
||||
data = frappe.db.sql(""" SELECT party, sum({0}) as amount
|
||||
FROM `tabGL Entry`
|
||||
|
||||
@@ -344,26 +344,28 @@ class ReceivablePayableReport(object):
|
||||
def allocate_outstanding_based_on_payment_terms(self, row):
|
||||
self.get_payment_terms(row)
|
||||
for term in row.payment_terms:
|
||||
term.outstanding = term.invoiced
|
||||
|
||||
# update "paid" and "oustanding" for this term
|
||||
self.allocate_closing_to_term(row, term, 'paid')
|
||||
if not term.paid:
|
||||
self.allocate_closing_to_term(row, term, 'paid')
|
||||
|
||||
# update "credit_note" and "oustanding" for this term
|
||||
if term.outstanding:
|
||||
self.allocate_closing_to_term(row, term, 'credit_note')
|
||||
|
||||
row.payment_terms = sorted(row.payment_terms, key=lambda x: x['due_date'])
|
||||
|
||||
def get_payment_terms(self, row):
|
||||
# build payment_terms for row
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
si.name = %s
|
||||
order by ps.due_date
|
||||
order by ps.paid_amount desc, due_date
|
||||
""".format(row.voucher_type), row.voucher_no, as_dict = 1)
|
||||
|
||||
|
||||
@@ -389,11 +391,14 @@ class ReceivablePayableReport(object):
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"paid": 0.0,
|
||||
"paid": d.paid_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": 0.0
|
||||
"outstanding": invoiced - d.paid_amount
|
||||
}))
|
||||
|
||||
if d.paid_amount:
|
||||
row['paid'] -= d.paid_amount
|
||||
|
||||
def allocate_closing_to_term(self, row, term, key):
|
||||
if row[key]:
|
||||
if row[key] > term.outstanding:
|
||||
|
||||
@@ -33,7 +33,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
self.get_party_total(args)
|
||||
|
||||
party_advance_amount = get_partywise_advanced_payment_amount(self.party_type,
|
||||
self.filters.report_date) or {}
|
||||
self.filters.report_date, self.filters.company) or {}
|
||||
|
||||
for party, party_dict in iteritems(self.party_total):
|
||||
if party_dict.outstanding == 0:
|
||||
|
||||
@@ -2,16 +2,19 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import datetime
|
||||
from six import iteritems
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt
|
||||
from frappe.utils import formatdate
|
||||
from frappe.utils import flt, formatdate
|
||||
|
||||
from erpnext.controllers.trends import get_period_date_ranges, get_period_month_ranges
|
||||
|
||||
from six import iteritems
|
||||
from pprint import pprint
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters: filters = {}
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
columns = get_columns(filters)
|
||||
if filters.get("budget_against_filter"):
|
||||
@@ -43,20 +46,25 @@ def execute(filters=None):
|
||||
|
||||
period_data[0] += last_total
|
||||
|
||||
if(filters.get("show_cumulative")):
|
||||
if filters.get("show_cumulative"):
|
||||
last_total = period_data[0] - period_data[1]
|
||||
|
||||
period_data[2] = period_data[0] - period_data[1]
|
||||
row += period_data
|
||||
totals[2] = totals[0] - totals[1]
|
||||
if filters["period"] != "Yearly" :
|
||||
if filters["period"] != "Yearly":
|
||||
row += totals
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = [_(filters.get("budget_against")) + ":Link/%s:150"%(filters.get("budget_against")), _("Account") + ":Link/Account:150"]
|
||||
columns = [
|
||||
_(filters.get("budget_against"))
|
||||
+ ":Link/%s:150" % (filters.get("budget_against")),
|
||||
_("Account") + ":Link/Account:150"
|
||||
]
|
||||
|
||||
group_months = False if filters["period"] == "Monthly" else True
|
||||
|
||||
@@ -65,84 +73,181 @@ def get_columns(filters):
|
||||
for year in fiscal_year:
|
||||
for from_date, to_date in get_period_date_ranges(filters["period"], year[0]):
|
||||
if filters["period"] == "Yearly":
|
||||
labels = [_("Budget") + " " + str(year[0]), _("Actual ") + " " + str(year[0]), _("Variance ") + " " + str(year[0])]
|
||||
labels = [
|
||||
_("Budget") + " " + str(year[0]),
|
||||
_("Actual ") + " " + str(year[0]),
|
||||
_("Variance ") + " " + str(year[0])
|
||||
]
|
||||
for label in labels:
|
||||
columns.append(label+":Float:150")
|
||||
columns.append(label + ":Float:150")
|
||||
else:
|
||||
for label in [_("Budget") + " (%s)" + " " + str(year[0]), _("Actual") + " (%s)" + " " + str(year[0]), _("Variance") + " (%s)" + " " + str(year[0])]:
|
||||
for label in [
|
||||
_("Budget") + " (%s)" + " " + str(year[0]),
|
||||
_("Actual") + " (%s)" + " " + str(year[0]),
|
||||
_("Variance") + " (%s)" + " " + str(year[0])
|
||||
]:
|
||||
if group_months:
|
||||
label = label % (formatdate(from_date, format_string="MMM") + "-" + formatdate(to_date, format_string="MMM"))
|
||||
label = label % (
|
||||
formatdate(from_date, format_string="MMM")
|
||||
+ "-"
|
||||
+ formatdate(to_date, format_string="MMM")
|
||||
)
|
||||
else:
|
||||
label = label % formatdate(from_date, format_string="MMM")
|
||||
|
||||
columns.append(label+":Float:150")
|
||||
columns.append(label + ":Float:150")
|
||||
|
||||
if filters["period"] != "Yearly" :
|
||||
return columns + [_("Total Budget") + ":Float:150", _("Total Actual") + ":Float:150",
|
||||
_("Total Variance") + ":Float:150"]
|
||||
if filters["period"] != "Yearly":
|
||||
return columns + [
|
||||
_("Total Budget") + ":Float:150",
|
||||
_("Total Actual") + ":Float:150",
|
||||
_("Total Variance") + ":Float:150"
|
||||
]
|
||||
else:
|
||||
return columns
|
||||
|
||||
|
||||
def get_cost_centers(filters):
|
||||
cond = "and 1=1"
|
||||
order_by = ""
|
||||
if filters.get("budget_against") == "Cost Center":
|
||||
cond = "order by lft"
|
||||
order_by = "order by lft"
|
||||
|
||||
if filters.get("budget_against") in ["Cost Center", "Project"]:
|
||||
return frappe.db.sql_list("""select name from `tab{tab}` where company=%s
|
||||
{cond}""".format(tab=filters.get("budget_against"), cond=cond), filters.get("company"))
|
||||
return frappe.db.sql_list(
|
||||
"""
|
||||
select
|
||||
name
|
||||
from
|
||||
`tab{tab}`
|
||||
where
|
||||
company = %s
|
||||
{order_by}
|
||||
""".format(tab=filters.get("budget_against"), order_by=order_by),
|
||||
filters.get("company"))
|
||||
else:
|
||||
return frappe.db.sql_list("""select name from `tab{tab}`""".format(tab=filters.get("budget_against"))) #nosec
|
||||
return frappe.db.sql_list(
|
||||
"""
|
||||
select
|
||||
name
|
||||
from
|
||||
`tab{tab}`
|
||||
""".format(tab=filters.get("budget_against"))) # nosec
|
||||
|
||||
#Get dimension & target details
|
||||
|
||||
# Get dimension & target details
|
||||
def get_dimension_target_details(filters):
|
||||
budget_against = frappe.scrub(filters.get("budget_against"))
|
||||
cond = ""
|
||||
if filters.get("budget_against_filter"):
|
||||
cond += " and b.{budget_against} in (%s)".format(budget_against = \
|
||||
frappe.scrub(filters.get('budget_against'))) % ', '.join(['%s']* len(filters.get('budget_against_filter')))
|
||||
cond += """ and b.{budget_against} in (%s)""".format(
|
||||
budget_against=budget_against) % ", ".join(["%s"] * len(filters.get("budget_against_filter")))
|
||||
|
||||
return frappe.db.sql("""
|
||||
select b.{budget_against} as budget_against, b.monthly_distribution, ba.account, ba.budget_amount,b.fiscal_year
|
||||
from `tabBudget` b, `tabBudget Account` ba
|
||||
where b.name=ba.parent and b.docstatus = 1 and b.fiscal_year between %s and %s
|
||||
and b.budget_against = %s and b.company=%s {cond} order by b.fiscal_year
|
||||
""".format(budget_against=filters.get("budget_against").replace(" ", "_").lower(), cond=cond),
|
||||
tuple([filters.from_fiscal_year,filters.to_fiscal_year,filters.budget_against, filters.company] + filters.get('budget_against_filter')),
|
||||
as_dict=True)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
b.{budget_against} as budget_against,
|
||||
b.monthly_distribution,
|
||||
ba.account,
|
||||
ba.budget_amount,
|
||||
b.fiscal_year
|
||||
from
|
||||
`tabBudget` b,
|
||||
`tabBudget Account` ba
|
||||
where
|
||||
b.name = ba.parent
|
||||
and b.docstatus = 1
|
||||
and b.fiscal_year between %s and %s
|
||||
and b.budget_against = %s
|
||||
and b.company = %s
|
||||
{cond}
|
||||
order by
|
||||
b.fiscal_year
|
||||
""".format(
|
||||
budget_against=budget_against,
|
||||
cond=cond,
|
||||
),
|
||||
tuple(
|
||||
[
|
||||
filters.from_fiscal_year,
|
||||
filters.to_fiscal_year,
|
||||
filters.budget_against,
|
||||
filters.company,
|
||||
]
|
||||
+ filters.get("budget_against_filter")
|
||||
), as_dict=True)
|
||||
|
||||
|
||||
#Get target distribution details of accounts of cost center
|
||||
# Get target distribution details of accounts of cost center
|
||||
def get_target_distribution_details(filters):
|
||||
target_details = {}
|
||||
for d in frappe.db.sql("""select md.name, mdp.month, mdp.percentage_allocation
|
||||
from `tabMonthly Distribution Percentage` mdp, `tabMonthly Distribution` md
|
||||
where mdp.parent=md.name and md.fiscal_year between %s and %s order by md.fiscal_year""",(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
|
||||
target_details.setdefault(d.name, {}).setdefault(d.month, flt(d.percentage_allocation))
|
||||
for d in frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
md.name,
|
||||
mdp.month,
|
||||
mdp.percentage_allocation
|
||||
from
|
||||
`tabMonthly Distribution Percentage` mdp,
|
||||
`tabMonthly Distribution` md
|
||||
where
|
||||
mdp.parent = md.name
|
||||
and md.fiscal_year between %s and %s
|
||||
order by
|
||||
md.fiscal_year
|
||||
""",
|
||||
(filters.from_fiscal_year, filters.to_fiscal_year), as_dict=1):
|
||||
target_details.setdefault(d.name, {}).setdefault(
|
||||
d.month, flt(d.percentage_allocation)
|
||||
)
|
||||
|
||||
return target_details
|
||||
|
||||
#Get actual details from gl entry
|
||||
# Get actual details from gl entry
|
||||
def get_actual_details(name, filters):
|
||||
cond = "1=1"
|
||||
budget_against=filters.get("budget_against").replace(" ", "_").lower()
|
||||
budget_against = frappe.scrub(filters.get("budget_against"))
|
||||
cond = ""
|
||||
|
||||
if filters.get("budget_against") == "Cost Center":
|
||||
cc_lft, cc_rgt = frappe.db.get_value("Cost Center", name, ["lft", "rgt"])
|
||||
cond = "lft>='{lft}' and rgt<='{rgt}'".format(lft = cc_lft, rgt=cc_rgt)
|
||||
cond = """
|
||||
and lft >= "{lft}"
|
||||
and rgt <= "{rgt}"
|
||||
""".format(lft=cc_lft, rgt=cc_rgt)
|
||||
|
||||
ac_details = frappe.db.sql("""select gl.account, gl.debit, gl.credit,gl.fiscal_year,
|
||||
MONTHNAME(gl.posting_date) as month_name, b.{budget_against} as budget_against
|
||||
from `tabGL Entry` gl, `tabBudget Account` ba, `tabBudget` b
|
||||
where
|
||||
b.name = ba.parent
|
||||
and b.docstatus = 1
|
||||
and ba.account=gl.account
|
||||
and b.{budget_against} = gl.{budget_against}
|
||||
and gl.fiscal_year between %s and %s
|
||||
and b.{budget_against}=%s
|
||||
and exists(select name from `tab{tab}` where name=gl.{budget_against} and {cond}) group by gl.name order by gl.fiscal_year
|
||||
""".format(tab = filters.budget_against, budget_against = budget_against, cond = cond,from_year=filters.from_fiscal_year,to_year=filters.to_fiscal_year),
|
||||
(filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
|
||||
ac_details = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
gl.account,
|
||||
gl.debit,
|
||||
gl.credit,
|
||||
gl.fiscal_year,
|
||||
MONTHNAME(gl.posting_date) as month_name,
|
||||
b.{budget_against} as budget_against
|
||||
from
|
||||
`tabGL Entry` gl,
|
||||
`tabBudget Account` ba,
|
||||
`tabBudget` b
|
||||
where
|
||||
b.name = ba.parent
|
||||
and b.docstatus = 1
|
||||
and ba.account=gl.account
|
||||
and b.{budget_against} = gl.{budget_against}
|
||||
and gl.fiscal_year between %s and %s
|
||||
and b.{budget_against} = %s
|
||||
and exists(
|
||||
select
|
||||
name
|
||||
from
|
||||
`tab{tab}`
|
||||
where
|
||||
name = gl.{budget_against}
|
||||
{cond}
|
||||
)
|
||||
group by
|
||||
gl.name
|
||||
order by gl.fiscal_year
|
||||
""".format(tab=filters.budget_against, budget_against=budget_against, cond=cond),
|
||||
(filters.from_fiscal_year, filters.to_fiscal_year, name), as_dict=1)
|
||||
|
||||
cc_actual_details = {}
|
||||
for d in ac_details:
|
||||
@@ -151,7 +256,6 @@ def get_actual_details(name, filters):
|
||||
return cc_actual_details
|
||||
|
||||
def get_dimension_account_month_map(filters):
|
||||
import datetime
|
||||
dimension_target_details = get_dimension_target_details(filters)
|
||||
tdd = get_target_distribution_details(filters)
|
||||
|
||||
@@ -161,28 +265,43 @@ def get_dimension_account_month_map(filters):
|
||||
actual_details = get_actual_details(ccd.budget_against, filters)
|
||||
|
||||
for month_id in range(1, 13):
|
||||
month = datetime.date(2013, month_id, 1).strftime('%B')
|
||||
cam_map.setdefault(ccd.budget_against, {}).setdefault(ccd.account, {}).setdefault(ccd.fiscal_year,{})\
|
||||
.setdefault(month, frappe._dict({
|
||||
"target": 0.0, "actual": 0.0
|
||||
}))
|
||||
month = datetime.date(2013, month_id, 1).strftime("%B")
|
||||
cam_map.setdefault(ccd.budget_against, {}).setdefault(
|
||||
ccd.account, {}
|
||||
).setdefault(ccd.fiscal_year, {}).setdefault(
|
||||
month, frappe._dict({"target": 0.0, "actual": 0.0})
|
||||
)
|
||||
|
||||
tav_dict = cam_map[ccd.budget_against][ccd.account][ccd.fiscal_year][month]
|
||||
month_percentage = tdd.get(ccd.monthly_distribution, {}).get(month, 0) \
|
||||
if ccd.monthly_distribution else 100.0/12
|
||||
month_percentage = (
|
||||
tdd.get(ccd.monthly_distribution, {}).get(month, 0)
|
||||
if ccd.monthly_distribution
|
||||
else 100.0 / 12
|
||||
)
|
||||
|
||||
tav_dict.target = flt(ccd.budget_amount) * month_percentage / 100
|
||||
|
||||
for ad in actual_details.get(ccd.account, []):
|
||||
if ad.month_name == month:
|
||||
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
|
||||
if ad.month_name == month and ad.fiscal_year == ccd.fiscal_year:
|
||||
tav_dict.actual += flt(ad.debit) - flt(ad.credit)
|
||||
|
||||
return cam_map
|
||||
|
||||
|
||||
def get_fiscal_years(filters):
|
||||
|
||||
fiscal_year = frappe.db.sql("""select name from `tabFiscal Year` where
|
||||
name between %(from_fiscal_year)s and %(to_fiscal_year)s""",
|
||||
{'from_fiscal_year': filters["from_fiscal_year"], 'to_fiscal_year': filters["to_fiscal_year"]})
|
||||
fiscal_year = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name
|
||||
from
|
||||
`tabFiscal Year`
|
||||
where
|
||||
name between %(from_fiscal_year)s and %(to_fiscal_year)s
|
||||
""",
|
||||
{
|
||||
"from_fiscal_year": filters["from_fiscal_year"],
|
||||
"to_fiscal_year": filters["to_fiscal_year"]
|
||||
})
|
||||
|
||||
return fiscal_year
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<h4 class="text-center">
|
||||
{% if (filters.party_name) { %}
|
||||
{%= filters.party_name %}
|
||||
{% } else if (filters.party) { %}
|
||||
{% } else if (filters.party && filters.party.length) { %}
|
||||
{%= filters.party %}
|
||||
{% } else if (filters.account) { %}
|
||||
{%= filters.account %}
|
||||
|
||||
@@ -293,6 +293,9 @@ def get_accountwise_gle(filters, gl_entries, gle_map):
|
||||
data[key].debit_in_account_currency += flt(gle.debit_in_account_currency)
|
||||
data[key].credit_in_account_currency += flt(gle.credit_in_account_currency)
|
||||
|
||||
if data[key].against_voucher:
|
||||
data[key].against_voucher += ', ' + gle.against_voucher
|
||||
|
||||
from_date, to_date = getdate(filters.from_date), getdate(filters.to_date)
|
||||
for gle in gl_entries:
|
||||
if (gle.posting_date < from_date or
|
||||
|
||||
@@ -8,7 +8,6 @@ from frappe.utils import flt
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
import copy
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
filters.periodicity, filters.accumulated_values, filters.company)
|
||||
@@ -27,17 +26,26 @@ def execute(filters=None):
|
||||
|
||||
|
||||
gross_income = get_revenue(income, period_list)
|
||||
|
||||
gross_expense = get_revenue(expense, period_list)
|
||||
|
||||
if(len(gross_income)==0 and len(gross_expense)== 0):
|
||||
data.append({"account_name": "'" + _("Nothing is included in gross") + "'",
|
||||
"account": "'" + _("Nothing is included in gross") + "'"})
|
||||
|
||||
data.append({
|
||||
"account_name": "'" + _("Nothing is included in gross") + "'",
|
||||
"account": "'" + _("Nothing is included in gross") + "'"
|
||||
})
|
||||
return columns, data
|
||||
|
||||
data.append({"account_name": "'" + _("Included in Gross Profit") + "'",
|
||||
"account": "'" + _("Included in Gross Profit") + "'"})
|
||||
|
||||
# to avoid error eg: gross_income[0] : list index out of range
|
||||
if not gross_income:
|
||||
gross_income = [{}]
|
||||
if not gross_expense:
|
||||
gross_expense = [{}]
|
||||
|
||||
data.append({
|
||||
"account_name": "'" + _("Included in Gross Profit") + "'",
|
||||
"account": "'" + _("Included in Gross Profit") + "'"
|
||||
})
|
||||
|
||||
data.append({})
|
||||
data.extend(gross_income or [])
|
||||
@@ -111,7 +119,6 @@ def set_total(node, value, complete_list, totals):
|
||||
|
||||
|
||||
def get_profit(gross_income, gross_expense, period_list, company, profit_type, currency=None, consolidated=False):
|
||||
|
||||
profit_loss = {
|
||||
"account_name": "'" + _(profit_type) + "'",
|
||||
"account": "'" + _(profit_type) + "'",
|
||||
@@ -123,7 +130,9 @@ def get_profit(gross_income, gross_expense, period_list, company, profit_type, c
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
profit_loss[key] = flt(gross_income[0].get(key, 0)) - flt(gross_expense[0].get(key, 0))
|
||||
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
|
||||
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
|
||||
profit_loss[key] = gross_income_for_period - gross_expense_for_period
|
||||
|
||||
if profit_loss[key]:
|
||||
has_value=True
|
||||
@@ -143,12 +152,18 @@ def get_net_profit(non_gross_income, gross_income, gross_expense, non_gross_expe
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
total_income = flt(gross_income[0].get(key, 0)) + flt(non_gross_income[0].get(key, 0))
|
||||
total_expense = flt(gross_expense[0].get(key, 0)) + flt(non_gross_expense[0].get(key, 0))
|
||||
gross_income_for_period = flt(gross_income[0].get(key, 0)) if gross_income else 0
|
||||
non_gross_income_for_period = flt(non_gross_income[0].get(key, 0)) if non_gross_income else 0
|
||||
|
||||
gross_expense_for_period = flt(gross_expense[0].get(key, 0)) if gross_expense else 0
|
||||
non_gross_expense_for_period = flt(non_gross_expense[0].get(key, 0)) if non_gross_expense else 0
|
||||
|
||||
total_income = gross_income_for_period + non_gross_income_for_period
|
||||
total_expense = gross_expense_for_period + non_gross_expense_for_period
|
||||
profit_loss[key] = flt(total_income) - flt(total_expense)
|
||||
|
||||
if profit_loss[key]:
|
||||
has_value=True
|
||||
|
||||
if has_value:
|
||||
return profit_loss
|
||||
return profit_loss
|
||||
@@ -55,27 +55,27 @@ def get_columns(group_wise_columns, filters):
|
||||
columns = []
|
||||
column_map = frappe._dict({
|
||||
"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
|
||||
"posting_date": _("Posting Date") + ":Date",
|
||||
"posting_time": _("Posting Time"),
|
||||
"item_code": _("Item Code") + ":Link/Item",
|
||||
"item_name": _("Item Name"),
|
||||
"item_group": _("Item Group") + ":Link/Item Group",
|
||||
"brand": _("Brand"),
|
||||
"description": _("Description"),
|
||||
"warehouse": _("Warehouse") + ":Link/Warehouse",
|
||||
"qty": _("Qty") + ":Float",
|
||||
"base_rate": _("Avg. Selling Rate") + ":Currency/currency",
|
||||
"buying_rate": _("Valuation Rate") + ":Currency/currency",
|
||||
"base_amount": _("Selling Amount") + ":Currency/currency",
|
||||
"buying_amount": _("Buying Amount") + ":Currency/currency",
|
||||
"gross_profit": _("Gross Profit") + ":Currency/currency",
|
||||
"gross_profit_percent": _("Gross Profit %") + ":Percent",
|
||||
"project": _("Project") + ":Link/Project",
|
||||
"posting_date": _("Posting Date") + ":Date:100",
|
||||
"posting_time": _("Posting Time") + ":Data:100",
|
||||
"item_code": _("Item Code") + ":Link/Item:100",
|
||||
"item_name": _("Item Name") + ":Data:100",
|
||||
"item_group": _("Item Group") + ":Link/Item Group:100",
|
||||
"brand": _("Brand") + ":Link/Brand:100",
|
||||
"description": _("Description") +":Data:100",
|
||||
"warehouse": _("Warehouse") + ":Link/Warehouse:100",
|
||||
"qty": _("Qty") + ":Float:80",
|
||||
"base_rate": _("Avg. Selling Rate") + ":Currency/currency:100",
|
||||
"buying_rate": _("Valuation Rate") + ":Currency/currency:100",
|
||||
"base_amount": _("Selling Amount") + ":Currency/currency:100",
|
||||
"buying_amount": _("Buying Amount") + ":Currency/currency:100",
|
||||
"gross_profit": _("Gross Profit") + ":Currency/currency:100",
|
||||
"gross_profit_percent": _("Gross Profit %") + ":Percent:100",
|
||||
"project": _("Project") + ":Link/Project:100",
|
||||
"sales_person": _("Sales person"),
|
||||
"allocated_amount": _("Allocated Amount") + ":Currency/currency",
|
||||
"customer": _("Customer") + ":Link/Customer",
|
||||
"customer_group": _("Customer Group") + ":Link/Customer Group",
|
||||
"territory": _("Territory") + ":Link/Territory"
|
||||
"allocated_amount": _("Allocated Amount") + ":Currency/currency:100",
|
||||
"customer": _("Customer") + ":Link/Customer:100",
|
||||
"customer_group": _("Customer Group") + ":Link/Customer Group:100",
|
||||
"territory": _("Territory") + ":Link/Territory:100"
|
||||
})
|
||||
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
@@ -85,7 +85,8 @@ def get_columns(group_wise_columns, filters):
|
||||
"fieldname": "currency",
|
||||
"label" : _("Currency"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Currency"
|
||||
"options": "Currency",
|
||||
"hidden": 1
|
||||
})
|
||||
|
||||
return columns
|
||||
@@ -277,7 +278,7 @@ class GrossProfitGenerator(object):
|
||||
from `tabPurchase Invoice Item` a
|
||||
where a.item_code = %s and a.docstatus=1
|
||||
and modified <= %s
|
||||
order by a.modified desc limit 1""", (item_code,self.filters.to_date))
|
||||
order by a.modified desc limit 1""", (item_code, self.filters.to_date))
|
||||
else:
|
||||
last_purchase_rate = frappe.db.sql("""
|
||||
select (a.base_rate / a.conversion_factor)
|
||||
|
||||
@@ -102,7 +102,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
data.append(row)
|
||||
|
||||
if filters.get('group_by'):
|
||||
if filters.get('group_by') and item_list:
|
||||
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
|
||||
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
|
||||
data.append(total_row)
|
||||
|
||||
@@ -111,7 +111,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
data.append(row)
|
||||
|
||||
if filters.get('group_by'):
|
||||
if filters.get('group_by') and item_list:
|
||||
total_row = total_row_map.get(prev_group_by_value or d.get('item_name'))
|
||||
total_row['percent_gt'] = flt(total_row['total']/grand_total * 100)
|
||||
data.append(total_row)
|
||||
|
||||
@@ -44,9 +44,14 @@ def get_result(filters):
|
||||
out = []
|
||||
for supplier in filters.supplier:
|
||||
tds = frappe.get_doc("Tax Withholding Category", supplier.tax_withholding_category)
|
||||
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year][0]
|
||||
rate = [d.tax_withholding_rate for d in tds.rates if d.fiscal_year == filters.fiscal_year]
|
||||
|
||||
if rate:
|
||||
rate = rate[0]
|
||||
|
||||
try:
|
||||
account = [d.account for d in tds.accounts if d.company == filters.company][0]
|
||||
|
||||
except IndexError:
|
||||
account = []
|
||||
total_invoiced_amount, tds_deducted = get_invoice_and_tds_amount(supplier.name, account,
|
||||
@@ -76,7 +81,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
|
||||
supplier_credit_amount = flt(sum([d.credit for d in entries]))
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
vouchers += get_advance_vouchers(supplier, company=company,
|
||||
vouchers += get_advance_vouchers([supplier], company=company,
|
||||
from_date=from_date, to_date=to_date)
|
||||
|
||||
tds_deducted = 0
|
||||
@@ -89,7 +94,7 @@ def get_invoice_and_tds_amount(supplier, account, company, from_date, to_date):
|
||||
""".format(', '.join(["'%s'" % d for d in vouchers])),
|
||||
(account, from_date, to_date, company))[0][0])
|
||||
|
||||
debit_note_amount = get_debit_note_amount(supplier, from_date, to_date, company=company)
|
||||
debit_note_amount = get_debit_note_amount([supplier], from_date, to_date, company=company)
|
||||
|
||||
total_invoiced_amount = supplier_credit_amount + tds_deducted - debit_note_amount
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
},
|
||||
{
|
||||
"fieldname":"cost_center",
|
||||
"fieldname": "cost_center",
|
||||
"label": __("Cost Center"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
@@ -61,7 +61,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname":"finance_book",
|
||||
"fieldname": "project",
|
||||
"label": __("Project"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
@@ -97,7 +103,7 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Trial Balance"].filters.splice(5, 0 ,{
|
||||
frappe.query_reports["Trial Balance"].filters.splice(6, 0 ,{
|
||||
"fieldname": dimension["fieldname"],
|
||||
"label": __(dimension["label"]),
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -69,6 +69,10 @@ def get_data(filters):
|
||||
gl_entries_by_account = {}
|
||||
|
||||
opening_balances = get_opening_balances(filters)
|
||||
|
||||
#add filter inside list so that the query in financial_statements.py doesn't break
|
||||
filters.project = [filters.project]
|
||||
|
||||
set_gl_entries_by_account(filters.company, filters.from_date,
|
||||
filters.to_date, min_lft, max_rgt, filters, gl_entries_by_account, ignore_closing_entries=not flt(filters.with_period_closing_entry))
|
||||
|
||||
@@ -102,6 +106,9 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
additional_conditions += """ and cost_center in (select name from `tabCost Center`
|
||||
where lft >= %s and rgt <= %s)""" % (lft, rgt)
|
||||
|
||||
if filters.project:
|
||||
additional_conditions += " and project = %(project)s"
|
||||
|
||||
if filters.finance_book:
|
||||
fb_conditions = " AND finance_book = %(finance_book)s"
|
||||
if filters.include_default_book_entries:
|
||||
@@ -116,6 +123,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
"from_date": filters.from_date,
|
||||
"report_type": report_type,
|
||||
"year_start_date": filters.year_start_date,
|
||||
"project": filters.project,
|
||||
"finance_book": filters.finance_book,
|
||||
"company_fb": frappe.db.get_value("Company", filters.company, 'default_finance_book')
|
||||
}
|
||||
|
||||
@@ -817,48 +817,37 @@ def create_payment_gateway_account(gateway):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_number_field(doctype_name, name, field_name, number_value, company):
|
||||
def update_cost_center(docname, cost_center_name, cost_center_number, company):
|
||||
'''
|
||||
doctype_name = Name of the DocType
|
||||
name = Docname being referred
|
||||
field_name = Name of the field thats holding the 'number' attribute
|
||||
number_value = Numeric value entered in field_name
|
||||
|
||||
Stores the number entered in the dialog to the DocType's field.
|
||||
|
||||
Renames the document by adding the number as a prefix to the current name and updates
|
||||
all transaction where it was present.
|
||||
'''
|
||||
doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name")
|
||||
validate_field_number("Cost Center", docname, cost_center_number, company, "cost_center_number")
|
||||
|
||||
validate_field_number(doctype_name, name, number_value, company, field_name)
|
||||
if cost_center_number:
|
||||
frappe.db.set_value("Cost Center", docname, "cost_center_number", cost_center_number.strip())
|
||||
else:
|
||||
frappe.db.set_value("Cost Center", docname, "cost_center_number", "")
|
||||
|
||||
frappe.db.set_value(doctype_name, name, field_name, number_value)
|
||||
frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip())
|
||||
|
||||
if doc_title[0].isdigit():
|
||||
separator = " - " if " - " in doc_title else " "
|
||||
doc_title = doc_title.split(separator, 1)[1]
|
||||
|
||||
frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title)
|
||||
|
||||
new_name = get_autoname_with_number(number_value, doc_title, name, company)
|
||||
|
||||
if name != new_name:
|
||||
frappe.rename_doc(doctype_name, name, new_name)
|
||||
new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
|
||||
if docname != new_name:
|
||||
frappe.rename_doc("Cost Center", docname, new_name, force=1)
|
||||
return new_name
|
||||
|
||||
def validate_field_number(doctype_name, name, number_value, company, field_name):
|
||||
def validate_field_number(doctype_name, docname, number_value, company, field_name):
|
||||
''' Validate if the number entered isn't already assigned to some other document. '''
|
||||
if number_value:
|
||||
filters = {field_name: number_value, "name": ["!=", docname]}
|
||||
if company:
|
||||
doctype_with_same_number = frappe.db.get_value(doctype_name,
|
||||
{field_name: number_value, "company": company, "name": ["!=", name]})
|
||||
else:
|
||||
doctype_with_same_number = frappe.db.get_value(doctype_name,
|
||||
{field_name: number_value, "name": ["!=", name]})
|
||||
filters["company"] = company
|
||||
|
||||
doctype_with_same_number = frappe.db.get_value(doctype_name, filters)
|
||||
|
||||
if doctype_with_same_number:
|
||||
frappe.throw(_("{0} Number {1} already used in account {2}")
|
||||
.format(doctype_name, number_value, doctype_with_same_number))
|
||||
frappe.throw(_("{0} Number {1} is already used in {2} {3}")
|
||||
.format(doctype_name, number_value, doctype_name.lower(), doctype_with_same_number))
|
||||
|
||||
def get_autoname_with_number(number_value, doc_title, name, company):
|
||||
''' append title with prefix as number and suffix as company's abbreviation separated by '-' '''
|
||||
|
||||
@@ -22,6 +22,7 @@ class Asset(AccountsController):
|
||||
self.validate_item()
|
||||
self.set_missing_values()
|
||||
self.prepare_depreciation_data()
|
||||
self.validate_gross_and_purchase_amount()
|
||||
if self.get("schedules"):
|
||||
self.validate_expected_value_after_useful_life()
|
||||
|
||||
@@ -31,7 +32,7 @@ class Asset(AccountsController):
|
||||
self.validate_in_use_date()
|
||||
self.set_status()
|
||||
self.make_asset_movement()
|
||||
if not self.booked_fixed_asset and is_cwip_accounting_enabled(self.asset_category):
|
||||
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||
self.make_gl_entries()
|
||||
|
||||
def before_cancel(self):
|
||||
@@ -123,6 +124,12 @@ class Asset(AccountsController):
|
||||
|
||||
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
||||
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
||||
|
||||
def validate_gross_and_purchase_amount(self):
|
||||
if self.gross_purchase_amount and self.gross_purchase_amount != self.purchase_receipt_amount:
|
||||
frappe.throw(_("Gross Purchase Amount should be {} to purchase amount of one single Asset. {}\
|
||||
Please do not book expense of multiple assets against one single Asset.")
|
||||
.format(frappe.bold("equal"), "<br>"), title=_("Invalid Gross Purchase Amount"))
|
||||
|
||||
def cancel_auto_gen_movement(self):
|
||||
movements = frappe.db.sql(
|
||||
@@ -447,18 +454,55 @@ class Asset(AccountsController):
|
||||
for d in self.get('finance_books'):
|
||||
if d.finance_book == self.default_finance_book:
|
||||
return cint(d.idx) - 1
|
||||
|
||||
def validate_make_gl_entry(self):
|
||||
purchase_document = self.get_purchase_document()
|
||||
asset_bought_with_invoice = purchase_document == self.purchase_invoice
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
cwip_enabled = is_cwip_accounting_enabled(self.asset_category)
|
||||
# check if expense already has been booked in case of cwip was enabled after purchasing asset
|
||||
expense_booked = False
|
||||
cwip_booked = False
|
||||
|
||||
if asset_bought_with_invoice:
|
||||
expense_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, fixed_asset_account), as_dict=1)
|
||||
else:
|
||||
cwip_booked = frappe.db.sql("""SELECT name FROM `tabGL Entry` WHERE voucher_no = %s and account = %s""",
|
||||
(purchase_document, cwip_account), as_dict=1)
|
||||
|
||||
if cwip_enabled and (expense_booked or not cwip_booked):
|
||||
# if expense has already booked from invoice or cwip is booked from receipt
|
||||
return False
|
||||
elif not cwip_enabled and (not expense_booked or cwip_booked):
|
||||
# if cwip is disabled but expense hasn't been booked yet
|
||||
return True
|
||||
elif cwip_enabled:
|
||||
# default condition
|
||||
return True
|
||||
|
||||
def get_purchase_document(self):
|
||||
asset_bought_with_invoice = self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')
|
||||
purchase_document = self.purchase_invoice if asset_bought_with_invoice else self.purchase_receipt
|
||||
|
||||
return purchase_document
|
||||
|
||||
def get_asset_accounts(self):
|
||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||
asset_category = self.asset_category, company = self.company)
|
||||
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
self.name, self.asset_category, self.company)
|
||||
|
||||
return fixed_asset_account, cwip_account
|
||||
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
|
||||
if ((self.purchase_receipt \
|
||||
or (self.purchase_invoice and frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')))
|
||||
and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||
fixed_asset_account = get_asset_category_account('fixed_asset_account', asset=self.name,
|
||||
asset_category = self.asset_category, company = self.company)
|
||||
purchase_document = self.get_purchase_document()
|
||||
fixed_asset_account, cwip_account = self.get_asset_accounts()
|
||||
|
||||
cwip_account = get_asset_account("capital_work_in_progress_account",
|
||||
self.name, self.asset_category, self.company)
|
||||
if (purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()):
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": cwip_account,
|
||||
@@ -468,7 +512,7 @@ class Asset(AccountsController):
|
||||
"credit": self.purchase_receipt_amount,
|
||||
"credit_in_account_currency": self.purchase_receipt_amount,
|
||||
"cost_center": self.cost_center
|
||||
}))
|
||||
}, item=self))
|
||||
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": fixed_asset_account,
|
||||
@@ -478,7 +522,7 @@ class Asset(AccountsController):
|
||||
"debit": self.purchase_receipt_amount,
|
||||
"debit_in_account_currency": self.purchase_receipt_amount,
|
||||
"cost_center": self.cost_center
|
||||
}))
|
||||
}, item=self))
|
||||
|
||||
if gl_entries:
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
@@ -611,7 +655,7 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
|
||||
if asset:
|
||||
account = get_asset_category_account(account_name, asset=asset,
|
||||
asset_category = asset_category, company = company)
|
||||
|
||||
|
||||
if not asset and not account:
|
||||
account = get_asset_category_account(account_name, asset_category = asset_category, company = company)
|
||||
|
||||
|
||||
@@ -82,7 +82,6 @@ class TestAsset(unittest.TestCase):
|
||||
doc.set_missing_values()
|
||||
self.assertEquals(doc.items[0].is_fixed_asset, 1)
|
||||
|
||||
|
||||
def test_schedule_for_straight_line_method(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=100000.0, location="Test Location")
|
||||
@@ -564,6 +563,81 @@ class TestAsset(unittest.TestCase):
|
||||
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
def test_gle_with_cwip_toggling(self):
|
||||
# TEST: purchase an asset with cwip enabled and then disable cwip and try submitting the asset
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=1, rate=5000, do_not_submit=True, location="Test Location")
|
||||
pr.set('taxes', [{
|
||||
'category': 'Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Service Tax - _TC',
|
||||
'description': '_Test Account Service Tax',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}, {
|
||||
'category': 'Valuation and Total',
|
||||
'add_deduct_tax': 'Add',
|
||||
'charge_type': 'On Net Total',
|
||||
'account_head': '_Test Account Shipping Charges - _TC',
|
||||
'description': '_Test Account Shipping Charges',
|
||||
'cost_center': 'Main - _TC',
|
||||
'rate': 5.0
|
||||
}])
|
||||
pr.submit()
|
||||
expected_gle = (
|
||||
("Asset Received But Not Billed - _TC", 0.0, 5250.0),
|
||||
("CWIP Account - _TC", 5250.0, 0.0)
|
||||
)
|
||||
pr_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no = %s
|
||||
order by account""", pr.name)
|
||||
self.assertEqual(pr_gle, expected_gle)
|
||||
|
||||
pi = make_invoice(pr.name)
|
||||
pi.submit()
|
||||
expected_gle = (
|
||||
("_Test Account Service Tax - _TC", 250.0, 0.0),
|
||||
("_Test Account Shipping Charges - _TC", 250.0, 0.0),
|
||||
("Asset Received But Not Billed - _TC", 5250.0, 0.0),
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
("Expenses Included In Asset Valuation - _TC", 0.0, 250.0),
|
||||
)
|
||||
pi_gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||
order by account""", pi.name)
|
||||
self.assertEqual(pi_gle, expected_gle)
|
||||
|
||||
asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name, 'docstatus': 0}, 'name')
|
||||
asset_doc = frappe.get_doc('Asset', asset)
|
||||
month_end_date = get_last_day(nowdate())
|
||||
asset_doc.available_for_use_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
|
||||
self.assertEqual(asset_doc.gross_purchase_amount, 5250.0)
|
||||
asset_doc.append("finance_books", {
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 3,
|
||||
"frequency_of_depreciation": 10,
|
||||
"depreciation_start_date": month_end_date
|
||||
})
|
||||
|
||||
# disable cwip and try submitting
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 0)
|
||||
asset_doc.submit()
|
||||
# asset should have gl entries even if cwip is disabled
|
||||
expected_gle = (
|
||||
("_Test Fixed Asset - _TC", 5250.0, 0.0),
|
||||
("CWIP Account - _TC", 0.0, 5250.0)
|
||||
)
|
||||
gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Asset' and voucher_no = %s
|
||||
order by account""", asset_doc.name)
|
||||
self.assertEqual(gle, expected_gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", 1)
|
||||
|
||||
def test_expense_head(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro",
|
||||
qty=2, rate=200000.0, location="Test Location")
|
||||
@@ -599,6 +673,7 @@ def create_asset(**args):
|
||||
"purchase_date": "2015-01-01",
|
||||
"calculate_depreciation": 0,
|
||||
"gross_purchase_amount": 100000,
|
||||
"purchase_receipt_amount": 100000,
|
||||
"expected_value_after_useful_life": 10000,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"available_for_use_date": "2020-06-06",
|
||||
|
||||
@@ -11,12 +11,54 @@ from frappe.model.document import Document
|
||||
class AssetCategory(Document):
|
||||
def validate(self):
|
||||
self.validate_finance_books()
|
||||
self.validate_account_types()
|
||||
self.validate_account_currency()
|
||||
|
||||
def validate_finance_books(self):
|
||||
for d in self.finance_books:
|
||||
for field in ("Total Number of Depreciations", "Frequency of Depreciation"):
|
||||
if cint(d.get(frappe.scrub(field)))<1:
|
||||
frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError)
|
||||
|
||||
def validate_account_currency(self):
|
||||
account_types = [
|
||||
'fixed_asset_account', 'accumulated_depreciation_account', 'depreciation_expense_account', 'capital_work_in_progress_account'
|
||||
]
|
||||
invalid_accounts = []
|
||||
for d in self.accounts:
|
||||
company_currency = frappe.get_value('Company', d.get('company_name'), 'default_currency')
|
||||
for type_of_account in account_types:
|
||||
if d.get(type_of_account):
|
||||
account_currency = frappe.get_value("Account", d.get(type_of_account), "account_currency")
|
||||
if account_currency != company_currency:
|
||||
invalid_accounts.append(frappe._dict({ 'type': type_of_account, 'idx': d.idx, 'account': d.get(type_of_account) }))
|
||||
|
||||
for d in invalid_accounts:
|
||||
frappe.throw(_("Row #{}: Currency of {} - {} doesn't matches company currency.")
|
||||
.format(d.idx, frappe.bold(frappe.unscrub(d.type)), frappe.bold(d.account)),
|
||||
title=_("Invalid Account"))
|
||||
|
||||
|
||||
def validate_account_types(self):
|
||||
account_type_map = {
|
||||
'fixed_asset_account': { 'account_type': 'Fixed Asset' },
|
||||
'accumulated_depreciation_account': { 'account_type': 'Accumulated Depreciation' },
|
||||
'depreciation_expense_account': { 'root_type': 'Expense' },
|
||||
'capital_work_in_progress_account': { 'account_type': 'Capital Work in Progress' }
|
||||
}
|
||||
for d in self.accounts:
|
||||
for fieldname in account_type_map.keys():
|
||||
if d.get(fieldname):
|
||||
selected_account = d.get(fieldname)
|
||||
key_to_match = next(iter(account_type_map.get(fieldname))) # acount_type or root_type
|
||||
selected_key_type = frappe.db.get_value('Account', selected_account, key_to_match)
|
||||
expected_key_type = account_type_map[fieldname][key_to_match]
|
||||
|
||||
if selected_key_type != expected_key_type:
|
||||
frappe.throw(_("Row #{}: {} of {} should be {}. Please modify the account or select a different account.")
|
||||
.format(d.idx, frappe.unscrub(key_to_match), frappe.bold(selected_account), frappe.bold(expected_key_type)),
|
||||
title=_("Invalid Account"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_category_account(fieldname, item=None, asset=None, account=None, asset_category = None, company = None):
|
||||
|
||||
@@ -110,6 +110,7 @@ class AssetMovement(Document):
|
||||
ORDER BY
|
||||
asm.transaction_date asc
|
||||
""", (d.asset, self.company, 'Receipt'), as_dict=1)
|
||||
|
||||
if auto_gen_movement_entry and auto_gen_movement_entry[0].get('name') == self.name:
|
||||
frappe.throw(_('{0} will be cancelled automatically on asset cancellation as it was \
|
||||
auto generated for Asset {1}').format(self.name, d.asset))
|
||||
|
||||
@@ -27,15 +27,6 @@ frappe.ui.form.on("Purchase Order", {
|
||||
frm.set_indicator_formatter('item_code',
|
||||
function(doc) { return (doc.qty<=doc.received_qty) ? "green" : "orange" })
|
||||
|
||||
frm.set_query("blanket_order", "items", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"docstatus": 1
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("expense_account", "items", function() {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_expense_account",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-05-21 16:16:39",
|
||||
@@ -63,9 +64,9 @@
|
||||
"base_total",
|
||||
"base_net_total",
|
||||
"column_break_26",
|
||||
"total_net_weight",
|
||||
"total",
|
||||
"net_total",
|
||||
"total_net_weight",
|
||||
"taxes_section",
|
||||
"tax_category",
|
||||
"column_break_50",
|
||||
@@ -171,6 +172,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.supplier && doc.docstatus===0 && (!(doc.items && doc.items.length) || (doc.items.length==1 && !doc.items[0].item_code))",
|
||||
"description": "Fetch items based on Default Supplier.",
|
||||
"fieldname": "get_items_from_open_material_requests",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Items from Open Material Requests"
|
||||
@@ -1053,7 +1055,8 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"modified": "2020-01-14 18:54:39.694448",
|
||||
"links": [],
|
||||
"modified": "2020-04-17 13:04:28.185197",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -141,19 +141,18 @@ def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " AND company=%s"% frappe.db.escape(filters.get('company'))
|
||||
conditions += " AND par.company=%s" % frappe.db.escape(filters.get('company'))
|
||||
|
||||
if filters.get("cost_center") or filters.get("project"):
|
||||
conditions += """
|
||||
AND (cost_center=%s
|
||||
OR project=%s)
|
||||
"""% (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
||||
AND (child.`cost_center`=%s OR child.`project`=%s)
|
||||
""" % (frappe.db.escape(filters.get('cost_center')), frappe.db.escape(filters.get('project')))
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " AND transaction_date>=%s"% filters.get('from_date')
|
||||
conditions += " AND par.transaction_date>='%s'" % filters.get('from_date')
|
||||
|
||||
if filters.get("to_date"):
|
||||
conditions += " AND transaction_date<=%s"% filters.get('to_date')
|
||||
conditions += " AND par.transaction_date<='%s'" % filters.get('to_date')
|
||||
return conditions
|
||||
|
||||
def get_data(filters):
|
||||
@@ -162,7 +161,6 @@ def get_data(filters):
|
||||
mr_records, procurement_record_against_mr = get_mapped_mr_details(conditions)
|
||||
pr_records = get_mapped_pr_records()
|
||||
pi_records = get_mapped_pi_records()
|
||||
print(pi_records)
|
||||
|
||||
procurement_record=[]
|
||||
if procurement_record_against_mr:
|
||||
@@ -198,16 +196,16 @@ def get_mapped_mr_details(conditions):
|
||||
mr_records = {}
|
||||
mr_details = frappe.db.sql("""
|
||||
SELECT
|
||||
mr.transaction_date,
|
||||
mr.per_ordered,
|
||||
mr_item.name,
|
||||
mr_item.parent,
|
||||
mr_item.amount
|
||||
FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
|
||||
par.transaction_date,
|
||||
par.per_ordered,
|
||||
child.name,
|
||||
child.parent,
|
||||
child.amount
|
||||
FROM `tabMaterial Request` par, `tabMaterial Request Item` child
|
||||
WHERE
|
||||
mr.per_ordered>=0
|
||||
AND mr.name=mr_item.parent
|
||||
AND mr.docstatus=1
|
||||
par.per_ordered>=0
|
||||
AND par.name=child.parent
|
||||
AND par.docstatus=1
|
||||
{conditions}
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
||||
|
||||
@@ -254,29 +252,29 @@ def get_mapped_pr_records():
|
||||
def get_po_entries(conditions):
|
||||
return frappe.db.sql("""
|
||||
SELECT
|
||||
po_item.name,
|
||||
po_item.parent,
|
||||
po_item.cost_center,
|
||||
po_item.project,
|
||||
po_item.warehouse,
|
||||
po_item.material_request,
|
||||
po_item.material_request_item,
|
||||
po_item.description,
|
||||
po_item.stock_uom,
|
||||
po_item.qty,
|
||||
po_item.amount,
|
||||
po_item.base_amount,
|
||||
po_item.schedule_date,
|
||||
po.transaction_date,
|
||||
po.supplier,
|
||||
po.status,
|
||||
po.owner
|
||||
FROM `tabPurchase Order` po, `tabPurchase Order Item` po_item
|
||||
child.name,
|
||||
child.parent,
|
||||
child.cost_center,
|
||||
child.project,
|
||||
child.warehouse,
|
||||
child.material_request,
|
||||
child.material_request_item,
|
||||
child.description,
|
||||
child.stock_uom,
|
||||
child.qty,
|
||||
child.amount,
|
||||
child.base_amount,
|
||||
child.schedule_date,
|
||||
par.transaction_date,
|
||||
par.supplier,
|
||||
par.status,
|
||||
par.owner
|
||||
FROM `tabPurchase Order` par, `tabPurchase Order Item` child
|
||||
WHERE
|
||||
po.docstatus = 1
|
||||
AND po.name = po_item.parent
|
||||
AND po.status not in ("Closed","Completed","Cancelled")
|
||||
par.docstatus = 1
|
||||
AND par.name = child.parent
|
||||
AND par.status not in ("Closed","Completed","Cancelled")
|
||||
{conditions}
|
||||
GROUP BY
|
||||
po.name,po_item.item_code
|
||||
par.name, child.item_code
|
||||
""".format(conditions=conditions), as_dict=1) #nosec
|
||||
@@ -819,7 +819,7 @@ class AccountsController(TransactionBase):
|
||||
else:
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = grand_total * flt(d.invoice_portion) / 100
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
@@ -834,7 +834,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
for d in self.get("payment_schedule"):
|
||||
if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date):
|
||||
frappe.throw(_("Row {0}: Due Date cannot be before posting date").format(d.idx))
|
||||
frappe.throw(_("Row {0}: Due Date in the Payment Terms table cannot be before Posting Date").format(d.idx))
|
||||
elif d.due_date in dates:
|
||||
li.append(_("{0} in row {1}").format(d.due_date, d.idx))
|
||||
dates.append(d.due_date)
|
||||
|
||||
@@ -100,7 +100,7 @@ class BuyingController(StockController):
|
||||
for d in tax_for_valuation:
|
||||
d.category = 'Total'
|
||||
msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items'))
|
||||
|
||||
|
||||
def validate_asset_return(self):
|
||||
if self.doctype not in ['Purchase Receipt', 'Purchase Invoice'] or not self.is_return:
|
||||
return
|
||||
@@ -663,10 +663,10 @@ class BuyingController(StockController):
|
||||
for qty in range(cint(d.qty)):
|
||||
asset = self.make_asset(d)
|
||||
created_assets.append(asset)
|
||||
|
||||
|
||||
if len(created_assets) > 5:
|
||||
# dont show asset form links if more than 5 assets are created
|
||||
messages.append(_('{} Asset{} created for {}').format(len(created_assets), is_plural, frappe.bold(d.item_code)))
|
||||
messages.append(_('{} Assets created for {}').format(len(created_assets), frappe.bold(d.item_code)))
|
||||
else:
|
||||
assets_link = list(map(lambda d: frappe.utils.get_link_to_form('Asset', d), created_assets))
|
||||
assets_link = frappe.bold(','.join(assets_link))
|
||||
|
||||
@@ -8,11 +8,14 @@ from frappe.desk.reportview import get_match_cond, get_filters_cond
|
||||
from frappe.utils import nowdate, getdate
|
||||
from collections import defaultdict
|
||||
from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
from frappe.utils import unique
|
||||
|
||||
# searches for active employees
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
return frappe.db.sql("""select name, employee_name from `tabEmployee`
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabEmployee`
|
||||
where status = 'Active'
|
||||
and docstatus < 2
|
||||
and ({key} like %(txt)s
|
||||
@@ -24,6 +27,7 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
name, employee_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'fields': ", ".join(fields),
|
||||
'key': searchfield,
|
||||
'fcond': get_filters_cond(doctype, filters, conditions),
|
||||
'mcond': get_match_cond(doctype)
|
||||
@@ -34,9 +38,12 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
# searches for leads which are not converted
|
||||
|
||||
# searches for leads which are not converted
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select name, lead_name, company_name from `tabLead`
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabLead`
|
||||
where docstatus < 2
|
||||
and ifnull(status, '') != 'Converted'
|
||||
and ({key} like %(txt)s
|
||||
@@ -50,6 +57,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
name, lead_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
'fields': ", ".join(fields),
|
||||
'key': searchfield,
|
||||
'mcond':get_match_cond(doctype)
|
||||
}), {
|
||||
@@ -59,6 +67,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
|
||||
# searches for customer
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
@@ -69,13 +78,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "customer_name", "customer_group", "territory"]
|
||||
|
||||
meta = frappe.get_meta("Customer")
|
||||
searchfields = meta.get_search_fields()
|
||||
searchfields = searchfields + [f for f in [searchfield or "name", "customer_name"] \
|
||||
if not f in searchfields]
|
||||
fields = fields + [f for f in searchfields if not f in fields]
|
||||
fields = get_fields("Customer", fields)
|
||||
|
||||
fields = ", ".join(fields)
|
||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabCustomer`
|
||||
@@ -88,7 +93,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
name, customer_name
|
||||
limit %(start)s, %(page_len)s""".format(**{
|
||||
"fields": fields,
|
||||
"fields": ", ".join(fields),
|
||||
"scond": searchfields,
|
||||
"mcond": get_match_cond(doctype),
|
||||
"fcond": get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
@@ -99,6 +104,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
|
||||
# searches for supplier
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
@@ -106,7 +112,8 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = ["name", "supplier_group"]
|
||||
else:
|
||||
fields = ["name", "supplier_name", "supplier_group"]
|
||||
fields = ", ".join(fields)
|
||||
|
||||
fields = get_fields("Supplier", fields)
|
||||
|
||||
return frappe.db.sql("""select {field} from `tabSupplier`
|
||||
where docstatus < 2
|
||||
@@ -119,7 +126,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
name, supplier_name
|
||||
limit %(start)s, %(page_len)s """.format(**{
|
||||
'field': fields,
|
||||
'field': ', '.join(fields),
|
||||
'key': searchfield,
|
||||
'mcond':get_match_cond(doctype)
|
||||
}), {
|
||||
@@ -129,6 +136,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
company_currency = erpnext.get_company_currency(filters.get('company'))
|
||||
|
||||
@@ -153,6 +161,7 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return tax_accounts
|
||||
|
||||
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
conditions = []
|
||||
|
||||
@@ -214,10 +223,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
"page_len": page_len
|
||||
}, as_dict=as_dict)
|
||||
|
||||
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
|
||||
return frappe.db.sql("""select tabBOM.name, tabBOM.item
|
||||
return frappe.db.sql("""select {fields}
|
||||
from tabBOM
|
||||
where tabBOM.docstatus=1
|
||||
and tabBOM.is_active=1
|
||||
@@ -227,6 +238,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
|
||||
idx desc, name
|
||||
limit %(start)s, %(page_len)s """.format(
|
||||
fields=", ".join(fields),
|
||||
fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'),
|
||||
mcond=get_match_cond(doctype).replace('%', '%%'),
|
||||
key=searchfield),
|
||||
@@ -237,13 +249,16 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
'page_len': page_len or 20
|
||||
})
|
||||
|
||||
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
cond = ''
|
||||
if filters.get('customer'):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
ifnull(`tabProject`.customer,"")="") and""" %(frappe.db.escape(filters.get("customer")))
|
||||
|
||||
return frappe.db.sql("""select `tabProject`.name from `tabProject`
|
||||
fields = get_fields("Project", ["name"])
|
||||
|
||||
return frappe.db.sql("""select {fields} from `tabProject`
|
||||
where `tabProject`.status not in ("Completed", "Cancelled")
|
||||
and {cond} `tabProject`.name like %(txt)s {match_cond}
|
||||
order by
|
||||
@@ -251,6 +266,7 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
idx desc,
|
||||
`tabProject`.name asc
|
||||
limit {start}, {page_len}""".format(
|
||||
fields=", ".join(['`tabProject`.{0}'.format(f) for f in fields]),
|
||||
cond=cond,
|
||||
match_cond=get_match_cond(doctype),
|
||||
start=start,
|
||||
@@ -261,8 +277,10 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
|
||||
return frappe.db.sql("""
|
||||
select `tabDelivery Note`.name, `tabDelivery Note`.customer, `tabDelivery Note`.posting_date
|
||||
select %(fields)s
|
||||
from `tabDelivery Note`
|
||||
where `tabDelivery Note`.`%(key)s` like %(txt)s and
|
||||
`tabDelivery Note`.docstatus = 1
|
||||
@@ -277,6 +295,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
)
|
||||
%(mcond)s order by `tabDelivery Note`.`%(key)s` asc limit %(start)s, %(page_len)s
|
||||
""" % {
|
||||
"fields": ", ".join(["`tabDelivery Note`.{0}".format(f) for f in fields]),
|
||||
"key": searchfield,
|
||||
"fcond": get_filters_cond(doctype, filters, []),
|
||||
"mcond": get_match_cond(doctype),
|
||||
@@ -342,6 +361,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
order by expiry_date, name desc
|
||||
limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args)
|
||||
|
||||
|
||||
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list = []
|
||||
|
||||
@@ -365,6 +385,21 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
limit_start=start, limit_page_length=page_len, as_list=True)
|
||||
|
||||
|
||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql("""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
||||
where
|
||||
boi.parent = bo.name
|
||||
and boi.item_code = {item_code}
|
||||
and bo.blanket_order_type = '{blanket_order_type}'
|
||||
and bo.company = {company}
|
||||
and bo.docstatus = 1"""
|
||||
.format(item_code = frappe.db.escape(filters.get("item")),
|
||||
blanket_order_type = filters.get("blanket_order_type"),
|
||||
company = frappe.db.escape(filters.get("company"))
|
||||
))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
@@ -470,6 +505,7 @@ def get_batch_numbers(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
item_filters = [
|
||||
@@ -487,6 +523,7 @@ def item_manufacturer_query(doctype, txt, searchfield, start, page_len, filters)
|
||||
)
|
||||
return item_manufacturers
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
@@ -500,6 +537,7 @@ def get_purchase_receipts(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
query = """
|
||||
@@ -513,6 +551,7 @@ def get_purchase_invoices(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
return frappe.db.sql(query, filters)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
@@ -536,3 +575,13 @@ def get_tax_template(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
taxes = _get_item_tax_template(args, taxes, for_validate=True)
|
||||
return [(d,) for d in set(taxes)]
|
||||
|
||||
|
||||
def get_fields(doctype, fields=[]):
|
||||
meta = frappe.get_meta(doctype)
|
||||
fields.extend(meta.get_search_fields())
|
||||
|
||||
if meta.title_field and not meta.title_field.strip() in fields:
|
||||
fields.insert(1, meta.title_field.strip())
|
||||
|
||||
return unique(fields)
|
||||
|
||||
@@ -165,9 +165,9 @@ class SellingController(StockController):
|
||||
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
|
||||
|
||||
def validate_selling_price(self):
|
||||
def throw_message(item_name, rate, ref_rate_field):
|
||||
frappe.throw(_("""Selling rate for item {0} is lower than its {1}. Selling rate should be atleast {2}""")
|
||||
.format(item_name, ref_rate_field, rate))
|
||||
def throw_message(idx, item_name, rate, ref_rate_field):
|
||||
frappe.throw(_("""Row #{}: Selling rate for item {} is lower than its {}. Selling rate should be atleast {}""")
|
||||
.format(idx, item_name, ref_rate_field, rate))
|
||||
|
||||
if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
|
||||
return
|
||||
@@ -182,7 +182,7 @@ class SellingController(StockController):
|
||||
last_purchase_rate, is_stock_item = frappe.get_cached_value("Item", it.item_code, ["last_purchase_rate", "is_stock_item"])
|
||||
last_purchase_rate_in_sales_uom = last_purchase_rate / (it.conversion_factor or 1)
|
||||
if flt(it.base_rate) < flt(last_purchase_rate_in_sales_uom):
|
||||
throw_message(it.item_name, last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_purchase_rate_in_sales_uom, "last purchase rate")
|
||||
|
||||
last_valuation_rate = frappe.db.sql("""
|
||||
SELECT valuation_rate FROM `tabStock Ledger Entry` WHERE item_code = %s
|
||||
@@ -192,7 +192,7 @@ class SellingController(StockController):
|
||||
if last_valuation_rate:
|
||||
last_valuation_rate_in_sales_uom = last_valuation_rate[0][0] / (it.conversion_factor or 1)
|
||||
if is_stock_item and flt(it.base_rate) < flt(last_valuation_rate_in_sales_uom):
|
||||
throw_message(it.name, last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
throw_message(it.idx, frappe.bold(it.item_name), last_valuation_rate_in_sales_uom, "valuation rate")
|
||||
|
||||
|
||||
def get_item_list(self):
|
||||
|
||||
@@ -69,17 +69,6 @@ status_map = {
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
["Closed", "eval:self.status=='Closed'"],
|
||||
],
|
||||
"Purchase Invoice": [
|
||||
["Draft", None],
|
||||
["Submitted", "eval:self.docstatus==1"],
|
||||
["Paid", "eval:self.outstanding_amount==0 and self.docstatus==1"],
|
||||
["Return", "eval:self.is_return==1 and self.docstatus==1"],
|
||||
["Debit Note Issued",
|
||||
"eval:self.outstanding_amount <= 0 and self.docstatus==1 and self.is_return==0 and get_value('Purchase Invoice', {'is_return': 1, 'return_against': self.name, 'docstatus': 1})"],
|
||||
["Unpaid", "eval:self.outstanding_amount > 0 and getdate(self.due_date) >= getdate(nowdate()) and self.docstatus==1"],
|
||||
["Overdue", "eval:self.outstanding_amount > 0 and getdate(self.due_date) < getdate(nowdate()) and self.docstatus==1"],
|
||||
["Cancelled", "eval:self.docstatus==2"],
|
||||
],
|
||||
"Material Request": [
|
||||
["Draft", None],
|
||||
["Stopped", "eval:self.status == 'Stopped'"],
|
||||
|
||||
@@ -643,8 +643,7 @@ def get_itemised_tax_breakup_html(doc):
|
||||
itemised_tax=itemised_tax,
|
||||
itemised_taxable_amount=itemised_taxable_amount,
|
||||
tax_accounts=tax_accounts,
|
||||
conversion_rate=doc.conversion_rate,
|
||||
currency=doc.currency
|
||||
doc=doc
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class EmailCampaign(Document):
|
||||
for entry in campaign.get("campaign_schedules"):
|
||||
send_after_days.append(entry.send_after_days)
|
||||
try:
|
||||
end_date = add_days(getdate(self.start_date), max(send_after_days))
|
||||
self.end_date = add_days(getdate(self.start_date), max(send_after_days))
|
||||
except ValueError:
|
||||
frappe.throw(_("Please set up the Campaign Schedule in the Campaign {0}").format(self.campaign_name))
|
||||
|
||||
|
||||
@@ -366,7 +366,7 @@
|
||||
"icon": "fa fa-user",
|
||||
"idx": 5,
|
||||
"image_field": "image",
|
||||
"modified": "2019-09-19 12:49:02.536647",
|
||||
"modified": "2020-05-11 20:30:02.536647",
|
||||
"modified_by": "Administrator",
|
||||
"module": "CRM",
|
||||
"name": "Lead",
|
||||
@@ -423,15 +423,6 @@
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User"
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Guest",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"search_fields": "lead_name,lead_owner,status",
|
||||
@@ -439,4 +430,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "lead_name"
|
||||
}
|
||||
}
|
||||
|
||||
23
erpnext/crm/utils.py
Normal file
23
erpnext/crm/utils.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def update_lead_phone_numbers(contact, method):
|
||||
if contact.phone_nos:
|
||||
contact_lead = contact.get_link_for("Lead")
|
||||
if contact_lead:
|
||||
phone = mobile_no = contact.phone_nos[0].phone
|
||||
|
||||
if len(contact.phone_nos) > 1:
|
||||
# get the default phone number
|
||||
primary_phones = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_phone]
|
||||
if primary_phones:
|
||||
phone = primary_phones[0]
|
||||
|
||||
# get the default mobile number
|
||||
primary_mobile_nos = [phone_doc.phone for phone_doc in contact.phone_nos if phone_doc.is_primary_mobile_no]
|
||||
if primary_mobile_nos:
|
||||
mobile_no = primary_mobile_nos[0]
|
||||
|
||||
lead = frappe.get_doc("Lead", contact_lead)
|
||||
lead.db_set("phone", phone)
|
||||
lead.db_set("mobile_no", mobile_no)
|
||||
@@ -1,790 +1,207 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 0,
|
||||
"autoname": "EDU-ASP-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2015-11-12 16:34:34.658092",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student_group",
|
||||
"assessment_name",
|
||||
"assessment_group",
|
||||
"grading_scale",
|
||||
"column_break_2",
|
||||
"course",
|
||||
"program",
|
||||
"academic_year",
|
||||
"academic_term",
|
||||
"section_break_5",
|
||||
"schedule_date",
|
||||
"room",
|
||||
"examiner",
|
||||
"examiner_name",
|
||||
"column_break_4",
|
||||
"from_time",
|
||||
"to_time",
|
||||
"supervisor",
|
||||
"supervisor_name",
|
||||
"section_break_20",
|
||||
"maximum_assessment_score",
|
||||
"assessment_criteria",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_group",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Student Group",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Group",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "assessment_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Assessment Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Assessment Name"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "assessment_group",
|
||||
"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": 1,
|
||||
"label": "Assessment Group",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Assessment Group",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "course.default_grading_scale",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "grading_scale",
|
||||
"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": 1,
|
||||
"label": "Grading Scale",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Grading Scale",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "student_group.course",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Course",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Course",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "student_group.program",
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"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
|
||||
"options": "Program"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "student_group.academic_year",
|
||||
"fieldname": "academic_year",
|
||||
"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": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Year",
|
||||
"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
|
||||
"options": "Academic Year"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "student_group.academic_term",
|
||||
"fieldname": "academic_term",
|
||||
"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": "Academic Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Term",
|
||||
"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
|
||||
"options": "Academic Term"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": "",
|
||||
"columns": 0,
|
||||
"depends_on": "",
|
||||
"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,
|
||||
"label": "Schedule",
|
||||
"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
|
||||
"label": "Schedule"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"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": "Schedule Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "room",
|
||||
"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": "Room",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Room",
|
||||
"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
|
||||
"options": "Room"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "examiner",
|
||||
"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": "Examiner",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Instructor",
|
||||
"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
|
||||
"options": "Instructor"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "examiner.instructor_name",
|
||||
"fieldname": "examiner_name",
|
||||
"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": "Examiner Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_4",
|
||||
"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
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"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 Time",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"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 Time",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "supervisor",
|
||||
"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": "Supervisor",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Instructor",
|
||||
"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
|
||||
"options": "Instructor"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "supervisor.instructor_name",
|
||||
"fieldname": "supervisor_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Supervisor Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_20",
|
||||
"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": "Evaluate",
|
||||
"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
|
||||
"label": "Evaluate"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "maximum_assessment_score",
|
||||
"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": "Maximum Assessment Score",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "assessment_criteria",
|
||||
"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": "Assessment Criteria",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Assessment Plan Criteria",
|
||||
"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
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"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": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Assessment Plan",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-08-30 00:48:03.475522",
|
||||
"links": [],
|
||||
"modified": "2020-05-09 16:44:04.313175",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Assessment Plan",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -794,28 +211,17 @@
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "assessment_name",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
"title_field": "assessment_name"
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2017-04-05 13:33:04.519313",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
@@ -42,12 +43,14 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "For Batch based Student Group, the Student Batch will be validated for every Student from the Program Enrollment.",
|
||||
"fieldname": "validate_batch",
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Batch for Students in Student Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "For Course based Student Group, the Course will be validated for every Student from the enrolled Courses in Program Enrollment.",
|
||||
"fieldname": "validate_course",
|
||||
"fieldtype": "Check",
|
||||
@@ -74,13 +77,13 @@
|
||||
{
|
||||
"fieldname": "web_academy_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "LMS Settings"
|
||||
"label": "Learning Management System Settings"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.enable_lms",
|
||||
"fieldname": "portal_title",
|
||||
"fieldtype": "Data",
|
||||
"label": "LMS Title"
|
||||
"label": "Learning Management System Title"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.enable_lms",
|
||||
@@ -89,9 +92,10 @@
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_lms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable LMS"
|
||||
"label": "Enable Learning Management System"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -102,7 +106,8 @@
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"modified": "2019-05-13 18:36:13.127563",
|
||||
"links": [],
|
||||
"modified": "2020-05-07 19:50:22.430576",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Education Settings",
|
||||
@@ -141,4 +146,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,8 @@ frappe.ui.form.on("Fees", {
|
||||
args: {
|
||||
"dt": frm.doc.doctype,
|
||||
"dn": frm.doc.name,
|
||||
"party_type": "Student",
|
||||
"party": frm.doc.student,
|
||||
"recipient_id": frm.doc.student_email
|
||||
},
|
||||
callback: function(r) {
|
||||
|
||||
@@ -75,7 +75,8 @@ class Fees(AccountsController):
|
||||
self.make_gl_entries()
|
||||
|
||||
if self.send_payment_request and self.student_email:
|
||||
pr = make_payment_request(dt="Fees", dn=self.name, recipient_id=self.student_email,
|
||||
pr = make_payment_request(party_type="Student", party=self.student, dt="Fees",
|
||||
dn=self.name, recipient_id=self.student_email,
|
||||
submit_doc=True, use_dummy_message=True)
|
||||
frappe.msgprint(_("Payment request {0} created").format(getlink("Payment Request", pr.name)))
|
||||
|
||||
@@ -96,14 +97,16 @@ class Fees(AccountsController):
|
||||
"debit_in_account_currency": self.grand_total,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher_type": self.doctype
|
||||
})
|
||||
}, item=self)
|
||||
|
||||
fee_gl_entry = self.get_gl_dict({
|
||||
"account": self.income_account,
|
||||
"against": self.student,
|
||||
"credit": self.grand_total,
|
||||
"credit_in_account_currency": self.grand_total,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
}, item=self)
|
||||
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries([student_gl_entries, fee_gl_entry], cancel=(self.docstatus == 2),
|
||||
update_outstanding="Yes", merge_entries=False)
|
||||
|
||||
@@ -144,6 +144,10 @@ def create_sales_order(order, woocommerce_settings, customer_name, sys_lang):
|
||||
def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_lang):
|
||||
company_abbr = frappe.db.get_value('Company', woocommerce_settings.company, 'abbr')
|
||||
|
||||
default_warehouse = _("Stores - {0}", sys_lang).format(company_abbr)
|
||||
if not frappe.db.exists("Warehouse", default_warehouse):
|
||||
frappe.throw(_("Please set Warehouse in Woocommerce Settings"))
|
||||
|
||||
for item in order.get("line_items"):
|
||||
woocomm_item_id = item.get("product_id")
|
||||
found_item = frappe.get_doc("Item", {"woocommerce_id": woocomm_item_id})
|
||||
@@ -158,7 +162,7 @@ def set_items_in_sales_order(new_sales_order, woocommerce_settings, order, sys_l
|
||||
"uom": woocommerce_settings.uom or _("Nos", sys_lang),
|
||||
"qty": item.get("quantity"),
|
||||
"rate": item.get("price"),
|
||||
"warehouse": woocommerce_settings.warehouse or _("Stores - {0}", sys_lang).format(company_abbr)
|
||||
"warehouse": woocommerce_settings.warehouse or default_warehouse
|
||||
})
|
||||
|
||||
add_tax_details(new_sales_order, ordered_items_tax, "Ordered Item tax", woocommerce_settings.tax_account)
|
||||
|
||||
@@ -209,7 +209,7 @@ def new_bank_transaction(transaction):
|
||||
result.append(new_transaction.name)
|
||||
|
||||
except Exception:
|
||||
frappe.throw(frappe.get_traceback())
|
||||
frappe.throw(title=_('Bank transaction creation error'))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -2,15 +2,40 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Tally Migration', {
|
||||
onload: function(frm) {
|
||||
onload: function (frm) {
|
||||
let reload_status = true;
|
||||
frappe.realtime.on("tally_migration_progress_update", function (data) {
|
||||
if (reload_status) {
|
||||
frappe.model.with_doc(frm.doc.doctype, frm.doc.name, () => {
|
||||
frm.refresh_header();
|
||||
});
|
||||
reload_status = false;
|
||||
}
|
||||
frm.dashboard.show_progress(data.title, (data.count / data.total) * 100, data.message);
|
||||
if (data.count == data.total) {
|
||||
window.setTimeout(title => frm.dashboard.hide_progress(title), 1500, data.title);
|
||||
let error_occurred = data.count === -1;
|
||||
if (data.count == data.total || error_occurred) {
|
||||
window.setTimeout((title) => {
|
||||
frm.dashboard.hide_progress(title);
|
||||
frm.reload_doc();
|
||||
if (error_occurred) {
|
||||
frappe.msgprint({
|
||||
message: __("An error has occurred during {0}. Check {1} for more details",
|
||||
[
|
||||
repl("<a href='#Form/Tally Migration/%(tally_document)s' class='variant-click'>%(tally_document)s</a>", {
|
||||
tally_document: frm.docname
|
||||
}),
|
||||
"<a href='#List/Error Log' class='variant-click'>Error Log</a>"
|
||||
]
|
||||
),
|
||||
title: __("Tally Migration Error"),
|
||||
indicator: "red"
|
||||
});
|
||||
}
|
||||
}, 2000, data.title);
|
||||
}
|
||||
});
|
||||
},
|
||||
refresh: function(frm) {
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.master_data && !frm.doc.is_master_data_imported) {
|
||||
if (frm.doc.is_master_data_processed) {
|
||||
if (frm.doc.status != "Importing Master Data") {
|
||||
@@ -34,17 +59,17 @@ frappe.ui.form.on('Tally Migration', {
|
||||
}
|
||||
}
|
||||
},
|
||||
add_button: function(frm, label, method) {
|
||||
add_button: function (frm, label, method) {
|
||||
frm.add_custom_button(
|
||||
label,
|
||||
() => frm.call({
|
||||
doc: frm.doc,
|
||||
method: method,
|
||||
freeze: true,
|
||||
callback: () => {
|
||||
frm.remove_custom_button(label);
|
||||
}
|
||||
})
|
||||
() => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: method,
|
||||
freeze: true
|
||||
});
|
||||
frm.reload_doc();
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"beta": 1,
|
||||
"creation": "2019-02-01 14:27:09.485238",
|
||||
"doctype": "DocType",
|
||||
@@ -14,6 +15,7 @@
|
||||
"tally_debtors_account",
|
||||
"company_section",
|
||||
"tally_company",
|
||||
"default_uom",
|
||||
"column_break_8",
|
||||
"erpnext_company",
|
||||
"processed_files_section",
|
||||
@@ -43,6 +45,7 @@
|
||||
"label": "Status"
|
||||
},
|
||||
{
|
||||
"description": "Data exported from Tally that consists of the Chart of Accounts, Customers, Suppliers, Addresses, Items and UOMs",
|
||||
"fieldname": "master_data",
|
||||
"fieldtype": "Attach",
|
||||
"in_list_view": 1,
|
||||
@@ -50,6 +53,7 @@
|
||||
},
|
||||
{
|
||||
"default": "Sundry Creditors",
|
||||
"description": "Creditors Account set in Tally",
|
||||
"fieldname": "tally_creditors_account",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tally Creditors Account",
|
||||
@@ -61,6 +65,7 @@
|
||||
},
|
||||
{
|
||||
"default": "Sundry Debtors",
|
||||
"description": "Debtors Account set in Tally",
|
||||
"fieldname": "tally_debtors_account",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tally Debtors Account",
|
||||
@@ -72,6 +77,7 @@
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Company Name as per Imported Tally Data",
|
||||
"fieldname": "tally_company",
|
||||
"fieldtype": "Data",
|
||||
"label": "Tally Company",
|
||||
@@ -82,9 +88,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Your Company set in ERPNext",
|
||||
"fieldname": "erpnext_company",
|
||||
"fieldtype": "Data",
|
||||
"label": "ERPNext Company"
|
||||
"label": "ERPNext Company",
|
||||
"read_only_depends_on": "eval:doc.is_master_data_processed == 1"
|
||||
},
|
||||
{
|
||||
"fieldname": "processed_files_section",
|
||||
@@ -155,24 +163,28 @@
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_master_data_processed",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Master Data Processed",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_day_book_data_processed",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Day Book Data Processed",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_day_book_data_imported",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Day Book Data Imported",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_master_data_imported",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Master Data Imported",
|
||||
@@ -188,13 +200,23 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"description": "Day Book Data exported from Tally that consists of all historic transactions",
|
||||
"fieldname": "day_book_data",
|
||||
"fieldtype": "Attach",
|
||||
"in_list_view": 1,
|
||||
"label": "Day Book Data"
|
||||
},
|
||||
{
|
||||
"default": "Unit",
|
||||
"description": "UOM in case unspecified in imported data",
|
||||
"fieldname": "default_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default UOM",
|
||||
"options": "UOM"
|
||||
}
|
||||
],
|
||||
"modified": "2019-04-29 05:46:54.394967",
|
||||
"links": [],
|
||||
"modified": "2020-04-16 13:03:28.894919",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Tally Migration",
|
||||
|
||||
@@ -4,20 +4,23 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from decimal import Decimal
|
||||
import json
|
||||
import re
|
||||
import traceback
|
||||
import zipfile
|
||||
from decimal import Decimal
|
||||
|
||||
from bs4 import BeautifulSoup as bs
|
||||
|
||||
import frappe
|
||||
from erpnext import encode_company_abbr
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
||||
from frappe import _
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import getseries, revert_series_if_last
|
||||
from frappe.utils.data import format_datetime
|
||||
from bs4 import BeautifulSoup as bs
|
||||
from erpnext import encode_company_abbr
|
||||
from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts
|
||||
|
||||
|
||||
PRIMARY_ACCOUNT = "Primary"
|
||||
VOUCHER_CHUNK_SIZE = 500
|
||||
@@ -39,13 +42,15 @@ class TallyMigration(Document):
|
||||
return string
|
||||
|
||||
master_file = frappe.get_doc("File", {"file_url": data_file})
|
||||
master_file_path = master_file.get_full_path()
|
||||
|
||||
with zipfile.ZipFile(master_file.get_full_path()) as zf:
|
||||
encoded_content = zf.read(zf.namelist()[0])
|
||||
try:
|
||||
content = encoded_content.decode("utf-8-sig")
|
||||
except UnicodeDecodeError:
|
||||
content = encoded_content.decode("utf-16")
|
||||
if zipfile.is_zipfile(master_file_path):
|
||||
with zipfile.ZipFile(master_file_path) as zf:
|
||||
encoded_content = zf.read(zf.namelist()[0])
|
||||
try:
|
||||
content = encoded_content.decode("utf-8-sig")
|
||||
except UnicodeDecodeError:
|
||||
content = encoded_content.decode("utf-16")
|
||||
|
||||
master = bs(sanitize(emptify(content)), "xml")
|
||||
collection = master.BODY.IMPORTDATA.REQUESTDATA
|
||||
@@ -58,13 +63,14 @@ class TallyMigration(Document):
|
||||
"file_name": key + ".json",
|
||||
"attached_to_doctype": self.doctype,
|
||||
"attached_to_name": self.name,
|
||||
"content": json.dumps(value)
|
||||
"content": json.dumps(value),
|
||||
"is_private": True
|
||||
}).insert()
|
||||
setattr(self, key, f.file_url)
|
||||
|
||||
def _process_master_data(self):
|
||||
def get_company_name(collection):
|
||||
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string
|
||||
return collection.find_all("REMOTECMPINFO.LIST")[0].REMOTECMPNAME.string.strip()
|
||||
|
||||
def get_coa_customers_suppliers(collection):
|
||||
root_type_map = {
|
||||
@@ -97,17 +103,17 @@ class TallyMigration(Document):
|
||||
# If Ledger doesn't have PARENT field then don't create Account
|
||||
# For example "Profit & Loss A/c"
|
||||
if account.PARENT:
|
||||
yield account.PARENT.string, account["NAME"], 0
|
||||
yield account.PARENT.string.strip(), account["NAME"], 0
|
||||
|
||||
def get_parent(account):
|
||||
if account.PARENT:
|
||||
return account.PARENT.string
|
||||
return account.PARENT.string.strip()
|
||||
return {
|
||||
("Yes", "No"): "Application of Funds (Assets)",
|
||||
("Yes", "Yes"): "Expenses",
|
||||
("No", "Yes"): "Income",
|
||||
("No", "No"): "Source of Funds (Liabilities)",
|
||||
}[(account.ISDEEMEDPOSITIVE.string, account.ISREVENUE.string)]
|
||||
}[(account.ISDEEMEDPOSITIVE.string.strip(), account.ISREVENUE.string.strip())]
|
||||
|
||||
def get_children_and_parent_dict(accounts):
|
||||
children, parents = {}, {}
|
||||
@@ -145,38 +151,38 @@ class TallyMigration(Document):
|
||||
parties, addresses = [], []
|
||||
for account in collection.find_all("LEDGER"):
|
||||
party_type = None
|
||||
if account.NAME.string in customers:
|
||||
if account.NAME.string.strip() in customers:
|
||||
party_type = "Customer"
|
||||
parties.append({
|
||||
"doctype": party_type,
|
||||
"customer_name": account.NAME.string,
|
||||
"tax_id": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
|
||||
"customer_name": account.NAME.string.strip(),
|
||||
"tax_id": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
|
||||
"customer_group": "All Customer Groups",
|
||||
"territory": "All Territories",
|
||||
"customer_type": "Individual",
|
||||
})
|
||||
elif account.NAME.string in suppliers:
|
||||
elif account.NAME.string.strip() in suppliers:
|
||||
party_type = "Supplier"
|
||||
parties.append({
|
||||
"doctype": party_type,
|
||||
"supplier_name": account.NAME.string,
|
||||
"pan": account.INCOMETAXNUMBER.string if account.INCOMETAXNUMBER else None,
|
||||
"supplier_name": account.NAME.string.strip(),
|
||||
"pan": account.INCOMETAXNUMBER.string.strip() if account.INCOMETAXNUMBER else None,
|
||||
"supplier_group": "All Supplier Groups",
|
||||
"supplier_type": "Individual",
|
||||
})
|
||||
if party_type:
|
||||
address = "\n".join([a.string for a in account.find_all("ADDRESS")])
|
||||
address = "\n".join([a.string.strip() for a in account.find_all("ADDRESS")])
|
||||
addresses.append({
|
||||
"doctype": "Address",
|
||||
"address_line1": address[:140].strip(),
|
||||
"address_line2": address[140:].strip(),
|
||||
"country": account.COUNTRYNAME.string if account.COUNTRYNAME else None,
|
||||
"state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
|
||||
"gst_state": account.LEDSTATENAME.string if account.LEDSTATENAME else None,
|
||||
"pin_code": account.PINCODE.string if account.PINCODE else None,
|
||||
"mobile": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
|
||||
"phone": account.LEDGERPHONE.string if account.LEDGERPHONE else None,
|
||||
"gstin": account.PARTYGSTIN.string if account.PARTYGSTIN else None,
|
||||
"country": account.COUNTRYNAME.string.strip() if account.COUNTRYNAME else None,
|
||||
"state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
|
||||
"gst_state": account.LEDSTATENAME.string.strip() if account.LEDSTATENAME else None,
|
||||
"pin_code": account.PINCODE.string.strip() if account.PINCODE else None,
|
||||
"mobile": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||
"phone": account.LEDGERPHONE.string.strip() if account.LEDGERPHONE else None,
|
||||
"gstin": account.PARTYGSTIN.string.strip() if account.PARTYGSTIN else None,
|
||||
"links": [{"link_doctype": party_type, "link_name": account["NAME"]}],
|
||||
})
|
||||
return parties, addresses
|
||||
@@ -184,41 +190,50 @@ class TallyMigration(Document):
|
||||
def get_stock_items_uoms(collection):
|
||||
uoms = []
|
||||
for uom in collection.find_all("UNIT"):
|
||||
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string})
|
||||
uoms.append({"doctype": "UOM", "uom_name": uom.NAME.string.strip()})
|
||||
|
||||
items = []
|
||||
for item in collection.find_all("STOCKITEM"):
|
||||
stock_uom = item.BASEUNITS.string.strip() if item.BASEUNITS else self.default_uom
|
||||
items.append({
|
||||
"doctype": "Item",
|
||||
"item_code" : item.NAME.string,
|
||||
"stock_uom": item.BASEUNITS.string,
|
||||
"item_code" : item.NAME.string.strip(),
|
||||
"stock_uom": stock_uom.strip(),
|
||||
"is_stock_item": 0,
|
||||
"item_group": "All Item Groups",
|
||||
"item_defaults": [{"company": self.erpnext_company}]
|
||||
})
|
||||
|
||||
return items, uoms
|
||||
|
||||
try:
|
||||
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
|
||||
collection = self.get_collection(self.master_data)
|
||||
company = get_company_name(collection)
|
||||
self.tally_company = company
|
||||
self.erpnext_company = company
|
||||
|
||||
self.publish("Process Master Data", _("Reading Uploaded File"), 1, 5)
|
||||
collection = self.get_collection(self.master_data)
|
||||
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
|
||||
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
|
||||
|
||||
company = get_company_name(collection)
|
||||
self.tally_company = company
|
||||
self.erpnext_company = company
|
||||
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
|
||||
parties, addresses = get_parties_addresses(collection, customers, suppliers)
|
||||
|
||||
self.publish("Process Master Data", _("Processing Chart of Accounts and Parties"), 2, 5)
|
||||
chart_of_accounts, customers, suppliers = get_coa_customers_suppliers(collection)
|
||||
self.publish("Process Master Data", _("Processing Party Addresses"), 3, 5)
|
||||
parties, addresses = get_parties_addresses(collection, customers, suppliers)
|
||||
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
|
||||
items, uoms = get_stock_items_uoms(collection)
|
||||
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
|
||||
self.publish("Process Master Data", _("Done"), 5, 5)
|
||||
self.publish("Process Master Data", _("Processing Items and UOMs"), 4, 5)
|
||||
items, uoms = get_stock_items_uoms(collection)
|
||||
data = {"chart_of_accounts": chart_of_accounts, "parties": parties, "addresses": addresses, "items": items, "uoms": uoms}
|
||||
|
||||
self.dump_processed_data(data)
|
||||
self.is_master_data_processed = 1
|
||||
self.status = ""
|
||||
self.save()
|
||||
self.publish("Process Master Data", _("Done"), 5, 5)
|
||||
self.dump_processed_data(data)
|
||||
|
||||
self.is_master_data_processed = 1
|
||||
|
||||
except:
|
||||
self.publish("Process Master Data", _("Process Failed"), -1, 5)
|
||||
self.log()
|
||||
|
||||
finally:
|
||||
self.set_status()
|
||||
|
||||
def publish(self, title, message, count, total):
|
||||
frappe.publish_realtime("tally_migration_progress_update", {"title": title, "message": message, "count": count, "total": total})
|
||||
@@ -256,7 +271,6 @@ class TallyMigration(Document):
|
||||
except:
|
||||
self.log(address)
|
||||
|
||||
|
||||
def create_items_uoms(items_file_url, uoms_file_url):
|
||||
uoms_file = frappe.get_doc("File", {"file_url": uoms_file_url})
|
||||
for uom in json.loads(uoms_file.get_content()):
|
||||
@@ -273,25 +287,35 @@ class TallyMigration(Document):
|
||||
except:
|
||||
self.log(item)
|
||||
|
||||
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
|
||||
create_company_and_coa(self.chart_of_accounts)
|
||||
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
|
||||
create_parties_and_addresses(self.parties, self.addresses)
|
||||
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
|
||||
create_items_uoms(self.items, self.uoms)
|
||||
self.publish("Import Master Data", _("Done"), 4, 4)
|
||||
self.status = ""
|
||||
self.is_master_data_imported = 1
|
||||
self.save()
|
||||
try:
|
||||
self.publish("Import Master Data", _("Creating Company and Importing Chart of Accounts"), 1, 4)
|
||||
create_company_and_coa(self.chart_of_accounts)
|
||||
|
||||
self.publish("Import Master Data", _("Importing Parties and Addresses"), 2, 4)
|
||||
create_parties_and_addresses(self.parties, self.addresses)
|
||||
|
||||
self.publish("Import Master Data", _("Importing Items and UOMs"), 3, 4)
|
||||
create_items_uoms(self.items, self.uoms)
|
||||
|
||||
self.publish("Import Master Data", _("Done"), 4, 4)
|
||||
|
||||
self.is_master_data_imported = 1
|
||||
|
||||
except:
|
||||
self.publish("Import Master Data", _("Process Failed"), -1, 5)
|
||||
self.log()
|
||||
|
||||
finally:
|
||||
self.set_status()
|
||||
|
||||
def _process_day_book_data(self):
|
||||
def get_vouchers(collection):
|
||||
vouchers = []
|
||||
for voucher in collection.find_all("VOUCHER"):
|
||||
if voucher.ISCANCELLED.string == "Yes":
|
||||
if voucher.ISCANCELLED.string.strip() == "Yes":
|
||||
continue
|
||||
inventory_entries = voucher.find_all("INVENTORYENTRIES.LIST") + voucher.find_all("ALLINVENTORYENTRIES.LIST") + voucher.find_all("INVENTORYENTRIESIN.LIST") + voucher.find_all("INVENTORYENTRIESOUT.LIST")
|
||||
if voucher.VOUCHERTYPENAME.string not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
|
||||
if voucher.VOUCHERTYPENAME.string.strip() not in ["Journal", "Receipt", "Payment", "Contra"] and inventory_entries:
|
||||
function = voucher_to_invoice
|
||||
else:
|
||||
function = voucher_to_journal_entry
|
||||
@@ -307,15 +331,15 @@ class TallyMigration(Document):
|
||||
accounts = []
|
||||
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
||||
for entry in ledger_entries:
|
||||
account = {"account": encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company), "cost_center": self.default_cost_center}
|
||||
if entry.ISPARTYLEDGER.string == "Yes":
|
||||
party_details = get_party(entry.LEDGERNAME.string)
|
||||
account = {"account": encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company), "cost_center": self.default_cost_center}
|
||||
if entry.ISPARTYLEDGER.string.strip() == "Yes":
|
||||
party_details = get_party(entry.LEDGERNAME.string.strip())
|
||||
if party_details:
|
||||
party_type, party_account = party_details
|
||||
account["party_type"] = party_type
|
||||
account["account"] = party_account
|
||||
account["party"] = entry.LEDGERNAME.string
|
||||
amount = Decimal(entry.AMOUNT.string)
|
||||
account["party"] = entry.LEDGERNAME.string.strip()
|
||||
amount = Decimal(entry.AMOUNT.string.strip())
|
||||
if amount > 0:
|
||||
account["credit_in_account_currency"] = str(abs(amount))
|
||||
else:
|
||||
@@ -324,21 +348,21 @@ class TallyMigration(Document):
|
||||
|
||||
journal_entry = {
|
||||
"doctype": "Journal Entry",
|
||||
"tally_guid": voucher.GUID.string,
|
||||
"posting_date": voucher.DATE.string,
|
||||
"tally_guid": voucher.GUID.string.strip(),
|
||||
"posting_date": voucher.DATE.string.strip(),
|
||||
"company": self.erpnext_company,
|
||||
"accounts": accounts,
|
||||
}
|
||||
return journal_entry
|
||||
|
||||
def voucher_to_invoice(voucher):
|
||||
if voucher.VOUCHERTYPENAME.string in ["Sales", "Credit Note"]:
|
||||
if voucher.VOUCHERTYPENAME.string.strip() in ["Sales", "Credit Note"]:
|
||||
doctype = "Sales Invoice"
|
||||
party_field = "customer"
|
||||
account_field = "debit_to"
|
||||
account_name = encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
||||
price_list_field = "selling_price_list"
|
||||
elif voucher.VOUCHERTYPENAME.string in ["Purchase", "Debit Note"]:
|
||||
elif voucher.VOUCHERTYPENAME.string.strip() in ["Purchase", "Debit Note"]:
|
||||
doctype = "Purchase Invoice"
|
||||
party_field = "supplier"
|
||||
account_field = "credit_to"
|
||||
@@ -351,10 +375,10 @@ class TallyMigration(Document):
|
||||
|
||||
invoice = {
|
||||
"doctype": doctype,
|
||||
party_field: voucher.PARTYNAME.string,
|
||||
"tally_guid": voucher.GUID.string,
|
||||
"posting_date": voucher.DATE.string,
|
||||
"due_date": voucher.DATE.string,
|
||||
party_field: voucher.PARTYNAME.string.strip(),
|
||||
"tally_guid": voucher.GUID.string.strip(),
|
||||
"posting_date": voucher.DATE.string.strip(),
|
||||
"due_date": voucher.DATE.string.strip(),
|
||||
"items": get_voucher_items(voucher, doctype),
|
||||
"taxes": get_voucher_taxes(voucher),
|
||||
account_field: account_name,
|
||||
@@ -375,15 +399,15 @@ class TallyMigration(Document):
|
||||
for entry in inventory_entries:
|
||||
qty, uom = entry.ACTUALQTY.string.strip().split()
|
||||
items.append({
|
||||
"item_code": entry.STOCKITEMNAME.string,
|
||||
"description": entry.STOCKITEMNAME.string,
|
||||
"item_code": entry.STOCKITEMNAME.string.strip(),
|
||||
"description": entry.STOCKITEMNAME.string.strip(),
|
||||
"qty": qty.strip(),
|
||||
"uom": uom.strip(),
|
||||
"conversion_factor": 1,
|
||||
"price_list_rate": entry.RATE.string.split("/")[0],
|
||||
"price_list_rate": entry.RATE.string.strip().split("/")[0],
|
||||
"cost_center": self.default_cost_center,
|
||||
"warehouse": self.default_warehouse,
|
||||
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string, self.erpnext_company),
|
||||
account_field: encode_company_abbr(entry.find_all("ACCOUNTINGALLOCATIONS.LIST")[0].LEDGERNAME.string.strip(), self.erpnext_company),
|
||||
})
|
||||
return items
|
||||
|
||||
@@ -391,13 +415,13 @@ class TallyMigration(Document):
|
||||
ledger_entries = voucher.find_all("ALLLEDGERENTRIES.LIST") + voucher.find_all("LEDGERENTRIES.LIST")
|
||||
taxes = []
|
||||
for entry in ledger_entries:
|
||||
if entry.ISPARTYLEDGER.string == "No":
|
||||
tax_account = encode_company_abbr(entry.LEDGERNAME.string, self.erpnext_company)
|
||||
if entry.ISPARTYLEDGER.string.strip() == "No":
|
||||
tax_account = encode_company_abbr(entry.LEDGERNAME.string.strip(), self.erpnext_company)
|
||||
taxes.append({
|
||||
"charge_type": "Actual",
|
||||
"account_head": tax_account,
|
||||
"description": tax_account,
|
||||
"tax_amount": entry.AMOUNT.string,
|
||||
"tax_amount": entry.AMOUNT.string.strip(),
|
||||
"cost_center": self.default_cost_center,
|
||||
})
|
||||
return taxes
|
||||
@@ -408,15 +432,24 @@ class TallyMigration(Document):
|
||||
elif frappe.db.exists({"doctype": "Customer", "customer_name": party}):
|
||||
return "Customer", encode_company_abbr(self.tally_debtors_account, self.erpnext_company)
|
||||
|
||||
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
|
||||
collection = self.get_collection(self.day_book_data)
|
||||
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
|
||||
vouchers = get_vouchers(collection)
|
||||
self.publish("Process Day Book Data", _("Done"), 3, 3)
|
||||
self.dump_processed_data({"vouchers": vouchers})
|
||||
self.status = ""
|
||||
self.is_day_book_data_processed = 1
|
||||
self.save()
|
||||
try:
|
||||
self.publish("Process Day Book Data", _("Reading Uploaded File"), 1, 3)
|
||||
collection = self.get_collection(self.day_book_data)
|
||||
|
||||
self.publish("Process Day Book Data", _("Processing Vouchers"), 2, 3)
|
||||
vouchers = get_vouchers(collection)
|
||||
|
||||
self.publish("Process Day Book Data", _("Done"), 3, 3)
|
||||
self.dump_processed_data({"vouchers": vouchers})
|
||||
|
||||
self.is_day_book_data_processed = 1
|
||||
|
||||
except:
|
||||
self.publish("Process Day Book Data", _("Process Failed"), -1, 5)
|
||||
self.log()
|
||||
|
||||
finally:
|
||||
self.set_status()
|
||||
|
||||
def _import_day_book_data(self):
|
||||
def create_fiscal_years(vouchers):
|
||||
@@ -454,23 +487,31 @@ class TallyMigration(Document):
|
||||
"currency": "INR"
|
||||
}).insert()
|
||||
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
|
||||
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
|
||||
try:
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_creditors_account, self.erpnext_company), "account_type", "Payable")
|
||||
frappe.db.set_value("Account", encode_company_abbr(self.tally_debtors_account, self.erpnext_company), "account_type", "Receivable")
|
||||
frappe.db.set_value("Company", self.erpnext_company, "round_off_account", self.round_off_account)
|
||||
|
||||
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
|
||||
vouchers = json.loads(vouchers_file.get_content())
|
||||
vouchers_file = frappe.get_doc("File", {"file_url": self.vouchers})
|
||||
vouchers = json.loads(vouchers_file.get_content())
|
||||
|
||||
create_fiscal_years(vouchers)
|
||||
create_price_list()
|
||||
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
|
||||
create_fiscal_years(vouchers)
|
||||
create_price_list()
|
||||
create_custom_fields(["Journal Entry", "Purchase Invoice", "Sales Invoice"])
|
||||
|
||||
total = len(vouchers)
|
||||
is_last = False
|
||||
for index in range(0, total, VOUCHER_CHUNK_SIZE):
|
||||
if index + VOUCHER_CHUNK_SIZE >= total:
|
||||
is_last = True
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
|
||||
total = len(vouchers)
|
||||
is_last = False
|
||||
|
||||
for index in range(0, total, VOUCHER_CHUNK_SIZE):
|
||||
if index + VOUCHER_CHUNK_SIZE >= total:
|
||||
is_last = True
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_import_vouchers", queue="long", timeout=3600, start=index+1, total=total, is_last=is_last)
|
||||
|
||||
except:
|
||||
self.log()
|
||||
|
||||
finally:
|
||||
self.set_status()
|
||||
|
||||
def _import_vouchers(self, start, total, is_last=False):
|
||||
frappe.flags.in_migrate = True
|
||||
@@ -494,25 +535,26 @@ class TallyMigration(Document):
|
||||
frappe.flags.in_migrate = False
|
||||
|
||||
def process_master_data(self):
|
||||
self.status = "Processing Master Data"
|
||||
self.save()
|
||||
self.set_status("Processing Master Data")
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
|
||||
|
||||
def import_master_data(self):
|
||||
self.status = "Importing Master Data"
|
||||
self.save()
|
||||
self.set_status("Importing Master Data")
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
|
||||
|
||||
def process_day_book_data(self):
|
||||
self.status = "Processing Day Book Data"
|
||||
self.save()
|
||||
self.set_status("Processing Day Book Data")
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
|
||||
|
||||
def import_day_book_data(self):
|
||||
self.status = "Importing Day Book Data"
|
||||
self.save()
|
||||
self.set_status("Importing Day Book Data")
|
||||
frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
|
||||
|
||||
def log(self, data=None):
|
||||
message = "\n".join(["Data", json.dumps(data, default=str, indent=4), "Exception", traceback.format_exc()])
|
||||
data = data or self.status
|
||||
message = "\n".join(["Data:", json.dumps(data, default=str, indent=4), "--" * 50, "\nException:", traceback.format_exc()])
|
||||
return frappe.log_error(title="Tally Migration Error", message=message)
|
||||
|
||||
def set_status(self, status=""):
|
||||
self.status = status
|
||||
self.save()
|
||||
|
||||
@@ -158,7 +158,7 @@ def get_item_dict(table, parent, parenttype):
|
||||
def create_stock_entry(doc):
|
||||
stock_entry = frappe.new_doc("Stock Entry")
|
||||
stock_entry = set_stock_items(stock_entry, doc.name, "Clinical Procedure")
|
||||
stock_entry.purpose = "Material Issue"
|
||||
stock_entry.stock_entry_type = "Material Issue"
|
||||
stock_entry.from_warehouse = doc.warehouse
|
||||
stock_entry.company = doc.company
|
||||
expense_account = get_account(None, "expense_account", "Healthcare Settings", doc.company)
|
||||
|
||||
@@ -78,6 +78,8 @@ def create_item_from_template(doc):
|
||||
if doc.is_billable:
|
||||
disabled = 0
|
||||
|
||||
uom = frappe.db.exists('UOM', 'Unit') or frappe.db.get_single_value('Stock Settings', 'stock_uom')
|
||||
|
||||
#insert item
|
||||
item = frappe.get_doc({
|
||||
'doctype': 'Item',
|
||||
@@ -92,7 +94,7 @@ def create_item_from_template(doc):
|
||||
'show_in_website': 0,
|
||||
'is_pro_applicable': 0,
|
||||
'disabled': disabled,
|
||||
'stock_uom': 'Unit'
|
||||
'stock_uom': uom
|
||||
}).insert(ignore_permissions=True)
|
||||
|
||||
#insert item price
|
||||
|
||||
@@ -69,12 +69,13 @@ class PatientAppointment(Document):
|
||||
if fee_validity.ref_invoice:
|
||||
frappe.db.set_value("Patient Appointment", appointment.name, "invoiced", True)
|
||||
frappe.msgprint(_("{0} has fee validity till {1}").format(appointment.patient, fee_validity.valid_till))
|
||||
confirm_sms(self)
|
||||
|
||||
if frappe.db.get_value("Healthcare Settings", None, "manage_appointment_invoice_automatically") == '1' and \
|
||||
frappe.db.get_value("Patient Appointment", self.name, "invoiced") != 1:
|
||||
invoice_appointment(self)
|
||||
|
||||
send_confirmation_msg(self)
|
||||
|
||||
@frappe.whitelist()
|
||||
def invoice_appointment(appointment_doc):
|
||||
if not appointment_doc.name:
|
||||
@@ -303,10 +304,14 @@ def set_pending_appointments():
|
||||
"('Scheduled','Open') and appointment_date < %s", today)
|
||||
|
||||
|
||||
def confirm_sms(doc):
|
||||
if frappe.db.get_value("Healthcare Settings", None, "app_con") == '1':
|
||||
message = frappe.db.get_value("Healthcare Settings", None, "app_con_msg")
|
||||
send_message(doc, message)
|
||||
def send_confirmation_msg(doc):
|
||||
if frappe.db.get_single_value("Healthcare Settings", "app_con"):
|
||||
message = frappe.db.get_single_value("Healthcare Settings", "app_con_msg")
|
||||
try:
|
||||
send_message(doc, message)
|
||||
except Exception:
|
||||
frappe.log_error(frappe.get_traceback(), _("Appointment Confirmation Message Not Sent"))
|
||||
frappe.msgprint(_("Appointment Confirmation Message Not Sent"), indicator="orange")
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_encounter(appointment):
|
||||
@@ -352,7 +357,7 @@ def send_message(doc, message):
|
||||
|
||||
# jinja to string convertion happens here
|
||||
message = frappe.render_template(message, context)
|
||||
number = [patient.mobile]
|
||||
number = [patient_mobile]
|
||||
send_sms(number, message)
|
||||
|
||||
|
||||
|
||||
@@ -256,7 +256,8 @@ doc_events = {
|
||||
},
|
||||
"Contact": {
|
||||
"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
|
||||
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
|
||||
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information",
|
||||
"validate": "erpnext.crm.utils.update_lead_phone_numbers"
|
||||
},
|
||||
"Lead": {
|
||||
"after_insert": "erpnext.communication.doctype.call_log.call_log.set_caller_information"
|
||||
@@ -268,7 +269,8 @@ doc_events = {
|
||||
|
||||
scheduler_events = {
|
||||
"all": [
|
||||
"erpnext.projects.doctype.project.project.project_status_update_reminder"
|
||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||
"erpnext.healthcare.doctype.patient_appointment.patient_appointment.set_appointment_reminder"
|
||||
],
|
||||
"hourly": [
|
||||
'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
|
||||
|
||||
@@ -38,7 +38,7 @@ class Attendance(Document):
|
||||
date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining")
|
||||
|
||||
# leaves can be marked for future dates
|
||||
if self.status not in ('On Leave', 'Half Day') and getdate(self.attendance_date) > getdate(nowdate()):
|
||||
if self.status != 'On Leave' and not self.leave_application and getdate(self.attendance_date) > getdate(nowdate()):
|
||||
frappe.throw(_("Attendance can not be marked for future dates"))
|
||||
elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
|
||||
frappe.throw(_("Attendance date can not be less than employee's joining date"))
|
||||
|
||||
@@ -136,9 +136,18 @@ def make_bank_entry(dt, dn):
|
||||
def make_return_entry(employee, company, employee_advance_name,
|
||||
return_amount, advance_account, mode_of_payment=None):
|
||||
return_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
|
||||
|
||||
mode_of_payment_type = ''
|
||||
if mode_of_payment:
|
||||
mode_of_payment_type = frappe.get_cached_value('Mode of Payment', mode_of_payment, 'type')
|
||||
if mode_of_payment_type not in ["Cash", "Bank"]:
|
||||
# if mode of payment is General then it unset the type
|
||||
mode_of_payment_type = None
|
||||
|
||||
je = frappe.new_doc('Journal Entry')
|
||||
je.posting_date = nowdate()
|
||||
je.voucher_type = 'Bank Entry'
|
||||
# if mode of payment is Bank then voucher type is Bank Entry
|
||||
je.voucher_type = '{} Entry'.format(mode_of_payment_type) if mode_of_payment_type else 'Cash Entry'
|
||||
je.company = company
|
||||
je.remark = 'Return against Employee Advance: ' + employee_advance_name
|
||||
|
||||
|
||||
@@ -76,25 +76,15 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-19 18:06:45.361830",
|
||||
"modified": "2020-05-18 17:17:38.883126",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee Other Income",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -104,9 +94,12 @@
|
||||
"report": 1,
|
||||
"role": "HR Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -116,9 +109,12 @@
|
||||
"report": 1,
|
||||
"role": "HR User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
@@ -128,6 +124,7 @@
|
||||
"report": 1,
|
||||
"role": "Employee",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
|
||||
@@ -115,8 +115,9 @@ class ExpenseClaim(AccountsController):
|
||||
"party_type": "Employee",
|
||||
"party": self.employee,
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name
|
||||
})
|
||||
"against_voucher": self.name,
|
||||
"cost_center": self.cost_center
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
# expense entries
|
||||
@@ -127,8 +128,8 @@ class ExpenseClaim(AccountsController):
|
||||
"debit": data.sanctioned_amount,
|
||||
"debit_in_account_currency": data.sanctioned_amount,
|
||||
"against": self.employee,
|
||||
"cost_center": self.cost_center
|
||||
})
|
||||
"cost_center": data.cost_center
|
||||
}, item=data)
|
||||
)
|
||||
|
||||
for data in self.advances:
|
||||
@@ -156,7 +157,7 @@ class ExpenseClaim(AccountsController):
|
||||
"credit": self.grand_total,
|
||||
"credit_in_account_currency": self.grand_total,
|
||||
"against": self.employee
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
gl_entry.append(
|
||||
@@ -169,7 +170,7 @@ class ExpenseClaim(AccountsController):
|
||||
"debit_in_account_currency": self.grand_total,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
})
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
return gl_entry
|
||||
@@ -186,7 +187,7 @@ class ExpenseClaim(AccountsController):
|
||||
"cost_center": self.cost_center,
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name
|
||||
})
|
||||
}, item=tax)
|
||||
)
|
||||
|
||||
def validate_account_details(self):
|
||||
|
||||
@@ -10,6 +10,22 @@
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"expense_date",
|
||||
"column_break_2",
|
||||
"expense_type",
|
||||
"default_account",
|
||||
"section_break_4",
|
||||
"description",
|
||||
"section_break_6",
|
||||
"amount",
|
||||
"column_break_8",
|
||||
"sanctioned_amount",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
@@ -348,6 +364,21 @@
|
||||
"translatable": 0,
|
||||
"unique": 0,
|
||||
"width": "150px"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
@@ -359,12 +390,12 @@
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-06-10 08:41:36.122565",
|
||||
"links": [],
|
||||
"modified": "2020-05-11 18:54:35.601592",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Claim Detail",
|
||||
"owner": "harshada@webnotestech.com",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
|
||||
@@ -7,14 +7,16 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account_head",
|
||||
"cost_center",
|
||||
"col_break1",
|
||||
"rate",
|
||||
"description",
|
||||
"section_break_6",
|
||||
"tax_amount",
|
||||
"column_break_8",
|
||||
"total"
|
||||
"total",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -90,10 +92,20 @@
|
||||
{
|
||||
"fieldname": "column_break_8",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-06-20 12:01:33.919555",
|
||||
"links": [],
|
||||
"modified": "2020-05-11 19:01:26.611758",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Expense Taxes and Charges",
|
||||
|
||||
@@ -30,16 +30,16 @@ class LeaveAllocation(Document):
|
||||
def validate_leave_allocation_days(self):
|
||||
company = frappe.db.get_value("Employee", self.employee, "company")
|
||||
leave_period = get_leave_period(self.from_date, self.to_date, company)
|
||||
max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed")
|
||||
max_leaves_allowed = flt(frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed"))
|
||||
if max_leaves_allowed > 0:
|
||||
leave_allocated = 0
|
||||
if leave_period:
|
||||
leave_allocated = get_leave_allocation_for_period(self.employee, self.leave_type,
|
||||
leave_period[0].from_date, leave_period[0].to_date)
|
||||
leave_allocated += self.new_leaves_allocated
|
||||
leave_allocated += flt(self.new_leaves_allocated)
|
||||
if leave_allocated > max_leaves_allowed:
|
||||
frappe.throw(_("Total allocated leaves are more days than maximum allocation of {0} leave type for employee {1} in the period")
|
||||
.format(self.leave_type, self.employee))
|
||||
.format(self.leave_type, self.employee))
|
||||
|
||||
def on_submit(self):
|
||||
self.create_leave_ledger_entry()
|
||||
|
||||
@@ -541,7 +541,7 @@ def get_remaining_leaves(allocation, leaves_taken, date, expiry):
|
||||
|
||||
return _get_remaining_leaves(total_leaves, allocation.to_date)
|
||||
|
||||
def get_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
def get_leaves_for_period(employee, leave_type, from_date, to_date, do_not_skip_expired_leaves=False):
|
||||
leave_entries = get_leave_entries(employee, leave_type, from_date, to_date)
|
||||
leave_days = 0
|
||||
|
||||
@@ -551,8 +551,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date):
|
||||
if inclusive_period and leave_entry.transaction_type == 'Leave Encashment':
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' \
|
||||
and leave_entry.is_expired and not skip_expiry_leaves(leave_entry, to_date):
|
||||
elif inclusive_period and leave_entry.transaction_type == 'Leave Allocation' and leave_entry.is_expired \
|
||||
and (do_not_skip_expired_leaves or not skip_expiry_leaves(leave_entry, to_date)):
|
||||
leave_days += leave_entry.leaves
|
||||
|
||||
elif leave_entry.transaction_type == 'Leave Application':
|
||||
|
||||
@@ -88,32 +88,40 @@ def get_previous_expiry_ledger_entry(ledger):
|
||||
}, fieldname=['name'])
|
||||
|
||||
def process_expired_allocation():
|
||||
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry '''
|
||||
''' Check if a carry forwarded allocation has expired and create a expiry ledger entry
|
||||
Case 1: carry forwarded expiry period is set for the leave type,
|
||||
create a separate leave expiry entry against each entry of carry forwarded and non carry forwarded leaves
|
||||
Case 2: leave type has no specific expiry period for carry forwarded leaves
|
||||
and there is no carry forwarded leave allocation, create a single expiry against the remaining leaves.
|
||||
'''
|
||||
|
||||
# fetch leave type records that has carry forwarded leaves expiry
|
||||
leave_type_records = frappe.db.get_values("Leave Type", filters={
|
||||
'expire_carry_forwarded_leaves_after_days': (">", 0)
|
||||
}, fieldname=['name'])
|
||||
|
||||
leave_type = [record[0] for record in leave_type_records]
|
||||
leave_type = [record[0] for record in leave_type_records] or ['']
|
||||
|
||||
expired_allocation = frappe.db.sql_list("""SELECT name
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
`transaction_type`='Leave Allocation'
|
||||
AND `is_expired`=1""")
|
||||
|
||||
expire_allocation = frappe.get_all("Leave Ledger Entry",
|
||||
fields=['leaves', 'to_date', 'employee', 'leave_type', 'is_carry_forward', 'transaction_name as name', 'transaction_type'],
|
||||
filters={
|
||||
'to_date': ("<", today()),
|
||||
'transaction_type': 'Leave Allocation',
|
||||
'transaction_name': ('not in', expired_allocation)
|
||||
},
|
||||
or_filters={
|
||||
'is_carry_forward': 0,
|
||||
'leave_type': ('in', leave_type)
|
||||
})
|
||||
# fetch non expired leave ledger entry of transaction_type allocation
|
||||
expire_allocation = frappe.db.sql("""
|
||||
SELECT
|
||||
leaves, to_date, employee, leave_type,
|
||||
is_carry_forward, transaction_name as name, transaction_type
|
||||
FROM `tabLeave Ledger Entry` l
|
||||
WHERE (NOT EXISTS
|
||||
(SELECT name
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE
|
||||
transaction_name = l.transaction_name
|
||||
AND transaction_type = 'Leave Allocation'
|
||||
AND name<>l.name
|
||||
AND docstatus = 1
|
||||
AND (
|
||||
is_carry_forward=l.is_carry_forward
|
||||
OR (is_carry_forward = 0 AND leave_type not in %s)
|
||||
)))
|
||||
AND transaction_type = 'Leave Allocation'
|
||||
AND to_date < %s""", (leave_type, today()), as_dict=1)
|
||||
|
||||
if expire_allocation:
|
||||
create_expiry_ledger_entry(expire_allocation)
|
||||
@@ -133,6 +141,7 @@ def get_remaining_leaves(allocation):
|
||||
'employee': allocation.employee,
|
||||
'leave_type': allocation.leave_type,
|
||||
'to_date': ('<=', allocation.to_date),
|
||||
'docstatus': 1
|
||||
}, fieldname=['SUM(leaves)'])
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -159,7 +168,8 @@ def expire_allocation(allocation, expiry_date=None):
|
||||
def expire_carried_forward_allocation(allocation):
|
||||
''' Expires remaining leaves in the on carried forward allocation '''
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period
|
||||
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type, allocation.from_date, allocation.to_date)
|
||||
leaves_taken = get_leaves_for_period(allocation.employee, allocation.leave_type,
|
||||
allocation.from_date, allocation.to_date, do_not_skip_expired_leaves=True)
|
||||
leaves = flt(allocation.leaves) + flt(leaves_taken)
|
||||
|
||||
# allow expired leaves entry to be created
|
||||
|
||||
@@ -249,7 +249,7 @@ const submit_salary_slip = function (frm) {
|
||||
|
||||
let make_bank_entry = function (frm) {
|
||||
var doc = frm.doc;
|
||||
if (doc.company && doc.start_date && doc.end_date && doc.payment_account) {
|
||||
if (doc.payment_account) {
|
||||
return frappe.call({
|
||||
doc: cur_frm.doc,
|
||||
method: "make_payment_entry",
|
||||
@@ -262,7 +262,8 @@ let make_bank_entry = function (frm) {
|
||||
freeze_message: __("Creating Payment Entries......")
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__("Company, Payment Account, From Date and To Date is mandatory"));
|
||||
frappe.msgprint(__("Payment Account is mandatory"));
|
||||
frm.scroll_to_field('payment_account');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -149,7 +149,7 @@ def get_existing_assignments(employees, salary_structure, from_date):
|
||||
return salary_structures_assignments
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0):
|
||||
def make_salary_slip(source_name, target_doc = None, employee = None, as_print = False, print_format = None, for_preview=0, ignore_permissions=False):
|
||||
def postprocess(source, target):
|
||||
if employee:
|
||||
employee_details = frappe.db.get_value("Employee", employee,
|
||||
@@ -169,7 +169,7 @@ def make_salary_slip(source_name, target_doc = None, employee = None, as_print =
|
||||
"name": "salary_structure"
|
||||
}
|
||||
}
|
||||
}, target_doc, postprocess, ignore_child_tables=True)
|
||||
}, target_doc, postprocess, ignore_child_tables=True, ignore_permissions=ignore_permissions)
|
||||
|
||||
if cint(as_print):
|
||||
doc.name = 'Preview for {0}'.format(employee)
|
||||
|
||||
@@ -8,9 +8,6 @@ from frappe.utils import flt
|
||||
from erpnext.hr.doctype.leave_application.leave_application \
|
||||
import get_leave_balance_on, get_leaves_for_period
|
||||
|
||||
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary \
|
||||
import get_department_leave_approver_map
|
||||
|
||||
def execute(filters=None):
|
||||
leave_types = frappe.db.sql_list("select name from `tabLeave Type` order by name asc")
|
||||
|
||||
@@ -28,6 +25,8 @@ def get_columns(leave_types):
|
||||
|
||||
for leave_type in leave_types:
|
||||
columns.append(_(leave_type) + " " + _("Opening") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Allocated") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Expired") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Taken") + ":Float:160")
|
||||
columns.append(_(leave_type) + " " + _("Balance") + ":Float:160")
|
||||
|
||||
@@ -68,18 +67,97 @@ def get_data(filters, leave_types):
|
||||
row = [employee.name, employee.employee_name, employee.department]
|
||||
|
||||
for leave_type in leave_types:
|
||||
# leaves taken
|
||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
|
||||
# opening balance
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
|
||||
|
||||
# closing balance
|
||||
closing = max(opening - leaves_taken, 0)
|
||||
|
||||
row += [opening, leaves_taken, closing]
|
||||
row += calculate_leaves_details(filters, leave_type, employee)
|
||||
|
||||
data.append(row)
|
||||
return data
|
||||
|
||||
return data
|
||||
def calculate_leaves_details(filters, leave_type, employee):
|
||||
ledger_entries = get_leave_ledger_entries(filters.from_date, filters.to_date, employee.name, leave_type)
|
||||
|
||||
#Leaves Deducted consist of both expired and leaves taken
|
||||
leaves_deducted = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
|
||||
# removing expired leaves
|
||||
leaves_taken = leaves_deducted - remove_expired_leave(ledger_entries)
|
||||
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
|
||||
|
||||
new_allocation , expired_allocation = get_allocated_and_expired_leaves(ledger_entries, filters.from_date, filters.to_date)
|
||||
|
||||
#removing leaves taken from expired_allocation
|
||||
expired_leaves = max(expired_allocation - leaves_taken, 0)
|
||||
|
||||
#Formula for calculating closing balance
|
||||
closing = max(opening + new_allocation - (leaves_taken + expired_leaves), 0)
|
||||
|
||||
return [opening, new_allocation, expired_leaves, leaves_taken, closing]
|
||||
|
||||
|
||||
def remove_expired_leave(records):
|
||||
expired_within_period = 0
|
||||
for record in records:
|
||||
if record.is_expired:
|
||||
expired_within_period += record.leaves
|
||||
return expired_within_period * -1
|
||||
|
||||
|
||||
def get_allocated_and_expired_leaves(records, from_date, to_date):
|
||||
|
||||
from frappe.utils import getdate
|
||||
|
||||
new_allocation = 0
|
||||
expired_leaves = 0
|
||||
|
||||
for record in records:
|
||||
if record.to_date <= getdate(to_date) and record.leaves>0:
|
||||
expired_leaves += record.leaves
|
||||
|
||||
if record.from_date >= getdate(from_date) and record.leaves>0:
|
||||
new_allocation += record.leaves
|
||||
|
||||
return new_allocation, expired_leaves
|
||||
|
||||
def get_leave_ledger_entries(from_date, to_date, employee, leave_type):
|
||||
records= frappe.db.sql("""
|
||||
SELECT
|
||||
employee, leave_type, from_date, to_date, leaves, transaction_name, transaction_type
|
||||
is_carry_forward, is_expired
|
||||
FROM `tabLeave Ledger Entry`
|
||||
WHERE employee=%(employee)s AND leave_type=%(leave_type)s
|
||||
AND docstatus=1
|
||||
AND (from_date between %(from_date)s AND %(to_date)s
|
||||
OR to_date between %(from_date)s AND %(to_date)s
|
||||
OR (from_date < %(from_date)s AND to_date > %(to_date)s))
|
||||
""", {
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"employee": employee,
|
||||
"leave_type": leave_type
|
||||
}, as_dict=1)
|
||||
|
||||
return records
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
conditions=''
|
||||
if department:
|
||||
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
|
||||
|
||||
# get current department and all its child
|
||||
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
|
||||
|
||||
# retrieve approvers list from current department and from its subsequent child departments
|
||||
approver_list = frappe.get_all('Department Approver', filters={
|
||||
'parentfield': 'leave_approvers',
|
||||
'parent': ('in', department_list)
|
||||
}, fields=['parent', 'approver'], as_list=1)
|
||||
|
||||
approvers = {}
|
||||
|
||||
for k, v in approver_list:
|
||||
approvers.setdefault(k, []).append(v)
|
||||
|
||||
return approvers
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
from frappe.utils import flt
|
||||
from frappe import _
|
||||
from erpnext.hr.doctype.leave_application.leave_application import get_leaves_for_period, get_leave_balance_on
|
||||
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import calculate_leaves_details , get_department_leave_approver_map
|
||||
|
||||
def execute(filters=None):
|
||||
if filters.to_date <= filters.from_date:
|
||||
@@ -38,17 +39,27 @@ def get_columns():
|
||||
'label': _('Opening Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'opening_balance',
|
||||
'width': 160,
|
||||
'width': 120,
|
||||
}, {
|
||||
'label': _('New Allocation'),
|
||||
'fieldtype': 'Float',
|
||||
'fieldname': 'new_allocation',
|
||||
'width': 120,
|
||||
}, {
|
||||
'label': _('Expired Leaves'),
|
||||
'fieldtype': 'Float',
|
||||
'fieldname': 'expired_leaves',
|
||||
'width': 120,
|
||||
}, {
|
||||
'label': _('Leaves Taken'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'leaves_taken',
|
||||
'width': 160,
|
||||
'width': 120,
|
||||
}, {
|
||||
'label': _('Closing Balance'),
|
||||
'fieldtype': 'float',
|
||||
'fieldname': 'closing_balance',
|
||||
'width': 160,
|
||||
'width': 120,
|
||||
}]
|
||||
|
||||
return columns
|
||||
@@ -72,7 +83,7 @@ def get_data(filters):
|
||||
'leave_type': leave_type
|
||||
})
|
||||
for employee in active_employees:
|
||||
|
||||
|
||||
leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver)
|
||||
|
||||
if (leave_approvers and len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \
|
||||
@@ -82,16 +93,13 @@ def get_data(filters):
|
||||
'employee_name': employee.employee_name
|
||||
})
|
||||
|
||||
leaves_taken = get_leaves_for_period(employee.name, leave_type,
|
||||
filters.from_date, filters.to_date) * -1
|
||||
leave_details = calculate_leaves_details(filters, leave_type, employee)
|
||||
row.opening_balance = flt(leave_details[0])
|
||||
row.new_allocation = flt(leave_details[1])
|
||||
row.expired_leaves = flt(leave_details[2])
|
||||
row.leaves_taken = flt(leave_details[3])
|
||||
row.closing_balance = flt(leave_details[4])
|
||||
|
||||
opening = get_leave_balance_on(employee.name, leave_type, filters.from_date)
|
||||
closing = get_leave_balance_on(employee.name, leave_type, filters.to_date)
|
||||
|
||||
row.opening_balance = opening
|
||||
row.leaves_taken = leaves_taken
|
||||
row.closing_balance = closing
|
||||
row.indent = 1
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
@@ -108,23 +116,3 @@ def get_conditions(filters):
|
||||
|
||||
return conditions
|
||||
|
||||
def get_department_leave_approver_map(department=None):
|
||||
conditions=''
|
||||
if department:
|
||||
conditions="and (department_name = '%(department)s' or parent_department = '%(department)s')"%{'department': department}
|
||||
|
||||
# get current department and all its child
|
||||
department_list = frappe.db.sql_list(""" SELECT name FROM `tabDepartment` WHERE disabled=0 {0}""".format(conditions)) #nosec
|
||||
|
||||
# retrieve approvers list from current department and from its subsequent child departments
|
||||
approver_list = frappe.get_all('Department Approver', filters={
|
||||
'parentfield': 'leave_approvers',
|
||||
'parent': ('in', department_list)
|
||||
}, fields=['parent', 'approver'], as_list=1)
|
||||
|
||||
approvers = {}
|
||||
|
||||
for k, v in approver_list:
|
||||
approvers.setdefault(k, []).append(v)
|
||||
|
||||
return approvers
|
||||
|
||||
@@ -175,6 +175,12 @@ frappe.ui.form.on("BOM", {
|
||||
});
|
||||
},
|
||||
|
||||
rm_cost_as_per: function(frm) {
|
||||
if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
|
||||
frm.set_value("plc_conversion_rate", 1.0);
|
||||
}
|
||||
},
|
||||
|
||||
routing: function(frm) {
|
||||
if (frm.doc.routing) {
|
||||
frappe.call({
|
||||
@@ -205,7 +211,7 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({
|
||||
item_code: function(doc, cdt, cdn){
|
||||
var scrap_items = false;
|
||||
var child = locals[cdt][cdn];
|
||||
if(child.doctype == 'BOM Scrap Item') {
|
||||
if (child.doctype == 'BOM Scrap Item') {
|
||||
scrap_items = true;
|
||||
}
|
||||
|
||||
@@ -215,8 +221,19 @@ erpnext.bom.BomController = erpnext.TransactionController.extend({
|
||||
|
||||
get_bom_material_detail(doc, cdt, cdn, scrap_items);
|
||||
},
|
||||
|
||||
buying_price_list: function(doc) {
|
||||
this.apply_price_list();
|
||||
},
|
||||
|
||||
plc_conversion_rate: function(doc) {
|
||||
if (!this.in_apply_price_list) {
|
||||
this.apply_price_list();
|
||||
}
|
||||
},
|
||||
|
||||
conversion_factor: function(doc, cdt, cdn) {
|
||||
if(frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
|
||||
if (frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
|
||||
item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"creation": "2013-01-22 15:11:38",
|
||||
"doctype": "DocType",
|
||||
@@ -6,23 +7,25 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item",
|
||||
"quantity",
|
||||
"set_rate_of_sub_assembly_item_based_on_bom",
|
||||
"company",
|
||||
"item_name",
|
||||
"uom",
|
||||
"cb0",
|
||||
"is_active",
|
||||
"is_default",
|
||||
"allow_alternative_item",
|
||||
"image",
|
||||
"item_name",
|
||||
"uom",
|
||||
"currency_detail",
|
||||
"company",
|
||||
"set_rate_of_sub_assembly_item_based_on_bom",
|
||||
"project",
|
||||
"quantity",
|
||||
"image",
|
||||
"currency_detail",
|
||||
"currency",
|
||||
"conversion_rate",
|
||||
"column_break_12",
|
||||
"currency",
|
||||
"rm_cost_as_per",
|
||||
"buying_price_list",
|
||||
"price_list_currency",
|
||||
"plc_conversion_rate",
|
||||
"section_break_21",
|
||||
"with_operations",
|
||||
"column_break_23",
|
||||
@@ -176,7 +179,8 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "currency_detail",
|
||||
"fieldtype": "Section Break"
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Currency and Price List"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@@ -324,7 +328,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "base_scrap_material_cost",
|
||||
"fieldtype": "Data",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Scrap Material Cost(Company Currency)",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
@@ -477,13 +481,31 @@
|
||||
{
|
||||
"fieldname": "column_break_52",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:doc.rm_cost_as_per=='Price List'",
|
||||
"fieldname": "plc_conversion_rate",
|
||||
"fieldtype": "Float",
|
||||
"label": "Price List Exchange Rate"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:doc.rm_cost_as_per=='Price List'",
|
||||
"fieldname": "price_list_currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Price List Currency",
|
||||
"options": "Currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-sitemap",
|
||||
"idx": 1,
|
||||
"image_field": "image",
|
||||
"is_submittable": 1,
|
||||
"modified": "2019-11-22 14:35:12.142150",
|
||||
"links": [],
|
||||
"modified": "2020-05-05 14:29:32.634952",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "BOM",
|
||||
|
||||
@@ -55,11 +55,13 @@ class BOM(WebsiteGenerator):
|
||||
self.validate_main_item()
|
||||
self.validate_currency()
|
||||
self.set_conversion_rate()
|
||||
self.set_plc_conversion_rate()
|
||||
self.validate_uom_is_interger()
|
||||
self.set_bom_material_details()
|
||||
self.validate_materials()
|
||||
self.validate_operations()
|
||||
self.calculate_cost()
|
||||
self.update_cost(update_parent=False, from_child_bom=True, save=False)
|
||||
|
||||
def get_context(self, context):
|
||||
context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
|
||||
@@ -155,7 +157,7 @@ class BOM(WebsiteGenerator):
|
||||
'rate' : rate,
|
||||
'qty' : args.get("qty") or args.get("stock_qty") or 1,
|
||||
'stock_qty' : args.get("qty") or args.get("stock_qty") or 1,
|
||||
'base_rate' : rate,
|
||||
'base_rate' : flt(rate) * (flt(self.conversion_rate) or 1),
|
||||
'include_item_in_manufacturing': cint(args['transfer_for_manufacture']) or 0
|
||||
}
|
||||
|
||||
@@ -183,7 +185,7 @@ class BOM(WebsiteGenerator):
|
||||
if self.rm_cost_as_per == 'Valuation Rate':
|
||||
rate = self.get_valuation_rate(arg) * (arg.get("conversion_factor") or 1)
|
||||
elif self.rm_cost_as_per == 'Last Purchase Rate':
|
||||
rate = (arg.get('last_purchase_rate') \
|
||||
rate = flt(arg.get('last_purchase_rate') \
|
||||
or frappe.db.get_value("Item", arg['item_code'], "last_purchase_rate")) \
|
||||
* (arg.get("conversion_factor") or 1)
|
||||
elif self.rm_cost_as_per == "Price List":
|
||||
@@ -216,7 +218,7 @@ class BOM(WebsiteGenerator):
|
||||
frappe.msgprint(_("{0} not found for item {1}")
|
||||
.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
|
||||
|
||||
return flt(rate) / (self.conversion_rate or 1)
|
||||
return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
|
||||
|
||||
def update_cost(self, update_parent=True, from_child_bom=False, save=True):
|
||||
if self.docstatus == 2:
|
||||
@@ -233,15 +235,21 @@ class BOM(WebsiteGenerator):
|
||||
"stock_uom": d.stock_uom,
|
||||
"conversion_factor": d.conversion_factor
|
||||
})
|
||||
|
||||
if rate:
|
||||
d.rate = rate
|
||||
d.amount = flt(d.rate) * flt(d.qty)
|
||||
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
|
||||
d.base_amount = flt(d.amount) * flt(self.conversion_rate)
|
||||
|
||||
if save:
|
||||
d.db_update()
|
||||
|
||||
if self.docstatus == 1:
|
||||
self.flags.ignore_validate_update_after_submit = True
|
||||
self.calculate_cost()
|
||||
if save:
|
||||
self.save()
|
||||
self.db_update()
|
||||
self.update_exploded_items()
|
||||
|
||||
# update parent BOMs
|
||||
@@ -361,6 +369,13 @@ class BOM(WebsiteGenerator):
|
||||
elif self.conversion_rate == 1 or flt(self.conversion_rate) <= 0:
|
||||
self.conversion_rate = get_exchange_rate(self.currency, self.company_currency(), args="for_buying")
|
||||
|
||||
def set_plc_conversion_rate(self):
|
||||
if self.rm_cost_as_per in ["Valuation Rate", "Last Purchase Rate"]:
|
||||
self.plc_conversion_rate = 1
|
||||
elif not self.plc_conversion_rate and self.price_list_currency:
|
||||
self.plc_conversion_rate = get_exchange_rate(self.price_list_currency,
|
||||
self.company_currency(), args="for_buying")
|
||||
|
||||
def validate_materials(self):
|
||||
""" Validate raw material entries """
|
||||
|
||||
|
||||
@@ -81,13 +81,13 @@ class TestBOM(unittest.TestCase):
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.operating_cost, 100)
|
||||
self.assertEqual(bom.raw_material_cost, 8000)
|
||||
self.assertEqual(bom.total_cost, 8100)
|
||||
self.assertEqual(bom.raw_material_cost, 351.68)
|
||||
self.assertEqual(bom.total_cost, 451.68)
|
||||
|
||||
# test amounts in selected currency
|
||||
self.assertEqual(bom.base_operating_cost, 6000)
|
||||
self.assertEqual(bom.base_raw_material_cost, 480000)
|
||||
self.assertEqual(bom.base_total_cost, 486000)
|
||||
self.assertEqual(bom.base_raw_material_cost, 21100.80)
|
||||
self.assertEqual(bom.base_total_cost, 27100.80)
|
||||
|
||||
def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
|
||||
frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user