diff --git a/erpnext/__init__.py b/erpnext/__init__.py index cb88a1122ea..cd1545c7e81 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -5,7 +5,7 @@ import frappe from erpnext.hooks import regional_overrides from frappe.utils import getdate -__version__ = '11.1.39' +__version__ = '12.1.2' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py index 648cc68dac3..bc07b6d8077 100644 --- a/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py +++ b/erpnext/accounts/dashboard_chart_source/account_balance_timeline/account_balance_timeline.py @@ -12,11 +12,14 @@ from frappe.utils.nestedset import get_descendants_of @frappe.whitelist() @cache_source -def get(chart_name=None, from_date = None, to_date = None): - chart = frappe.get_doc('Dashboard Chart', chart_name) +def get(chart_name = None, chart = None, no_cache = None, from_date = None, to_date = None): + if chart_name: + chart = frappe.get_doc('Dashboard Chart', chart_name) + else: + chart = frappe._dict(frappe.parse_json(chart)) timespan = chart.timespan timegrain = chart.time_interval - filters = json.loads(chart.filters_json) + filters = frappe.parse_json(chart.filters_json) account = filters.get("account") company = filters.get("company") diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 0e57b3f1986..1adc4c4d2f8 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -128,7 +128,8 @@ class Account(NestedSet): "account_currency": self.account_currency, "parent_account": parent_acc_name_map[company] }) - doc.save() + if not self.check_if_child_acc_exists(doc): + doc.save() frappe.msgprint(_("Account {0} is added in the child company {1}") .format(doc.name, company)) @@ -172,6 +173,24 @@ class Account(NestedSet): if frappe.db.get_value("GL Entry", {"account": self.name}): frappe.throw(_("Currency can not be changed after making entries using some other currency")) + def check_if_child_acc_exists(self, doc): + ''' Checks if a account in parent company exists in the ''' + info = frappe.db.get_value("Account", { + "account_name": doc.account_name, + "account_number": doc.account_number + }, ['company', 'account_currency', 'is_group', 'root_type', 'account_type', 'balance_must_be', 'account_name'], as_dict=1) + + if not info: + return + + doc = vars(doc) + dict_diff = [k for k in info if k in doc and info[k] != doc[k] and k != "company"] + if dict_diff: + frappe.throw(_("Account {0} already exists in child company {1}. The following fields have different values, they should be same:") + .format(info.account_name, info.company, '
  • '.join(dict_diff))) + else: + return True + def convert_group_to_ledger(self): if self.check_if_child_exists(): throw(_("Account with child nodes cannot be converted to ledger")) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 6fdd7974376..efac1af551e 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -123,7 +123,8 @@ frappe.treeview_settings["Account"] = { if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ // show Dr if positive since balance is calculated as debit - credit else show Cr - let dr_or_cr = node.data.balance_in_account_currency > 0 ? "Dr": "Cr"; + let balance = node.data.balance_in_account_currency || node.data.balance; + let dr_or_cr = balance > 0 ? "Dr": "Cr"; if (node.data && node.data.balance!==undefined) { $('' diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json index b042bcc4204..adfa9f89e23 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/de_kontenplan_SKR04_with_account_number.json @@ -406,7 +406,11 @@ "is_group": 1, "Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": { "account_number": "9960" - }, + }, + "Debitoren": { + "is_group": 1, + "account_number": "10000" + }, "Forderungen aus Lieferungen und Leistungen": { "account_number": "1200", "account_type": "Receivable" @@ -1077,7 +1081,7 @@ } } }, - "C - Verb.": { + "C - Verbindlichkeiten": { "account_type": "Payable", "1 - Anleihen": { "is_group": 1, @@ -1193,7 +1197,15 @@ "is_group": 1, "Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": { "account_number": "9964" - }, + }, + "Kreditoren": { + "account_number": "70000", + "is_group": 1, + "Wareneingangs-­Verrechnungskonto" : { + "account_number": "70001", + "account_type": "Stock Received But Not Billed" + } + }, "Verb. aus Lieferungen und Leistungen": { "account_number": "3300", "account_type": "Payable" @@ -1682,90 +1694,6 @@ "account_type": "Income Account" } }, - "Erl\u00f6sschm\u00e4lerungen (Gruppe)": { - "is_group": 1, - "Erl\u00f6sschm\u00e4lerungen": { - "account_number": "4700" - }, - "Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": { - "account_number": "4705" - }, - "Erl\u00f6sschm\u00e4lerungen 7 % USt": { - "account_number": "4710" - }, - "Erl\u00f6sschm\u00e4lerungen 19 % USt": { - "account_number": "4720" - }, - "Erl\u00f6sschm\u00e4lerungen 16 % USt": { - "account_number": "4723" - }, - "Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": { - "account_number": "4724" - }, - "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": { - "account_number": "4725" - }, - "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": { - "account_number": "4726" - }, - "Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": { - "account_number": "4727" - }, - "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { - "account_number": "4729" - }, - "Gew\u00e4hrte Skonti (Gruppe)": { - "is_group": 1, - "Gew. Skonti": { - "account_number": "4730" - }, - "Gew. Skonti 7 % USt": { - "account_number": "4731" - }, - "Gew. Skonti 19 % USt": { - "account_number": "4736" - }, - "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4738" - }, - "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { - "account_number": "4741" - }, - "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { - "account_number": "4742" - }, - "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { - "account_number": "4743" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { - "account_number": "4745" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { - "account_number": "4746" - }, - "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { - "account_number": "4748" - } - }, - "Gew\u00e4hrte Boni 7 % USt": { - "account_number": "4750" - }, - "Gew\u00e4hrte Boni 19 % USt": { - "account_number": "4760" - }, - "Gew\u00e4hrte Boni": { - "account_number": "4769" - }, - "Gew\u00e4hrte Rabatte": { - "account_number": "4770" - }, - "Gew\u00e4hrte Rabatte 7 % USt": { - "account_number": "4780" - }, - "Gew\u00e4hrte Rabatte 19 % USt": { - "account_number": "4790" - } - }, "Grundst\u00fccksertr\u00e4ge (Gruppe)": { "is_group": 1, "Grundst\u00fccksertr\u00e4ge": { @@ -2049,48 +1977,6 @@ "Erh. Skonti aus Erwerb Waren als letzter Abnehmer innerh. Dreiecksgesch. 19% Vorst. u. 19% Ust.": { "account_number": "5793" } - }, - "Erhaltene Boni (Gruppe)": { - "is_group": 1, - "Erhaltene Boni 7 % Vorsteuer": { - "account_number": "5750" - }, - "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { - "account_number": "5753" - }, - "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { - "account_number": "5754" - }, - "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { - "account_number": "5755" - }, - "Erhaltene Boni 19 % Vorsteuer": { - "account_number": "5760" - }, - "Erhaltene Boni": { - "account_number": "5769" - } - }, - "Erhaltene Rabatte (Gruppe)": { - "is_group": 1, - "Erhaltene Rabatte": { - "account_number": "5770" - }, - "Erhaltene Rabatte 7 % Vorsteuer": { - "account_number": "5780" - }, - "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { - "account_number": "5783" - }, - "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { - "account_number": "5784" - }, - "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { - "account_number": "5785" - }, - "Erhaltene Rabatte 19 % Vorsteuer": { - "account_number": "5790" - } } }, "Bezugsnebenkosten (Gruppe)": { @@ -2409,7 +2295,49 @@ }, "6 - sonstige betriebliche Ertr\u00e4ge": { "root_type": "Income", - "is_group": 1, + "is_group": 1, + "Erhaltene Boni (Gruppe)": { + "is_group": 1, + "Erhaltene Boni 7 % Vorsteuer": { + "account_number": "5750" + }, + "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { + "account_number": "5753" + }, + "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { + "account_number": "5754" + }, + "Erhaltene Boni aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { + "account_number": "5755" + }, + "Erhaltene Boni 19 % Vorsteuer": { + "account_number": "5760" + }, + "Erhaltene Boni": { + "account_number": "5769" + } + }, + "Erhaltene Rabatte (Gruppe)": { + "is_group": 1, + "Erhaltene Rabatte": { + "account_number": "5770" + }, + "Erhaltene Rabatte 7 % Vorsteuer": { + "account_number": "5780" + }, + "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe": { + "account_number": "5783" + }, + "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 7% Vorsteuer": { + "account_number": "5784" + }, + "Erhaltene Rabatte aus Einkauf Roh-, Hilfs- und Betriebsstoffe 19% Vorsteuer": { + "account_number": "5785" + }, + "Erhaltene Rabatte 19 % Vorsteuer": { + "account_number": "5790" + } + }, "Andere aktivierte Eigenleistungen": { "account_number": "4820" }, @@ -2732,7 +2660,91 @@ }, "7 - sonstige betriebliche Aufwendungen": { "root_type": "Expense", - "is_group": 1, + "is_group": 1, + "Erl\u00f6sschm\u00e4lerungen (Gruppe)": { + "is_group": 1, + "Erl\u00f6sschm\u00e4lerungen": { + "account_number": "4700" + }, + "Erl\u00f6sschm\u00e4lerungen aus steuerfreien Ums\u00e4tzen \u00a7 4 Nr. 1a UStG": { + "account_number": "4705" + }, + "Erl\u00f6sschm\u00e4lerungen 7 % USt": { + "account_number": "4710" + }, + "Erl\u00f6sschm\u00e4lerungen 19 % USt": { + "account_number": "4720" + }, + "Erl\u00f6sschm\u00e4lerungen 16 % USt": { + "account_number": "4723" + }, + "Erl\u00f6sschm\u00e4lerungen aus steuerfreien innergem. Lieferungen": { + "account_number": "4724" + }, + "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 7 % USt": { + "account_number": "4725" + }, + "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 19 % USt": { + "account_number": "4726" + }, + "Erl\u00f6sschm\u00e4lerungen aus im anderen EU-Land steuerpfl. Lieferungen": { + "account_number": "4727" + }, + "Erl\u00f6sschm\u00e4lerungen aus im Inland steuerpfl. EU-Lieferungen 16 % USt": { + "account_number": "4729" + }, + "Gew\u00e4hrte Skonti (Gruppe)": { + "is_group": 1, + "Gew. Skonti": { + "account_number": "4730" + }, + "Gew. Skonti 7 % USt": { + "account_number": "4731" + }, + "Gew. Skonti 19 % USt": { + "account_number": "4736" + }, + "Gew. Skonti aus Lieferungen von Mobilfunkger./Schaltkr., f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4738" + }, + "Gew. Skonti aus Leistungen, f. die der Leistungsempf. die Umsatzsteuer nach \u00a7 13b UStG schuldet": { + "account_number": "4741" + }, + "Gew. Skonti aus Erl\u00f6sen aus im anderen EU-Land steuerpfl. Leistungen, f. die der Leistungsempf. die Ust. schuldet": { + "account_number": "4742" + }, + "Gew. Skonti aus steuerfreien innergem. Lieferungen \u00a7 4 Nr. 1b UStG": { + "account_number": "4743" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen": { + "account_number": "4745" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 7% USt": { + "account_number": "4746" + }, + "Gew. Skonti aus im Inland steuerpfl. EU-Lieferungen 19% USt": { + "account_number": "4748" + } + }, + "Gew\u00e4hrte Boni 7 % USt": { + "account_number": "4750" + }, + "Gew\u00e4hrte Boni 19 % USt": { + "account_number": "4760" + }, + "Gew\u00e4hrte Boni": { + "account_number": "4769" + }, + "Gew\u00e4hrte Rabatte": { + "account_number": "4770" + }, + "Gew\u00e4hrte Rabatte 7 % USt": { + "account_number": "4780" + }, + "Gew\u00e4hrte Rabatte 19 % USt": { + "account_number": "4790" + } + }, "Sonstige betriebliche Aufwendungen": { "account_number": "6300" }, @@ -3609,18 +3621,6 @@ "Ertr\u00e4ge aus der Aufl\u00f6sung von R\u00fcckstellungen f. sonstige Steuern": { "account_number": "7694" } - }, - "Debitoren": { - "root_type": "Asset", - "is_group": 1 - }, - "Kreditoren": { - "root_type": "Liability", - "is_group": 1, - "Wareneingangs-­Verrechnungskonto" : { - "account_number": "70001", - "account_type": "Stock Received But Not Billed" - } } } } diff --git a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json index 1ccef6cc7a5..e9e1f43f990 100644 --- a/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json +++ b/erpnext/accounts/doctype/accounting_dimension_detail/accounting_dimension_detail.json @@ -17,8 +17,7 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Company", - "options": "Company", - "reqd": 1 + "options": "Company" }, { "fieldname": "reference_document", @@ -34,8 +33,7 @@ "fieldtype": "Dynamic Link", "in_list_view": 1, "label": "Default Dimension", - "options": "reference_document", - "reqd": 1 + "options": "reference_document" }, { "columns": 3, @@ -55,7 +53,7 @@ } ], "istable": 1, - "modified": "2019-07-17 23:34:33.026883", + "modified": "2019-08-15 11:59:09.389891", "modified_by": "Administrator", "module": "Accounts", "name": "Accounting Dimension Detail", diff --git a/erpnext/accounts/doctype/bank/bank_dashboard.py b/erpnext/accounts/doctype/bank/bank_dashboard.py index 4a1dad89526..1e2383de5fd 100644 --- a/erpnext/accounts/doctype/bank/bank_dashboard.py +++ b/erpnext/accounts/doctype/bank/bank_dashboard.py @@ -8,7 +8,7 @@ def get_data(): 'fieldname': 'bank', 'transactions': [ { - 'label': _('Bank Deatils'), + 'label': _('Bank Details'), 'items': ['Bank Account', 'Bank Guarantee'] } ] diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py index f943b345810..b8ebebaa93d 100644 --- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py +++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py @@ -45,12 +45,12 @@ class BankTransaction(StatusUpdater): def clear_linked_payment_entries(self): for payment_entry in self.payment_entries: allocated_amount = get_total_allocated_amount(payment_entry) - paid_amount = get_paid_amount(payment_entry) + paid_amount = get_paid_amount(payment_entry, self.currency) if paid_amount and allocated_amount: if flt(allocated_amount[0]["allocated_amount"]) > flt(paid_amount): frappe.throw(_("The total allocated amount ({0}) is greated than the paid amount ({1}).".format(flt(allocated_amount[0]["allocated_amount"]), flt(paid_amount)))) - elif flt(allocated_amount[0]["allocated_amount"]) == flt(paid_amount): + else: if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]: self.clear_simple_entry(payment_entry) @@ -80,9 +80,17 @@ def get_total_allocated_amount(payment_entry): AND bt.docstatus = 1""", (payment_entry.payment_document, payment_entry.payment_entry), as_dict=True) -def get_paid_amount(payment_entry): +def get_paid_amount(payment_entry, currency): if payment_entry.payment_document in ["Payment Entry", "Sales Invoice", "Purchase Invoice"]: - return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "paid_amount") + + paid_amount_field = "paid_amount" + if payment_entry.payment_document == 'Payment Entry': + doc = frappe.get_doc("Payment Entry", payment_entry.payment_entry) + paid_amount_field = ("base_paid_amount" + if doc.paid_to_account_currency == currency else "paid_amount") + + return frappe.db.get_value(payment_entry.payment_document, + payment_entry.payment_entry, paid_amount_field) elif payment_entry.payment_document == "Journal Entry": return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_credit") diff --git a/erpnext/accounts/doctype/budget/test_budget.py b/erpnext/accounts/doctype/budget/test_budget.py index b126b1fb0ae..33aefd67d1c 100644 --- a/erpnext/accounts/doctype/budget/test_budget.py +++ b/erpnext/accounts/doctype/budget/test_budget.py @@ -11,32 +11,32 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry -class TestBudget(unittest.TestCase): +class TestBudget(unittest.TestCase): def test_monthly_budget_crossed_ignore(self): set_total_expense_zero("2013-02-28", "Cost Center") budget = make_budget(budget_against="Cost Center") - + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) self.assertTrue(frappe.db.get_value("GL Entry", {"voucher_type": "Journal Entry", "voucher_no": jv.name})) - + budget.cancel() def test_monthly_budget_crossed_stop1(self): set_total_expense_zero("2013-02-28", "Cost Center") budget = make_budget(budget_against="Cost Center") - + frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-02-28") self.assertRaises(BudgetError, jv.submit) - + budget.load_from_db() budget.cancel() @@ -46,7 +46,7 @@ class TestBudget(unittest.TestCase): budget = make_budget(budget_against="Cost Center") frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") - + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", posting_date="2013-03-02") @@ -117,14 +117,14 @@ class TestBudget(unittest.TestCase): set_total_expense_zero("2013-02-28", "Project") budget = make_budget(budget_against="Project") - + frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 40000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-02-28") self.assertRaises(BudgetError, jv.submit) - + budget.load_from_db() budget.cancel() @@ -132,31 +132,31 @@ class TestBudget(unittest.TestCase): set_total_expense_zero("2013-02-28", "Cost Center") budget = make_budget(budget_against="Cost Center") - + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 150000, "_Test Cost Center - _TC", posting_date="2013-03-28") self.assertRaises(BudgetError, jv.submit) - + budget.cancel() def test_yearly_budget_crossed_stop2(self): set_total_expense_zero("2013-02-28", "Project") budget = make_budget(budget_against="Project") - + jv = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 150000, "_Test Cost Center - _TC", project="_Test Project", posting_date="2013-03-28") self.assertRaises(BudgetError, jv.submit) - + budget.cancel() def test_monthly_budget_on_cancellation1(self): set_total_expense_zero("2013-02-28", "Cost Center") budget = make_budget(budget_against="Cost Center") - + jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True) @@ -170,9 +170,9 @@ class TestBudget(unittest.TestCase): {"voucher_type": "Journal Entry", "voucher_no": jv2.name})) frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") - + self.assertRaises(BudgetError, jv1.cancel) - + budget.load_from_db() budget.cancel() @@ -180,7 +180,7 @@ class TestBudget(unittest.TestCase): set_total_expense_zero("2013-02-28", "Project") budget = make_budget(budget_against="Project") - + jv1 = make_journal_entry("_Test Account Cost for Goods Sold - _TC", "_Test Bank - _TC", 20000, "_Test Cost Center - _TC", posting_date="2013-02-28", submit=True, project="_Test Project") @@ -194,16 +194,16 @@ class TestBudget(unittest.TestCase): {"voucher_type": "Journal Entry", "voucher_no": jv2.name})) frappe.db.set_value("Budget", budget.name, "action_if_accumulated_monthly_budget_exceeded", "Stop") - + self.assertRaises(BudgetError, jv1.cancel) - + budget.load_from_db() 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") - + 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") @@ -211,7 +211,7 @@ class TestBudget(unittest.TestCase): "_Test Bank - _TC", 40000, "_Test Cost Center 2 - _TC", posting_date="2013-02-28") self.assertRaises(BudgetError, jv.submit) - + budget.load_from_db() budget.cancel() @@ -239,8 +239,6 @@ class TestBudget(unittest.TestCase): budget.cancel() jv.cancel() - frappe.delete_doc('Journal Entry', jv.name) - frappe.delete_doc('Cost Center', cost_center) def set_total_expense_zero(posting_date, budget_against_field=None, budget_against_CC=None): if budget_against_field == "Project": @@ -256,7 +254,7 @@ def set_total_expense_zero(posting_date, budget_against_field=None, budget_again "budget_against_field": budget_against_field, "budget_against": budget_against })) - + if existing_expense: if budget_against_field == "Cost Center": make_journal_entry("_Test Account Cost for Goods Sold - _TC", @@ -281,13 +279,13 @@ def make_budget(**args): frappe.db.sql("delete from `tabBudget Account` where parent = %(name)s", d) budget = frappe.new_doc("Budget") - + if budget_against == "Project": budget.project = "_Test Project" else: budget.cost_center =cost_center or "_Test Cost Center - _TC" - - + + budget.fiscal_year = "_Test Fiscal Year 2013" budget.monthly_distribution = "_Test Distribution" budget.company = "_Test Company" @@ -299,7 +297,7 @@ def make_budget(**args): "account": "_Test Account Cost for Goods Sold - _TC", "budget_amount": 100000 }) - + if args.applicable_on_material_request: budget.applicable_on_material_request = 1 budget.action_if_annual_budget_exceeded_on_mr = args.action_if_annual_budget_exceeded_on_mr or 'Warn' diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py index 4683c7ae466..9bf5887b38f 100644 --- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py +++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py @@ -150,7 +150,7 @@ def validate_accounts(file_name): accounts_dict = {} for account in accounts: accounts_dict.setdefault(account["account_name"], account) - if account["parent_account"] and accounts_dict[account["parent_account"]]: + if account["parent_account"] and accounts_dict.get(account["parent_account"]): accounts_dict[account["parent_account"]]["is_group"] = 1 message = validate_root(accounts_dict) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index 4da21f11fea..ff55c21497d 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.json +++ b/erpnext/accounts/doctype/cost_center/cost_center.json @@ -1,457 +1,171 @@ { - "allow_copy": 1, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:cost_center_name", - "beta": 0, - "creation": "2013-01-23 19:57:17", - "custom": 0, - "description": "Track separate Income and Expense for product verticals or divisions.", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_copy": 1, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:cost_center_name", + "creation": "2013-01-23 19:57:17", + "description": "Track separate Income and Expense for product verticals or divisions.", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "sb0", + "cost_center_name", + "cost_center_number", + "parent_cost_center", + "company", + "cb0", + "is_group", + "enabled", + "lft", + "rgt", + "old_parent" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb0", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "sb0", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cost_center_name", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Cost Center Name", - "length": 0, - "no_copy": 1, - "oldfieldname": "cost_center_name", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "fieldname": "cost_center_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Cost Center Name", + "no_copy": 1, + "oldfieldname": "cost_center_name", + "oldfieldtype": "Data", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cost_center_number", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Cost Center Number", - "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 - }, + "fieldname": "cost_center_number", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Cost Center Number", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "parent_cost_center", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Parent Cost Center", - "length": 0, - "no_copy": 0, - "oldfieldname": "parent_cost_center", - "oldfieldtype": "Link", - "options": "Cost Center", - "permlevel": 0, - "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 - }, + "fieldname": "parent_cost_center", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_list_view": 1, + "label": "Parent Cost Center", + "oldfieldname": "parent_cost_center", + "oldfieldtype": "Link", + "options": "Cost Center", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company_name", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company_name", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb0", - "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, - "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": "cb0", + "fieldtype": "Column Break", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "is_group", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Group", - "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 - }, + "default": "0", + "fieldname": "is_group", + "fieldtype": "Check", + "label": "Is Group" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "lft", - "fieldtype": "Int", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "lft", - "length": 0, - "no_copy": 1, - "oldfieldname": "lft", - "oldfieldtype": "Int", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "lft", + "fieldtype": "Int", + "hidden": 1, + "label": "lft", + "no_copy": 1, + "oldfieldname": "lft", + "oldfieldtype": "Int", + "print_hide": 1, + "report_hide": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rgt", - "fieldtype": "Int", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "rgt", - "length": 0, - "no_copy": 1, - "oldfieldname": "rgt", - "oldfieldtype": "Int", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "rgt", + "fieldtype": "Int", + "hidden": 1, + "label": "rgt", + "no_copy": 1, + "oldfieldname": "rgt", + "oldfieldtype": "Int", + "print_hide": 1, + "report_hide": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "old_parent", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "old_parent", - "length": 0, - "no_copy": 1, - "oldfieldname": "old_parent", - "oldfieldtype": "Data", - "options": "Cost Center", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "old_parent", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "old_parent", + "no_copy": 1, + "oldfieldname": "old_parent", + "oldfieldtype": "Data", + "options": "Cost Center", + "print_hide": 1, + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-money", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2018-04-26 15:26:25.325778", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Cost Center", - "owner": "Administrator", + ], + "icon": "fa fa-money", + "idx": 1, + "modified": "2019-08-22 15:05:05.559862", + "modified_by": "sammish.thundiyil@gmail.com", + "module": "Accounts", + "name": "Cost Center", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "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, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Auditor", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "export": 1, + "read": 1, + "report": 1, + "role": "Auditor" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "role": "Sales User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 0, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "role": "Purchase User" } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "parent_cost_center, is_group", - "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0 -} \ No newline at end of file + ], + "quick_entry": 1, + "search_fields": "parent_cost_center, is_group", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "ASC" +} diff --git a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json index 8d7ed74eb9e..04d6303774a 100644 --- a/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json +++ b/erpnext/accounts/doctype/discounted_invoice/discounted_invoice.json @@ -8,7 +8,8 @@ "customer", "column_break_3", "posting_date", - "outstanding_amount" + "outstanding_amount", + "debit_to" ], "fields": [ { @@ -48,10 +49,18 @@ { "fieldname": "column_break_3", "fieldtype": "Column Break" + }, + { + "fetch_from": "sales_invoice.debit_to", + "fieldname": "debit_to", + "fieldtype": "Link", + "label": "Debit to", + "options": "Account", + "read_only": 1 } ], "istable": 1, - "modified": "2019-05-30 19:27:29.436153", + "modified": "2019-08-07 15:13:55.808349", "modified_by": "Administrator", "module": "Accounts", "name": "Discounted Invoice", diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js index 5563f031bb9..f1f88a8d649 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.js @@ -13,41 +13,57 @@ frappe.ui.form.on('Invoice Discounting', { }; }); - frm.events.filter_accounts("bank_account", frm, {"account_type": "Bank"}); - frm.events.filter_accounts("bank_charges_account", frm, {"root_type": "Expense"}); - frm.events.filter_accounts("short_term_loan", frm, {"root_type": "Liability"}); - frm.events.filter_accounts("accounts_receivable_credit", frm, {"account_type": "Receivable"}); - frm.events.filter_accounts("accounts_receivable_discounted", frm, {"account_type": "Receivable"}); - frm.events.filter_accounts("accounts_receivable_unpaid", frm, {"account_type": "Receivable"}); + + frm.events.filter_accounts("bank_account", frm, [["account_type", "=", "Bank"]]); + frm.events.filter_accounts("bank_charges_account", frm, [["root_type", "=", "Expense"]]); + frm.events.filter_accounts("short_term_loan", frm, [["root_type", "=", "Liability"]]); + frm.events.filter_accounts("accounts_receivable_discounted", frm, [["account_type", "=", "Receivable"]]); + frm.events.filter_accounts("accounts_receivable_credit", frm, [["account_type", "=", "Receivable"]]); + frm.events.filter_accounts("accounts_receivable_unpaid", frm, [["account_type", "=", "Receivable"]]); }, filter_accounts: (fieldname, frm, addl_filters) => { - let filters = { - "company": frm.doc.company, - "is_group": 0 - }; - if(addl_filters) Object.assign(filters, addl_filters); + let filters = [ + ["company", "=", frm.doc.company], + ["is_group", "=", 0] + ]; + if(addl_filters){ + filters = $.merge(filters , addl_filters); + } frm.set_query(fieldname, () => { return { "filters": filters }; }); }, + refresh_filters: (frm) =>{ + let invoice_accounts = Object.keys(frm.doc.invoices).map(function(key) { + return frm.doc.invoices[key].debit_to; + }); + let filters = [ + ["account_type", "=", "Receivable"], + ["name", "not in", invoice_accounts] + ]; + frm.events.filter_accounts("accounts_receivable_credit", frm, filters); + frm.events.filter_accounts("accounts_receivable_discounted", frm, filters); + frm.events.filter_accounts("accounts_receivable_unpaid", frm, filters); + }, + refresh: (frm) => { frm.events.show_general_ledger(frm); - if(frm.doc.docstatus === 0) { + if (frm.doc.docstatus === 0) { frm.add_custom_button(__('Get Invoices'), function() { frm.events.get_invoices(frm); }); } - if(frm.doc.docstatus === 1 && frm.doc.status !== "Settled") { - if(frm.doc.status == "Sanctioned") { + if (frm.doc.docstatus === 1 && frm.doc.status !== "Settled") { + if (frm.doc.status == "Sanctioned") { frm.add_custom_button(__('Disburse Loan'), function() { frm.events.create_disbursement_entry(frm); }).addClass("btn-primary"); } - if(frm.doc.status == "Disbursed") { + if (frm.doc.status == "Disbursed") { frm.add_custom_button(__('Close Loan'), function() { frm.events.close_loan(frm); }).addClass("btn-primary"); @@ -64,7 +80,7 @@ frappe.ui.form.on('Invoice Discounting', { }, set_end_date: (frm) => { - if(frm.doc.loan_start_date && frm.doc.loan_period) { + if (frm.doc.loan_start_date && frm.doc.loan_period) { let end_date = frappe.datetime.add_days(frm.doc.loan_start_date, frm.doc.loan_period); frm.set_value("loan_end_date", end_date); } @@ -132,6 +148,7 @@ frappe.ui.form.on('Invoice Discounting', { frm.doc.invoices = frm.doc.invoices.filter(row => row.sales_invoice); let row = frm.add_child("invoices"); $.extend(row, v); + frm.events.refresh_filters(frm); }); refresh_field("invoices"); } @@ -190,8 +207,10 @@ frappe.ui.form.on('Invoice Discounting', { frappe.ui.form.on('Discounted Invoice', { sales_invoice: (frm) => { frm.events.calculate_total_amount(frm); + frm.events.refresh_filters(frm); }, invoices_remove: (frm) => { frm.events.calculate_total_amount(frm); + frm.events.refresh_filters(frm); } -}); \ No newline at end of file +}); diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py index 29475d5644d..36c29113eaa 100644 --- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py +++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py @@ -12,6 +12,7 @@ from erpnext.accounts.general_ledger import make_gl_entries class InvoiceDiscounting(AccountsController): def validate(self): self.validate_mandatory() + self.validate_invoices() self.calculate_total_amount() self.set_status() self.set_end_date() @@ -24,6 +25,15 @@ class InvoiceDiscounting(AccountsController): if self.docstatus == 1 and not (self.loan_start_date and self.loan_period): frappe.throw(_("Loan Start Date and Loan Period are mandatory to save the Invoice Discounting")) + def validate_invoices(self): + discounted_invoices = [record.sales_invoice for record in + frappe.get_all("Discounted Invoice",fields = ["sales_invoice"], filters= {"docstatus":1})] + + for record in self.invoices: + if record.sales_invoice in discounted_invoices: + frappe.throw("Row({0}): {1} is already discounted in {2}" + .format(record.idx, frappe.bold(record.sales_invoice), frappe.bold(record.parent))) + def calculate_total_amount(self): self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices]) @@ -212,7 +222,8 @@ def get_invoices(filters): name as sales_invoice, customer, posting_date, - outstanding_amount + outstanding_amount, + debit_to from `tabSales Invoice` si where docstatus = 1 diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py index 56a0d2f39c3..4a7406e0cb4 100644 --- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py +++ b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.py @@ -49,7 +49,6 @@ class TestLoyaltyProgram(unittest.TestCase): # cancel and delete for d in [si_redeem, si_original]: d.cancel() - frappe.delete_doc('Sales Invoice', d.name) def test_loyalty_points_earned_multiple_tier(self): frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Multiple Loyalty") @@ -91,7 +90,6 @@ class TestLoyaltyProgram(unittest.TestCase): # cancel and delete for d in [si_redeem, si_original]: d.cancel() - frappe.delete_doc('Sales Invoice', d.name) def test_cancel_sales_invoice(self): ''' cancelling the sales invoice should cancel the earned points''' @@ -143,7 +141,6 @@ class TestLoyaltyProgram(unittest.TestCase): d.cancel() except frappe.TimestampMismatchError: frappe.get_doc('Sales Invoice', d.name).cancel() - frappe.delete_doc('Sales Invoice', d.name) def test_loyalty_points_for_dashboard(self): doc = frappe.get_doc('Customer', 'Test Loyalty Customer') diff --git a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json index 9609e3d08b2..f3df1f0bc98 100644 --- a/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json +++ b/erpnext/accounts/doctype/mode_of_payment/mode_of_payment.json @@ -1,171 +1,74 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:mode_of_payment", - "beta": 0, - "creation": "2012-12-04 17:49:20", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:mode_of_payment", + "creation": "2012-12-04 17:49:20", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "mode_of_payment", + "enabled", + "type", + "accounts" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "mode_of_payment", - "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": "Mode of Payment", - "length": 0, - "no_copy": 0, - "oldfieldname": "mode_of_payment", - "oldfieldtype": "Data", - "permlevel": 0, - "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": "mode_of_payment", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Mode of Payment", + "oldfieldname": "mode_of_payment", + "oldfieldtype": "Data", + "reqd": 1, + "unique": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Type", - "length": 0, - "no_copy": 0, - "options": "Cash\nBank\nGeneral", - "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": "type", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Type", + "options": "Cash\nBank\nGeneral" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "accounts", - "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": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Mode of Payment Account", - "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": "accounts", + "fieldtype": "Table", + "label": "Accounts", + "options": "Mode of Payment Account" + }, + { + "default": "1", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-credit-card", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2017-02-17 16:31:34.207683", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Mode of Payment", - "owner": "harshada@webnotestech.com", + ], + "icon": "fa fa-credit-card", + "idx": 1, + "modified": "2019-08-14 14:58:42.079115", + "modified_by": "sammish.thundiyil@gmail.com", + "module": "Accounts", + "name": "Mode of Payment", + "owner": "harshada@webnotestech.com", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "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, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "share": 1, "write": 1 - }, + }, { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "read": 1, + "report": 1, + "role": "Accounts User" } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0 + ], + "quick_entry": 1, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "ASC" } \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index f17b2cbeda4..172d5372a61 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -720,7 +720,7 @@ frappe.ui.form.on('Payment Entry', { $.each(frm.doc.references || [], function(i, row) { row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount - if(frappe.flags.allocate_payment_amount){ + if(frappe.flags.allocate_payment_amount != 0){ if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) { if(row.outstanding_amount >= allocated_positive_outstanding) { row.allocated_amount = allocated_positive_outstanding; diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e0d3e7ada3d..3529900d56d 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -126,7 +126,7 @@ class PaymentEntry(AccountsController): if not self.party: frappe.throw(_("Party is mandatory")) - _party_name = "title" if self.party_type == "Student" else self.party_type.lower() + "_name" + _party_name = "title" if self.party_type in ("Student", "Shareholder") else self.party_type.lower() + "_name" self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name) if self.party: @@ -624,8 +624,8 @@ def get_outstanding_reference_documents(args): data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed if not data: - frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.") - .format(args.get("party_type").lower(), args.get("party"))) + frappe.msgprint(_("No outstanding invoices found for the {0} {1} which qualify the filters you have specified.") + .format(args.get("party_type").lower(), frappe.bold(args.get("party")))) return data @@ -683,8 +683,8 @@ def get_orders_to_be_billed(posting_date, party_type, party, order_list = [] for d in orders: - if not (d.outstanding_amount >= filters.get("outstanding_amt_greater_than") - and d.outstanding_amount <= filters.get("outstanding_amt_less_than")): + if not (flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than")) + and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))): continue d["voucher_type"] = voucher_type @@ -947,10 +947,15 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount= paid_amount = abs(outstanding_amount) if bank_amount: received_amount = bank_amount + else: + received_amount = paid_amount * doc.conversion_rate else: received_amount = abs(outstanding_amount) if bank_amount: paid_amount = bank_amount + else: + # if party account currency and bank currency is different then populate paid amount as well + paid_amount = received_amount * doc.conversion_rate pe = frappe.new_doc("Payment Entry") pe.payment_type = payment_type diff --git a/erpnext/accounts/doctype/payment_order/payment_order.js b/erpnext/accounts/doctype/payment_order/payment_order.js index f2f00cebcba..ce9cfe527c3 100644 --- a/erpnext/accounts/doctype/payment_order/payment_order.js +++ b/erpnext/accounts/doctype/payment_order/payment_order.js @@ -66,6 +66,7 @@ frappe.ui.form.on('Payment Order', { get_query_filters: { bank: frm.doc.bank, docstatus: 1, + payment_type: ("!=", "Receive"), bank_account: frm.doc.company_bank_account, paid_from: frm.doc.account, payment_order_status: ["=", "Initiated"], diff --git a/erpnext/accounts/doctype/payment_order/regional/india.js b/erpnext/accounts/doctype/payment_order/regional/india.js deleted file mode 100644 index 66d0f60c618..00000000000 --- a/erpnext/accounts/doctype/payment_order/regional/india.js +++ /dev/null @@ -1,29 +0,0 @@ -frappe.ui.form.on('Payment Order', { - refresh: function(frm) { - if (frm.doc.docstatus==1 && frm.doc.payment_order_type==='Payment Entry') { - frm.add_custom_button(__('Generate Text File'), function() { - frm.trigger("generate_text_and_download_file"); - }); - } - }, - generate_text_and_download_file: (frm) => { - return frappe.call({ - method: "erpnext.regional.india.bank_remittance.generate_report", - args: { - name: frm.doc.name - }, - freeze: true, - callback: function(r) { - { - frm.reload_doc(); - const a = document.createElement('a'); - let file_obj = r.message; - a.href = file_obj.file_url; - a.target = '_blank'; - a.download = file_obj.file_name; - a.click(); - } - } - }); - } -}); \ No newline at end of file diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py index b74eed58414..4665d755100 100644 --- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py +++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py @@ -93,7 +93,7 @@ class PaymentReconciliation(Document): and `tab{doc}`.is_return = 1 and `tabGL Entry`.against_voucher_type = %(voucher_type)s and `tab{doc}`.docstatus = 1 and `tabGL Entry`.party = %(party)s and `tabGL Entry`.party_type = %(party_type)s and `tabGL Entry`.account = %(account)s - GROUP BY `tabSales Invoice`.name + GROUP BY `tab{doc}`.name Having amount > 0 """.format(doc=voucher_type, dr_or_cr=dr_or_cr, reconciled_dr_or_cr=reconciled_dr_or_cr), { @@ -257,11 +257,8 @@ def reconcile_dr_cr_note(dr_cr_notes): voucher_type = ('Credit Note' if d.voucher_type == 'Sales Invoice' else 'Debit Note') - dr_or_cr = ('credit_in_account_currency' - if d.reference_type == 'Sales Invoice' else 'debit_in_account_currency') - reconcile_dr_or_cr = ('debit_in_account_currency' - if dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') + if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency') jv = frappe.get_doc({ "doctype": "Journal Entry", @@ -272,8 +269,7 @@ def reconcile_dr_cr_note(dr_cr_notes): 'account': d.account, 'party': d.party, 'party_type': d.party_type, - reconcile_dr_or_cr: (abs(d.allocated_amount) - if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), + d.dr_or_cr: abs(d.allocated_amount), 'reference_type': d.against_voucher_type, 'reference_name': d.against_voucher }, @@ -281,7 +277,8 @@ def reconcile_dr_cr_note(dr_cr_notes): 'account': d.account, 'party': d.party, 'party_type': d.party_type, - dr_or_cr: abs(d.allocated_amount), + reconcile_dr_or_cr: (abs(d.allocated_amount) + if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)), 'reference_type': d.voucher_type, 'reference_name': d.voucher_no } diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js index a455e74940a..e2510f675f3 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.js +++ b/erpnext/accounts/doctype/payment_request/payment_request.js @@ -1,7 +1,6 @@ -cur_frm.add_fetch("payment_gateway", "payment_account", "payment_account") -cur_frm.add_fetch("payment_gateway", "payment_gateway", "payment_gateway") -cur_frm.add_fetch("payment_gateway", "message", "message") -cur_frm.add_fetch("payment_gateway", "payment_url_message", "payment_url_message") +cur_frm.add_fetch("payment_gateway_account", "payment_account", "payment_account") +cur_frm.add_fetch("payment_gateway_account", "payment_gateway", "payment_gateway") +cur_frm.add_fetch("payment_gateway_account", "message", "message") frappe.ui.form.on("Payment Request", "onload", function(frm, dt, dn){ if (frm.doc.reference_doctype) { diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py index 73758bea855..eda59abf04e 100644 --- a/erpnext/accounts/doctype/payment_request/payment_request.py +++ b/erpnext/accounts/doctype/payment_request/payment_request.py @@ -20,7 +20,7 @@ class PaymentRequest(Document): if self.get("__islocal"): self.status = 'Draft' self.validate_reference_document() - self.validate_payment_request() + self.validate_payment_request_amount() self.validate_currency() self.validate_subscription_details() @@ -28,10 +28,19 @@ class PaymentRequest(Document): if not self.reference_doctype or not self.reference_name: frappe.throw(_("To create a Payment Request reference document is required")) - def validate_payment_request(self): - if frappe.db.get_value("Payment Request", {"reference_name": self.reference_name, - "name": ("!=", self.name), "status": ("not in", ["Initiated", "Paid"]), "docstatus": 1}, "name"): - frappe.throw(_("Payment Request already exists {0}".format(self.reference_name))) + def validate_payment_request_amount(self): + existing_payment_request_amount = \ + get_existing_payment_request_amount(self.reference_doctype, self.reference_name) + + if existing_payment_request_amount: + ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) + if (hasattr(ref_doc, "order_type") \ + and getattr(ref_doc, "order_type") != "Shopping Cart"): + ref_amount = get_amount(ref_doc) + + if existing_payment_request_amount + flt(self.grand_total)> ref_amount: + frappe.throw(_("Total Payment Request amount cannot be greater than {0} amount" + .format(self.reference_doctype))) def validate_currency(self): ref_doc = frappe.get_doc(self.reference_doctype, self.reference_name) @@ -271,7 +280,7 @@ def make_payment_request(**args): args = frappe._dict(args) ref_doc = frappe.get_doc(args.dt, args.dn) - grand_total = get_amount(ref_doc, args.dt) + grand_total = get_amount(ref_doc) if args.loyalty_points and args.dt == "Sales Order": from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) @@ -281,17 +290,25 @@ def make_payment_request(**args): gateway_account = get_gateway_details(args) or frappe._dict() - existing_payment_request = frappe.db.get_value("Payment Request", - {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ["!=", 2]}) - bank_account = (get_party_bank_account(args.get('party_type'), args.get('party')) if args.get('party_type') else '') + existing_payment_request = None + if args.order_type == "Shopping Cart": + existing_payment_request = frappe.db.get_value("Payment Request", + {"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)}) + if existing_payment_request: frappe.db.set_value("Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False) pr = frappe.get_doc("Payment Request", existing_payment_request) - else: + if args.order_type != "Shopping Cart": + existing_payment_request_amount = \ + get_existing_payment_request_amount(args.dt, args.dn) + + if existing_payment_request_amount: + grand_total -= existing_payment_request_amount + pr = frappe.new_doc("Payment Request") pr.update({ "payment_gateway_account": gateway_account.get("name"), @@ -327,8 +344,9 @@ def make_payment_request(**args): return pr.as_dict() -def get_amount(ref_doc, dt): +def get_amount(ref_doc): """get amount based on doctype""" + dt = ref_doc.doctype if dt in ["Sales Order", "Purchase Order"]: grand_total = flt(ref_doc.grand_total) - flt(ref_doc.advance_paid) @@ -347,6 +365,17 @@ def get_amount(ref_doc, dt): else: frappe.throw(_("Payment Entry is already created")) +def get_existing_payment_request_amount(ref_dt, ref_dn): + existing_payment_request_amount = frappe.db.sql(""" + select sum(grand_total) + from `tabPayment Request` + where + reference_doctype = %s + and reference_name = %s + and docstatus = 1 + """, (ref_dt, ref_dn)) + return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0 + def get_gateway_details(args): """return gateway and payment account of default payment gateway""" if args.get("payment_gateway"): diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index bfd6d54824f..188ab0aecf3 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -37,12 +37,12 @@ class TestPaymentRequest(unittest.TestCase): def setUp(self): if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"): frappe.get_doc(payment_gateway).insert(ignore_permissions=True) - + for method in payment_method: - if not frappe.db.get_value("Payment Gateway Account", {"payment_gateway": method["payment_gateway"], + if not frappe.db.get_value("Payment Gateway Account", {"payment_gateway": method["payment_gateway"], "currency": method["currency"]}, "name"): frappe.get_doc(method).insert(ignore_permissions=True) - + def test_payment_request_linkings(self): so_inr = make_sales_order(currency="INR") pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com") @@ -100,3 +100,23 @@ class TestPaymentRequest(unittest.TestCase): self.assertEqual(expected_gle[gle.account][1], gle.debit) self.assertEqual(expected_gle[gle.account][2], gle.credit) self.assertEqual(expected_gle[gle.account][3], gle.against_voucher) + + def test_multiple_payment_entries_against_sales_order(self): + # Make Sales Order, grand_total = 1000 + so = make_sales_order() + + # Payment Request amount = 200 + pr1 = make_payment_request(dt="Sales Order", dn=so.name, + recipient_id="nabin@erpnext.com", return_doc=1) + pr1.grand_total = 200 + pr1.submit() + + # Make a 2nd Payment Request + pr2 = make_payment_request(dt="Sales Order", dn=so.name, + recipient_id="nabin@erpnext.com", return_doc=1) + + self.assertEqual(pr2.grand_total, 800) + + # Try to make Payment Request more than SO amount, should give validation + pr2.grand_total = 900 + self.assertRaises(frappe.ValidationError, pr2.save) \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index e2f99d6ea3f..7e237937000 100755 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -227,8 +227,8 @@ def get_contacts(customers): customers = [frappe._dict({'name': customers})] for data in customers: - contact = frappe.db.sql(""" select email_id, phone, mobile_no from `tabContact` - where is_primary_contact =1 and name in + contact = frappe.db.sql(""" select email_id, phone from `tabContact` + where is_primary_contact=1 and name in (select parent from `tabDynamic Link` where link_doctype = 'Customer' and link_name = %s and parenttype = 'Contact')""", data.name, as_dict=1) if contact: @@ -307,7 +307,7 @@ def get_item_tax_data(): # example: {'Consulting Services': {'Excise 12 - TS': '12.000'}} itemwise_tax = {} - taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax`""", as_dict=1) + taxes = frappe.db.sql(""" select parent, tax_type, tax_rate from `tabItem Tax Template Detail`""", as_dict=1) for tax in taxes: if tax.parent not in itemwise_tax: @@ -432,7 +432,6 @@ def get_customer_id(doc, customer=None): return cust_id - def make_customer_and_address(customers): customers_list = [] for customer, data in iteritems(customers): @@ -449,7 +448,6 @@ def make_customer_and_address(customers): frappe.db.commit() return customers_list - def add_customer(data): customer = data.get('full_name') or data.get('customer') if frappe.db.exists("Customer", customer.strip()): @@ -466,21 +464,18 @@ def add_customer(data): frappe.db.commit() return customer_doc.name - def get_territory(data): if data.get('territory'): return data.get('territory') return frappe.db.get_single_value('Selling Settings','territory') or _('All Territories') - def get_customer_group(data): if data.get('customer_group'): return data.get('customer_group') return frappe.db.get_single_value('Selling Settings', 'customer_group') or frappe.db.get_value('Customer Group', {'is_group': 0}, 'name') - def make_contact(args, customer): if args.get('email_id') or args.get('phone'): name = frappe.db.get_value('Dynamic Link', @@ -506,7 +501,6 @@ def make_contact(args, customer): doc.flags.ignore_mandatory = True doc.save(ignore_permissions=True) - def make_address(args, customer): if not args.get('address_line1'): return @@ -521,7 +515,10 @@ def make_address(args, customer): address = frappe.get_doc('Address', name) else: address = frappe.new_doc('Address') - address.country = frappe.get_cached_value('Company', args.get('company'), 'country') + if args.get('company'): + address.country = frappe.get_cached_value('Company', + args.get('company'), 'country') + address.append('links', { 'link_doctype': 'Customer', 'link_name': customer @@ -533,7 +530,6 @@ def make_address(args, customer): address.flags.ignore_mandatory = True address.save(ignore_permissions=True) - def make_email_queue(email_queue): name_list = [] for key, data in iteritems(email_queue): @@ -550,7 +546,6 @@ def make_email_queue(email_queue): return name_list - def validate_item(doc): for item in doc.get('items'): if not frappe.db.exists('Item', item.get('item_code')): @@ -569,7 +564,6 @@ def validate_item(doc): item_doc.save(ignore_permissions=True) frappe.db.commit() - def submit_invoice(si_doc, name, doc, name_list): try: si_doc.insert() @@ -585,7 +579,6 @@ def submit_invoice(si_doc, name, doc, name_list): return name_list - def save_invoice(doc, name, name_list): try: if not frappe.db.exists('Sales Invoice', {'offline_pos_name': name}): diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 74e9186e37d..3c852106635 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -158,7 +158,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; cur_frm.meta.default_print_format = cur_frm.pos_print_format; } - } else if(cur_frm.doc.is_return) { + } else if(cur_frm.doc.is_return && !cur_frm.meta.default_print_format) { if(cur_frm.return_print_format) { cur_frm.meta._default_print_format = cur_frm.meta.default_print_format; cur_frm.meta.default_print_format = cur_frm.return_print_format; diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 874230052a6..abbac77783b 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -78,6 +78,7 @@ class SalesInvoice(SellingController): self.so_dn_required() self.validate_proj_cust() + self.validate_pos_return() self.validate_with_previous_doc() self.validate_uom_is_integer("stock_uom", "stock_qty") self.validate_uom_is_integer("uom", "qty") @@ -199,6 +200,16 @@ class SalesInvoice(SellingController): if "Healthcare" in active_domains: manage_invoice_submit_cancel(self, "on_submit") + def validate_pos_return(self): + + if self.is_pos and self.is_return: + total_amount_in_payments = 0 + for payment in self.payments: + total_amount_in_payments += payment.amount + invoice_total = self.rounded_total or self.grand_total + if total_amount_in_payments < invoice_total: + frappe.throw(_("Total payments amount can't be greater than {}".format(-invoice_total))) + def validate_pos_paid_amount(self): if len(self.payments) == 0 and self.is_pos: frappe.throw(_("At least one mode of payment is required for POS invoice.")) @@ -293,8 +304,10 @@ class SalesInvoice(SellingController): from erpnext.selling.doctype.customer.customer import check_credit_limit validate_against_credit_limit = False - bypass_credit_limit_check_at_sales_order = cint(frappe.get_cached_value("Customer", self.customer, - "bypass_credit_limit_check_at_sales_order")) + bypass_credit_limit_check_at_sales_order = frappe.db.get_value("Customer Credit Limit", + filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company}, + fieldname=["bypass_credit_limit_check"]) + if bypass_credit_limit_check_at_sales_order: validate_against_credit_limit = True @@ -1499,4 +1512,4 @@ def create_invoice_discounting(source_name, target_doc=None): "outstanding_amount": invoice.outstanding_amount }) - return invoice_discounting \ No newline at end of file + return invoice_discounting diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index dff55947dfe..4f253b69f79 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -818,7 +818,6 @@ class TestSalesInvoice(unittest.TestCase): self.assertEqual(expected_gl_entries[i][2], gle.credit) si.cancel() - frappe.delete_doc('Sales Invoice', si.name) gle = frappe.db.sql("""select * from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s""", si.name) diff --git a/erpnext/accounts/doctype/share_transfer/share_transfer.py b/erpnext/accounts/doctype/share_transfer/share_transfer.py index 1a1f036278d..e95c69413f5 100644 --- a/erpnext/accounts/doctype/share_transfer/share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/share_transfer.py @@ -86,17 +86,23 @@ class ShareTransfer(Document): frappe.throw(_('The field From Shareholder cannot be blank')) if self.from_folio_no is None or self.from_folio_no is '': self.to_folio_no = self.autoname_folio(self.to_shareholder) + if self.asset_account is None: + frappe.throw(_('The field Asset Account cannot be blank')) elif (self.transfer_type == 'Issue'): self.from_shareholder = '' if self.to_shareholder is None or self.to_shareholder == '': frappe.throw(_('The field To Shareholder cannot be blank')) if self.to_folio_no is None or self.to_folio_no is '': self.to_folio_no = self.autoname_folio(self.to_shareholder) + if self.asset_account is None: + frappe.throw(_('The field Asset Account cannot be blank')) else: if self.from_shareholder is None or self.to_shareholder is None: frappe.throw(_('The fields From Shareholder and To Shareholder cannot be blank')) if self.to_folio_no is None or self.to_folio_no is '': self.to_folio_no = self.autoname_folio(self.to_shareholder) + if self.equity_or_liability_account is None: + frappe.throw(_('The field Equity/Liability Account cannot be blank')) if self.from_shareholder == self.to_shareholder: frappe.throw(_('The seller and the buyer cannot be the same')) if self.no_of_shares != self.to_no - self.from_no + 1: diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py index f1cf31b8205..910dfd05dab 100644 --- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.py +++ b/erpnext/accounts/doctype/share_transfer/test_share_transfer.py @@ -15,67 +15,74 @@ class TestShareTransfer(unittest.TestCase): frappe.db.sql("delete from `tabShare Balance`") share_transfers = [ { - "doctype" : "Share Transfer", - "transfer_type" : "Issue", - "date" : "2018-01-01", - "to_shareholder" : "SH-00001", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 500, - "no_of_shares" : 500, - "rate" : 10, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Issue", + "date" : "2018-01-01", + "to_shareholder" : "SH-00001", + "share_type" : "Equity", + "from_no" : 1, + "to_no" : 500, + "no_of_shares" : 500, + "rate" : 10, + "company" : "_Test Company", + "asset_account" : "Cash - _TC", + "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-02", - "from_shareholder" : "SH-00001", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 101, - "to_no" : 200, - "no_of_shares" : 100, - "rate" : 15, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Transfer", + "date" : "2018-01-02", + "from_shareholder" : "SH-00001", + "to_shareholder" : "SH-00002", + "share_type" : "Equity", + "from_no" : 101, + "to_no" : 200, + "no_of_shares" : 100, + "rate" : 15, + "company" : "_Test Company", + "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-03", - "from_shareholder" : "SH-00001", - "to_shareholder" : "SH-00003", - "share_type" : "Equity", - "from_no" : 201, - "to_no" : 500, - "no_of_shares" : 300, - "rate" : 20, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Transfer", + "date" : "2018-01-03", + "from_shareholder" : "SH-00001", + "to_shareholder" : "SH-00003", + "share_type" : "Equity", + "from_no" : 201, + "to_no" : 500, + "no_of_shares" : 300, + "rate" : 20, + "company" : "_Test Company", + "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-04", - "from_shareholder" : "SH-00003", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 201, - "to_no" : 400, - "no_of_shares" : 200, - "rate" : 15, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Transfer", + "date" : "2018-01-04", + "from_shareholder" : "SH-00003", + "to_shareholder" : "SH-00002", + "share_type" : "Equity", + "from_no" : 201, + "to_no" : 400, + "no_of_shares" : 200, + "rate" : 15, + "company" : "_Test Company", + "equity_or_liability_account": "Creditors - _TC" }, { - "doctype" : "Share Transfer", - "transfer_type" : "Purchase", - "date" : "2018-01-05", - "from_shareholder" : "SH-00003", - "share_type" : "Equity", - "from_no" : 401, - "to_no" : 500, - "no_of_shares" : 100, - "rate" : 25, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Purchase", + "date" : "2018-01-05", + "from_shareholder" : "SH-00003", + "share_type" : "Equity", + "from_no" : 401, + "to_no" : 500, + "no_of_shares" : 100, + "rate" : 25, + "company" : "_Test Company", + "asset_account" : "Cash - _TC", + "equity_or_liability_account": "Creditors - _TC" } ] for d in share_transfers: @@ -84,30 +91,33 @@ class TestShareTransfer(unittest.TestCase): def test_invalid_share_transfer(self): doc = frappe.get_doc({ - "doctype" : "Share Transfer", - "transfer_type" : "Transfer", - "date" : "2018-01-05", - "from_shareholder" : "SH-00003", - "to_shareholder" : "SH-00002", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 100, - "no_of_shares" : 100, - "rate" : 15, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Transfer", + "date" : "2018-01-05", + "from_shareholder" : "SH-00003", + "to_shareholder" : "SH-00002", + "share_type" : "Equity", + "from_no" : 1, + "to_no" : 100, + "no_of_shares" : 100, + "rate" : 15, + "company" : "_Test Company", + "equity_or_liability_account": "Creditors - _TC" }) self.assertRaises(ShareDontExists, doc.insert) doc = frappe.get_doc({ - "doctype" : "Share Transfer", - "transfer_type" : "Purchase", - "date" : "2018-01-02", - "from_shareholder" : "SH-00001", - "share_type" : "Equity", - "from_no" : 1, - "to_no" : 200, - "no_of_shares" : 200, - "rate" : 15, - "company" : "_Test Company" + "doctype" : "Share Transfer", + "transfer_type" : "Purchase", + "date" : "2018-01-02", + "from_shareholder" : "SH-00001", + "share_type" : "Equity", + "from_no" : 1, + "to_no" : 200, + "no_of_shares" : 200, + "rate" : 15, + "company" : "_Test Company", + "asset_account" : "Cash - _TC", + "equity_or_liability_account": "Creditors - _TC" }) self.assertRaises(ShareDontExists, doc.insert) diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py index 638e57ed2b9..b1468999fc1 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py @@ -48,7 +48,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): #delete invoices to avoid clashing for d in invoices: d.cancel() - frappe.delete_doc("Purchase Invoice", d.name) def test_single_threshold_tds(self): invoices = [] @@ -83,7 +82,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): # delete invoices to avoid clashing for d in invoices: d.cancel() - frappe.delete_doc("Purchase Invoice", d.name) def test_single_threshold_tds_with_previous_vouchers(self): invoices = [] @@ -102,7 +100,6 @@ class TestTaxWithholdingCategory(unittest.TestCase): # delete invoices to avoid clashing for d in invoices: d.cancel() - frappe.delete_doc("Purchase Invoice", d.name) def create_purchase_invoice(**args): # return sales invoice doc object diff --git a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py index 36c939996fe..bd4b4d7e0b1 100644 --- a/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py +++ b/erpnext/accounts/page/bank_reconciliation/bank_reconciliation.py @@ -124,8 +124,6 @@ def check_matching_amount(bank_account, company, transaction): 'txt': '%%%s%%' % amount }, as_dict=True) - frappe.errprint(journal_entries) - if transaction.credit > 0: sales_invoices = frappe.db.sql(""" SELECT diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 3834c96bfb7..0cd8bfefb82 100755 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -816,7 +816,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ contact = me.contacts[data.name]; if(reg.test(data.name.toLowerCase()) || reg.test(data.customer_name.toLowerCase()) - || (contact && reg.test(contact["mobile_no"])) || (contact && reg.test(contact["phone"])) || (data.customer_group && reg.test(data.customer_group.toLowerCase()))){ return data; @@ -834,7 +833,6 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if(contact && !c['phone']) { c["phone"] = contact["phone"]; c["email_id"] = contact["email_id"]; - c["mobile_no"] = contact["mobile_no"]; } me.customers_mapper.push({ @@ -844,10 +842,9 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ customer_group: c.customer_group, territory: c.territory, phone: contact ? contact["phone"] : '', - mobile_no: contact ? contact["mobile_no"] : '', email_id: contact ? contact["email_id"] : '', searchtext: ['customer_name', 'customer_group', 'name', 'value', - 'label', 'email_id', 'phone', 'mobile_no'] + 'label', 'email_id', 'phone'] .map(key => c[key]).join(' ') .toLowerCase() }); @@ -1121,7 +1118,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if (key) { return $.grep(this.items_list, function (item) { if (search_status) { - if (in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) { + if (me.batch_no_data[item.item_code] && + in_list(me.batch_no_data[item.item_code], me.search_item.$input.val())) { search_status = false; return me.item_batch_no[item.item_code] = me.search_item.$input.val() } else if (me.serial_no_data[item.item_code] @@ -1129,7 +1127,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ search_status = false; me.item_serial_no[item.item_code] = [me.search_item.$input.val(), me.serial_no_data[item.item_code][me.search_item.$input.val()]] return true - } else if (in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) { + } else if (me.barcode_data[item.item_code] && + in_list(me.barcode_data[item.item_code], me.search_item.$input.val())) { search_status = false; return true; } else if (reg.test(item.item_code.toLowerCase()) || (item.description && reg.test(item.description.toLowerCase())) || @@ -1692,20 +1691,13 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ if(this.si_docs) { this.si_docs.forEach((row) => { - existing_pos_list.push(Object.keys(row)); + existing_pos_list.push(Object.keys(row)[0]); }); } if (this.frm.doc.offline_pos_name - && in_list(existing_pos_list, this.frm.doc.offline_pos_name)) { + && in_list(existing_pos_list, cstr(this.frm.doc.offline_pos_name))) { this.update_invoice() - //to retrieve and set the default payment - invoice_data[this.frm.doc.offline_pos_name] = this.frm.doc; - invoice_data[this.frm.doc.offline_pos_name].payments[0].amount = this.frm.doc.net_total - invoice_data[this.frm.doc.offline_pos_name].payments[0].base_amount = this.frm.doc.net_total - - this.frm.doc.paid_amount = this.frm.doc.net_total - this.frm.doc.outstanding_amount = 0 } else if(!this.frm.doc.offline_pos_name) { this.frm.doc.offline_pos_name = frappe.datetime.now_datetime(); this.frm.doc.posting_date = frappe.datetime.get_today(); @@ -1762,18 +1754,11 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ this.si_docs = this.get_submitted_invoice() || []; this.email_queue_list = this.get_email_queue() || {}; this.customers_list = this.get_customers_details() || {}; - if(this.customer_doc) { - this.freeze = this.customer_doc.display - } - - freeze_screen = this.freeze_screen || false; - - if ((this.si_docs.length || this.email_queue_list || this.customers_list) && !this.freeze) { - this.freeze = true; + if (this.si_docs.length || this.email_queue_list || this.customers_list) { frappe.call({ method: "erpnext.accounts.doctype.sales_invoice.pos.make_invoice", - freeze: freeze_screen, + freeze: true, args: { doc_list: me.si_docs, email_queue_list: me.email_queue_list, diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index cb947226518..59936d5116d 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -292,8 +292,11 @@ def validate_party_accounts(doc): party_account_currency = frappe.db.get_value("Account", account.account, "account_currency", cache=True) existing_gle_currency = get_party_gle_currency(doc.doctype, doc.name, account.company) - company_default_currency = frappe.get_cached_value('Company', - frappe.db.get_default("Company"), "default_currency") + if frappe.db.get_default("Company"): + company_default_currency = frappe.get_cached_value('Company', + frappe.db.get_default("Company"), "default_currency") + else: + company_default_currency = frappe.db.get_value('Company', account.company, "default_currency") if existing_gle_currency and party_account_currency != existing_gle_currency: frappe.throw(_("Accounting entries have already been made in currency {0} for company {1}. Please select a receivable or payable account with currency {0}.").format(existing_gle_currency, account.company)) @@ -365,7 +368,7 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b .format(formatdate(default_due_date))) @frappe.whitelist() -def get_address_tax_category(tax_category, billing_address=None, shipping_address=None): +def get_address_tax_category(tax_category=None, billing_address=None, shipping_address=None): addr_tax_category_from = frappe.db.get_single_value("Accounts Settings", "determine_address_tax_category_from") if addr_tax_category_from == "Shipping Address": if shipping_address: @@ -469,7 +472,9 @@ def get_timeline_data(doctype, name): # fetch and append data from Activity Log data += frappe.db.sql("""select {fields} from `tabActivity Log` - where reference_doctype={doctype} and reference_name={name} + 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}") 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, @@ -605,4 +610,4 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None): .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) if data: - return frappe._dict(data) \ No newline at end of file + return frappe._dict(data) diff --git a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json b/erpnext/accounts/print_format/point_of_sale/point_of_sale.json index a8bedc2b662..c0c50cb4e26 100644 --- a/erpnext/accounts/print_format/point_of_sale/point_of_sale.json +++ b/erpnext/accounts/print_format/point_of_sale/point_of_sale.json @@ -1,22 +1,23 @@ { - "align_labels_right": 0, - "creation": "2016-05-05 17:16:18.564460", - "custom_format": 1, - "disabled": 0, - "doc_type": "Sales Invoice", - "docstatus": 0, - "doctype": "Print Format", - "font": "Default", - "html": "\n\n

    \n\t{{ company }}
    \n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}
    \n

    \n

    \n\t{{ __(\"Customer\") }}: {{ customer }}
    \n

    \n\n

    \n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
    \n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{% for item in items %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endfor %}\n\t\n
    {{ __(\"Item\") }}{{ __(\"Qty\") }}{{ __(\"Amount\") }}
    \n\t\t\t\t{{ item.item_name }}\n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }}
    @ {{ format_currency(item.rate, currency) }}
    {{ format_currency(item.amount, currency) }}
    \n\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
    \n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t
    \n\t\t\t\t{{ row.description }}\n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Qty Total\") }}\n\t\t\t\n\t\t\t\t{{ qty_total }}\n\t\t\t
    \n\n\n
    \n

    {{ terms }}

    \n

    {{ __(\"Thank you, please visit again.\") }}

    ", - "idx": 0, - "line_breaks": 0, - "modified": "2018-03-21 09:10:16.693732", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Point of Sale", - "owner": "Administrator", - "print_format_builder": 0, - "print_format_type": "Js", - "show_section_headings": 0, + "align_labels_right": 0, + "creation": "2016-05-05 17:16:18.564460", + "custom_format": 1, + "disabled": 0, + "doc_type": "Sales Invoice", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "html": "\n\n

    \n\t{{ company }}
    \n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}
    \n

    \n

    \n\t{{ __(\"Customer\") }}: {{ customer }}
    \n

    \n\n

    \n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
    \n

    \n\n
    \n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n\t\n\t\t{% for item in items %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endfor %}\n\t\n
    {{ __(\"Item\") }}{{ __(\"Qty\") }}{{ __(\"Amount\") }}
    \n\t\t\t\t{{ item.item_name }}\n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }}
    @ {{ format_currency(item.rate, currency) }}
    {{ format_currency(item.amount, currency) }}
    \n\n\n\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% for row in taxes %}\n\t\t{% if not row.included_in_print_rate %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t{% endfor %}\n\t\t{% if discount_amount %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t{% endif %}\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\t\n\t\t\t\n\t\t\t\n\t\t\n\t\n
    \n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t
    \n\t\t\t\t{{ row.description }}\n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t
    \n\t\t\t\t{{ __(\"Qty Total\") }}\n\t\t\t\n\t\t\t\t{{ qty_total }}\n\t\t\t
    \n\n\n
    \n

    {{ terms }}

    \n

    {{ __(\"Thank you, please visit again.\") }}

    ", + "idx": 0, + "line_breaks": 0, + "modified": "2019-09-05 17:20:30.726659", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Point of Sale", + "owner": "Administrator", + "print_format_builder": 0, + "print_format_type": "JS", + "raw_printing": 0, + "show_section_headings": 0, "standard": "Yes" } \ No newline at end of file diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html index 192b6d7be38..791f3f8008b 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.html +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.html @@ -1,275 +1,269 @@ + .print-format { + padding: 4mm; + font-size: 8.0pt !important; + } + .print-format td { + vertical-align:middle !important; + } + -

    {%= __(report.report_name) %}

    -

    - {% if (filters.customer_name) { %} - {%= filters.customer_name %} - {% } else { %} - {%= filters.customer || filters.supplier %} - {% } %} -

    -
    - {% if (filters.tax_id) { %} - {%= __("Tax Id: ")%} {%= filters.tax_id %} +

    {%= __(report.report_name) %}

    +

    + {% if (filters.customer_name) { %} + {%= filters.customer_name %} + {% } else { %} + {%= filters.customer || filters.supplier %} {% } %} -

    -
    - {%= __(filters.ageing_based_on) %} - {%= __("Until") %} - {%= frappe.datetime.str_to_user(filters.report_date) %} -
    + +
    + {% if (filters.tax_id) { %} + {%= __("Tax Id: ")%} {%= filters.tax_id %} + {% } %} +
    +
    + {%= __(filters.ageing_based_on) %} + {%= __("Until") %} + {%= frappe.datetime.str_to_user(filters.report_date) %} +
    -
    -
    - {% if(filters.payment_terms) { %} - {%= __("Payment Terms") %}: {%= filters.payment_terms %} - {% } %} +
    +
    + {% if(filters.payment_terms) { %} + {%= __("Payment Terms") %}: {%= filters.payment_terms %} + {% } %} +
    +
    + {% if(filters.credit_limit) { %} + {%= __("Credit Limit") %}: {%= format_currency(filters.credit_limit) %} + {% } %} +
    -
    - {% if(filters.credit_limit) { %} - {%= __("Credit Limit") %}: {%= format_currency(filters.credit_limit) %} + + {% if(filters.show_future_payments) { %} + {% var balance_row = data.slice(-1).pop(); + var range1 = report.columns[11].label; + var range2 = report.columns[12].label; + var range3 = report.columns[13].label; + var range4 = report.columns[14].label; + var range5 = report.columns[15].label; + %} + {% if(balance_row) { %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    (Amount in {%= data[0]["currency"] || "" %})
    {%= __(" ") %}{%= __(range1) %}{%= __(range2) %}{%= __(range3) %}{%= __(range4) %}{%= __(range5) %}{%= __("Total") %}
    {%= __("Total Outstanding") %}{%= format_number(balance_row["range1"], null, 2) %}{%= format_currency(balance_row["range2"]) %}{%= format_currency(balance_row["range3"]) %}{%= format_currency(balance_row["range4"]) %}{%= format_currency(balance_row["range5"]) %} + {%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %} +
    {%= __("Future Payments") %} + {%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %} +
    + {% } %} {% } %} -
    -
    - -{% if(filters.show_pdc_in_print) { %} - {% var balance_row = data.slice(-1).pop(); - var range1 = report.columns[11].label; - var range2 = report.columns[12].label; - var range3 = report.columns[13].label; - var range4 = report.columns[14].label; - var range5 = report.columns[15].label; - var range6 = report.columns[16].label; - %} - {% if(balance_row) { %} - - - - - - - - - - - - - - +
    (Amount in {%= data[0][__("currency")] || "" %})
    - - - - - - - - + {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} + + + + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + + + {% } else { %} + + {% } %} + {% if(!filters.show_future_payments) { %} + + {% } %} + + {% if(!filters.show_future_payments) { %} + + + {% } %} + + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} + + {% } %} + + + + {% } %} + {% } else { %} + + + + + + {% } %} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    {%= __(" ") %}{%= __(range1) %}{%= __(range2) %}{%= __(range3) %}{%= __(range4) %}{%= __(range5) %}{%= __(range6) %}{%= __("Total") %}{%= __("Date") %}{%= __("Age (Days)") %}{%= __("Reference") %}{%= __("Sales Person") %}{%= __("Reference") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Invoiced Amount") %}{%= __("Paid Amount") %}{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}{%= __("Outstanding Amount") %}{%= __("Customer LPO No.") %}{%= __("Future Payment Ref") %}{%= __("Future Payment Amount") %}{%= __("Remaining Balance") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Total Invoiced Amount") %}{%= __("Total Paid Amount") %}{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}{%= __("Total Outstanding Amount") %}
    {%= __("Total Outstanding") %}{%= format_number(balance_row[range1], null, 2) %}{%= format_currency(balance_row[range2]) %}{%= format_currency(balance_row[range3]) %}{%= format_currency(balance_row[range4]) %}{%= format_currency(balance_row[range5]) %}{%= format_currency(balance_row[range6]) %} - {%= format_currency(flt(balance_row[("outstanding_amount")]), data[data.length-1]["currency"]) %} -
    {%= __("PDC/LC") %} - {%= format_currency(flt(balance_row[("pdc/lc_amount")]), data[data.length-1]["currency"]) %} -
    - {% } %} -{% } %} - - - - {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - - - - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} - - - {% } else { %} - - {% } %} - {% if(!filters.show_pdc_in_print) { %} - - {% } %} - - {% if(!filters.show_pdc_in_print) { %} - - - {% } %} - - {% if(filters.show_pdc_in_print) { %} - {% if(report.report_name === "Accounts Receivable") { %} - - {% } %} - - - - {% } %} - {% } else { %} - - - - - - {% } %} - - - - {% for(var i=0, l=data.length; i - {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} - {% if(data[i][__("Customer")] || data[i][__("Supplier")]) { %} - - - - - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} - - {% } %} - - {% if(!filters.show_pdc_in_print) { %} - + + + + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + {% } %} -
    - {% if data[i][__("Remarks")] %} - {%= __("Remarks") %}: - {%= data[i][__("Remarks")] %} - {% } %} -
    - - {% } %} - - - {% if(!filters.show_pdc_in_print) { %} - - - {% } %} - - - {% if(filters.show_pdc_in_print) { %} - {% if(report.report_name === "Accounts Receivable") { %} - - {% } %} - - - - {% } %} - {% } else { %} - - {% if(!filters.show_pdc_in_print) { %} - - {% } %} - {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person_in_print) { %} - - {% } %} - - - - - {% if(!filters.show_pdc_in_print) { %} - - - {% } %} - - - {% if(filters.show_pdc_in_print) { %} - {% if(report.report_name === "Accounts Receivable") { %} - - {% } %} - - - - {% } %} - {% } %} - {% } else { %} - {% if(data[i][__("Customer")] || data[i][__("Supplier")]|| " ") { %} - {% if((data[i][__("Customer")] || data[i][__("Supplier")]) != __("'Total'")) { %} + {% if(!filters.show_future_payments) { %} + {% } %} + + + + {% if(!filters.show_future_payments) { %} + + + {% } %} + + + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} + + {% } %} + + + + {% } %} {% } else { %} - + + {% if(!filters.show_future_payments) { %} + + {% } %} + {% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %} + + {% } %} + + + + + {% if(!filters.show_future_payments) { %} + + + {% } %} + + + {% if(filters.show_future_payments) { %} + {% if(report.report_name === "Accounts Receivable") { %} + + {% } %} + + + + {% } %} + {% } %} + {% } else { %} + {% if(data[i]["party"]|| " ") { %} + {% if((data[i]["party"]) != __("'Total'")) { %} + + {% } else { %} + + {% } %} + + + + {% } %} - - - - {% } %} + {% } %} - - {% } %} - -
    {%= __("Date") %}{%= __("Age (Days)") %}{%= __("Reference") %}{%= __("Sales Person") %}{%= __("Reference") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Invoiced Amount") %}{%= __("Paid Amount") %}{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}{%= __("Outstanding Amount") %}{%= __("Customer LPO No.") %}{%= __("PDC/LC Ref") %}{%= __("PDC/LC Amount") %}{%= __("Remaining Balance") %}{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}{%= __("Total Invoiced Amount") %}{%= __("Total Paid Amount") %}{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}{%= __("Total Outstanding Amount") %}
    {%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}{%= data[i][__("Age (Days)")] %} - {% if(!filters.show_pdc_in_print) { %} - {%= data[i]["voucher_type"] %} -
    - {% } %} - {%= data[i]["voucher_no"] %} -
    {%= data[i]["sales_person"] %} - {% if(!(filters.customer || filters.supplier)) { %} - {%= data[i][__("Customer")] || data[i][__("Supplier")] %} - {% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %} -
    {%= data[i][__("Customer Name")] %} - {% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %} -
    {%= data[i][__("Supplier Name")] %} + {% for(var i=0, l=data.length; i + {% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %} + {% if(data[i]["party"]) { %} +
    {%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}{%= data[i]["age"] %} + {% if(!filters.show_future_payments) { %} + {%= data[i]["voucher_type"] %} +
    {% } %} + {%= data[i]["voucher_no"] %} +
    {%= data[i]["sales_person"] %} - {%= format_currency(data[i]["invoiced_amount"], data[i]["currency"]) %} - {%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %} - {%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} - {%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %} - {%= data[i]["po_no"] %}{%= data[i][("pdc/lc_ref")] %}{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %}{%= __("Total") %} - {%= format_currency(data[i]["invoiced_amount"], data[i]["currency"] ) %} - {%= format_currency(data[i]["paid_amount"], data[i]["currency"]) %}{%= report.report_name === "Accounts Receivable" ? format_currency(data[i]["credit_note"], data[i]["currency"]) : format_currency(data[i]["debit_note"], data[i]["currency"]) %} - {%= format_currency(data[i]["outstanding_amount"], data[i]["currency"]) %} - {%= data[i][__("Customer LPO")] %}{%= data[i][("pdc/lc_ref")] %}{%= format_currency(data[i][("pdc/lc_amount")], data[i]["currency"]) %}{%= format_currency(data[i][("remaining_balance")], data[i]["currency"]) %} {% if(!(filters.customer || filters.supplier)) { %} - {%= data[i][__("Customer")] || data[i][__("Supplier")] %} - {% if(data[i][__("Customer Name")] && data[i][__("Customer Name")] != data[i][__("Customer")]) { %} -
    {%= data[i][__("Customer Name")] %} - {% } else if(data[i][__("Supplier Name")] != data[i][__("Supplier")]) { %} -
    {%= data[i][__("Supplier Name")] %} + {%= data[i]["party"] %} + {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} +
    {%= data[i]["customer_name"] %} + {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} +
    {%= data[i]["supplier_name"] %} {% } %} {% } %} -
    {%= __("Remarks") %}: - {%= data[i][__("Remarks")] %} +
    + {% if data[i]["remarks"] %} + {%= __("Remarks") %}: + {%= data[i]["remarks"] %} + {% } %} +
    + {%= format_currency(data[i]["invoiced"], data[i]["currency"]) %} + {%= format_currency(data[i]["paid"], data[i]["currency"]) %} + {%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} + {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} + {%= data[i]["po_no"] %}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}{%= __("Total") %}{%= __("Total") %} + {%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %} + {%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} + {%= format_currency(data[i]["outstanding"], data[i]["currency"]) %} + {%= data[i]["po_no"] %}{%= data[i]["future_ref"] %}{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %} + {% if(!(filters.customer || filters.supplier)) { %} + {%= data[i]["party"] %} + {% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %} +
    {%= data[i]["customer_name"] %} + {% } else if(data[i]["supplier_name"] != data[i]["party"]) { %} +
    {%= data[i]["supplier_name"] %} + {% } %} + {% } %} +
    {%= __("Remarks") %}: + {%= data[i]["remarks"] %} +
    {%= __("Total") %}{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}{%= format_currency(data[i]["paid"], data[i]["currency"]) %}{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}{%= format_currency(data[i][("total_invoiced_amt")], data[i]["currency"]) %}{%= format_currency(data[i][("total_paid_amt")], data[i]["currency"]) %}{%= report.report_name === "Accounts Receivable Summary" ? format_currency(data[i][__("credit_note_amt")], data[i]["currency"]) : format_currency(data[i][__("debit_note_amt")], data[i]["currency"]) %}{%= format_currency(data[i][("total_outstanding_amt")], data[i]["currency"]) %}
    -

    {{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

    + + +

    {{ __("Printed On ") }}{%= frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string()) %}

    diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 4551973ac60..228be18d219 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -130,13 +130,18 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Check", }, { - "fieldname":"show_pdc_in_print", - "label": __("Show PDC in Print"), + "fieldname":"show_future_payments", + "label": __("Show Future Payments"), "fieldtype": "Check", }, { - "fieldname":"show_sales_person_in_print", - "label": __("Show Sales Person in Print"), + "fieldname":"show_delivery_notes", + "label": __("Show Delivery Notes"), + "fieldtype": "Check", + }, + { + "fieldname":"show_sales_person", + "label": __("Show Sales Person"), "fieldtype": "Check", }, { diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index ecf149b335a..a7279f75f1c 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -4,9 +4,33 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _, scrub -from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr +from frappe.utils import getdate, nowdate, flt, cint, formatdate, cstr, now, time_diff_in_seconds +from collections import OrderedDict +from erpnext.accounts.utils import get_currency_precision from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions +# This report gives a summary of all Outstanding Invoices considering the following + +# 1. Invoice can be booked via Sales/Purchase Invoice or Journal Entry +# 2. Report handles both receivable and payable +# 3. Key balances for each row are "Invoiced Amount", "Paid Amount", "Credit/Debit Note Amount", "Oustanding Amount" +# 4. For explicit payment terms in invoice (example: 30% advance, 30% on delivery, 40% post delivery), +# the invoice will be broken up into multiple rows, one for each payment term +# 5. If there are payments after the report date (post dated), these will be updated in additional columns +# for future amount +# 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters +# 7. For overpayment against an invoice with payment terms, there will be an additional row +# 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated +# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party +# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable" + +def execute(filters=None): + args = { + "party_type": "Customer", + "naming_by": ["Selling Settings", "cust_master_name"], + } + return ReceivablePayableReport(filters).run(args) + class ReceivablePayableReport(object): def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) @@ -16,455 +40,429 @@ class ReceivablePayableReport(object): else self.filters.report_date def run(self, args): - party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) - columns = self.get_columns(party_naming_by, args) - data = self.get_data(party_naming_by, args) - chart = self.get_chart_data(columns, data) - return columns, data, None, chart - - def get_columns(self, party_naming_by, args): - columns = [] - columns.append({ - "label": _("Posting Date"), - "fieldtype": "Date", - "fieldname": "posting_date", - "width": 90 - }) - - columns += [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"] - - if args.get("party_type") == 'Customer': - columns.append({ - "label": _("Customer Contact"), - "fieldtype": "Link", - "fieldname": "contact", - "options":"Contact", - "width": 100 - }) - - if party_naming_by == "Naming Series": - columns += [args.get("party_type") + " Name::110"] - - columns.append({ - "label": _("Voucher Type"), - "fieldtype": "Data", - "fieldname": "voucher_type", - "width": 110 - }) - - columns.append({ - "label": _("Voucher No"), - "fieldtype": "Dynamic Link", - "fieldname": "voucher_no", - "width": 110, - "options": "voucher_type", - }) - - columns += [_("Due Date") + ":Date:80"] - - if args.get("party_type") == "Supplier": - columns += [_("Bill No") + "::80", _("Bill Date") + ":Date:80"] - - credit_or_debit_note = "Credit Note" if args.get("party_type") == "Customer" else "Debit Note" - - if self.filters.based_on_payment_terms: - columns.append({ - "label": _("Payment Term"), - "fieldname": "payment_term", - "fieldtype": "Data", - "width": 120 - }) - columns.append({ - "label": _("Invoice Grand Total"), - "fieldname": "invoice_grand_total", - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }) - - for label in ("Invoiced Amount", "Paid Amount", credit_or_debit_note, "Outstanding Amount"): - columns.append({ - "label": _(label), - "fieldname": frappe.scrub(label), - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }) - - columns += [_("Age (Days)") + ":Int:80"] - - self.ageing_col_idx_start = len(columns) - - if not "range1" in self.filters: - self.filters["range1"] = "30" - if not "range2" in self.filters: - self.filters["range2"] = "60" - if not "range3" in self.filters: - self.filters["range3"] = "90" - if not "range4" in self.filters: - self.filters["range4"] = "120" - - for label in ("0-{range1}".format(range1=self.filters["range1"]), - "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]), - "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), - "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]), - "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))): - columns.append({ - "label": label, - "fieldname":label, - "fieldtype": "Currency", - "options": "currency", - "width": 120 - }) - - columns += [ - { - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Link", - "options": "Currency", - "width": 100 - }, - { - "fieldname": "pdc/lc_ref", - "label": _("PDC/LC Ref"), - "fieldtype": "Data", - "width": 110 - }, - { - "fieldname": "pdc/lc_amount", - "label": _("PDC/LC Amount"), - "fieldtype": "Currency", - "options": "currency", - "width": 130 - }, - { - "fieldname": "remaining_balance", - "label": _("Remaining Balance"), - "fieldtype": "Currency", - "options": "currency", - "width": 130 - }] - - if args.get('party_type') == 'Customer': - columns += [ - { - "label": _("Customer LPO"), - "fieldtype": "Data", - "fieldname": "po_no", - "width": 100, - }, - _("Delivery Note") + ":Data:100", - _("Territory") + ":Link/Territory:80", - _("Customer Group") + ":Link/Customer Group:120", - { - "label": _("Sales Person"), - "fieldtype": "Data", - "fieldname": "sales_person", - "width": 120, - } - ] - if args.get("party_type") == "Supplier": - columns += [_("Supplier Group") + ":Link/Supplier Group:80"] - - columns.append(_("Remarks") + "::200") - - return columns - - def get_data(self, party_naming_by, args): - from erpnext.accounts.utils import get_currency_precision - self.currency_precision = get_currency_precision() or 2 - self.dr_or_cr = "debit" if args.get("party_type") == "Customer" else "credit" - - future_vouchers = self.get_entries_after(self.filters.report_date, args.get("party_type")) + self.filters.update(args) + self.set_defaults() + self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) + self.get_columns() + self.get_data() + self.get_chart_data() + return self.columns, self.data, None, self.chart + def set_defaults(self): if not self.filters.get("company"): - self.filters["company"] = frappe.db.get_single_value('Global Defaults', 'default_company') - + self.filters.company = frappe.db.get_single_value('Global Defaults', 'default_company') self.company_currency = frappe.get_cached_value('Company', self.filters.get("company"), "default_currency") + self.currency_precision = get_currency_precision() or 2 + self.dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit" + self.party_type = self.filters.party_type + self.party_details = {} + self.invoices = set() - return_entries = self.get_return_entries(args.get("party_type")) + def get_data(self): + t1 = now() + self.get_gl_entries() + self.voucher_balance = OrderedDict() + self.init_voucher_balance() # invoiced, paid, credit_note, outstanding - data = [] - self.pdc_details = get_pdc_details(args.get("party_type"), self.filters.report_date) - gl_entries_data = self.get_entries_till(self.filters.report_date, args.get("party_type")) + # Build delivery note map against all sales invoices + self.build_delivery_note_map() - if gl_entries_data: - voucher_nos = [d.voucher_no for d in gl_entries_data] or [] - dn_details = get_dn_details(args.get("party_type"), voucher_nos) - self.voucher_details = get_voucher_details(args.get("party_type"), voucher_nos, dn_details) + # Get invoice details like bill_no, due_date etc for all invoices + self.get_invoice_details() - if self.filters.based_on_payment_terms and gl_entries_data: - self.payment_term_map = self.get_payment_term_detail(voucher_nos) + # fetch future payments against invoices + self.get_future_payments() - for gle in gl_entries_data: - if self.is_receivable_or_payable(gle, self.dr_or_cr, future_vouchers, return_entries): - outstanding_amount, credit_note_amount, payment_amount = self.get_outstanding_amount( - gle,self.filters.report_date, self.dr_or_cr, return_entries) - temp_outstanding_amt = outstanding_amount - temp_credit_note_amt = credit_note_amount + self.data = [] + for gle in self.gl_entries: + self.update_voucher_balance(gle) - if abs(outstanding_amount) > 0.1/10**self.currency_precision: - if self.filters.based_on_payment_terms and self.payment_term_map.get(gle.voucher_no): - for d in self.payment_term_map.get(gle.voucher_no): - # Allocate payment amount based on payment terms(FIFO order) - payment_amount, d.payment_amount = self.allocate_based_on_fifo(payment_amount, d.payment_term_amount) + self.build_data() - term_outstanding_amount = d.payment_term_amount - d.payment_amount + def init_voucher_balance(self): + # build all keys, since we want to exclude vouchers beyond the report date + for gle in self.gl_entries: + # get the balance object for voucher_type + key = (gle.voucher_type, gle.voucher_no, gle.party) + if not key in self.voucher_balance: + self.voucher_balance[key] = frappe._dict( + voucher_type = gle.voucher_type, + voucher_no = gle.voucher_no, + party = gle.party, + posting_date = gle.posting_date, + remarks = gle.remarks, + invoiced = 0.0, + paid = 0.0, + credit_note = 0.0, + outstanding = 0.0 + ) + self.get_invoices(gle) - # Allocate credit note based on payment terms(FIFO order) - credit_note_amount, d.credit_note_amount = self.allocate_based_on_fifo(credit_note_amount, term_outstanding_amount) + def get_invoices(self, gle): + if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): + self.invoices.add(gle.voucher_no) - term_outstanding_amount -= d.credit_note_amount - - row_outstanding = term_outstanding_amount - # Allocate PDC based on payment terms(FIFO order) - d.pdc_details, d.pdc_amount = self.allocate_pdc_amount_in_fifo(gle, row_outstanding) - - if term_outstanding_amount > 0: - row = self.prepare_row(party_naming_by, args, gle, term_outstanding_amount, - d.credit_note_amount, d.due_date, d.payment_amount , d.payment_term_amount, - d.description, d.pdc_amount, d.pdc_details) - data.append(row) - - if credit_note_amount: - row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, temp_outstanding_amt, - temp_credit_note_amt) - data.append(row) - - else: - row = self.prepare_row_without_payment_terms(party_naming_by, args, gle, outstanding_amount, - credit_note_amount) - data.append(row) - return data - - def allocate_pdc_amount_in_fifo(self, gle, row_outstanding): - pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) - - pdc_details = [] - pdc_amount = 0 - for pdc in pdc_list: - if row_outstanding <= pdc.pdc_amount: - pdc_amount += row_outstanding - pdc.pdc_amount -= row_outstanding - if row_outstanding and pdc.pdc_ref and pdc.pdc_date: - pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) - row_outstanding = 0 + def update_voucher_balance(self, gle): + # get the row where this balance needs to be updated + # if its a payment, it will return the linked invoice or will be considered as advance + row = self.get_voucher_balance(gle) + # gle_balance will be the total "debit - credit" for receivable type reports and + # and vice-versa for payable type reports + gle_balance = self.get_gle_balance(gle) + if gle_balance > 0: + if gle.voucher_type in ('Journal Entry', 'Payment Entry') and gle.against_voucher: + # debit against sales / purchase invoice + row.paid -= gle_balance else: - pdc_amount = pdc.pdc_amount - if pdc.pdc_amount and pdc.pdc_ref and pdc.pdc_date: - pdc_details.append(cstr(pdc.pdc_ref) + "/" + formatdate(pdc.pdc_date)) - pdc.pdc_amount = 0 - row_outstanding -= pdc_amount - - return pdc_details, pdc_amount - - def prepare_row_without_payment_terms(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount): - pdc_list = self.pdc_details.get((gle.voucher_no, gle.party), []) - pdc_amount = 0 - pdc_details = [] - for d in pdc_list: - pdc_amount += flt(d.pdc_amount) - if pdc_amount and d.pdc_ref and d.pdc_date: - pdc_details.append(cstr(d.pdc_ref) + "/" + formatdate(d.pdc_date)) - - row = self.prepare_row(party_naming_by, args, gle, outstanding_amount, - credit_note_amount, pdc_amount=pdc_amount, pdc_details=pdc_details) - - return row - - - def allocate_based_on_fifo(self, total_amount, row_amount): - allocated_amount = 0 - if row_amount <= total_amount: - allocated_amount = row_amount - total_amount -= row_amount + # invoice + row.invoiced += gle_balance else: - allocated_amount = total_amount - total_amount = 0 + # payment or credit note for receivables + if self.is_invoice(gle): + # stand alone debit / credit note + row.credit_note -= gle_balance + else: + # advance / unlinked payment or other adjustment + row.paid -= gle_balance - return total_amount, allocated_amount + def get_voucher_balance(self, gle): + voucher_balance = None - def prepare_row(self, party_naming_by, args, gle, outstanding_amount, credit_note_amount, - due_date=None, paid_amt=None, payment_term_amount=None, payment_term=None, pdc_amount=None, pdc_details=None): - row = [gle.posting_date, gle.party] + if gle.against_voucher: + # find invoice + voucher_balance = self.voucher_balance.get((gle.against_voucher_type, gle.against_voucher, gle.party)) - # customer / supplier name - if party_naming_by == "Naming Series": - row += [self.get_party_name(gle.party_type, gle.party)] + if not voucher_balance: + # no invoice, this is an invoice / stand-alone payment / credit note + voucher_balance = self.voucher_balance.get((gle.voucher_type, gle.voucher_no, gle.party)) - if args.get("party_type") == 'Customer': - row += [self.get_customer_contact(gle.party_type, gle.party)] + return voucher_balance - # get due date - if not due_date: - due_date = self.voucher_details.get(gle.voucher_no, {}).get("due_date", "") - bill_date = self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") + def build_data(self): + # set outstanding for all the accumulated balances + # as we can use this to filter out invoices without outstanding + for key, row in self.voucher_balance.items(): + row.outstanding = flt(row.invoiced - row.paid - row.credit_note, self.currency_precision) + row.invoice_grand_total = row.invoiced - row += [gle.voucher_type, gle.voucher_no, due_date] + if abs(row.outstanding) > 0.1/10 ** self.currency_precision: + # non-zero oustanding, we must consider this row - # get supplier bill details - if args.get("party_type") == "Supplier": - row += [ - self.voucher_details.get(gle.voucher_no, {}).get("bill_no", ""), - self.voucher_details.get(gle.voucher_no, {}).get("bill_date", "") - ] + if self.is_invoice(row) and self.filters.based_on_payment_terms: + # is an invoice, allocate based on fifo + # adds a list `payment_terms` which contains new rows for each term + self.allocate_outstanding_based_on_payment_terms(row) - # invoiced and paid amounts - invoiced_amount = gle.get(self.dr_or_cr) if (gle.get(self.dr_or_cr) > 0) else 0 + if row.payment_terms: + # make separate rows for each payment term + for d in row.payment_terms: + if d.outstanding > 0: + self.append_row(d) - if self.filters.based_on_payment_terms: - row+=[payment_term, invoiced_amount] - if payment_term_amount: - invoiced_amount = payment_term_amount - - if not payment_term_amount: - paid_amt = invoiced_amount - outstanding_amount - credit_note_amount - row += [invoiced_amount, paid_amt, credit_note_amount, outstanding_amount] - - # ageing data - if self.filters.ageing_based_on == "Due Date": - entry_date = due_date - elif self.filters.ageing_based_on == "Supplier Invoice Date": - entry_date = bill_date - else: - entry_date = gle.posting_date - - row += get_ageing_data(cint(self.filters.range1), cint(self.filters.range2), - cint(self.filters.range3), cint(self.filters.range4), self.age_as_on, entry_date, outstanding_amount) - - # issue 6371-Ageing buckets should not have amounts if due date is not reached - if self.filters.ageing_based_on == "Due Date" \ - and getdate(due_date) > getdate(self.filters.report_date): - row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0 - - if self.filters.ageing_based_on == "Supplier Invoice Date" \ - and getdate(bill_date) > getdate(self.filters.report_date): - - row[-1]=row[-2]=row[-3]=row[-4]=row[-5]=0 - - if self.filters.get(scrub(args.get("party_type"))): - row.append(gle.account_currency) - else: - row.append(self.company_currency) - - remaining_balance = outstanding_amount - flt(pdc_amount) - pdc_details = ", ".join(pdc_details) - row += [pdc_details, pdc_amount, remaining_balance] - - if args.get('party_type') == 'Customer': - # customer LPO - row += [self.voucher_details.get(gle.voucher_no, {}).get("po_no")] - - # Delivery Note - row += [self.voucher_details.get(gle.voucher_no, {}).get("delivery_note")] - - # customer territory / supplier group - if args.get("party_type") == "Customer": - row += [self.get_territory(gle.party), self.get_customer_group(gle.party), - self.voucher_details.get(gle.voucher_no, {}).get("sales_person")] - if args.get("party_type") == "Supplier": - row += [self.get_supplier_group(gle.party)] - - row.append(gle.remarks) - - return row - - def get_entries_after(self, report_date, party_type): - # returns a distinct list - return list(set([(e.voucher_type, e.voucher_no) for e in self.get_gl_entries(party_type, report_date, for_future=True)])) - - def get_entries_till(self, report_date, party_type): - # returns a generator - return self.get_gl_entries(party_type, report_date) - - def is_receivable_or_payable(self, gle, dr_or_cr, future_vouchers, return_entries): - return ( - # advance - (not gle.against_voucher) or - - # against sales order/purchase order - (gle.against_voucher_type in ["Sales Order", "Purchase Order"]) or - - # sales invoice/purchase invoice - (gle.against_voucher==gle.voucher_no and gle.get(dr_or_cr) > 0) or - - # standalone credit notes - (gle.against_voucher==gle.voucher_no and gle.voucher_no in return_entries and not return_entries.get(gle.voucher_no)) or - - # entries adjusted with future vouchers - ((gle.against_voucher_type, gle.against_voucher) in future_vouchers) - ) - - def get_return_entries(self, party_type): - doctype = "Sales Invoice" if party_type=="Customer" else "Purchase Invoice" - return_entries = frappe._dict(frappe.get_all(doctype, - filters={"is_return": 1, "docstatus": 1}, fields=["name", "return_against"], as_list=1)) - return return_entries - - def get_outstanding_amount(self, gle, report_date, dr_or_cr, return_entries): - payment_amount, credit_note_amount = 0.0, 0.0 - reverse_dr_or_cr = "credit" if dr_or_cr=="debit" else "debit" - for e in self.get_gl_entries_for(gle.party, gle.party_type, gle.voucher_type, gle.voucher_no): - if getdate(e.posting_date) <= report_date \ - and (e.name!=gle.name or (e.voucher_no in return_entries and not return_entries.get(e.voucher_no))): - - amount = flt(e.get(reverse_dr_or_cr), self.currency_precision) - flt(e.get(dr_or_cr), self.currency_precision) - if e.voucher_no not in return_entries: - payment_amount += amount + # if there is overpayment, add another row + self.allocate_extra_payments_or_credits(row) + else: + self.append_row(row) else: - credit_note_amount += amount + self.append_row(row) - voucher_amount = flt(gle.get(dr_or_cr), self.currency_precision) - flt(gle.get(reverse_dr_or_cr), self.currency_precision) - if gle.voucher_no in return_entries and not return_entries.get(gle.voucher_no): - voucher_amount = 0 + def append_row(self, row): + self.allocate_future_payments(row) + self.set_invoice_details(row) + self.set_party_details(row) + self.set_ageing(row) + self.data.append(row) - outstanding_amount = flt((voucher_amount - payment_amount - credit_note_amount), self.currency_precision) - credit_note_amount = flt(credit_note_amount, self.currency_precision) + def set_invoice_details(self, row): + row.update(self.invoice_details.get(row.voucher_no, {})) + if row.voucher_type == 'Sales Invoice': + if self.filters.show_delivery_notes: + self.set_delivery_notes(row) - return outstanding_amount, credit_note_amount, payment_amount + if self.filters.show_sales_person and row.sales_team: + row.sales_person = ", ".join(row.sales_team) + del row['sales_team'] - def get_party_name(self, party_type, party_name): - return self.get_party_map(party_type).get(party_name, {}).get("customer_name" if party_type == "Customer" else "supplier_name") or "" + def set_delivery_notes(self, row): + delivery_notes = self.delivery_notes.get(row.voucher_no, []) + if delivery_notes: + row.delivery_notes = ', '.join(delivery_notes) - def get_customer_contact(self, party_type, party_name): - return self.get_party_map(party_type).get(party_name, {}).get("customer_primary_contact") + def build_delivery_note_map(self): + if self.invoices and self.filters.show_delivery_notes: + self.delivery_notes = frappe._dict() - def get_territory(self, party_name): - return self.get_party_map("Customer").get(party_name, {}).get("territory") or "" + # delivery note link inside sales invoice + si_against_dn = frappe.db.sql(""" + select parent, delivery_note + from `tabSales Invoice Item` + where docstatus=1 and parent in (%s) + """ % (','.join(['%s'] * len(self.invoices))), tuple(self.invoices), as_dict=1) - def get_customer_group(self, party_name): - return self.get_party_map("Customer").get(party_name, {}).get("customer_group") or "" + for d in si_against_dn: + if d.delivery_note: + self.delivery_notes.setdefault(d.parent, set()).add(d.delivery_note) - def get_supplier_group(self, party_name): - return self.get_party_map("Supplier").get(party_name, {}).get("supplier_group") or "" + dn_against_si = frappe.db.sql(""" + select distinct parent, against_sales_invoice + from `tabDelivery Note Item` + where against_sales_invoice in (%s) + """ % (','.join(['%s'] * len(self.invoices))), tuple(self.invoices) , as_dict=1) - def get_party_map(self, party_type): - if not hasattr(self, "party_map"): - if party_type == "Customer": - select_fields = "name, customer_name, territory, customer_group, customer_primary_contact" - elif party_type == "Supplier": - select_fields = "name, supplier_name, supplier_group" + for d in dn_against_si: + self.delivery_notes.setdefault(d.against_sales_invoice, set()).add(d.parent) - self.party_map = dict(((r.name, r) for r in frappe.db.sql("select {0} from `tab{1}`" - .format(select_fields, party_type), as_dict=True))) + def get_invoice_details(self): + self.invoice_details = frappe._dict() + if self.party_type == "Customer": + si_list = frappe.db.sql(""" + select name, due_date, po_no + from `tabSales Invoice` + where posting_date <= %s + """,self.filters.report_date, as_dict=1) + for d in si_list: + self.invoice_details.setdefault(d.name, d) - return self.party_map + # Get Sales Team + if self.filters.show_sales_person: + sales_team = frappe.db.sql(""" + select parent, sales_person + from `tabSales Team` + where parenttype = 'Sales Invoice' + """, as_dict=1) + for d in sales_team: + self.invoice_details.setdefault(d.parent, {})\ + .setdefault('sales_team', []).append(d.sales_person) - def get_gl_entries(self, party_type, date=None, for_future=False): - conditions, values = self.prepare_conditions(party_type) + if self.party_type == "Supplier": + for pi in frappe.db.sql(""" + select name, due_date, bill_no, bill_date + from `tabPurchase Invoice` + where posting_date <= %s + """, self.filters.report_date, as_dict=1): + self.invoice_details.setdefault(pi.name, pi) - if self.filters.get(scrub(party_type)): - select_fields = "sum(debit_in_account_currency) as debit, sum(credit_in_account_currency) as credit" + # Invoices booked via Journal Entries + journal_entries = frappe.db.sql(""" + select name, due_date, bill_no, bill_date + from `tabJournal Entry` + where posting_date <= %s + """, self.filters.report_date, as_dict=1) + + for je in journal_entries: + if je.bill_no: + self.invoice_details.setdefault(je.name, je) + + def set_party_details(self, row): + # customer / supplier name + party_details = self.get_party_details(row.party) + row.update(party_details) + + if self.filters.get(scrub(self.filters.party_type)): + row.currency = row.account_currency else: - select_fields = "sum(debit) as debit, sum(credit) as credit" + row.currency = self.company_currency - if date and not for_future: - conditions += " and posting_date <= '%s'" % date + def allocate_outstanding_based_on_payment_terms(self, row): + self.get_payment_terms(row) + for term in row.payment_terms: + term.outstanding = term.invoiced - if date and for_future: - conditions += " and posting_date > '%s'" % date + # update "paid" and "oustanding" for this term + 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') + + 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 + from `tab{0}` si, `tabPayment Schedule` ps + where + si.name = ps.parent and + si.name = %s + order by ps.due_date + """.format(row.voucher_type), row.voucher_no, as_dict = 1) + + + original_row = frappe._dict(row) + row.payment_terms = [] + + # If no or single payment terms, no need to split the row + if len(payment_terms_details) <= 1: + return + + for d in payment_terms_details: + term = frappe._dict(original_row) + self.append_payment_term(row, d, term) + + def append_payment_term(self, row, d, term): + if self.filters.get("customer") and d.currency == d.party_account_currency: + invoiced = d.payment_amount + else: + invoiced = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + + row.payment_terms.append(term.update({ + "due_date": d.due_date, + "invoiced": invoiced, + "invoice_grand_total": row.invoiced, + "payment_term": d.description, + "paid": 0.0, + "credit_note": 0.0, + "outstanding": 0.0 + })) + + def allocate_closing_to_term(self, row, term, key): + if row[key]: + if row[key] > term.outstanding: + term[key] = term.outstanding + row[key] -= term.outstanding + else: + term[key] = row[key] + row[key] = 0 + term.outstanding -= term[key] + + def allocate_extra_payments_or_credits(self, row): + # allocate extra payments / credits + additional_row = None + for key in ('paid', 'credit_note'): + if row[key] > 0: + if not additional_row: + additional_row = frappe._dict(row) + additional_row.invoiced = 0.0 + additional_row[key] = row[key] + + if additional_row: + additional_row.outstanding = additional_row.invoiced - additional_row.paid - additional_row.credit_note + self.append_row(additional_row) + + def get_future_payments(self): + if self.filters.show_future_payments: + self.future_payments = frappe._dict() + future_payments = list(self.get_future_payments_from_payment_entry()) + future_payments += list(self.get_future_payments_from_journal_entry()) + if future_payments: + for d in future_payments: + if d.future_amount and d.invoice_no: + self.future_payments.setdefault((d.invoice_no, d.party), []).append(d) + + def get_future_payments_from_payment_entry(self): + return frappe.db.sql(""" + select + ref.reference_name as invoice_no, + payment_entry.party, + payment_entry.party_type, + payment_entry.posting_date as future_date, + ref.allocated_amount as future_amount, + payment_entry.reference_no as future_ref + from + `tabPayment Entry` as payment_entry inner join `tabPayment Entry Reference` as ref + on + (ref.parent = payment_entry.name) + where + payment_entry.docstatus < 2 + and payment_entry.posting_date > %s + and payment_entry.party_type = %s + """, (self.filters.report_date, self.party_type), as_dict=1) + + def get_future_payments_from_journal_entry(self): + if self.filters.get('party'): + amount_field = ("jea.debit_in_account_currency - jea.credit_in_account_currency" + if self.party_type == 'Supplier' else "jea.credit_in_account_currency - jea.debit_in_account_currency") + else: + amount_field = ("jea.debit - " if self.party_type == 'Supplier' else "jea.credit") + + return frappe.db.sql(""" + select + jea.reference_name as invoice_no, + jea.party, + jea.party_type, + je.posting_date as future_date, + sum({0}) as future_amount, + je.cheque_no as future_ref + from + `tabJournal Entry` as je inner join `tabJournal Entry Account` as jea + on + (jea.parent = je.name) + where + je.docstatus < 2 + and je.posting_date > %s + and jea.party_type = %s + and jea.reference_name is not null and jea.reference_name != '' + group by je.name, jea.reference_name + having future_amount > 0 + """.format(amount_field), (self.filters.report_date, self.party_type), as_dict=1) + + def allocate_future_payments(self, row): + # future payments are captured in additional columns + # this method allocates pending future payments against a voucher to + # the current row (which could be generated from payment terms) + if not self.filters.show_future_payments: + return + + row.remaining_balance = row.outstanding + row.future_amount = 0.0 + for future in self.future_payments.get((row.voucher_no, row.party), []): + if row.remaining_balance > 0 and future.future_amount: + if future.future_amount > row.outstanding: + row.future_amount = row.outstanding + future.future_amount = future.future_amount - row.outstanding + row.remaining_balance = 0 + else: + row.future_amount += future.future_amount + future.future_amount = 0 + row.remaining_balance = row.outstanding - row.future_amount + + row.setdefault('future_ref', []).append(cstr(future.future_ref) + '/' + cstr(future.future_date)) + + if row.future_ref: + row.future_ref = ', '.join(row.future_ref) + + def set_ageing(self, row): + if self.filters.ageing_based_on == "Due Date": + entry_date = row.due_date + elif self.filters.ageing_based_on == "Supplier Invoice Date": + entry_date = row.bill_date + else: + entry_date = row.posting_date + + self.get_ageing_data(entry_date, row) + + # ageing buckets should not have amounts if due date is not reached + if getdate(entry_date) > getdate(self.filters.report_date): + row.range1 = row.range2 = row.range3 = row.range4 = row.range5 = 0.0 + + def get_ageing_data(self, entry_date, row): + # [0-30, 30-60, 60-90, 90-120, 120-above] + row.range1 = row.range2 = row.range3 = row.range4 = range5 = 0.0 + + if not (self.age_as_on and entry_date): + return + + row.age = (getdate(self.age_as_on) - getdate(entry_date)).days or 0 + index = None + for i, days in enumerate([self.filters.range1, self.filters.range2, self.filters.range3, self.filters.range4]): + if row.age <= days: + index = i + break + + if index is None: index = 4 + row['range' + str(index+1)] = row.outstanding + + def get_gl_entries(self): + # get all the GL entries filtered by the given filters + + conditions, values = self.prepare_conditions() + + if self.filters.get(scrub(self.party_type)): + select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit" + else: + select_fields = "debit, credit" self.gl_entries = frappe.db.sql(""" select @@ -473,91 +471,95 @@ class ReceivablePayableReport(object): from `tabGL Entry` where - docstatus < 2 and party_type=%s and (party is not null and party != '') {1} - group by voucher_type, voucher_no, against_voucher_type, against_voucher, party - order by posting_date, party""" + docstatus < 2 + and party_type=%s + and (party is not null and party != '') + and posting_date <= %s + {1} + order by posting_date, party""" .format(select_fields, conditions), values, as_dict=True) - return self.gl_entries - - def prepare_conditions(self, party_type): + def prepare_conditions(self): conditions = [""] - values = [party_type] + values = [self.party_type, self.filters.report_date] + party_type_field = scrub(self.party_type) - party_type_field = scrub(party_type) + self.add_common_filters(conditions, values, party_type_field) + if party_type_field=="customer": + self.add_customer_filters(conditions, values) + + elif party_type_field=="supplier": + self.add_supplier_filters(conditions, values) + + self.add_accounting_dimensions_filters(conditions, values) + + return " and ".join(conditions), values + + def add_common_filters(self, conditions, values, party_type_field): if self.filters.company: conditions.append("company=%s") values.append(self.filters.company) if self.filters.finance_book: - conditions.append("ifnull(finance_book,'') in (%s, '')") + conditions.append("ifnull(finance_book, '') in (%s, '')") values.append(self.filters.finance_book) if self.filters.get(party_type_field): conditions.append("party=%s") values.append(self.filters.get(party_type_field)) - if party_type_field=="customer": - account_type = "Receivable" - if self.filters.get("customer_group"): - lft, rgt = frappe.db.get_value("Customer Group", - self.filters.get("customer_group"), ["lft", "rgt"]) + # get GL with "receivable" or "payable" account_type + account_type = "Receivable" if self.party_type == "Customer" else "Payable" + accounts = [d.name for d in frappe.get_all("Account", + filters={"account_type": account_type, "company": self.filters.company})] + conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) + values += accounts - conditions.append("""party in (select name from tabCustomer - where exists(select name from `tabCustomer Group` where lft >= {0} and rgt <= {1} - and name=tabCustomer.customer_group))""".format(lft, rgt)) + def add_customer_filters(self, conditions, values): + if self.filters.get("customer_group"): + conditions.append(self.get_hierarchical_filters('Customer Group', 'customer_group')) - if self.filters.get("territory"): - lft, rgt = frappe.db.get_value("Territory", - self.filters.get("territory"), ["lft", "rgt"]) + if self.filters.get("territory"): + conditions.append(self.get_hierarchical_filters('Territory', 'territory')) - conditions.append("""party in (select name from tabCustomer - where exists(select name from `tabTerritory` where lft >= {0} and rgt <= {1} - and name=tabCustomer.territory))""".format(lft, rgt)) + if self.filters.get("payment_terms_template"): + conditions.append("party in (select name from tabCustomer where payment_terms=%s)") + values.append(self.filters.get("payment_terms_template")) - if self.filters.get("payment_terms_template"): - conditions.append("party in (select name from tabCustomer where payment_terms=%s)") - values.append(self.filters.get("payment_terms_template")) + if self.filters.get("sales_partner"): + conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)") + values.append(self.filters.get("sales_partner")) - if self.filters.get("sales_partner"): - conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)") - values.append(self.filters.get("sales_partner")) + if self.filters.get("sales_person"): + lft, rgt = frappe.db.get_value("Sales Person", + self.filters.get("sales_person"), ["lft", "rgt"]) - if self.filters.get("sales_person"): - lft, rgt = frappe.db.get_value("Sales Person", - self.filters.get("sales_person"), ["lft", "rgt"]) + conditions.append("""exists(select name from `tabSales Team` steam where + steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) + and ((steam.parent = voucher_no and steam.parenttype = voucher_type) + or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) + or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) - conditions.append("""exists(select name from `tabSales Team` steam where - steam.sales_person in (select name from `tabSales Person` where lft >= {0} and rgt <= {1}) - and ((steam.parent = voucher_no and steam.parenttype = voucher_type) - or (steam.parent = against_voucher and steam.parenttype = against_voucher_type) - or (steam.parent = party and steam.parenttype = 'Customer')))""".format(lft, rgt)) + def add_supplier_filters(self, conditions, values): + if self.filters.get("supplier_group"): + conditions.append("""party in (select name from tabSupplier + where supplier_group=%s)""") + values.append(self.filters.get("supplier_group")) - elif party_type_field=="supplier": - account_type = "Payable" - if self.filters.get("supplier_group"): - conditions.append("""party in (select name from tabSupplier - where supplier_group=%s)""") - values.append(self.filters.get("supplier_group")) + if self.filters.get("payment_terms_template"): + conditions.append("party in (select name from tabSupplier where payment_terms=%s)") + values.append(self.filters.get("payment_terms_template")) - if self.filters.get("payment_terms_template"): - conditions.append("party in (select name from tabSupplier where payment_terms=%s)") - values.append(self.filters.get("payment_terms_template")) + def get_hierarchical_filters(self, doctype, key): + lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"]) - if self.filters.get("cost_center"): - lft, rgt = frappe.get_cached_value("Cost Center", - self.filters.get("cost_center"), ['lft', 'rgt']) - - conditions.append("""cost_center in (select name from `tabCost Center` where - lft >= {0} and rgt <= {1})""".format(lft, rgt)) - - if self.filters.company: - accounts = [d.name for d in frappe.get_all("Account", - filters={"account_type": account_type, "company": self.filters.company})] - conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) - values += accounts + return """party in (select name from tabCustomer + where exists(select name from `tab{doctype}` where lft >= {lft} and rgt <= {rgt} + and name=tabCustomer.{key}))""".format( + doctype=doctype, lft=lft, rgt=rgt, key=key) + def add_accounting_dimensions_filters(self, conditions, values): accounting_dimensions = get_accounting_dimensions() if accounting_dimensions: @@ -566,195 +568,133 @@ class ReceivablePayableReport(object): conditions.append("{0} = %s".format(dimension)) values.append(self.filters.get(dimension)) - return " and ".join(conditions), values + def get_gle_balance(self, gle): + # get the balance of the GL (debit - credit) or reverse balance based on report type + return gle.get(self.dr_or_cr) - self.get_reverse_balance(gle) - def get_gl_entries_for(self, party, party_type, against_voucher_type, against_voucher): - if not hasattr(self, "gl_entries_map"): - self.gl_entries_map = {} - for gle in self.get_gl_entries(party_type): - if gle.against_voucher_type and gle.against_voucher: - self.gl_entries_map.setdefault(gle.party, {})\ - .setdefault(gle.against_voucher_type, {})\ - .setdefault(gle.against_voucher, [])\ - .append(gle) + def get_reverse_balance(self, gle): + # get "credit" balance if report type is "debit" and vice versa + return gle.get('debit' if self.dr_or_cr=='credit' else 'credit') - return self.gl_entries_map.get(party, {})\ - .get(against_voucher_type, {})\ - .get(against_voucher, []) + def is_invoice(self, gle): + if gle.voucher_type in ('Sales Invoice', 'Purchase Invoice'): + return True - def get_payment_term_detail(self, voucher_nos): - payment_term_map = frappe._dict() - payment_terms_details = frappe.db.sql(""" select si.name, - party_account_currency, currency, si.conversion_rate, - ps.due_date, ps.payment_amount, ps.description - from `tabSales Invoice` si, `tabPayment Schedule` ps - where si.name = ps.parent and - si.docstatus = 1 and si.company = %s and - si.name in (%s) order by ps.due_date - """ % (frappe.db.escape(self.filters.company), ','.join(['%s'] *len(voucher_nos))), - (tuple(voucher_nos)), as_dict = 1) - - for d in payment_terms_details: - if self.filters.get("customer") and d.currency == d.party_account_currency: - payment_term_amount = d.payment_amount + def get_party_details(self, party): + if not party in self.party_details: + if self.party_type == 'Customer': + self.party_details[party] = frappe.db.get_value('Customer', party, ['customer_name', + 'territory', 'customer_group', 'customer_primary_contact'], as_dict=True) else: - payment_term_amount = flt(flt(d.payment_amount) * flt(d.conversion_rate), self.currency_precision) + self.party_details[party] = frappe.db.get_value('Supplier', party, ['supplier_name', + 'supplier_group'], as_dict=True) - payment_term_map.setdefault(d.name, []).append(frappe._dict({ - "due_date": d.due_date, - "payment_term_amount": payment_term_amount, - "description": d.description - })) - return payment_term_map + return self.party_details[party] - def get_chart_data(self, columns, data): - ageing_columns = columns[self.ageing_col_idx_start : self.ageing_col_idx_start+5] + def get_columns(self): + self.columns = [] + self.add_column('Posting Date', fieldtype='Date') + self.add_column(label=_(self.party_type), fieldname='party', + fieldtype='Link', options=self.party_type, width=180) + + if self.party_naming_by == "Naming Series": + self.add_column(_('{0} Name').format(self.party_type), + fieldname = scrub(self.party_type) + '_name', fieldtype='Data') + + if self.party_type == 'Customer': + self.add_column(_("Customer Contact"), fieldname='customer_primary_contact', + fieldtype='Link', options='Contact') + + self.add_column(label=_('Voucher Type'), fieldname='voucher_type', fieldtype='Data') + self.add_column(label=_('Voucher No'), fieldname='voucher_no', fieldtype='Dynamic Link', + options='voucher_type', width=180) + self.add_column(label='Due Date', fieldtype='Date') + + if self.party_type == "Supplier": + self.add_column(label=_('Bill No'), fieldname='bill_no', fieldtype='Data') + self.add_column(label=_('Bill Date'), fieldname='bill_date', fieldtype='Date') + + if self.filters.based_on_payment_terms: + self.add_column(label=_('Payment Term'), fieldname='payment_term', fieldtype='Data') + self.add_column(label=_('Invoice Grand Total'), fieldname='invoice_grand_total') + + self.add_column(_('Invoiced Amount'), fieldname='invoiced') + self.add_column(_('Paid Amount'), fieldname='paid') + if self.party_type == "Customer": + self.add_column(_('Credit Note'), fieldname='credit_note') + else: + # note: fieldname is still `credit_note` + self.add_column(_('Debit Note'), fieldname='credit_note') + self.add_column(_('Outstanding Amount'), fieldname='outstanding') + + self.setup_ageing_columns() + + self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link', options='Currency', width=80) + + if self.filters.show_future_payments: + self.add_column(label=_('Future Payment Ref'), fieldname='future_ref', fieldtype='Data') + self.add_column(label=_('Future Payment Amount'), fieldname='future_amount') + self.add_column(label=_('Remaining Balance'), fieldname='remaining_balance') + + if self.filters.party_type == 'Customer': + self.add_column(label=_('Customer LPO'), fieldname='po_no', fieldtype='Data') + + # comma separated list of linked delivery notes + if self.filters.show_delivery_notes: + self.add_column(label=_('Delivery Notes'), fieldname='delivery_notes', fieldtype='Data') + self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link', + options='Territory') + self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link', + options='Customer Group') + if self.filters.show_sales_person: + self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data') + + if self.filters.party_type == "Supplier": + self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', + options='Supplier Group') + + self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200) + + def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120): + if not fieldname: fieldname = scrub(label) + if fieldtype=='Currency': options='currency' + if fieldtype=='Date': width = 90 + + self.columns.append(dict( + label=label, + fieldname=fieldname, + fieldtype=fieldtype, + options=options, + width=width + )) + + def setup_ageing_columns(self): + # for charts + self.ageing_column_labels = [] + self.add_column(label=_('Age (Days)'), fieldname='age', fieldtype='Int', width=80) + + for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]), + "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]), + "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), + "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]), + "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]): + self.add_column(label=label, fieldname='range' + str(i+1)) + self.ageing_column_labels.append(label) + + def get_chart_data(self): rows = [] - for d in data: - values = d[self.ageing_col_idx_start : self.ageing_col_idx_start+5] - precision = cint(frappe.db.get_default("float_precision")) or 2 - formatted_values = [frappe.utils.rounded(val, precision) for val in values] + for row in self.data: rows.append( { - 'values': formatted_values + 'values': [row.range1, row.range2, row.range3, row.range4, row.range5] } ) - return { + self.chart = { "data": { - 'labels': [d.get("label") for d in ageing_columns], + 'labels': self.ageing_column_labels, 'datasets': rows }, "type": 'percentage' } - -def execute(filters=None): - args = { - "party_type": "Customer", - "naming_by": ["Selling Settings", "cust_master_name"], - } - return ReceivablePayableReport(filters).run(args) - -def get_ageing_data(first_range, second_range, third_range, - fourth_range, age_as_on, entry_date, outstanding_amount): - # [0-30, 30-60, 60-90, 90-120, 120-above] - outstanding_range = [0.0, 0.0, 0.0, 0.0, 0.0] - - if not (age_as_on and entry_date): - return [0] + outstanding_range - - age = (getdate(age_as_on) - getdate(entry_date)).days or 0 - index = None - for i, days in enumerate([first_range, second_range, third_range, fourth_range]): - if age <= days: - index = i - break - - if index is None: index = 4 - outstanding_range[index] = outstanding_amount - - return [age] + outstanding_range - -def get_pdc_details(party_type, report_date): - pdc_details = frappe._dict() - pdc_via_pe = frappe.db.sql(""" - select - pref.reference_name as invoice_no, pent.party, pent.party_type, - pent.posting_date as pdc_date, ifnull(pref.allocated_amount,0) as pdc_amount, - pent.reference_no as pdc_ref - from - `tabPayment Entry` as pent inner join `tabPayment Entry Reference` as pref - on - (pref.parent = pent.name) - where - pent.docstatus < 2 and pent.posting_date > %s - and pent.party_type = %s - """, (report_date, party_type), as_dict=1) - - for pdc in pdc_via_pe: - pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) - - if scrub(party_type): - amount_field = ("jea.debit_in_account_currency" - if party_type == 'Supplier' else "jea.credit_in_account_currency") - else: - amount_field = "jea.debit + jea.credit" - - pdc_via_je = frappe.db.sql(""" - select - jea.reference_name as invoice_no, jea.party, jea.party_type, - je.posting_date as pdc_date, ifnull({0},0) as pdc_amount, - je.cheque_no as pdc_ref - from - `tabJournal Entry` as je inner join `tabJournal Entry Account` as jea - on - (jea.parent = je.name) - where - je.docstatus < 2 and je.posting_date > %s - and jea.party_type = %s - """.format(amount_field), (report_date, party_type), as_dict=1) - - for pdc in pdc_via_je: - pdc_details.setdefault((pdc.invoice_no, pdc.party), []).append(pdc) - - return pdc_details - -def get_dn_details(party_type, voucher_nos): - dn_details = frappe._dict() - - if party_type == "Customer": - for si in frappe.db.sql(""" - select - parent, GROUP_CONCAT(delivery_note SEPARATOR ', ') as dn - from - `tabSales Invoice Item` - where - docstatus=1 and delivery_note is not null and delivery_note != '' - and parent in (%s) group by parent - """ %(','.join(['%s'] * len(voucher_nos))), tuple(voucher_nos) , as_dict=1): - dn_details.setdefault(si.parent, si.dn) - - for si in frappe.db.sql(""" - select - against_sales_invoice as parent, GROUP_CONCAT(parent SEPARATOR ', ') as dn - from - `tabDelivery Note Item` - where - docstatus=1 and against_sales_invoice is not null and against_sales_invoice != '' - and against_sales_invoice in (%s) - group by against_sales_invoice - """ %(','.join(['%s'] * len(voucher_nos))), tuple(voucher_nos) , as_dict=1): - if si.parent in dn_details: - dn_details[si.parent] += ', %s' %(si.dn) - else: - dn_details.setdefault(si.parent, si.dn) - - return dn_details - -def get_voucher_details(party_type, voucher_nos, dn_details): - voucher_details = frappe._dict() - - if party_type == "Customer": - for si in frappe.db.sql(""" - select inv.name, inv.due_date, inv.po_no, GROUP_CONCAT(steam.sales_person SEPARATOR ', ') as sales_person - from `tabSales Invoice` inv - left join `tabSales Team` steam on steam.parent = inv.name and steam.parenttype = 'Sales Invoice' - where inv.docstatus=1 and inv.name in (%s) - group by inv.name - """ %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1): - si['delivery_note'] = dn_details.get(si.name) - voucher_details.setdefault(si.name, si) - - if party_type == "Supplier": - for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date - from `tabPurchase Invoice` where docstatus = 1 and name in (%s) - """ %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1): - voucher_details.setdefault(pi.name, pi) - - for pi in frappe.db.sql("""select name, due_date, bill_no, bill_date from - `tabJournal Entry` where docstatus = 1 and bill_no is not NULL and name in (%s) - """ %(','.join(['%s'] *len(voucher_nos))), (tuple(voucher_nos)), as_dict=1): - voucher_details.setdefault(pi.name, pi) - - return voucher_details diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py index 43786a44464..f0274b44723 100644 --- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py @@ -14,33 +14,44 @@ class TestAccountsReceivable(unittest.TestCase): filters = { 'company': '_Test Company 2', - 'based_on_payment_terms': 1 + 'based_on_payment_terms': 1, + 'report_date': today(), + 'range1': 30, + 'range2': 60, + 'range3': 90, + 'range4': 120 } + # check invoice grand total and invoiced column's value for 3 payment terms name = make_sales_invoice() report = execute(filters) - expected_data = [[100,30], [100,50], [100,20]] + expected_data = [[100, 30], [100, 50], [100, 20]] - self.assertEqual(expected_data[0], report[1][0][7:9]) - self.assertEqual(expected_data[1], report[1][1][7:9]) - self.assertEqual(expected_data[2], report[1][2][7:9]) + for i in range(3): + row = report[1][i-1] + self.assertEqual(expected_data[i-1], [row.invoice_grand_total, row.invoiced]) + # check invoice grand total, invoiced, paid and outstanding column's value after payment make_payment(name) report = execute(filters) - expected_data_after_payment = [[100,50], [100,20]] + expected_data_after_payment = [[100, 50, 10, 40], [100, 20, 0, 20]] - self.assertEqual(expected_data_after_payment[0], report[1][0][7:9]) - self.assertEqual(expected_data_after_payment[1], report[1][1][7:9]) + for i in range(2): + row = report[1][i-1] + self.assertEqual(expected_data_after_payment[i-1], + [row.invoice_grand_total, row.invoiced, row.paid, row.outstanding]) + # check invoice grand total, invoiced, paid and outstanding column's value after credit note make_credit_note(name) report = execute(filters) - expected_data_after_credit_note = [[100,100,30,100,-30]] - - self.assertEqual(expected_data_after_credit_note[0], report[1][0][7:12]) + expected_data_after_credit_note = [100, 0, 0, 40, -40] + row = report[1][0] + self.assertEqual(expected_data_after_credit_note, + [row.invoice_grand_total, row.invoiced, row.paid, row.credit_note, row.outstanding]) def make_sales_invoice(): frappe.set_user("Administrator") @@ -64,7 +75,7 @@ def make_sales_invoice(): return si.name def make_payment(docname): - pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=30) + pe = get_payment_entry("Sales Invoice", docname, bank_account="Cash - _TC2", party_amount=40) pe.paid_from = "Debtors - _TC2" pe.insert() pe.submit() diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index ec24aece5f3..350e0819577 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -3,236 +3,11 @@ from __future__ import unicode_literals import frappe -from frappe import _, scrub -from frappe.utils import flt +from frappe import _ +from frappe.utils import flt, cint from erpnext.accounts.party import get_partywise_advanced_payment_amount from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport - from six import iteritems -from six.moves import zip - -class AccountsReceivableSummary(ReceivablePayableReport): - def run(self, args): - party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) - return self.get_columns(party_naming_by, args), self.get_data(party_naming_by, args) - - def get_columns(self, party_naming_by, args): - columns = [_(args.get("party_type")) + ":Link/" + args.get("party_type") + ":200"] - - if party_naming_by == "Naming Series": - columns += [ args.get("party_type") + " Name::140"] - - credit_debit_label = "Credit Note Amt" if args.get('party_type') == 'Customer' else "Debit Note Amt" - - columns += [{ - "label": _("Advance Amount"), - "fieldname": "advance_amount", - "fieldtype": "Currency", - "options": "currency", - "width": 100 - },{ - "label": _("Total Invoiced Amt"), - "fieldname": "total_invoiced_amt", - "fieldtype": "Currency", - "options": "currency", - "width": 100 - }, - { - "label": _("Total Paid Amt"), - "fieldname": "total_paid_amt", - "fieldtype": "Currency", - "options": "currency", - "width": 100 - }] - - columns += [ - { - "label": _(credit_debit_label), - "fieldname": scrub(credit_debit_label), - "fieldtype": "Currency", - "options": "currency", - "width": 140 - }, - { - "label": _("Total Outstanding Amt"), - "fieldname": "total_outstanding_amt", - "fieldtype": "Currency", - "options": "currency", - "width": 160 - }, - { - "label": _("0-" + str(self.filters.range1)), - "fieldname": scrub("0-" + str(self.filters.range1)), - "fieldtype": "Currency", - "options": "currency", - "width": 160 - }, - { - "label": _(str(self.filters.range1) + "-" + str(self.filters.range2)), - "fieldname": scrub(str(self.filters.range1) + "-" + str(self.filters.range2)), - "fieldtype": "Currency", - "options": "currency", - "width": 160 - }, - { - "label": _(str(self.filters.range2) + "-" + str(self.filters.range3)), - "fieldname": scrub(str(self.filters.range2) + "-" + str(self.filters.range3)), - "fieldtype": "Currency", - "options": "currency", - "width": 160 - }, - { - "label": _(str(self.filters.range3) + "-" + str(self.filters.range4)), - "fieldname": scrub(str(self.filters.range3) + "-" + str(self.filters.range4)), - "fieldtype": "Currency", - "options": "currency", - "width": 160 - }, - { - "label": _(str(self.filters.range4) + _("-Above")), - "fieldname": scrub(str(self.filters.range4) + _("-Above")), - "fieldtype": "Currency", - "options": "currency", - "width": 160 - } - ] - - if args.get("party_type") == "Customer": - columns += [{ - "label": _("Territory"), - "fieldname": "territory", - "fieldtype": "Link", - "options": "Territory", - "width": 80 - }, - { - "label": _("Customer Group"), - "fieldname": "customer_group", - "fieldtype": "Link", - "options": "Customer Group", - "width": 80 - }, - { - "label": _("Sales Person"), - "fieldtype": "Data", - "fieldname": "sales_person", - "width": 120, - }] - - if args.get("party_type") == "Supplier": - columns += [{ - "label": _("Supplier Group"), - "fieldname": "supplier_group", - "fieldtype": "Link", - "options": "Supplier Group", - "width": 80 - }] - - columns.append({ - "fieldname": "currency", - "label": _("Currency"), - "fieldtype": "Link", - "options": "Currency", - "width": 80 - }) - - return columns - - def get_data(self, party_naming_by, args): - data = [] - - partywise_total = self.get_partywise_total(party_naming_by, args) - - partywise_advance_amount = get_partywise_advanced_payment_amount(args.get("party_type"), - self.filters.get("report_date")) or {} - for party, party_dict in iteritems(partywise_total): - row = [party] - - if party_naming_by == "Naming Series": - row += [self.get_party_name(args.get("party_type"), party)] - - row += [partywise_advance_amount.get(party, 0)] - - paid_amt = 0 - if party_dict.paid_amt > 0: - paid_amt = flt(party_dict.paid_amt - partywise_advance_amount.get(party, 0)) - - row += [ - party_dict.invoiced_amt, paid_amt, party_dict.credit_amt, party_dict.outstanding_amt, - party_dict.range1, party_dict.range2, party_dict.range3, party_dict.range4, party_dict.range5 - ] - - if args.get("party_type") == "Customer": - row += [self.get_territory(party), self.get_customer_group(party), ", ".join(set(party_dict.sales_person))] - if args.get("party_type") == "Supplier": - row += [self.get_supplier_group(party)] - - row.append(party_dict.currency) - data.append(row) - - return data - - def get_partywise_total(self, party_naming_by, args): - party_total = frappe._dict() - for d in self.get_voucherwise_data(party_naming_by, args): - party_total.setdefault(d.party, - frappe._dict({ - "invoiced_amt": 0, - "paid_amt": 0, - "credit_amt": 0, - "outstanding_amt": 0, - "range1": 0, - "range2": 0, - "range3": 0, - "range4": 0, - "range5": 0, - "sales_person": [] - }) - ) - for k in list(party_total[d.party]): - if k not in ["currency", "sales_person"]: - party_total[d.party][k] += flt(d.get(k, 0)) - - party_total[d.party].currency = d.currency - - if d.sales_person: - party_total[d.party].sales_person.append(d.sales_person) - - return party_total - - def get_voucherwise_data(self, party_naming_by, args): - voucherwise_data = ReceivablePayableReport(self.filters).run(args)[1] - - cols = ["posting_date", "party"] - - if party_naming_by == "Naming Series": - cols += ["party_name"] - - if args.get("party_type") == 'Customer': - cols += ["contact"] - - cols += ["voucher_type", "voucher_no", "due_date"] - - if args.get("party_type") == "Supplier": - cols += ["bill_no", "bill_date"] - - cols += ["invoiced_amt", "paid_amt", "credit_amt", - "outstanding_amt", "age", "range1", "range2", "range3", "range4", "range5", "currency", "pdc/lc_date", "pdc/lc_ref", - "pdc/lc_amount"] - - if args.get("party_type") == "Supplier": - cols += ["supplier_group", "remarks"] - if args.get("party_type") == "Customer": - cols += ["po_no", "do_no", "territory", "customer_group", "sales_person", "remarks"] - - return self.make_data_dict(cols, voucherwise_data) - - def make_data_dict(self, cols, data): - data_dict = [] - for d in data: - data_dict.append(frappe._dict(zip(cols, d))) - - return data_dict def execute(filters=None): args = { @@ -241,3 +16,119 @@ def execute(filters=None): } return AccountsReceivableSummary(filters).run(args) + +class AccountsReceivableSummary(ReceivablePayableReport): + def run(self, args): + self.party_type = args.get('party_type') + self.party_naming_by = frappe.db.get_value(args.get("naming_by")[0], None, args.get("naming_by")[1]) + self.get_columns() + self.get_data(args) + return self.columns, self.data + + def get_data(self, args): + self.data = [] + + self.receivables = ReceivablePayableReport(self.filters).run(args)[1] + + self.get_party_total(args) + + party_advance_amount = get_partywise_advanced_payment_amount(self.party_type, + self.filters.report_date) or {} + + for party, party_dict in iteritems(self.party_total): + row = frappe._dict() + + row.party = party + if self.party_naming_by == "Naming Series": + row.party_name = frappe.get_cached_value(self.party_type, party, [self.party_type + "_name"]) + + row.update(party_dict) + + # Advance against party + row.advance = party_advance_amount.get(party, 0) + + # In AR/AP, advance shown in paid columns, + # but in summary report advance shown in separate column + row.paid -= row.advance + + self.data.append(row) + + def get_party_total(self, args): + self.party_total = frappe._dict() + + for d in self.receivables: + self.init_party_total(d) + + # Add all amount columns + for k in list(self.party_total[d.party]): + if k not in ["currency", "sales_person"]: + + self.party_total[d.party][k] += d.get(k, 0.0) + + # set territory, customer_group, sales person etc + self.set_party_details(d) + + def init_party_total(self, row): + self.party_total.setdefault(row.party, frappe._dict({ + "invoiced": 0.0, + "paid": 0.0, + "credit_note": 0.0, + "outstanding": 0.0, + "range1": 0.0, + "range2": 0.0, + "range3": 0.0, + "range4": 0.0, + "range5": 0.0, + "sales_person": [] + })) + + def set_party_details(self, row): + self.party_total[row.party].currency = row.currency + + for key in ('territory', 'customer_group', 'supplier_group'): + if row.get(key): + self.party_total[row.party][key] = row.get(key) + + if row.sales_person: + self.party_total[row.party].sales_person.append(row.sales_person) + + def get_columns(self): + self.columns = [] + self.add_column(label=_(self.party_type), fieldname='party', + fieldtype='Link', options=self.party_type, width=180) + + if self.party_naming_by == "Naming Series": + self.add_column(_('{0} Name').format(self.party_type), + fieldname = 'party_name', fieldtype='Data') + + credit_debit_label = "Credit Note" if self.party_type == 'Customer' else "Debit Note" + + self.add_column(_('Advance Amount'), fieldname='advance') + self.add_column(_('Invoiced Amount'), fieldname='invoiced') + self.add_column(_('Paid Amount'), fieldname='paid') + self.add_column(_(credit_debit_label), fieldname='credit_note') + self.add_column(_('Outstanding Amount'), fieldname='outstanding') + + self.setup_ageing_columns() + + if self.party_type == "Customer": + self.add_column(label=_('Territory'), fieldname='territory', fieldtype='Link', + options='Territory') + self.add_column(label=_('Customer Group'), fieldname='customer_group', fieldtype='Link', + options='Customer Group') + if self.filters.show_sales_person: + self.add_column(label=_('Sales Person'), fieldname='sales_person', fieldtype='Data') + else: + self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link', + options='Supplier Group') + + self.add_column(label=_('Currency'), fieldname='currency', fieldtype='Link', + options='Currency', width=80) + + def setup_ageing_columns(self): + for i, label in enumerate(["0-{range1}".format(range1=self.filters["range1"]), + "{range1}-{range2}".format(range1=cint(self.filters["range1"])+ 1, range2=self.filters["range2"]), + "{range2}-{range3}".format(range2=cint(self.filters["range2"])+ 1, range3=self.filters["range3"]), + "{range3}-{range4}".format(range3=cint(self.filters["range3"])+ 1, range4=self.filters["range4"]), + "{range4}-{above}".format(range4=cint(self.filters["range4"])+ 1, above=_("Above"))]): + self.add_column(label=label, fieldname='range' + str(i+1)) \ No newline at end of file diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py index 7d9acf26ed3..97ce4f21e85 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.py +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py @@ -135,11 +135,11 @@ def get_chart_data(filters, columns, asset, liability, equity): datasets = [] if asset_data: - datasets.append({'name':'Assets', 'values': asset_data}) + datasets.append({'name': _('Assets'), 'values': asset_data}) if liability_data: - datasets.append({'name':'Liabilities', 'values': liability_data}) + datasets.append({'name': _('Liabilities'), 'values': liability_data}) if equity_data: - datasets.append({'name':'Equity', 'values': equity_data}) + datasets.append({'name': _('Equity'), 'values': equity_data}) chart = { "data": { diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 7b9c939b475..3c8de6026a6 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -425,9 +425,12 @@ def get_cost_centers_with_children(cost_centers): all_cost_centers = [] for d in cost_centers: - lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"]) - children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) - all_cost_centers += [c.name for c in children] + if frappe.db.exists("Cost Center", d): + lft, rgt = frappe.db.get_value("Cost Center", d, ["lft", "rgt"]) + children = frappe.get_all("Cost Center", filters={"lft": [">=", lft], "rgt": ["<=", rgt]}) + all_cost_centers += [c.name for c in children] + else: + frappe.throw(_("Cost Center: {0} does not exist".format(d))) return list(set(all_cost_centers)) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 86fd1088f53..ec3fb1fc9c2 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -119,19 +119,11 @@ def get_gl_entries(filters): select_fields = """, debit, credit, debit_in_account_currency, credit_in_account_currency """ - group_by_statement = '' order_by_statement = "order by posting_date, account" if filters.get("group_by") == _("Group by Voucher"): order_by_statement = "order by posting_date, voucher_type, voucher_no" - if filters.get("group_by") == _("Group by Voucher (Consolidated)"): - group_by_statement = "group by voucher_type, voucher_no, account, cost_center" - - select_fields = """, sum(debit) as debit, sum(credit) as credit, - sum(debit_in_account_currency) as debit_in_account_currency, - sum(credit_in_account_currency) as credit_in_account_currency""" - if filters.get("include_default_book_entries"): filters['company_fb'] = frappe.db.get_value("Company", filters.get("company"), 'default_finance_book') @@ -144,11 +136,10 @@ def get_gl_entries(filters): against_voucher_type, against_voucher, account_currency, remarks, against, is_opening {select_fields} from `tabGL Entry` - where company=%(company)s {conditions} {group_by_statement} + where company=%(company)s {conditions} {order_by_statement} """.format( select_fields=select_fields, conditions=get_conditions(filters), - group_by_statement=group_by_statement, order_by_statement=order_by_statement ), filters, as_dict=1) @@ -185,7 +176,8 @@ def get_conditions(filters): if not (filters.get("account") or filters.get("party") or filters.get("group_by") in ["Group by Account", "Group by Party"]): conditions.append("posting_date >=%(from_date)s") - conditions.append("posting_date <=%(to_date)s") + + conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')") if filters.get("project"): conditions.append("project in %(project)s") @@ -286,6 +278,7 @@ def initialize_gle_map(gl_entries, filters): def get_accountwise_gle(filters, gl_entries, gle_map): totals = get_totals_dict() entries = [] + consolidated_gle = OrderedDict() group_by = group_by_field(filters.get('group_by')) def update_value_in_dict(data, key, gle): @@ -310,12 +303,20 @@ def get_accountwise_gle(filters, gl_entries, gle_map): update_value_in_dict(totals, 'total', gle) if filters.get("group_by") != _('Group by Voucher (Consolidated)'): gle_map[gle.get(group_by)].entries.append(gle) - else: - entries.append(gle) + elif filters.get("group_by") == _('Group by Voucher (Consolidated)'): + key = (gle.get("voucher_type"), gle.get("voucher_no"), + gle.get("account"), gle.get("cost_center")) + if key not in consolidated_gle: + consolidated_gle.setdefault(key, gle) + else: + update_value_in_dict(consolidated_gle, key, gle) update_value_in_dict(gle_map[gle.get(group_by)].totals, 'closing', gle) update_value_in_dict(totals, 'closing', gle) + for key, value in consolidated_gle.items(): + entries.append(value) + return totals, entries def get_result_as_list(data, filters): diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js index ca243944e9e..2343eaa8461 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.js @@ -27,8 +27,8 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = { fieldname:"payment_type", label: __("Payment Type"), fieldtype: "Select", - options: "Incoming\nOutgoing", - default: "Incoming" + options: __("Incoming") + "\n" + __("Outgoing"), + default: __("Incoming") }, { "fieldname":"party_type", diff --git a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py index 89e0113fc8d..24b5d87b5b9 100644 --- a/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py +++ b/erpnext/accounts/report/payment_period_based_on_invoice_date/payment_period_based_on_invoice_date.py @@ -39,8 +39,8 @@ def execute(filters=None): return columns, data def validate_filters(filters): - if (filters.get("payment_type") == "Incoming" and filters.get("party_type") == "Supplier") or \ - (filters.get("payment_type") == "Outgoing" and filters.get("party_type") == "Customer"): + if (filters.get("payment_type") == _("Incoming") and filters.get("party_type") == "Supplier") or \ + (filters.get("payment_type") == _("Outgoing") and filters.get("party_type") == "Customer"): frappe.throw(_("{0} payment entries can not be filtered by {1}")\ .format(filters.payment_type, filters.party_type)) @@ -51,7 +51,7 @@ def get_columns(filters): _("Party Type") + "::100", _("Party") + ":Dynamic Link/Party Type:140", _("Posting Date") + ":Date:100", - _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == "Outgoing" else ":Link/Sales Invoice:130"), + _("Invoice") + (":Link/Purchase Invoice:130" if filters.get("payment_type") == _("Outgoing") else ":Link/Sales Invoice:130"), _("Invoice Posting Date") + ":Date:130", _("Payment Due Date") + ":Date:130", _("Debit") + ":Currency:120", @@ -69,7 +69,7 @@ def get_conditions(filters): conditions = [] if not filters.party_type: - if filters.payment_type == "Outgoing": + if filters.payment_type == _("Outgoing"): filters.party_type = "Supplier" else: filters.party_type = "Customer" @@ -101,7 +101,7 @@ def get_entries(filters): def get_invoice_posting_date_map(filters): invoice_details = {} - dt = "Sales Invoice" if filters.get("payment_type") == "Incoming" else "Purchase Invoice" + dt = "Sales Invoice" if filters.get("payment_type") == _("Incoming") else "Purchase Invoice" for t in frappe.db.sql("select name, posting_date, due_date from `tab{0}`".format(dt), as_dict=1): invoice_details[t.name] = t diff --git a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py index ac11868cabb..d500c8116ec 100644 --- a/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py +++ b/erpnext/accounts/report/profit_and_loss_statement/profit_and_loss_statement.py @@ -75,11 +75,11 @@ def get_chart_data(filters, columns, income, expense, net_profit_loss): datasets = [] if income_data: - datasets.append({'name': 'Income', 'values': income_data}) + datasets.append({'name': _('Income'), 'values': income_data}) if expense_data: - datasets.append({'name': 'Expense', 'values': expense_data}) + datasets.append({'name': _('Expense'), 'values': expense_data}) if net_profit: - datasets.append({'name': 'Net Profit/Loss', 'values': net_profit}) + datasets.append({'name': _('Net Profit/Loss'), 'values': net_profit}) chart = { "data": { diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 2d78d2693d0..c5cad738018 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -303,14 +303,17 @@ frappe.ui.form.on('Asset', { }, set_depreciation_rate: function(frm, row) { - if (row.total_number_of_depreciations && row.frequency_of_depreciation) { + if (row.total_number_of_depreciations && row.frequency_of_depreciation + && row.expected_value_after_useful_life) { frappe.call({ method: "get_depreciation_rate", doc: frm.doc, args: row, callback: function(r) { if (r.message) { - frappe.model.set_value(row.doctype, row.name, "rate_of_depreciation", r.message); + frappe.flags.dont_change_rate = true; + frappe.model.set_value(row.doctype, row.name, + "rate_of_depreciation", flt(r.message, precision("rate_of_depreciation", row))); } } }); @@ -338,6 +341,14 @@ frappe.ui.form.on('Asset Finance Book', { total_number_of_depreciations: function(frm, cdt, cdn) { const row = locals[cdt][cdn]; frm.events.set_depreciation_rate(frm, row); + }, + + rate_of_depreciation: function(frm, cdt, cdn) { + if(!frappe.flags.dont_change_rate) { + frappe.model.set_value(cdt, cdn, "expected_value_after_useful_life", 0); + } + + frappe.flags.dont_change_rate = false; } }); diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index c398a7342a6..8f208e2cdbc 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe, erpnext, math, json from frappe import _ from six import string_types -from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff +from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff, add_days from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ @@ -101,97 +101,88 @@ class Asset(AccountsController): def set_depreciation_rate(self): for d in self.get("finance_books"): - d.rate_of_depreciation = self.get_depreciation_rate(d, on_validate=True) + d.rate_of_depreciation = flt(self.get_depreciation_rate(d, on_validate=True), + d.precision("rate_of_depreciation")) def make_depreciation_schedule(self): - depreciation_method = [d.depreciation_method for d in self.finance_books] - - if 'Manual' not in depreciation_method: + if 'Manual' not in [d.depreciation_method for d in self.finance_books]: self.schedules = [] - if not self.get("schedules") and self.available_for_use_date: - total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) + if self.get("schedules") or not self.available_for_use_date: + return - for d in self.get('finance_books'): - self.validate_asset_finance_books(d) + for d in self.get('finance_books'): + self.validate_asset_finance_books(d) - value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) - d.value_after_depreciation = value_after_depreciation + d.value_after_depreciation = value_after_depreciation - no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked) - end_date = add_months(d.depreciation_start_date, - no_of_depreciations * cint(d.frequency_of_depreciation)) + number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) - total_days = date_diff(end_date, self.available_for_use_date) - rate_per_day = (value_after_depreciation - d.get("expected_value_after_useful_life")) / total_days + has_pro_rata = self.check_is_pro_rata(d) - number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) + if has_pro_rata: + number_of_pending_depreciations += 1 - from_date = self.available_for_use_date - if number_of_pending_depreciations: - next_depr_date = getdate(add_months(self.available_for_use_date, - number_of_pending_depreciations * 12)) - if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 - and getdate(d.depreciation_start_date) < next_depr_date): + skip_row = False + for n in range(number_of_pending_depreciations): + # If depreciation is already completed (for double declining balance) + if skip_row: continue - number_of_pending_depreciations += 1 - for n in range(number_of_pending_depreciations): - if n == list(range(number_of_pending_depreciations))[-1]: - schedule_date = add_months(self.available_for_use_date, n * 12) - previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - d, previous_scheduled_date, schedule_date) + depreciation_amount = self.get_depreciation_amount(value_after_depreciation, + d.total_number_of_depreciations, d) - elif n == list(range(number_of_pending_depreciations))[0]: - schedule_date = d.depreciation_start_date - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - d, self.available_for_use_date, schedule_date) + if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1: + schedule_date = add_months(d.depreciation_start_date, + n * cint(d.frequency_of_depreciation)) - else: - schedule_date = add_months(d.depreciation_start_date, n * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d) + # For first row + if has_pro_rata and n==0: + depreciation_amount, days = get_pro_rata_amt(d, depreciation_amount, + self.available_for_use_date, d.depreciation_start_date) + # For last row + elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1: + to_date = add_months(self.available_for_use_date, + n * cint(d.frequency_of_depreciation)) - if value_after_depreciation != 0: - value_after_depreciation -= flt(depreciation_amount) + depreciation_amount, days = get_pro_rata_amt(d, + depreciation_amount, schedule_date, to_date) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) - else: - for n in range(number_of_pending_depreciations): - schedule_date = add_months(d.depreciation_start_date, - n * cint(d.frequency_of_depreciation)) + schedule_date = add_days(schedule_date, days) - if d.depreciation_method in ("Straight Line", "Manual"): - days = date_diff(schedule_date, from_date) - if n == 0: days += 1 + if not depreciation_amount: continue + value_after_depreciation -= flt(depreciation_amount, + self.precision("gross_purchase_amount")) - depreciation_amount = days * rate_per_day - from_date = schedule_date - else: - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, - d.total_number_of_depreciations, d) + # Adjust depreciation amount in the last period based on the expected value after useful life + if d.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1 + and value_after_depreciation != d.expected_value_after_useful_life) + or value_after_depreciation < d.expected_value_after_useful_life): + depreciation_amount += (value_after_depreciation - d.expected_value_after_useful_life) + skip_row = True - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount) + if depreciation_amount > 0: + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - "depreciation_method": d.depreciation_method, - "finance_book": d.finance_book, - "finance_book_id": d.idx - }) + def check_is_pro_rata(self, row): + has_pro_rata = False + + days = date_diff(row.depreciation_start_date, self.available_for_use_date) + 1 + total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation) + + if days < total_days: + has_pro_rata = True + + return has_pro_rata def validate_asset_finance_books(self, row): if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): @@ -261,31 +252,20 @@ class Asset(AccountsController): return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): - if row.depreciation_method in ["Straight Line", "Manual"]: - amt = (flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) - - flt(self.opening_accumulated_depreciation)) - - depreciation_amount = amt * row.rate_of_depreciation - else: - depreciation_amount = flt(depreciable_value) * (flt(row.rate_of_depreciation) / 100) - value_after_depreciation = flt(depreciable_value) - depreciation_amount - if value_after_depreciation < flt(row.expected_value_after_useful_life): - depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life) - - return depreciation_amount - - def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None): - if start_date and end_date: - prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1) - else: - prorata_temporis = 1 + precision = self.precision("gross_purchase_amount") if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_left = (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) + + if not depreciation_left: + frappe.msgprint(_("All the depreciations has been booked")) + depreciation_amount = flt(row.expected_value_after_useful_life) + return depreciation_amount + depreciation_amount = (flt(row.value_after_depreciation) - - flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - - cint(self.number_of_depreciations_booked)) * prorata_temporis + flt(row.expected_value_after_useful_life)) / depreciation_left else: - depreciation_amount = self.get_depreciation_amount(depreciable_value, row.total_number_of_depreciations, row) + depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100), precision) return depreciation_amount @@ -301,20 +281,17 @@ class Asset(AccountsController): flt(accumulated_depreciation_after_full_schedule), self.precision('gross_purchase_amount')) - if row.expected_value_after_useful_life < asset_value_after_full_schedule: + if (row.expected_value_after_useful_life and + row.expected_value_after_useful_life < asset_value_after_full_schedule): frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") .format(row.idx, asset_value_after_full_schedule)) + elif not row.expected_value_after_useful_life: + row.expected_value_after_useful_life = asset_value_after_full_schedule def validate_cancellation(self): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): frappe.throw(_("Asset cannot be cancelled, as it is already {0}").format(self.status)) - if self.purchase_invoice: - frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice)) - - if self.purchase_receipt: - frappe.throw(_("Please cancel Purchase Receipt {0} first").format(self.purchase_receipt)) - def delete_depreciation_entries(self): for d in self.get("schedules"): if d.journal_entry: @@ -412,15 +389,7 @@ class Asset(AccountsController): if isinstance(args, string_types): args = json.loads(args) - number_of_depreciations_booked = 0 - if self.is_existing_asset: - number_of_depreciations_booked = self.number_of_depreciations_booked - float_precision = cint(frappe.db.get_default("float_precision")) or 2 - tot_no_of_depreciation = flt(args.get("total_number_of_depreciations")) - flt(number_of_depreciations_booked) - - if args.get("depreciation_method") in ["Straight Line", "Manual"]: - return 1.0 / tot_no_of_depreciation if args.get("depreciation_method") == 'Double Declining Balance': return 200.0 / args.get("total_number_of_depreciations") @@ -600,3 +569,15 @@ def make_journal_entry(asset_name): def is_cwip_accounting_disabled(): return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")) + +def get_pro_rata_amt(row, depreciation_amount, from_date, to_date): + days = date_diff(to_date, from_date) + total_days = get_total_days(to_date, row.frequency_of_depreciation) + + return (depreciation_amount * flt(days)) / flt(total_days), days + +def get_total_days(date, frequency): + period_start_date = add_months(date, + cint(frequency) * -1) + + return date_diff(date, period_start_date) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py index fceccfbd1c9..c09b94fa8ea 100644 --- a/erpnext/assets/doctype/asset/test_asset.py +++ b/erpnext/assets/doctype/asset/test_asset.py @@ -88,23 +88,23 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.save() + self.assertEqual(asset.status, "Draft") expected_schedules = [ - ["2020-06-06", 147.54, 147.54], - ["2021-04-06", 44852.46, 45000.0], - ["2022-02-06", 45000.0, 90000.00] + ["2030-12-31", 30000.00, 30000.00], + ["2031-12-31", 30000.00, 60000.00], + ["2032-12-31", 30000.00, 90000.00] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -118,20 +118,21 @@ class TestAsset(unittest.TestCase): asset.calculate_depreciation = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 40000 + asset.available_for_use_date = "2030-06-06" asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() self.assertEqual(asset.status, "Draft") asset.save() expected_schedules = [ - ["2020-06-06", 164.47, 40164.47], - ["2021-04-06", 49835.53, 90000.00] + ["2030-12-31", 14246.58, 54246.58], + ["2031-12-31", 25000.00, 79246.58], + ["2032-06-06", 10753.42, 90000.00] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount] for d in asset.get("schedules")] @@ -145,24 +146,23 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Double Declining Balance", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": '2030-12-31' }) asset.insert() self.assertEqual(asset.status, "Draft") asset.save() expected_schedules = [ - ["2020-06-06", 66666.67, 66666.67], - ["2021-04-06", 22222.22, 88888.89], - ["2022-02-06", 1111.11, 90000.0] + ['2030-12-31', 66667.00, 66667.00], + ['2031-12-31', 22222.11, 88889.11], + ['2032-12-31', 1110.89, 90000.0] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -177,23 +177,21 @@ class TestAsset(unittest.TestCase): asset.is_existing_asset = 1 asset.number_of_depreciations_booked = 1 asset.opening_accumulated_depreciation = 50000 + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2029-11-30' asset.append("finance_books", { "expected_value_after_useful_life": 10000, - "next_depreciation_date": "2020-12-31", "depreciation_method": "Double Declining Balance", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() self.assertEqual(asset.status, "Draft") - asset.save() - - asset.save() expected_schedules = [ - ["2020-06-06", 33333.33, 83333.33], - ["2021-04-06", 6666.67, 90000.0] + ["2030-12-31", 33333.50, 83333.50], + ["2031-12-31", 6666.50, 90000.0] ] schedules = [[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount] @@ -209,25 +207,25 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.purchase_date = '2020-01-30' + asset.purchase_date = '2030-01-30' asset.is_existing_asset = 0 - asset.available_for_use_date = "2020-01-30" + asset.available_for_use_date = "2030-01-30" asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, - "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-12-31" + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" }) asset.insert() asset.save() expected_schedules = [ - ["2020-12-31", 28000.0, 28000.0], - ["2021-12-31", 30000.0, 58000.0], - ["2022-12-31", 30000.0, 88000.0], - ["2023-01-30", 2000.0, 90000.0] + ["2030-12-31", 27534.25, 27534.25], + ["2031-12-31", 30000.0, 57534.25], + ["2032-12-31", 30000.0, 87534.25], + ["2033-01-30", 2465.75, 90000.0] ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -266,8 +264,8 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.get("schedules")[0].journal_entry[:4], "DEPR") expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 0.0, 32129.24), - ("_Test Depreciations - _TC", 32129.24, 0.0) + ("_Test Accumulated Depreciations - _TC", 0.0, 30000.0), + ("_Test Depreciations - _TC", 30000.0, 0.0) ) gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` @@ -277,15 +275,15 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 0) - def test_depreciation_entry_for_wdv(self): + def test_depreciation_entry_for_wdv_without_pro_rata(self): pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=8000.0, location="Test Location") asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2030-06-06' - asset.purchase_date = '2030-06-06' + asset.available_for_use_date = '2030-01-01' + asset.purchase_date = '2030-01-01' asset.append("finance_books", { "expected_value_after_useful_life": 1000, "depreciation_method": "Written Down Value", @@ -298,9 +296,41 @@ class TestAsset(unittest.TestCase): self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) expected_schedules = [ - ["2030-12-31", 4000.0, 4000.0], - ["2031-12-31", 2000.0, 6000.0], - ["2032-12-31", 1000.0, 7000.0], + ["2030-12-31", 4000.00, 4000.00], + ["2031-12-31", 2000.00, 6000.00], + ["2032-12-31", 1000.00, 7000.0], + ] + + schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] + for d in asset.get("schedules")] + + self.assertEqual(schedules, expected_schedules) + + def test_pro_rata_depreciation_entry_for_wdv(self): + pr = make_purchase_receipt(item_code="Macbook Pro", + qty=1, rate=8000.0, location="Test Location") + + asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') + asset = frappe.get_doc('Asset', asset_name) + asset.calculate_depreciation = 1 + asset.available_for_use_date = '2030-06-06' + asset.purchase_date = '2030-01-01' + asset.append("finance_books", { + "expected_value_after_useful_life": 1000, + "depreciation_method": "Written Down Value", + "total_number_of_depreciations": 3, + "frequency_of_depreciation": 12, + "depreciation_start_date": "2030-12-31" + }) + asset.save(ignore_permissions=True) + + self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0) + + expected_schedules = [ + ["2030-12-31", 2279.45, 2279.45], + ["2031-12-31", 2860.28, 5139.73], + ["2032-12-31", 1430.14, 6569.87], + ["2033-06-06", 430.13, 7000.0], ] schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)] @@ -346,18 +376,19 @@ class TestAsset(unittest.TestCase): asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, 'name') asset = frappe.get_doc('Asset', asset_name) asset.calculate_depreciation = 1 - asset.available_for_use_date = '2020-06-06' - asset.purchase_date = '2020-06-06' + asset.available_for_use_date = nowdate() + asset.purchase_date = nowdate() asset.append("finance_books", { "expected_value_after_useful_life": 10000, "depreciation_method": "Straight Line", "total_number_of_depreciations": 3, "frequency_of_depreciation": 10, - "depreciation_start_date": "2020-06-06" + "depreciation_start_date": nowdate() }) asset.insert() asset.submit() - post_depreciation_entries(date="2021-01-01") + + post_depreciation_entries(date=add_months(nowdate(), 10)) scrap_asset(asset.name) @@ -366,9 +397,9 @@ class TestAsset(unittest.TestCase): self.assertTrue(asset.journal_entry_for_scrap) expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 147.54, 0.0), + ("_Test Accumulated Depreciations - _TC", 30000.0, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 99852.46, 0.0) + ("_Test Gain/Loss on Asset Disposal - _TC", 70000.0, 0.0) ) gle = frappe.db.sql("""select account, debit, credit from `tabGL Entry` @@ -412,9 +443,9 @@ class TestAsset(unittest.TestCase): self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold") expected_gle = ( - ("_Test Accumulated Depreciations - _TC", 23051.47, 0.0), + ("_Test Accumulated Depreciations - _TC", 20392.16, 0.0), ("_Test Fixed Asset - _TC", 0.0, 100000.0), - ("_Test Gain/Loss on Asset Disposal - _TC", 51948.53, 0.0), + ("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0), ("Debtors - _TC", 25000.0, 0.0) ) @@ -425,8 +456,6 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) si.cancel() - frappe.delete_doc("Sales Invoice", si.name) - self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated") def test_asset_expected_value_after_useful_life(self): diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json index a3fee96f4ee..edc5ce169ca 100644 --- a/erpnext/assets/doctype/asset_settings/asset_settings.json +++ b/erpnext/assets/doctype/asset_settings/asset_settings.json @@ -46,75 +46,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "schedule_based_on_fiscal_year", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Calculate Prorated Depreciation Schedule Based on Fiscal Year", - "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, - "default": "360", - "depends_on": "eval:doc.schedule_based_on_fiscal_year", - "description": "This value is used for pro-rata temporis calculation", - "fetch_if_empty": 0, - "fieldname": "number_of_days_in_fiscal_year", - "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": "Number of Days in Fiscal Year", - "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, @@ -159,7 +90,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2019-03-08 10:44:41.924547", + "modified": "2019-05-26 18:31:19.930563", "modified_by": "Administrator", "module": "Assets", "name": "Asset Settings", diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.js b/erpnext/buying/doctype/buying_settings/buying_settings.js new file mode 100644 index 00000000000..403b1c95e74 --- /dev/null +++ b/erpnext/buying/doctype/buying_settings/buying_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Buying Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index f2eec4f7226..a492519591b 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -1,379 +1,111 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-06-25 11:04:03", - "custom": 0, - "description": "Settings for Buying Module", - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 0, + "creation": "2013-06-25 11:04:03", + "description": "Settings for Buying Module", + "doctype": "DocType", + "document_type": "Other", + "field_order": [ + "supp_master_name", + "supplier_group", + "buying_price_list", + "column_break_3", + "po_required", + "pr_required", + "maintain_same_rate", + "allow_multiple_items", + "subcontract", + "backflush_raw_materials_of_subcontract_based_on", + "column_break_11", + "over_transfer_allowance" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Supplier Name", - "fieldname": "supp_master_name", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Supplier Naming By", - "length": 0, - "no_copy": 0, - "options": "Supplier Name\nNaming Series", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "supplier_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": 0, - "label": "Default Supplier Group", - "length": 0, - "no_copy": 0, - "options": "Supplier Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "buying_price_list", - "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": "Default Buying Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "po_required", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Order Required", - "length": 0, - "no_copy": 0, - "options": "No\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pr_required", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Purchase Receipt Required", - "length": 0, - "no_copy": 0, - "options": "No\nYes", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "maintain_same_rate", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Maintain same rate throughout purchase cycle", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allow_multiple_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Item to be added multiple times in a transaction", - "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 + "default": "Supplier Name", + "fieldname": "supp_master_name", + "fieldtype": "Select", + "label": "Supplier Naming By", + "options": "Supplier Name\nNaming Series" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subcontract", - "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": "Subcontract", - "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 - }, + "fieldname": "supplier_group", + "fieldtype": "Link", + "label": "Default Supplier Group", + "options": "Supplier Group" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Material Transferred for Subcontract", - "fieldname": "backflush_raw_materials_of_subcontract_based_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Backflush Raw Materials of Subcontract Based On", - "length": 0, - "no_copy": 0, - "options": "BOM\nMaterial Transferred for Subcontract", - "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": "buying_price_list", + "fieldtype": "Link", + "label": "Default Buying Price List", + "options": "Price List" + }, + { + "fieldname": "column_break_3", + "fieldtype": "Column Break" + }, + { + "fieldname": "po_required", + "fieldtype": "Select", + "label": "Purchase Order Required", + "options": "No\nYes" + }, + { + "fieldname": "pr_required", + "fieldtype": "Select", + "label": "Purchase Receipt Required", + "options": "No\nYes" + }, + { + "default": "0", + "fieldname": "maintain_same_rate", + "fieldtype": "Check", + "label": "Maintain same rate throughout purchase cycle" + }, + { + "default": "0", + "fieldname": "allow_multiple_items", + "fieldtype": "Check", + "label": "Allow Item to be added multiple times in a transaction" + }, + { + "fieldname": "subcontract", + "fieldtype": "Section Break", + "label": "Subcontract" + }, + { + "default": "Material Transferred for Subcontract", + "fieldname": "backflush_raw_materials_of_subcontract_based_on", + "fieldtype": "Select", + "label": "Backflush Raw Materials of Subcontract Based On", + "options": "BOM\nMaterial Transferred for Subcontract" + }, + { + "depends_on": "eval:doc.backflush_raw_materials_of_subcontract_based_on == \"BOM\"", + "description": "Percentage you are allowed to transfer more against the quantity ordered. For example: If you have ordered 100 units. and your Allowance is 10% then you are allowed to transfer 110 units.", + "fieldname": "over_transfer_allowance", + "fieldtype": "Float", + "label": "Over Transfer Allowance (%)" + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-07-31 07:52:38.062488", - "modified_by": "Administrator", - "module": "Buying", - "name": "Buying Settings", - "owner": "Administrator", + ], + "icon": "fa fa-cog", + "idx": 1, + "issingle": 1, + "modified": "2019-08-20 13:13:09.055189", + "modified_by": "Administrator", + "module": "Buying", + "name": "Buying Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, - "track_seen": 0 -} + ] +} \ No newline at end of file diff --git a/erpnext/buying/doctype/buying_settings/test_buying_settings.py b/erpnext/buying/doctype/buying_settings/test_buying_settings.py new file mode 100644 index 00000000000..bf6eec67d4c --- /dev/null +++ b/erpnext/buying/doctype/buying_settings/test_buying_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBuyingSettings(unittest.TestCase): + pass diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index 8117d9d5149..e3e2f1edde1 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -477,13 +477,14 @@ def make_rm_stock_entry(purchase_order, rm_items): rm_item_code = rm_item_data["rm_item_code"] items_dict = { rm_item_code: { + "po_detail": rm_item_data.get("name"), "item_name": rm_item_data["item_name"], "description": item_wh.get(rm_item_code, {}).get('description', ""), 'qty': rm_item_data["qty"], 'from_warehouse': rm_item_data["warehouse"], 'stock_uom': rm_item_data["stock_uom"], 'main_item_code': rm_item_data["item_code"], - 'allow_alternative_item': item_wh[rm_item_code].get('allow_alternative_item') + 'allow_alternative_item': item_wh.get(rm_item_code, {}).get('allow_alternative_item') } } stock_entry.add_to_stock_entry_detail(items_dict) diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js index a67d69dc26b..8413eb65c3f 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js +++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js @@ -16,9 +16,9 @@ frappe.listview_settings['Purchase Order'] = { return [__("To Receive"), "orange", "per_received,<,100|per_billed,=,100|status,!=,Closed"]; } - } else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") { + } else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) < 100 && doc.status !== "Closed") { return [__("To Bill"), "orange", "per_received,=,100|per_billed,<,100|status,!=,Closed"]; - } else if (flt(doc.per_received, 2) == 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") { + } else if (flt(doc.per_received, 2) >= 100 && flt(doc.per_billed, 2) == 100 && doc.status !== "Closed") { return [__("Completed"), "green", "per_received,=,100|per_billed,=,100|status,!=,Closed"]; } }, diff --git a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json index 70168530580..8435bbb06e8 100644 --- a/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json +++ b/erpnext/buying/doctype/purchase_order_item_supplied/purchase_order_item_supplied.json @@ -1,404 +1,134 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:27:42", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, + "creation": "2013-02-22 01:27:42", + "doctype": "DocType", + "editable_grid": 1, + "field_order": [ + "main_item_code", + "rm_item_code", + "required_qty", + "supplied_qty", + "rate", + "amount", + "column_break_6", + "bom_detail_no", + "reference_name", + "conversion_factor", + "stock_uom", + "reserve_warehouse" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "main_item_code", - "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": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "main_item_code", - "oldfieldtype": "Data", - "options": "Item", - "permlevel": 0, - "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 - }, + "columns": 2, + "fieldname": "main_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "main_item_code", + "oldfieldtype": "Data", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "rm_item_code", - "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": "Raw Material Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "rm_item_code", - "oldfieldtype": "Data", - "options": "Item", - "permlevel": 0, - "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 - }, + "columns": 2, + "fieldname": "rm_item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Raw Material Item Code", + "oldfieldname": "rm_item_code", + "oldfieldtype": "Data", + "options": "Item", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "required_qty", - "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": "Supplied Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "required_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "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 - }, + "columns": 2, + "fieldname": "required_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Required Qty", + "oldfieldname": "required_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "rate", - "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": "Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "rate", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "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": "rate", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Rate", + "oldfieldname": "rate", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Amount", - "length": 0, - "no_copy": 0, - "oldfieldname": "amount", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "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 - }, + "fieldname": "amount", + "fieldtype": "Currency", + "label": "Amount", + "oldfieldname": "amount", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "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_6", - "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 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bom_detail_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": "BOM Detail No", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_detail_no", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "fieldname": "bom_detail_no", + "fieldtype": "Data", + "label": "BOM Detail No", + "oldfieldname": "bom_detail_no", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "reference_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": "Reference Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "reference_name", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "fieldname": "reference_name", + "fieldtype": "Data", + "label": "Reference Name", + "oldfieldname": "reference_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "conversion_factor", - "fieldtype": "Float", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Conversion Factor", - "length": 0, - "no_copy": 0, - "oldfieldname": "conversion_factor", - "oldfieldtype": "Currency", - "permlevel": 0, - "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 - }, + "fieldname": "conversion_factor", + "fieldtype": "Float", + "hidden": 1, + "label": "Conversion Factor", + "oldfieldname": "conversion_factor", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "stock_uom", - "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": "Stock Uom", - "length": 0, - "no_copy": 0, - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "permlevel": 0, - "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 - }, + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock Uom", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 2, - "fieldname": "reserve_warehouse", - "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": "Reserve Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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": "reserve_warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Reserve Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "supplied_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Supplied Qty", + "read_only": 1 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 1, - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2019-01-07 16:51:58.016007", - "modified_by": "Administrator", - "module": "Buying", - "name": "Purchase Order Item Supplied", - "owner": "dhanalekshmi@webnotestech.com", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "hide_toolbar": 1, + "idx": 1, + "istable": 1, + "modified": "2019-08-20 13:37:32.702068", + "modified_by": "Administrator", + "module": "Buying", + "name": "Purchase Order Item Supplied", + "owner": "dhanalekshmi@webnotestech.com", + "permissions": [] } \ No newline at end of file diff --git a/erpnext/change_log/v12/v12_0_0.md b/erpnext/change_log/v12/v12_0_0.md new file mode 100644 index 00000000000..c2f33a8b7a8 --- /dev/null +++ b/erpnext/change_log/v12/v12_0_0.md @@ -0,0 +1,41 @@ +# Version 12 Release Notes + +### Accounting +1. [Accounting Dimensions](https://erpnext.com/docs/user/manual/en/accounts/accounting-dimensions) +1. [Chart of Accounts Importer](https://erpnext.com/docs/user/manual/en/setting-up/chart-of-accounts-importer) +1. [Invoice Discounting](https://erpnext.com/docs/user/manual/en/accounts/invoice_discounting) +1. [Tally Migrator](https://github.com/frappe/erpnext/pull/17405) + +### Stock +1. [Serialized & Batched Item Reconciliation](https://erpnext.com/docs/user/manual/en/setting-up/stock-reconciliation#12-for-serialized-items) +1. [Auto Fetch Serialized Items](https://erpnext.com/version-12/release-notes/features#new-upload-dialog) +1. [Item Tax Templates](https://erpnext.com/docs/user/manual/en/accounts/item-tax-template) + +### HR +1. [Auto Attendance](https://erpnext.com/docs/user/manual/en/human-resources/auto-attendance) +1. [Employee Skill Map](https://erpnext.com/docs/user/manual/en/human-resources/employee_skill_map) +1. [Encrypted Salary Slips](https://erpnext.com/docs/user/manual/en/human-resources/hr-settings#24-encrypt-salary-slips-in-emails) +1. [Leave Ledger](https://erpnext.com/docs/user/manual/en/human-resources/leave-ledger-entry) +1. [Staffing Plan](https://erpnext.com/docs/user/manual/en/human-resources/staffing-plan) + +### CRM +1. [Promotional Scheme](https://erpnext.com/docs/user/manual/en/accounts/promotional-schemes) +1. [SLA](https://erpnext.com/docs/user/manual/en/support/service-level-agreement) +1. [Exotel Call Integration](https://erpnext.com/docs/user/manual/en/erpnext_integration/exotel_integration) +1. [Email Campaign](https://erpnext.com/docs/user/manual/en/CRM/email-campaign) + +### Domain Specific Features +1. [Learning Management System](https://erpnext.com/docs/user/manual/en/education/setting-up-lms) +1. [Quality Management System](https://erpnext.com/docs/user/manual/en/quality-management) +1. [Production Planning Enhancements](https://erpnext.com/docs/user/manual/en/manufacturing/production-plan/planning-for-material-requests) +1. [Project Template](https://erpnext.com/docs/user/manual/en/projects/project-template) + +### New Reports +1. [Bank Remittance](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#bank-remittance-report) +1. [BOM Explorer](https://erpnext.com/docs/user/manual/en/stock/articles/bom_explorer) +1. [Billing Summary Report](https://erpnext.com/docs/user/manual/en/projects/reports/billing_summary_reports) +1. [Procurement Tracker Report](docs/user/manual/en/buying/articles/procurement-tracker-report) +1. [Loan Repayment](https://erpnext.com/docs/user/manual/en/human-resources/human-resources-reports#loan-repayment-report) +1. [GSTR-3B](https://erpnext.com/docs/user/manual/en/regional/india/gst-3b-report) +1. [Sales Partner](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-reports) +1. [Sales Partner Target Variance based on Item Group](https://erpnext.com/docs/user/manual/en/selling/sales-partner#sales-partner-target-variance-based-on-item-group) diff --git a/erpnext/change_log/v12/v12_1_0.md b/erpnext/change_log/v12/v12_1_0.md new file mode 100644 index 00000000000..aed09c7e395 --- /dev/null +++ b/erpnext/change_log/v12/v12_1_0.md @@ -0,0 +1,6 @@ +# Version 12.1.0 Release Notes + +### Stock + +1. [Pick List](https://erpnext.com/docs/user/manual/en/stock/pick-list) +2. [Refactored Accounts Receivable Reports](https://erpnext.com/docs/user/manual/en/accounts/accounting-reports#2-accounting-statements) diff --git a/erpnext/communication/doctype/call_log/call_log.py b/erpnext/communication/doctype/call_log/call_log.py index c9fdfbe4470..5343bef62ce 100644 --- a/erpnext/communication/doctype/call_log/call_log.py +++ b/erpnext/communication/doctype/call_log/call_log.py @@ -6,15 +6,13 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup +from erpnext.crm.doctype.utils import get_scheduled_employees_for_popup, strip_number from frappe.contacts.doctype.contact.contact import get_contact_with_phone_number from erpnext.crm.doctype.lead.lead import get_lead_with_phone_number class CallLog(Document): def before_insert(self): - # strip 0 from the start of the number for proper number comparisions - # eg. 07888383332 should match with 7888383332 - number = self.get('from').lstrip('0') + number = strip_number(self.get('from')) self.contact = get_contact_with_phone_number(number) self.lead = get_lead_with_phone_number(number) @@ -30,7 +28,7 @@ class CallLog(Document): self.trigger_call_popup() def trigger_call_popup(self): - scheduled_employees = get_scheduled_employees_for_popup(self.to) + scheduled_employees = get_scheduled_employees_for_popup(self.medium) employee_emails = get_employees_with_number(self.to) # check if employees with matched number are scheduled to receive popup @@ -48,13 +46,14 @@ def add_call_summary(call_log, summary): doc.add_comment('Comment', frappe.bold(_('Call Summary')) + '

    ' + summary) def get_employees_with_number(number): + number = strip_number(number) if not number: return [] employee_emails = frappe.cache().hget('employees_with_number', number) if employee_emails: return employee_emails employees = frappe.get_all('Employee', filters={ - 'cell_number': ['like', '%{}'.format(number.lstrip('0'))], + 'cell_number': ['like', '%{}%'.format(number)], 'user_id': ['!=', ''] }, fields=['user_id']) @@ -64,23 +63,33 @@ def get_employees_with_number(number): return employee def set_caller_information(doc, state): - '''Called from hoooks on creation of Lead or Contact''' + '''Called from hooks on creation of Lead or Contact''' if doc.doctype not in ['Lead', 'Contact']: return numbers = [doc.get('phone'), doc.get('mobile_no')] - for_doc = doc.doctype.lower() + # contact for Contact and lead for Lead + fieldname = doc.doctype.lower() + + # contact_name or lead_name + display_name_field = '{}_name'.format(fieldname) + + # Contact now has all the nos saved in child table + if doc.doctype == 'Contact': + numbers = [d.phone for d in doc.phone_nos] for number in numbers: + number = strip_number(number) if not number: continue - print(number) + filters = frappe._dict({ - 'from': ['like', '%{}'.format(number.lstrip('0'))], - for_doc: '' + 'from': ['like', '%{}'.format(number)], + fieldname: '' }) logs = frappe.get_all('Call Log', filters=filters) for log in logs: - call_log = frappe.get_doc('Call Log', log.name) - call_log.set(for_doc, doc.name) - call_log.save(ignore_permissions=True) + frappe.db.set_value('Call Log', log.name, { + fieldname: doc.name, + display_name_field: doc.get_title() + }, update_modified=False) diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index f5d8da74c56..6f5ab32b631 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -14,6 +14,12 @@ def get_data(): "dependencies": ["Item", "Supplier"], "description": _("Purchase Orders given to Suppliers."), }, + { + "type": "doctype", + "name": "Purchase Invoice", + "onboard": 1, + "dependencies": ["Item", "Supplier"] + }, { "type": "doctype", "name": "Material Request", diff --git a/erpnext/config/crm.py b/erpnext/config/crm.py index 70784f3d5f7..eba6c7a02a5 100644 --- a/erpnext/config/crm.py +++ b/erpnext/config/crm.py @@ -41,6 +41,11 @@ def get_data(): "name": "Lead Source", "description": _("Track Leads by Lead Source.") }, + { + "type": "doctype", + "name": "Contract", + "description": _("Helps you keep tracks of Contracts based on Supplier, Customer and Employee"), + }, ] }, { diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 0d05cb183d4..4e5e9037b3a 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -134,6 +134,12 @@ def get_data(): "name": "Employee Leave Balance", "doctype": "Leave Application" }, + { + "type": "report", + "is_query_report": True, + "name": "Leave Ledger Entry", + "doctype": "Leave Ledger Entry" + }, ] }, { @@ -160,6 +166,10 @@ def get_data(): "name": "Salary Slip", "onboard": 1, }, + { + "type": "doctype", + "name": "Payroll Period", + }, { "type": "doctype", "name": "Salary Component", diff --git a/erpnext/config/integrations.py b/erpnext/config/integrations.py index 52c9ab8e46c..f8b3257b5c2 100644 --- a/erpnext/config/integrations.py +++ b/erpnext/config/integrations.py @@ -40,6 +40,11 @@ def get_data(): "type": "doctype", "name": "Plaid Settings", "description": _("Connect your bank accounts to ERPNext"), + }, + { + "type": "doctype", + "name": "Exotel Settings", + "description": _("Connect your Exotel Account to ERPNext and track call logs"), } ] } diff --git a/erpnext/config/manufacturing.py b/erpnext/config/manufacturing.py index c79c5b8b117..2c18eeb83a1 100644 --- a/erpnext/config/manufacturing.py +++ b/erpnext/config/manufacturing.py @@ -94,6 +94,13 @@ def get_data(): "name": "BOM Update Tool", "description": _("Replace BOM and update latest price in all BOMs"), }, + { + "type": "page", + "label": _("BOM Comparison Tool"), + "name": "bom-comparison-tool", + "description": _("Compare BOMs for changes in Raw Materials and Operations"), + "data_doctype": "BOM" + }, ] }, { diff --git a/erpnext/config/selling.py b/erpnext/config/selling.py index 844710d47c5..928bd5fa328 100644 --- a/erpnext/config/selling.py +++ b/erpnext/config/selling.py @@ -304,12 +304,6 @@ def get_data(): "name": "Customers Without Any Sales Transactions", "doctype": "Customer" }, - { - "type": "report", - "is_query_report": True, - "name": "Sales Partners Commission", - "doctype": "Customer" - }, { "type": "report", "is_query_report": True, diff --git a/erpnext/config/stock.py b/erpnext/config/stock.py index 7d66df2360d..441a3ab4ec3 100644 --- a/erpnext/config/stock.py +++ b/erpnext/config/stock.py @@ -30,6 +30,12 @@ def get_data(): "onboard": 1, "dependencies": ["Item"], }, + { + "type": "doctype", + "name": "Pick List", + "onboard": 1, + "dependencies": ["Item"], + }, { "type": "doctype", "name": "Delivery Trip" @@ -329,5 +335,5 @@ def get_data(): } ] }, - + ] diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 14bf9d57a1b..37548ea9b80 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -89,7 +89,7 @@ class AccountsController(TransactionBase): self.validate_currency() if self.doctype == 'Purchase Invoice': - self.validate_paid_amount() + self.calculate_paid_amount() if self.doctype in ['Purchase Invoice', 'Sales Invoice']: pos_check_field = "is_pos" if self.doctype=="Sales Invoice" else "is_paid" @@ -135,22 +135,23 @@ class AccountsController(TransactionBase): else: df.set("print_hide", 1) - def validate_paid_amount(self): + def calculate_paid_amount(self): if hasattr(self, "is_pos") or hasattr(self, "is_paid"): is_paid = self.get("is_pos") or self.get("is_paid") - if cint(is_paid) == 1: - if flt(self.paid_amount) == 0 and flt(self.outstanding_amount) > 0: - if self.cash_bank_account: - self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount")) - self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, - self.precision("base_paid_amount")) - else: - # show message that the amount is not paid - self.paid_amount = 0 - frappe.throw( - _("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) - else: - frappe.db.set(self, 'paid_amount', 0) + + if is_paid: + if not self.cash_bank_account: + # show message that the amount is not paid + frappe.throw(_("Note: Payment Entry will not be created since 'Cash or Bank Account' was not specified")) + + if cint(self.is_return) and self.grand_total > self.paid_amount: + self.paid_amount = flt(flt(self.grand_total), self.precision("paid_amount")) + + elif not flt(self.paid_amount) and flt(self.outstanding_amount) > 0: + self.paid_amount = flt(flt(self.outstanding_amount), self.precision("paid_amount")) + + self.base_paid_amount = flt(self.paid_amount * self.conversion_rate, + self.precision("base_paid_amount")) def set_missing_values(self, for_validate=False): if frappe.flags.in_test: @@ -263,7 +264,7 @@ class AccountsController(TransactionBase): if self.get("is_subcontracted"): args["is_subcontracted"] = self.is_subcontracted - ret = get_item_details(args, self) + ret = get_item_details(args, self, overwrite_warehouse=False) for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 588f74dbc93..084f84b1f9b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -337,7 +337,7 @@ class BuyingController(StockController): if self.doctype in ["Purchase Receipt", "Purchase Invoice"]: rm.consumed_qty = required_qty rm.description = bom_item.description - if item.batch_no and not rm.batch_no: + if item.batch_no and frappe.db.get_value("Item", rm.rm_item_code, "has_batch_no") and not rm.batch_no: rm.batch_no = item.batch_no # get raw materials rate @@ -727,7 +727,7 @@ def get_items_from_bom(item_code, bom, exploded_item=1): where t2.parent = t1.name and t1.item = %s and t1.docstatus = 1 and t1.is_active = 1 and t1.name = %s - and t2.item_code = t3.name and t3.is_stock_item = 1""".format(doctype), + and t2.item_code = t3.name""".format(doctype), (item_code, bom), as_dict=1) if not bom_items: diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 57c063a72ae..19ec053e744 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -371,7 +371,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters): return frappe.db.sql("""select tabAccount.name from `tabAccount` where (tabAccount.report_type = "Profit and Loss" - or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed")) + or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress")) and tabAccount.is_group=0 and tabAccount.docstatus!=2 and tabAccount.{key} LIKE %(txt)s diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index 2fddcdf24c5..859529204be 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -18,34 +18,31 @@ def validate_return(doc): validate_returned_items(doc) def validate_return_against(doc): - filters = {"doctype": doc.doctype, "docstatus": 1, "company": doc.company} - if doc.meta.get_field("customer") and doc.customer: - filters["customer"] = doc.customer - elif doc.meta.get_field("supplier") and doc.supplier: - filters["supplier"] = doc.supplier - - if not frappe.db.exists(filters): + if not frappe.db.exists(doc.doctype, doc.return_against): frappe.throw(_("Invalid {0}: {1}") .format(doc.meta.get_label("return_against"), doc.return_against)) else: ref_doc = frappe.get_doc(doc.doctype, doc.return_against) - # validate posting date time - return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") - ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") + party_type = "customer" if doc.doctype in ("Sales Invoice", "Delivery Note") else "supplier" - if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime): - frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))) + if ref_doc.company == doc.company and ref_doc.get(party_type) == doc.get(party_type) and ref_doc.docstatus == 1: + # validate posting date time + return_posting_datetime = "%s %s" % (doc.posting_date, doc.get("posting_time") or "00:00:00") + ref_posting_datetime = "%s %s" % (ref_doc.posting_date, ref_doc.get("posting_time") or "00:00:00") - # validate same exchange rate - if doc.conversion_rate != ref_doc.conversion_rate: - frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})") - .format(doc.doctype, doc.return_against, ref_doc.conversion_rate)) + if get_datetime(return_posting_datetime) < get_datetime(ref_posting_datetime): + frappe.throw(_("Posting timestamp must be after {0}").format(format_datetime(ref_posting_datetime))) - # validate update stock - if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock: - frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}") - .format(doc.return_against)) + # validate same exchange rate + if doc.conversion_rate != ref_doc.conversion_rate: + frappe.throw(_("Exchange Rate must be same as {0} {1} ({2})") + .format(doc.doctype, doc.return_against, ref_doc.conversion_rate)) + + # validate update stock + if doc.doctype == "Sales Invoice" and doc.update_stock and not ref_doc.update_stock: + frappe.throw(_("'Update Stock' can not be checked because items are not delivered via {0}") + .format(doc.return_against)) def validate_returned_items(doc): from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos @@ -249,6 +246,8 @@ def make_return_doc(doctype, source_name, target_doc=None): elif doc.doctype == 'Purchase Invoice': doc.paid_amount = -1 * source.paid_amount doc.base_paid_amount = -1 * source.base_paid_amount + doc.payment_terms_template = '' + doc.payment_schedule = [] if doc.get("is_return") and hasattr(doc, "packed_items"): for d in doc.get("packed_items"): diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index 2cbe596770b..9dbd5be9188 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -45,6 +45,7 @@ class SellingController(StockController): self.set_gross_profit() set_default_income_account_for_item(self) self.set_customer_address() + self.validate_for_duplicate_items() def set_missing_values(self, for_validate=False): @@ -381,6 +382,34 @@ class SellingController(StockController): if self.get(address_field): self.set(address_display_field, get_address_display(self.get(address_field))) + def validate_for_duplicate_items(self): + check_list, chk_dupl_itm = [], [] + if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")): + return + + for d in self.get('items'): + if self.doctype == "Sales Invoice": + e = [d.item_code, d.description, d.warehouse, d.sales_order or d.delivery_note, d.batch_no or ''] + f = [d.item_code, d.description, d.sales_order or d.delivery_note] + elif self.doctype == "Delivery Note": + e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or ''] + f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice] + elif self.doctype in ["Sales Order", "Quotation"]: + e = [d.item_code, d.description, d.warehouse, ''] + f = [d.item_code, d.description] + + if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: + if e in check_list: + frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + else: + check_list.append(e) + else: + if f in chk_dupl_itm: + frappe.throw(_("Note: Item {0} entered multiple times").format(d.item_code)) + else: + chk_dupl_itm.append(f) + + def validate_items(self): # validate items to see if they have is_sales_item enabled from erpnext.controllers.buying_controller import validate_item_type diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index b2057ca40f9..64d49b45492 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -57,9 +57,9 @@ status_map = { "Purchase Order": [ ["Draft", None], ["To Receive and Bill", "eval:self.per_received < 100 and self.per_billed < 100 and self.docstatus == 1"], - ["To Bill", "eval:self.per_received == 100 and self.per_billed < 100 and self.docstatus == 1"], + ["To Bill", "eval:self.per_received >= 100 and self.per_billed < 100 and self.docstatus == 1"], ["To Receive", "eval:self.per_received < 100 and self.per_billed == 100 and self.docstatus == 1"], - ["Completed", "eval:self.per_received == 100 and self.per_billed == 100 and self.docstatus == 1"], + ["Completed", "eval:self.per_received >= 100 and self.per_billed == 100 and self.docstatus == 1"], ["Delivered", "eval:self.status=='Delivered'"], ["Cancelled", "eval:self.docstatus==2"], ["On Hold", "eval:self.status=='On Hold'"], diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index 28a8fddfacb..da44325a9eb 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -39,7 +39,6 @@ def validate_filters(filters): frappe.throw(_("'Based On' and 'Group By' can not be same")) def get_data(filters, conditions): - data = [] inc, cond= '','' query_details = conditions["based_on_select"] + conditions["period_wise_select"] @@ -47,13 +46,17 @@ def get_data(filters, conditions): posting_date = 't1.transaction_date' if conditions.get('trans') in ['Sales Invoice', 'Purchase Invoice', 'Purchase Receipt', 'Delivery Note']: posting_date = 't1.posting_date' + if filters.period_based_on: + posting_date = 't1.'+filters.period_based_on if conditions["based_on_select"] in ["t1.project,", "t2.project,"]: cond = ' and '+ conditions["based_on_select"][:-1] +' IS Not NULL' - if conditions.get('trans') in ['Sales Order', 'Purchase Order']: cond += " and t1.status != 'Closed'" + if conditions.get('trans') == 'Quotation' and filters.get("group_by") == 'Customer': + cond += " and t1.quotation_to = 'Customer'" + year_start_date, year_end_date = frappe.db.get_value("Fiscal Year", filters.get('fiscal_year'), ["year_start_date", "year_end_date"]) @@ -64,7 +67,7 @@ def get_data(filters, conditions): if filters.get("group_by") == 'Item': sel_col = 't2.item_code' elif filters.get("group_by") == 'Customer': - sel_col = 't1.customer' + sel_col = 't1.party_name' if conditions.get('trans') == 'Quotation' else 't1.customer' elif filters.get("group_by") == 'Supplier': sel_col = 't1.supplier' @@ -225,7 +228,7 @@ def based_wise_columns_query(based_on, trans): elif based_on == "Customer": based_on_details["based_on_cols"] = ["Customer:Link/Customer:120", "Territory:Link/Territory:120"] based_on_details["based_on_select"] = "t1.customer_name, t1.territory, " - based_on_details["based_on_group_by"] = 't1.customer' + based_on_details["based_on_group_by"] = 't1.party_name' if trans == 'Quotation' else 't1.customer' based_on_details["addl_tables"] = '' elif based_on == "Customer Group": diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py index ed48fd1ab4a..0738fd506f2 100644 --- a/erpnext/controllers/website_list_for_contact.py +++ b/erpnext/controllers/website_list_for_contact.py @@ -21,42 +21,45 @@ def get_list_context(context=None): def get_transaction_list(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by="modified"): user = frappe.session.user - key = None + ignore_permissions = False if not filters: filters = [] if doctype == 'Supplier Quotation': - filters.append((doctype, "docstatus", "<", 2)) + filters.append((doctype, 'docstatus', '<', 2)) else: - filters.append((doctype, "docstatus", "=", 1)) + filters.append((doctype, 'docstatus', '=', 1)) - if (user != "Guest" and is_website_user()) or doctype == 'Request for Quotation': + if (user != 'Guest' and is_website_user()) or doctype == 'Request for Quotation': parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype # find party for this contact customers, suppliers = get_customers_suppliers(parties_doctype, user) - if not customers and not suppliers: return [] - - key, parties = get_party_details(customers, suppliers) - - if doctype == 'Request for Quotation': - return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length) - - filters.append((doctype, key, "in", parties)) - - if key: - return post_process(doctype, get_list_for_transactions(doctype, txt, - filters=filters, fields="name",limit_start=limit_start, - limit_page_length=limit_page_length,ignore_permissions=True, - order_by="modified desc")) + if customers: + if doctype == 'Quotation': + filters.append(('quotation_to', '=', 'Customer')) + filters.append(('party_name', 'in', customers)) + else: + filters.append(('customer', 'in', customers)) + elif suppliers: + filters.append(('supplier', 'in', suppliers)) else: return [] - return post_process(doctype, get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, - fields="name", order_by="modified desc")) + if doctype == 'Request for Quotation': + parties = customers or suppliers + return rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length) + + # Since customers and supplier do not have direct access to internal doctypes + ignore_permissions = True + + transactions = get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length, + fields='name', ignore_permissions=ignore_permissions, order_by='modified desc') + + return post_process(doctype, transactions) def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_length=20, - ignore_permissions=False,fields=None, order_by=None): + ignore_permissions=False, fields=None, order_by=None): """ Get List of transactions like Invoices, Orders """ from frappe.www.list import get_list meta = frappe.get_meta(doctype) @@ -77,22 +80,12 @@ def get_list_for_transactions(doctype, txt, filters, limit_start, limit_page_len if or_filters: for r in frappe.get_list(doctype, fields=fields,filters=filters, or_filters=or_filters, - limit_start=limit_start, limit_page_length=limit_page_length, + limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=ignore_permissions, order_by=order_by): data.append(r) return data -def get_party_details(customers, suppliers): - if customers: - key, parties = "customer", customers - elif suppliers: - key, parties = "supplier", suppliers - else: - key, parties = "customer", [] - - return key, parties - def rfq_transaction_list(parties_doctype, doctype, parties, limit_start, limit_page_length): data = frappe.db.sql("""select distinct parent as name, supplier from `tab{doctype}` where supplier = '{supplier}' and docstatus=1 order by modified desc limit {start}, {len}""". @@ -130,38 +123,56 @@ def get_customers_suppliers(doctype, user): suppliers = [] meta = frappe.get_meta(doctype) + customer_field_name = get_customer_field_name(doctype) + + has_customer_field = meta.has_field(customer_field_name) + has_supplier_field = meta.has_field('supplier') + if has_common(["Supplier", "Customer"], frappe.get_roles(user)): contacts = frappe.db.sql(""" - select + select `tabContact`.email_id, `tabDynamic Link`.link_doctype, `tabDynamic Link`.link_name - from + from `tabContact`, `tabDynamic Link` where `tabContact`.name=`tabDynamic Link`.parent and `tabContact`.email_id =%s """, user, as_dict=1) - customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] \ - if meta.get_field("customer") else None - suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] \ - if meta.get_field("supplier") else None + customers = [c.link_name for c in contacts if c.link_doctype == 'Customer'] + suppliers = [c.link_name for c in contacts if c.link_doctype == 'Supplier'] elif frappe.has_permission(doctype, 'read', user=user): - customers = [customer.name for customer in frappe.get_list("Customer")] \ - if meta.get_field("customer") else None - suppliers = [supplier.name for supplier in frappe.get_list("Customer")] \ - if meta.get_field("supplier") else None + customer_list = frappe.get_list("Customer") + customers = suppliers = [customer.name for customer in customer_list] - return customers, suppliers + return customers if has_customer_field else None, \ + suppliers if has_supplier_field else None def has_website_permission(doc, ptype, user, verbose=False): doctype = doc.doctype customers, suppliers = get_customers_suppliers(doctype, user) if customers: - return frappe.get_all(doctype, filters=[(doctype, "customer", "in", customers), - (doctype, "name", "=", doc.name)]) and True or False + return frappe.db.exists(doctype, get_customer_filter(doc, customers)) elif suppliers: fieldname = 'suppliers' if doctype == 'Request for Quotation' else 'supplier' - return frappe.get_all(doctype, filters=[(doctype, fieldname, "in", suppliers), - (doctype, "name", "=", doc.name)]) and True or False + return frappe.db.exists(doctype, filters={ + 'name': doc.name, + fieldname: ["in", suppliers] + }) else: return False + +def get_customer_filter(doc, customers): + doctype = doc.doctype + filters = frappe._dict() + filters.name = doc.name + filters[get_customer_field_name(doctype)] = ['in', customers] + if doctype == 'Quotation': + filters.quotation_to = 'Customer' + return filters + +def get_customer_field_name(doctype): + if doctype == 'Quotation': + return 'party_name' + else: + return 'customer' \ No newline at end of file diff --git a/erpnext/crm/doctype/contract/contract.py b/erpnext/crm/doctype/contract/contract.py index 64cc97b5033..18444cc7e6e 100644 --- a/erpnext/crm/doctype/contract/contract.py +++ b/erpnext/crm/doctype/contract/contract.py @@ -88,7 +88,7 @@ def get_status(start_date, end_date): end_date = getdate(end_date) now_date = getdate(nowdate()) - return "Active" if start_date < now_date < end_date else "Inactive" + return "Active" if start_date <= now_date <= end_date else "Inactive" def update_status_for_contracts(): diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py index 1a9f66ad9a8..8f61edf00eb 100644 --- a/erpnext/crm/doctype/opportunity/test_opportunity.py +++ b/erpnext/crm/doctype/opportunity/test_opportunity.py @@ -45,15 +45,16 @@ class TestOpportunity(unittest.TestCase): # create new customer and create new contact against 'new.opportunity@example.com' customer = make_customer(opp_doc.party_name).insert(ignore_permissions=True) - frappe.get_doc({ + contact = frappe.get_doc({ "doctype": "Contact", - "email_id": new_lead_email_id, "first_name": "_Test Opportunity Customer", "links": [{ "link_doctype": "Customer", "link_name": customer.name }] - }).insert(ignore_permissions=True) + }) + contact.add_email(new_lead_email_id) + contact.insert(ignore_permissions=True) opp_doc = frappe.get_doc(args).insert(ignore_permissions=True) self.assertTrue(opp_doc.party_name) diff --git a/erpnext/crm/doctype/utils.py b/erpnext/crm/doctype/utils.py index 55532761c21..535458af21b 100644 --- a/erpnext/crm/doctype/utils.py +++ b/erpnext/crm/doctype/utils.py @@ -54,6 +54,8 @@ def get_last_issue_from_customer(customer_name): def get_scheduled_employees_for_popup(communication_medium): + if not communication_medium: return [] + now_time = frappe.utils.nowtime() weekday = frappe.utils.get_weekday() @@ -73,3 +75,10 @@ def get_scheduled_employees_for_popup(communication_medium): employee_emails = set([employee.user_id for employee in employees]) return employee_emails + +def strip_number(number): + if not number: return + # strip 0 from the start of the number for proper number comparisions + # eg. 07888383332 should match with 7888383332 + number = number.lstrip('0') + return number \ No newline at end of file diff --git a/erpnext/education/doctype/course_activity/course_activity.json b/erpnext/education/doctype/course_activity/course_activity.json index 99ae9aee561..3e23c90da07 100644 --- a/erpnext/education/doctype/course_activity/course_activity.json +++ b/erpnext/education/doctype/course_activity/course_activity.json @@ -30,7 +30,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Enrollment", + "label": "Course Enrollment", "length": 0, "no_copy": 0, "options": "Course Enrollment", @@ -298,4 +298,4 @@ "track_changes": 1, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/program_course/program_course.json b/erpnext/education/doctype/program_course/program_course.json index 34650404158..a24e88a8611 100644 --- a/erpnext/education/doctype/program_course/program_course.json +++ b/erpnext/education/doctype/program_course/program_course.json @@ -5,6 +5,7 @@ "engine": "InnoDB", "field_order": [ "course", + "course_name", "required" ], "fields": [ @@ -16,6 +17,14 @@ "label": "Course", "options": "Course", "reqd": 1 + }, + { + "fieldname": "course_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Course Name", + "fetch_from": "course.course_name", + "read_only":1 }, { "default": "0", @@ -36,4 +45,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/student/student.py b/erpnext/education/doctype/student/student.py index da25880c81b..705c6e4e98e 100644 --- a/erpnext/education/doctype/student/student.py +++ b/erpnext/education/doctype/student/student.py @@ -54,6 +54,7 @@ class Student(Document): 'send_welcome_email': 1, 'user_type': 'Website User' }) + student_user.flags.ignore_permissions = True student_user.add_roles("Student") student_user.save() update_password_link = student_user.reset_password() diff --git a/erpnext/education/doctype/student_applicant/student_applicant.json b/erpnext/education/doctype/student_applicant/student_applicant.json index 71134e0907d..e5d0bd37de4 100644 --- a/erpnext/education/doctype/student_applicant/student_applicant.json +++ b/erpnext/education/doctype/student_applicant/student_applicant.json @@ -705,7 +705,6 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "INDIAN", "fieldname": "nationality", "fieldtype": "Data", "hidden": 0, @@ -1231,4 +1230,4 @@ "track_changes": 0, "track_seen": 0, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py index 16933dcfe09..c0a73596ac9 100644 --- a/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py +++ b/erpnext/education/doctype/student_report_generation_tool/student_report_generation_tool.py @@ -55,7 +55,7 @@ def preview_report_card(doc): "courses": courses, "assessment_groups": assessment_groups, "course_criteria": course_criteria, - "letterhead": letterhead.content, + "letterhead": letterhead and letterhead.get('content', None), "add_letterhead": doc.add_letterhead if doc.add_letterhead else 0 }) final_template = frappe.render_template(base_template_path, {"body": html, "title": "Report Card"}) @@ -89,4 +89,4 @@ def get_attendance_count(student, academic_year, academic_term=None): attendance["Present"] = 0 return attendance else: - frappe.throw(_("Provide the academic year and set the starting and ending date.")) \ No newline at end of file + frappe.throw(_("Provide the academic year and set the starting and ending date.")) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py index fd364e87fb0..cc4ccc5f4d0 100755 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py @@ -71,7 +71,7 @@ def remove_empty(d): Helper function that removes all keys from a dictionary (d), that have an empty value. """ - for key in d.keys(): + for key in list(d): if not d[key]: del d[key] return d diff --git a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py index 77de84ce5c9..6a846efad70 100644 --- a/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py +++ b/erpnext/erpnext_integrations/doctype/exotel_settings/exotel_settings.py @@ -3,7 +3,6 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe from frappe.model.document import Document import requests import frappe diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py index fbb0ebc2c80..532e19cffd9 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_connector.py @@ -3,30 +3,31 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe from frappe import _ -import requests +from frappe.utils.password import get_decrypted_password from plaid import Client from plaid.errors import APIError, ItemError +import frappe +import requests + class PlaidConnector(): def __init__(self, access_token=None): - if not(frappe.conf.get("plaid_client_id") and frappe.conf.get("plaid_secret") and frappe.conf.get("plaid_public_key")): - frappe.throw(_("Please complete your Plaid API configuration before synchronizing your account")) + plaid_settings = frappe.get_single("Plaid Settings") self.config = { - "plaid_client_id": frappe.conf.get("plaid_client_id"), - "plaid_secret": frappe.conf.get("plaid_secret"), - "plaid_public_key": frappe.conf.get("plaid_public_key"), - "plaid_env": frappe.conf.get("plaid_env") + "plaid_client_id": plaid_settings.plaid_client_id, + "plaid_secret": get_decrypted_password("Plaid Settings", "Plaid Settings", 'plaid_secret'), + "plaid_public_key": plaid_settings.plaid_public_key, + "plaid_env": plaid_settings.plaid_env } - self.client = Client(client_id=self.config["plaid_client_id"], - secret=self.config["plaid_secret"], - public_key=self.config["plaid_public_key"], - environment=self.config["plaid_env"] - ) + self.client = Client(client_id=self.config.get("plaid_client_id"), + secret=self.config.get("plaid_secret"), + public_key=self.config.get("plaid_public_key"), + environment=self.config.get("plaid_env") + ) self.access_token = access_token @@ -78,4 +79,4 @@ class PlaidConnector(): transactions.extend(response['transactions']) return transactions except Exception: - frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) \ No newline at end of file + frappe.log_error(frappe.get_traceback(), _("Plaid transactions sync error")) diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js index ace4fbf9e30..0ffbb877ea7 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.js @@ -4,8 +4,18 @@ frappe.provide("erpnext.integrations"); frappe.ui.form.on('Plaid Settings', { - link_new_account: function(frm) { - new erpnext.integrations.plaidLink(frm); + enabled: function(frm) { + frm.toggle_reqd('plaid_client_id', frm.doc.enabled); + frm.toggle_reqd('plaid_secret', frm.doc.enabled); + frm.toggle_reqd('plaid_public_key', frm.doc.enabled); + frm.toggle_reqd('plaid_env', frm.doc.enabled); + }, + refresh: function(frm) { + if(frm.doc.enabled) { + frm.add_custom_button('Link a new bank account', () => { + new erpnext.integrations.plaidLink(frm); + }); + } } }); @@ -19,20 +29,10 @@ erpnext.integrations.plaidLink = class plaidLink { init_config() { const me = this; - frappe.xcall('erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.plaid_configuration') - .then(result => { - if (result !== "disabled") { - if (result.plaid_env == undefined || result.plaid_public_key == undefined) { - frappe.throw(__("Please add valid Plaid api keys in site_config.json first")); - } - me.plaid_env = result.plaid_env; - me.plaid_public_key = result.plaid_public_key; - me.client_name = result.client_name; - me.init_plaid(); - } else { - frappe.throw(__("Please save your document before adding a new account")); - } - }); + me.plaid_env = me.frm.doc.plaid_env; + me.plaid_public_key = me.frm.doc.plaid_public_key; + me.client_name = frappe.boot.sitename; + me.init_plaid(); } init_plaid() { @@ -104,4 +104,4 @@ erpnext.integrations.plaidLink = class plaidLink { }); }, __("Select a company"), __("Continue")); } -}; \ No newline at end of file +}; diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json index ed51c4e8f80..9903048d0be 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.json @@ -1,161 +1,96 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2018-10-25 10:02:48.656165", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2018-10-25 10:02:48.656165", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enabled", + "column_break_2", + "automatic_sync", + "section_break_4", + "plaid_client_id", + "plaid_secret", + "column_break_7", + "plaid_public_key", + "plaid_env" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Enabled", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.enabled==1", - "fieldname": "automatic_sync", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Synchronize all accounts every hour", - "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 - }, + "default": "0", + "depends_on": "eval:doc.enabled==1", + "fieldname": "automatic_sync", + "fieldtype": "Check", + "label": "Synchronize all accounts every hour" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.enabled==1)&&(!doc.__islocal)", - "fieldname": "link_new_account", - "fieldtype": "Button", - "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": "Link a new bank account", - "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 + "depends_on": "eval:doc.enabled==1", + "fieldname": "plaid_client_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Plaid Client ID", + "reqd": 1 + }, + { + "depends_on": "eval:doc.enabled==1", + "fieldname": "plaid_secret", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Plaid Secret", + "reqd": 1 + }, + { + "depends_on": "eval:doc.enabled==1", + "fieldname": "plaid_public_key", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Plaid Public Key", + "reqd": 1 + }, + { + "depends_on": "eval:doc.enabled==1", + "fieldname": "plaid_env", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Plaid Environment", + "reqd": 1 + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2018-12-14 12:51:12.331395", - "modified_by": "Administrator", - "module": "ERPNext Integrations", - "name": "Plaid Settings", - "name_case": "", - "owner": "Administrator", + ], + "issingle": 1, + "modified": "2019-08-13 17:00:06.939422", + "modified_by": "Administrator", + "module": "ERPNext Integrations", + "name": "Plaid Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System 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": 0, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py index 8d31e24cd6c..4af1d740946 100644 --- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py +++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py @@ -16,8 +16,13 @@ class PlaidSettings(Document): @frappe.whitelist() def plaid_configuration(): - if frappe.db.get_value("Plaid Settings", None, "enabled") == "1": - return {"plaid_public_key": frappe.conf.get("plaid_public_key") or None, "plaid_env": frappe.conf.get("plaid_env") or None, "client_name": frappe.local.site } + if frappe.db.get_single_value("Plaid Settings", "enabled"): + plaid_settings = frappe.get_single("Plaid Settings") + return { + "plaid_public_key": plaid_settings.plaid_public_key, + "plaid_env": plaid_settings.plaid_env, + "client_name": frappe.local.site + } else: return "disabled" diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py index 7f4b7bab0a0..4943053663a 100644 --- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py +++ b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py @@ -58,7 +58,7 @@ class ShopifySettings(Document): d.raise_for_status() self.update_webhook_table(method, d.json()) except Exception as e: - make_shopify_log(status="Warning", message=e.message, exception=False) + make_shopify_log(status="Warning", message=e, exception=False) def unregister_webhooks(self): session = get_request_session() @@ -71,7 +71,7 @@ class ShopifySettings(Document): res.raise_for_status() deleted_webhooks.append(d) except Exception as e: - frappe.log_error(message=frappe.get_traceback(), title=e.message[:140]) + frappe.log_error(message=frappe.get_traceback(), title=e) for d in deleted_webhooks: self.remove(d) diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py index ed9eae3529f..ad32e946312 100644 --- a/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py +++ b/erpnext/healthcare/doctype/healthcare_practitioner/healthcare_practitioner.py @@ -69,33 +69,10 @@ def validate_service_item(item, msg): def get_practitioner_list(doctype, txt, searchfield, start, page_len, filters=None): fields = ["name", "first_name", "mobile_phone"] - match_conditions = build_match_conditions("Healthcare Practitioner") - match_conditions = "and {}".format(match_conditions) if match_conditions else "" - if filters: - filter_conditions = get_filters_cond(doctype, filters, []) - match_conditions += "{}".format(filter_conditions) + filters = { + 'name': ("like", "%%%s%%" % txt) + } - return frappe.db.sql("""select %s from `tabHealthcare Practitioner` where docstatus < 2 - and (%s like %s or first_name like %s) - and active = 1 - {match_conditions} - order by - case when name like %s then 0 else 1 end, - case when first_name like %s then 0 else 1 end, - name, first_name limit %s, %s""".format( - match_conditions=match_conditions) % - ( - ", ".join(fields), - frappe.db.escape(searchfield), - "%s", "%s", "%s", "%s", "%s", "%s" - ), - ( - "%%%s%%" % frappe.db.escape(txt), - "%%%s%%" % frappe.db.escape(txt), - "%%%s%%" % frappe.db.escape(txt), - "%%%s%%" % frappe.db.escape(txt), - start, - page_len - ) - ) + return frappe.get_all("Healthcare Practitioner", fields = fields, + filters = filters, start=start, page_length=page_len, order_by="name, first_name", as_list=1) diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py index bf15cad5d52..e3eea96f859 100644 --- a/erpnext/healthcare/doctype/patient/patient.py +++ b/erpnext/healthcare/doctype/patient/patient.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.utils import cint, cstr, getdate +from frappe.utils import cint, cstr, getdate, flt import dateutil from frappe.model.naming import set_name_by_naming_series from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account,get_income_account,send_registration_sms @@ -64,7 +64,7 @@ class Patient(Document): def invoice_patient_registration(self): frappe.db.set_value("Patient", self.name, "disabled", 0) send_registration_sms(self) - if(frappe.get_value("Healthcare Settings", None, "registration_fee")>0): + if(flt(frappe.get_value("Healthcare Settings", None, "registration_fee"))>0): company = frappe.defaults.get_user_default('company') if not company: company = frappe.db.get_value("Global Defaults", None, "default_company") diff --git a/erpnext/hooks.py b/erpnext/hooks.py index be9a4fb2648..7e33a14d518 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -283,7 +283,9 @@ scheduler_events = { "erpnext.crm.doctype.email_campaign.email_campaign.set_email_campaign_status" ], "daily_long": [ - "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" + "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", + "erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.process_expired_allocation", + "erpnext.hr.utils.generate_leave_encashment" ], "monthly_long": [ "erpnext.accounts.deferred_revenue.convert_deferred_revenue_to_income", diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index 82f2a22e81b..8498b3d277d 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -11,7 +11,7 @@ from frappe.utils import getdate, date_diff class AdditionalSalary(Document): def before_insert(self): if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component, - "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company}): + "amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}): frappe.throw(_("Additional Salary Component Exists.")) diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json index eb38147a98c..bc89b368d30 100644 --- a/erpnext/hr/doctype/attendance/attendance.json +++ b/erpnext/hr/doctype/attendance/attendance.json @@ -4,6 +4,7 @@ "creation": "2013-01-10 16:34:13", "doctype": "DocType", "document_type": "Setup", + "engine": "InnoDB", "field_order": [ "attendance_details", "naming_series", @@ -19,7 +20,9 @@ "department", "shift", "attendance_request", - "amended_from" + "amended_from", + "late_entry", + "early_exit" ], "fields": [ { @@ -153,12 +156,24 @@ "fieldtype": "Link", "label": "Shift", "options": "Shift Type" + }, + { + "default": "0", + "fieldname": "late_entry", + "fieldtype": "Check", + "label": "Late Entry" + }, + { + "default": "0", + "fieldname": "early_exit", + "fieldtype": "Check", + "label": "Early Exit" } ], "icon": "fa fa-ok", "idx": 1, "is_submittable": 1, - "modified": "2019-06-05 19:37:30.410071", + "modified": "2019-07-29 20:35:40.845422", "modified_by": "Administrator", "module": "HR", "name": "Attendance", diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 5202218ed3b..9291820524e 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -7,6 +7,7 @@ "doctype": "DocType", "document_type": "Setup", "editable_grid": 1, + "engine": "InnoDB", "field_order": [ "basic_information", "employee", @@ -54,6 +55,7 @@ "column_break_44", "holiday_list", "default_shift", + "leave_approver", "salary_information", "salary_mode", "bank_name", @@ -169,6 +171,8 @@ "read_only": 1 }, { + "fetch_from": "user_id.user_image", + "fetch_if_empty": 1, "fieldname": "image", "fieldtype": "Attach Image", "hidden": 1, @@ -767,12 +771,18 @@ "fieldtype": "Link", "label": "Default Shift", "options": "Shift Type" + }, + { + "fieldname": "leave_approver", + "fieldtype": "Link", + "label": "Leave Approver", + "options": "User" } ], "icon": "fa fa-user", "idx": 24, "image_field": "image", - "modified": "2019-06-01 16:05:55.132180", + "modified": "2019-09-12 14:21:12.711280", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.json b/erpnext/hr/doctype/employee_checkin/employee_checkin.json index 15ec7c0b1bc..08fa4afa5cb 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.json +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.json @@ -14,8 +14,6 @@ "device_id", "skip_auto_attendance", "attendance", - "entry_grace_period_consequence", - "exit_grace_period_consequence", "shift_start", "shift_end", "shift_actual_start", @@ -80,20 +78,6 @@ "options": "Attendance", "read_only": 1 }, - { - "default": "0", - "fieldname": "entry_grace_period_consequence", - "fieldtype": "Check", - "hidden": 1, - "label": "Entry Grace Period Consequence" - }, - { - "default": "0", - "fieldname": "exit_grace_period_consequence", - "fieldtype": "Check", - "hidden": 1, - "label": "Exit Grace Period Consequence" - }, { "fieldname": "shift_start", "fieldtype": "Datetime", @@ -119,7 +103,7 @@ "label": "Shift Actual End" } ], - "modified": "2019-06-10 15:33:22.731697", + "modified": "2019-07-23 23:47:33.975263", "modified_by": "Administrator", "module": "HR", "name": "Employee Checkin", diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py index b0e15d96ed3..86705121ac5 100644 --- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py @@ -72,7 +72,7 @@ def add_log_based_on_employee_field(employee_field_value, timestamp, device_id=N return doc -def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, shift=None): +def mark_attendance_and_link_log(logs, attendance_status, attendance_date, working_hours=None, late_entry=False, early_exit=False, shift=None): """Creates an attendance and links the attendance to the Employee Checkin. Note: If attendance is already present for the given date, the logs are marked as skipped and no exception is thrown. @@ -98,7 +98,9 @@ def mark_attendance_and_link_log(logs, attendance_status, attendance_date, worki 'status': attendance_status, 'working_hours': working_hours, 'company': employee_doc.company, - 'shift': shift + 'shift': shift, + 'late_entry': late_entry, + 'early_exit': early_exit } attendance = frappe.get_doc(doc_dict).insert() attendance.submit() @@ -124,25 +126,36 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): :param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out' """ total_hours = 0 + in_time = out_time = None if check_in_out_type == 'Alternating entries as IN and OUT during the same shift': + in_time = logs[0].time + if len(logs) >= 2: + out_time = logs[-1].time if working_hours_calc_type == 'First Check-in and Last Check-out': # assumption in this case: First log always taken as IN, Last log always taken as OUT - total_hours = time_diff_in_hours(logs[0].time, logs[-1].time) + total_hours = time_diff_in_hours(in_time, logs[-1].time) elif working_hours_calc_type == 'Every Valid Check-in and Check-out': + logs = logs[:] while len(logs) >= 2: total_hours += time_diff_in_hours(logs[0].time, logs[1].time) del logs[:2] elif check_in_out_type == 'Strictly based on Log Type in Employee Checkin': if working_hours_calc_type == 'First Check-in and Last Check-out': - first_in_log = logs[find_index_in_dict(logs, 'log_type', 'IN')] - last_out_log = logs[len(logs)-1-find_index_in_dict(reversed(logs), 'log_type', 'OUT')] + first_in_log_index = find_index_in_dict(logs, 'log_type', 'IN') + first_in_log = logs[first_in_log_index] if first_in_log_index or first_in_log_index == 0 else None + last_out_log_index = find_index_in_dict(reversed(logs), 'log_type', 'OUT') + last_out_log = logs[len(logs)-1-last_out_log_index] if last_out_log_index or last_out_log_index == 0 else None if first_in_log and last_out_log: - total_hours = time_diff_in_hours(first_in_log.time, last_out_log.time) + in_time, out_time = first_in_log.time, last_out_log.time + total_hours = time_diff_in_hours(in_time, out_time) elif working_hours_calc_type == 'Every Valid Check-in and Check-out': in_log = out_log = None for log in logs: if in_log and out_log: + if not in_time: + in_time = in_log.time + out_time = out_log.time total_hours += time_diff_in_hours(in_log.time, out_log.time) in_log = out_log = None if not in_log: @@ -150,8 +163,9 @@ def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type): elif not out_log: out_log = log if log.log_type == 'OUT' else None if in_log and out_log: + out_time = out_log.time total_hours += time_diff_in_hours(in_log.time, out_log.time) - return total_hours + return total_hours, in_time, out_time def time_diff_in_hours(start, end): return round((end-start).total_seconds() / 3600, 1) diff --git a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py index 424d1a3c1bc..9f12ef24e62 100644 --- a/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py +++ b/erpnext/hr/doctype/employee_checkin/test_employee_checkin.py @@ -70,16 +70,16 @@ class TestEmployeeCheckin(unittest.TestCase): logs_type_2 = [frappe._dict(x) for x in logs_type_2] working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[0]) - self.assertEqual(working_hours, 6.5) + self.assertEqual(working_hours, (6.5, logs_type_1[0].time, logs_type_1[-1].time)) working_hours = calculate_working_hours(logs_type_1,check_in_out_type[0],working_hours_calc_type[1]) - self.assertEqual(working_hours, 4.5) + self.assertEqual(working_hours, (4.5, logs_type_1[0].time, logs_type_1[-1].time)) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[0]) - self.assertEqual(working_hours, 5) + self.assertEqual(working_hours, (5, logs_type_2[1].time, logs_type_2[-1].time)) working_hours = calculate_working_hours(logs_type_2,check_in_out_type[1],working_hours_calc_type[1]) - self.assertEqual(working_hours, 4.5) + self.assertEqual(working_hours, (4.5, logs_type_2[1].time, logs_type_2[-1].time)) def make_n_checkins(employee, n, hours_to_reverse=1): logs = [make_checkin(employee, now_datetime() - timedelta(hours=hours_to_reverse, minutes=n+1))] diff --git a/erpnext/hr/doctype/employee_incentive/employee_incentive.js b/erpnext/hr/doctype/employee_incentive/employee_incentive.js index d2ddfb67a84..db0f83aac9a 100644 --- a/erpnext/hr/doctype/employee_incentive/employee_incentive.js +++ b/erpnext/hr/doctype/employee_incentive/employee_incentive.js @@ -2,7 +2,21 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Incentive', { - refresh: function(frm) { + setup: function(frm) { + frm.set_query("employee", function() { + return { + filters: { + "status": "Active" + } + }; + }); + frm.set_query("salary_component", function() { + return { + filters: { + "type": "Earning" + } + }; + }); } }); diff --git a/erpnext/hr/doctype/employee_incentive/employee_incentive.json b/erpnext/hr/doctype/employee_incentive/employee_incentive.json index 5ba1d636be6..3bc772cfa68 100644 --- a/erpnext/hr/doctype/employee_incentive/employee_incentive.json +++ b/erpnext/hr/doctype/employee_incentive/employee_incentive.json @@ -1,330 +1,130 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "HR-EINV-.YY.-.MM.-.#####", - "beta": 0, - "creation": "2018-04-13 16:13:43.404546", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "autoname": "HR-EINV-.YY.-.MM.-.#####", + "creation": "2018-04-13 16:13:43.404546", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "employee", + "incentive_amount", + "payroll_date", + "salary_component", + "amended_from", + "column_break_5", + "employee_name", + "department", + "additional_salary" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "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": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "incentive_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Incentive Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "incentive_amount", + "fieldtype": "Currency", + "label": "Incentive Amount", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "payroll_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": "Payroll Date", - "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 - }, + "fieldname": "payroll_date", + "fieldtype": "Date", + "label": "Payroll Date", + "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": "Employee Incentive", - "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 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Employee Incentive", + "print_hide": 1, + "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_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_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": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "additional_salary", + "fieldtype": "Link", + "label": "Additional Salary", + "no_copy": 1, + "options": "Additional Salary", + "read_only": 1 + }, + { + "fieldname": "salary_component", + "fieldtype": "Link", + "label": "Salary Component", + "options": "Salary Component", + "reqd": 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, - "modified": "2018-08-21 16:15:51.811149", - "modified_by": "Administrator", - "module": "HR", - "name": "Employee Incentive", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "modified": "2019-09-03 16:48:16.822252", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Incentive", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "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", - "title_field": "employee_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_incentive/employee_incentive.py b/erpnext/hr/doctype/employee_incentive/employee_incentive.py index 6c9a315336e..2e138f8ef56 100644 --- a/erpnext/hr/doctype/employee_incentive/employee_incentive.py +++ b/erpnext/hr/doctype/employee_incentive/employee_incentive.py @@ -7,4 +7,39 @@ import frappe from frappe.model.document import Document class EmployeeIncentive(Document): - pass + def on_submit(self): + company = frappe.db.get_value('Employee', self.employee, 'company') + additional_salary = frappe.db.exists('Additional Salary', { + 'employee': self.employee, + 'salary_component': self.salary_component, + 'payroll_date': self.payroll_date, + 'company': company, + 'docstatus': 1 + }) + + if not additional_salary: + additional_salary = frappe.new_doc('Additional Salary') + additional_salary.employee = self.employee + additional_salary.salary_component = self.salary_component + additional_salary.amount = self.incentive_amount + additional_salary.payroll_date = self.payroll_date + additional_salary.company = company + additional_salary.submit() + self.db_set('additional_salary', additional_salary.name) + + else: + incentive_added = frappe.db.get_value('Additional Salary', additional_salary, 'amount') + self.incentive_amount + frappe.db.set_value('Additional Salary', additional_salary, 'amount', incentive_added) + self.db_set('additional_salary', additional_salary) + + def on_cancel(self): + if self.additional_salary: + incentive_removed = frappe.db.get_value('Additional Salary', self.additional_salary, 'amount') - self.incentive_amount + if incentive_removed == 0: + frappe.get_doc('Additional Salary', self.additional_salary).cancel() + else: + frappe.db.set_value('Additional Salary', self.additional_salary, 'amount', incentive_removed) + + self.db_set('additional_salary', '') + + diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py index 6618a4f7c57..b559dfd81d1 100644 --- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py @@ -45,7 +45,6 @@ class TestExpenseClaim(unittest.TestCase): self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 700) expense_claim2.cancel() - frappe.delete_doc("Expense Claim", expense_claim2.name) self.assertEqual(frappe.db.get_value("Task", task_name, "total_expense_claim"), 200) self.assertEqual(frappe.db.get_value("Project", "_Test Project 1", "total_expense_claim"), 200) diff --git a/erpnext/hr/doctype/hr_settings/hr_settings.json b/erpnext/hr/doctype/hr_settings/hr_settings.json index 8dd0acf4551..a41c887852e 100644 --- a/erpnext/hr/doctype/hr_settings/hr_settings.json +++ b/erpnext/hr/doctype/hr_settings/hr_settings.json @@ -24,6 +24,7 @@ "column_break_18", "leave_approver_mandatory_in_leave_application", "show_leaves_of_all_department_members_in_calendar", + "auto_leave_encashment", "hiring_settings", "check_vacancies" ], @@ -153,12 +154,18 @@ "fieldname": "check_vacancies", "fieldtype": "Check", "label": "Check Vacancies On Job Offer Creation" + }, + { + "default": "0", + "fieldname": "auto_leave_encashment", + "fieldtype": "Check", + "label": "Auto Leave Encashment" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, - "modified": "2019-07-01 18:59:55.256878", + "modified": "2019-08-05 13:07:17.993968", "modified_by": "Administrator", "module": "HR", "name": "HR Settings", diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.js b/erpnext/hr/doctype/leave_allocation/leave_allocation.js index 4b4bfafd161..210a73cfe55 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.js @@ -21,11 +21,41 @@ frappe.ui.form.on("Leave Allocation", { }) }, + refresh: function(frm) { + if(frm.doc.docstatus === 1 && frm.doc.expired) { + var valid_expiry = moment(frappe.datetime.get_today()).isBetween(frm.doc.from_date, frm.doc.to_date); + if(valid_expiry) { + // expire current allocation + frm.add_custom_button(__('Expire Allocation'), function() { + frm.trigger("expire_allocation"); + }); + } + } + }, + + expire_allocation: function(frm) { + frappe.call({ + method: 'erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry.expire_allocation', + args: { + 'allocation': frm.doc, + 'expiry_date': frappe.datetime.get_today() + }, + freeze: true, + callback: function(r){ + if(!r.exc){ + frappe.msgprint(__("Allocation Expired!")); + } + frm.refresh(); + } + }); + }, + employee: function(frm) { frm.trigger("calculate_total_leaves_allocated"); }, leave_type: function(frm) { + frm.trigger("leave_policy"); frm.trigger("calculate_total_leaves_allocated"); }, @@ -33,37 +63,38 @@ frappe.ui.form.on("Leave Allocation", { frm.trigger("calculate_total_leaves_allocated"); }, - carry_forwarded_leaves: function(frm) { + unused_leaves: function(frm) { frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); }, new_leaves_allocated: function(frm) { frm.set_value("total_leaves_allocated", - flt(frm.doc.carry_forwarded_leaves) + flt(frm.doc.new_leaves_allocated)); + flt(frm.doc.unused_leaves) + flt(frm.doc.new_leaves_allocated)); }, + leave_policy: function(frm) { + if(frm.doc.leave_policy && frm.doc.leave_type) { + frappe.db.get_value("Leave Policy Detail",{ + 'parent': frm.doc.leave_policy, + 'leave_type': frm.doc.leave_type + }, 'annual_allocation', (r) => { + if (r && !r.exc) frm.set_value("new_leaves_allocated", flt(r.annual_allocation)); + }, "Leave Policy"); + } + }, calculate_total_leaves_allocated: function(frm) { if (cint(frm.doc.carry_forward) == 1 && frm.doc.leave_type && frm.doc.employee) { return frappe.call({ - method: "erpnext.hr.doctype.leave_allocation.leave_allocation.get_carry_forwarded_leaves", - args: { - "employee": frm.doc.employee, - "date": frm.doc.from_date, - "leave_type": frm.doc.leave_type, - "carry_forward": frm.doc.carry_forward - }, + method: "set_total_leaves_allocated", + doc: frm.doc, callback: function(r) { - if (!r.exc && r.message) { - frm.set_value('carry_forwarded_leaves', r.message); - frm.set_value("total_leaves_allocated", - flt(r.message) + flt(frm.doc.new_leaves_allocated)); - } + frm.refresh_fields(); } }) } else if (cint(frm.doc.carry_forward) == 0) { - frm.set_value("carry_forwarded_leaves", 0); + frm.set_value("unused_leaves", 0); frm.set_value("total_leaves_allocated", flt(frm.doc.new_leaves_allocated)); } } -}) +}); \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json index 6d61fe3d5c2..007497e34a5 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json @@ -1,683 +1,220 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, "allow_import": 1, - "allow_rename": 0, "autoname": "naming_series:", - "beta": 0, "creation": "2013-02-20 19:10:38", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "department", + "column_break1", + "leave_type", + "from_date", + "to_date", + "section_break_6", + "new_leaves_allocated", + "carry_forward", + "unused_leaves", + "total_leaves_allocated", + "total_leaves_encashed", + "column_break_10", + "compensatory_request", + "leave_period", + "leave_policy", + "carry_forwarded_leaves_count", + "expired", + "amended_from", + "notes", + "description" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "options": "HR-LAL-.YYYY.-", - "permlevel": 0, - "precision": "", "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "employee", "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": "Employee", - "length": 0, - "no_copy": 0, "oldfieldname": "employee", "oldfieldtype": "Link", "options": "Employee", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.employee_name", "fieldname": "employee_name", "fieldtype": "Data", - "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": "Employee Name", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "employee.department", "fieldname": "department", "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": "Department", - "length": 0, - "no_copy": 0, "options": "Department", - "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_break1", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_type", "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": 1, "label": "Leave Type", - "length": 0, - "no_copy": 0, "oldfieldname": "leave_type", "oldfieldtype": "Link", "options": "Leave Type", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "from_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "From Date", - "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": "to_date", "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "To Date", - "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": "section_break_6", "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": "Allocation", - "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": "Allocation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, "bold": 1, - "collapsible": 0, - "columns": 0, "fieldname": "new_leaves_allocated", "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": "New Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "New Leaves Allocated" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", + "default": "0", "fieldname": "carry_forward", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Add unused leaves from previous allocations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Add unused leaves from previous allocations" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "carry_forward", - "fieldname": "carry_forwarded_leaves", + "fieldname": "unused_leaves", "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": "Unused leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "total_leaves_allocated", "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": "Total Leaves Allocated", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, "read_only": 1, - "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, "depends_on": "eval:doc.total_leaves_encashed>0", "fieldname": "total_leaves_encashed", "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": "Total Leaves Encashed", - "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_10", - "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": "compensatory_request", "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": "Compensatory Leave Request", - "length": 0, - "no_copy": 0, "options": "Compensatory Leave Request", - "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": "leave_period", "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": "Leave Period", - "length": 0, - "no_copy": 0, "options": "Leave Period", - "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 + }, + { + "fetch_from": "employee.leave_policy", + "fieldname": "leave_policy", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Leave Policy", + "options": "Leave Policy", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "expired", + "fieldtype": "Check", + "hidden": 1, + "in_standard_filter": 1, + "label": "Expired", + "read_only": 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": 1, - "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, "oldfieldname": "amended_from", "oldfieldtype": "Data", "options": "Leave Allocation", - "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 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "notes", "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": "Notes", - "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": "Notes" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "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": 0, - "in_standard_filter": 0, "label": "Description", - "length": 0, - "no_copy": 0, "oldfieldname": "reason", "oldfieldtype": "Small Text", - "permlevel": 0, - "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, "width": "300px" + }, + { + "depends_on": "carry_forwarded_leaves_count", + "fieldname": "carry_forwarded_leaves_count", + "fieldtype": "Float", + "label": "Carry Forwarded Leaves", + "read_only": 1 } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-ok", "idx": 1, - "image_view": 0, - "in_create": 0, "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-30 11:28:09.360525", + "modified": "2019-08-08 15:08:42.440909", "modified_by": "Administrator", "module": "HR", "name": "Leave Allocation", @@ -689,15 +226,10 @@ "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR User", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 @@ -709,28 +241,19 @@ "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 0, "share": 1, "submit": 1, "write": 1 } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, "search_fields": "employee,employee_name,leave_type,total_leaves_allocated", "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC", - "timeline_field": "employee", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "timeline_field": "employee" } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index dc270dba412..874ae7a1bc2 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -3,11 +3,11 @@ from __future__ import unicode_literals import frappe -from frappe.utils import flt, date_diff, formatdate +from frappe.utils import flt, date_diff, formatdate, add_days, today, getdate from frappe import _ from frappe.model.document import Document from erpnext.hr.utils import set_employee_name, get_leave_period -from erpnext.hr.doctype.leave_application.leave_application import get_approved_leaves_for_period +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation, create_leave_ledger_entry class OverlapError(frappe.ValidationError): pass class BackDatedAllocationError(frappe.ValidationError): pass @@ -34,20 +34,25 @@ class LeaveAllocation(Document): 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 = 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 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")\ + 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)) - def on_update_after_submit(self): - self.validate_new_leaves_allocated_value() - self.set_total_leaves_allocated() + def on_submit(self): + self.create_leave_ledger_entry() - frappe.db.set(self,'carry_forwarded_leaves', flt(self.carry_forwarded_leaves)) - frappe.db.set(self,'total_leaves_allocated',flt(self.total_leaves_allocated)) + # expire all unused leaves in the ledger on creation of carry forward allocation + allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if self.carry_forward and allocation: + expire_allocation(allocation) - self.validate_against_leave_applications() + def on_cancel(self): + self.create_leave_ledger_entry(submit=False) + if self.carry_forward: + self.set_carry_forwarded_leaves_in_previous_allocation(on_cancel=True) def validate_period(self): if date_diff(self.to_date, self.from_date) <= 0: @@ -87,13 +92,36 @@ class LeaveAllocation(Document): BackDatedAllocationError) def set_total_leaves_allocated(self): - self.carry_forwarded_leaves = get_carry_forwarded_leaves(self.employee, + self.unused_leaves = get_carry_forwarded_leaves(self.employee, self.leave_type, self.from_date, self.carry_forward) - self.total_leaves_allocated = flt(self.carry_forwarded_leaves) + flt(self.new_leaves_allocated) + self.total_leaves_allocated = flt(self.unused_leaves) + flt(self.new_leaves_allocated) - if not self.total_leaves_allocated and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): - frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}".format(self.leave_type))) + self.limit_carry_forward_based_on_max_allowed_leaves() + + if self.carry_forward: + self.set_carry_forwarded_leaves_in_previous_allocation() + + if not self.total_leaves_allocated \ + and not frappe.db.get_value("Leave Type", self.leave_type, "is_earned_leave") \ + and not frappe.db.get_value("Leave Type", self.leave_type, "is_compensatory"): + frappe.throw(_("Total leaves allocated is mandatory for Leave Type {0}") + .format(self.leave_type)) + + def limit_carry_forward_based_on_max_allowed_leaves(self): + max_leaves_allowed = frappe.db.get_value("Leave Type", self.leave_type, "max_leaves_allowed") + if max_leaves_allowed and self.total_leaves_allocated > flt(max_leaves_allowed): + self.total_leaves_allocated = flt(max_leaves_allowed) + self.unused_leaves = max_leaves_allowed - flt(self.new_leaves_allocated) + + def set_carry_forwarded_leaves_in_previous_allocation(self, on_cancel=False): + ''' Set carry forwarded leaves in previous allocation ''' + previous_allocation = get_previous_allocation(self.from_date, self.leave_type, self.employee) + if on_cancel: + self.unused_leaves = 0.0 + if previous_allocation: + frappe.db.set_value("Leave Allocation", previous_allocation.name, + 'carry_forwarded_leaves_count', self.unused_leaves) def validate_total_leaves_allocated(self): # Adding a day to include To Date in the difference @@ -101,15 +129,37 @@ class LeaveAllocation(Document): if date_difference < self.total_leaves_allocated: frappe.throw(_("Total allocated leaves are more than days in the period"), OverAllocationError) - def validate_against_leave_applications(self): - leaves_taken = get_approved_leaves_for_period(self.employee, self.leave_type, - self.from_date, self.to_date) + def create_leave_ledger_entry(self, submit=True): + if self.unused_leaves: + expiry_days = frappe.db.get_value("Leave Type", self.leave_type, "expire_carry_forwarded_leaves_after_days") + end_date = add_days(self.from_date, expiry_days - 1) if expiry_days else self.to_date + args = dict( + leaves=self.unused_leaves, + from_date=self.from_date, + to_date= min(getdate(end_date), getdate(self.to_date)), + is_carry_forward=1 + ) + create_leave_ledger_entry(self, args, submit) - if flt(leaves_taken) > flt(self.total_leaves_allocated): - if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): - frappe.msgprint(_("Note: Total allocated leaves {0} shouldn't be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken)) - else: - frappe.throw(_("Total allocated leaves {0} cannot be less than already approved leaves {1} for the period").format(self.total_leaves_allocated, leaves_taken), LessAllocationError) + args = dict( + leaves=self.new_leaves_allocated, + from_date=self.from_date, + to_date=self.to_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args, submit) + +def get_previous_allocation(from_date, leave_type, employee): + ''' Returns document properties of previous allocation ''' + return frappe.db.get_value("Leave Allocation", + filters={ + 'to_date': ("<", from_date), + 'leave_type': leave_type, + 'employee': employee, + 'docstatus': 1 + }, + order_by='to_date DESC', + fieldname=['name', 'from_date', 'to_date', 'employee', 'leave_type'], as_dict=1) def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): leave_allocated = 0 @@ -136,25 +186,34 @@ def get_leave_allocation_for_period(employee, leave_type, from_date, to_date): @frappe.whitelist() def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None): - carry_forwarded_leaves = 0 - - if carry_forward: + ''' Returns carry forwarded leaves for the given employee ''' + unused_leaves = 0.0 + previous_allocation = get_previous_allocation(date, leave_type, employee) + if carry_forward and previous_allocation: validate_carry_forward(leave_type) + unused_leaves = get_unused_leaves(employee, leave_type, + previous_allocation.from_date, previous_allocation.to_date) + if unused_leaves: + max_carry_forwarded_leaves = frappe.db.get_value("Leave Type", + leave_type, "maximum_carry_forwarded_leaves") + if max_carry_forwarded_leaves and unused_leaves > flt(max_carry_forwarded_leaves): + unused_leaves = flt(max_carry_forwarded_leaves) - previous_allocation = frappe.db.sql(""" - select name, from_date, to_date, total_leaves_allocated - from `tabLeave Allocation` - where employee=%s and leave_type=%s and docstatus=1 and to_date < %s - order by to_date desc limit 1 - """, (employee, leave_type, date), as_dict=1) - if previous_allocation: - leaves_taken = get_approved_leaves_for_period(employee, leave_type, - previous_allocation[0].from_date, previous_allocation[0].to_date) + return unused_leaves - carry_forwarded_leaves = flt(previous_allocation[0].total_leaves_allocated) - flt(leaves_taken) - - return carry_forwarded_leaves +def get_unused_leaves(employee, leave_type, from_date, to_date): + ''' Returns unused leaves between the given period while skipping leave allocation expiry ''' + leaves = frappe.get_all("Leave Ledger Entry", filters={ + 'employee': employee, + 'leave_type': leave_type, + 'from_date': ('>=', from_date), + 'to_date': ('<=', to_date) + }, or_filters={ + 'is_expired': 0, + 'is_carry_forward': 1 + }, fields=['sum(leaves) as leaves']) + return flt(leaves[0]['leaves']) def validate_carry_forward(leave_type): if not frappe.db.get_value("Leave Type", leave_type, "is_carry_forward"): - frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) + frappe.throw(_("Leave Type {0} cannot be carry-forwarded").format(leave_type)) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py index 72a1b7c1948..7456aebb457 100644 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_dashboard.py @@ -12,4 +12,9 @@ def get_data(): 'items': ['Leave Encashment'] } ], + 'reports': [ + { + 'items': ['Employee Leave Balance'] + } + ] } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js new file mode 100644 index 00000000000..93f7b8356b3 --- /dev/null +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation_list.js @@ -0,0 +1,11 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +// render +frappe.listview_settings['Leave Allocation'] = { + get_indicator: function(doc) { + if(doc.status==="Expired") { + return [__("Expired"), "darkgrey", "expired, =, 1"]; + } + }, +}; diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js index b8f4fafa6d8..0ef78f2f883 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.js @@ -34,7 +34,7 @@ QUnit.test("Test: Leave allocation [HR]", function (assert) { () => assert.equal(today_date, cur_frm.doc.from_date, "from date correctly set"), // check for total leaves - () => assert.equal(cur_frm.doc.carry_forwarded_leaves + 2, cur_frm.doc.total_leaves_allocated, + () => assert.equal(cur_frm.doc.unused_leaves + 2, cur_frm.doc.total_leaves_allocated, "total leave calculation is correctly set"), () => done() ]); diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py index 3b22eb2e44c..26f077a6499 100644 --- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py @@ -1,12 +1,14 @@ from __future__ import unicode_literals import frappe import unittest -from frappe.utils import getdate +from frappe.utils import nowdate, add_months, getdate, add_days +from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation class TestLeaveAllocation(unittest.TestCase): def test_overlapping_allocation(self): frappe.db.sql("delete from `tabLeave Allocation`") - + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) leaves = [ { @@ -18,7 +20,7 @@ class TestLeaveAllocation(unittest.TestCase): "from_date": getdate("2015-10-01"), "to_date": getdate("2015-10-31"), "new_leaves_allocated": 5, - "docstatus": 1 + "docstatus": 1 }, { "doctype": "Leave Allocation", @@ -28,17 +30,17 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-01"), "to_date": getdate("2015-11-30"), - "new_leaves_allocated": 5 + "new_leaves_allocated": 5 } ] frappe.get_doc(leaves[0]).save() self.assertRaises(frappe.ValidationError, frappe.get_doc(leaves[1]).save) - - def test_invalid_period(self): + + def test_invalid_period(self): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) - - d = frappe.get_doc({ + + doc = frappe.get_doc({ "doctype": "Leave Allocation", "__islocal": 1, "employee": employee.name, @@ -46,15 +48,15 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-30"), "to_date": getdate("2015-09-1"), - "new_leaves_allocated": 5 + "new_leaves_allocated": 5 }) - + #invalid period - self.assertRaises(frappe.ValidationError, d.save) - + self.assertRaises(frappe.ValidationError, doc.save) + def test_allocated_leave_days_over_period(self): employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) - d = frappe.get_doc({ + doc = frappe.get_doc({ "doctype": "Leave Allocation", "__islocal": 1, "employee": employee.name, @@ -62,10 +64,117 @@ class TestLeaveAllocation(unittest.TestCase): "leave_type": "_Test Leave Type", "from_date": getdate("2015-09-1"), "to_date": getdate("2015-09-30"), - "new_leaves_allocated": 35 + "new_leaves_allocated": 35 }) - - #allocated leave more than period - self.assertRaises(frappe.ValidationError, d.save) - + #allocated leave more than period + self.assertRaises(frappe.ValidationError, doc.save) + + def test_carry_forward_calculation(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + leave_type = create_leave_type(leave_type_name="_Test_CF_leave", is_carry_forward=1) + leave_type.maximum_carry_forwarded_leaves = 10 + leave_type.max_leaves_allowed = 30 + leave_type.save() + + # initial leave allocation = 15 + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave", + from_date=add_months(nowdate(), -12), + to_date=add_months(nowdate(), -1), + carry_forward=0) + leave_allocation.submit() + + # carry forwarded leaves considering maximum_carry_forwarded_leaves + # new_leaves = 15, carry_forwarded = 10 + leave_allocation_1 = create_leave_allocation( + leave_type="_Test_CF_leave", + carry_forward=1) + leave_allocation_1.submit() + + self.assertEquals(leave_allocation_1.unused_leaves, 10) + + leave_allocation_1.cancel() + + # carry forwarded leaves considering max_leave_allowed + # max_leave_allowed = 30, new_leaves = 25, carry_forwarded = 5 + leave_allocation_2 = create_leave_allocation( + leave_type="_Test_CF_leave", + carry_forward=1, + new_leaves_allocated=25) + leave_allocation_2.submit() + + self.assertEquals(leave_allocation_2.unused_leaves, 5) + + def test_carry_forward_leaves_expiry(self): + frappe.db.sql("delete from `tabLeave Allocation`") + frappe.db.sql("delete from `tabLeave Ledger Entry`") + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=90) + leave_type.save() + + # initial leave allocation + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + from_date=add_months(nowdate(), -24), + to_date=add_months(nowdate(), -12), + carry_forward=0) + leave_allocation.submit() + + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + from_date=add_days(nowdate(), -90), + to_date=add_days(nowdate(), 100), + carry_forward=1) + leave_allocation.submit() + + # expires all the carry forwarded leaves after 90 days + process_expired_allocation() + + # leave allocation with carry forward of only new leaves allocated + leave_allocation_1 = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + carry_forward=1, + from_date=add_months(nowdate(), 6), + to_date=add_months(nowdate(), 12)) + leave_allocation_1.submit() + + self.assertEquals(leave_allocation_1.unused_leaves, leave_allocation.new_leaves_allocated) + + def test_creation_of_leave_ledger_entry_on_submit(self): + frappe.db.sql("delete from `tabLeave Allocation`") + + leave_allocation = create_leave_allocation() + leave_allocation.submit() + + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_allocation.name)) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, leave_allocation.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_allocation.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_allocation.new_leaves_allocated) + + # check if leave ledger entry is deleted on cancellation + leave_allocation.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_allocation.name})) + +def create_leave_allocation(**args): + args = frappe._dict(args) + + employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0]) + leave_allocation = frappe.get_doc({ + "doctype": "Leave Allocation", + "__islocal": 1, + "employee": args.employee or employee.name, + "employee_name": args.employee_name or employee.employee_name, + "leave_type": args.leave_type or "_Test Leave Type", + "from_date": args.from_date or nowdate(), + "new_leaves_allocated": args.new_leaves_allocated or 15, + "carry_forward": args.carry_forward or 0, + "to_date": args.to_date or add_months(nowdate(), 12) + }) + return leave_allocation + test_dependencies = ["Employee", "Leave Type"] \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 5bce348489e..174641048b8 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -49,7 +49,7 @@ frappe.ui.form.on("Leave Application", { async: false, args: { employee: frm.doc.employee, - date: frm.doc.posting_date + date: frm.doc.from_date || frm.doc.posting_date }, callback: function(r) { if (!r.exc && r.message['leave_allocation']) { @@ -60,9 +60,8 @@ frappe.ui.form.on("Leave Application", { } } }); - $("div").remove(".form-dashboard-section"); - let section = frm.dashboard.add_section( + frm.dashboard.add_section( frappe.render_template('leave_application_dashboard', { data: leave_details }) @@ -115,6 +114,7 @@ frappe.ui.form.on("Leave Application", { }, from_date: function(frm) { + frm.trigger("make_dashboard"); frm.trigger("half_day_datepicker"); frm.trigger("calculate_total_days"); }, @@ -138,12 +138,13 @@ frappe.ui.form.on("Leave Application", { }, get_leave_balance: function(frm) { - if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date) { + if(frm.doc.docstatus==0 && frm.doc.employee && frm.doc.leave_type && frm.doc.from_date && frm.doc.to_date) { return frappe.call({ method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_balance_on", args: { employee: frm.doc.employee, date: frm.doc.from_date, + to_date: frm.doc.to_date, leave_type: frm.doc.leave_type, consider_all_leaves_in_the_allocation_period: true }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.json b/erpnext/hr/doctype/leave_application/leave_application.json index b3800e01bd4..cdb1add3918 100644 --- a/erpnext/hr/doctype/leave_application/leave_application.json +++ b/erpnext/hr/doctype/leave_application/leave_application.json @@ -1,332 +1,332 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-02-20 11:18:11", - "description": "Apply / Approve Leaves", - "doctype": "DocType", - "document_type": "Document", - "engine": "InnoDB", - "field_order": [ - "naming_series", - "employee", - "employee_name", - "column_break_4", - "leave_type", - "department", - "leave_balance", - "section_break_5", - "from_date", - "to_date", - "half_day", - "half_day_date", - "total_leave_days", - "column_break1", - "description", - "section_break_7", - "leave_approver", - "leave_approver_name", - "column_break_18", - "status", - "salary_slip", - "sb10", - "posting_date", - "follow_via_email", - "color", - "column_break_17", - "company", - "letter_head", - "amended_from" - ], - "fields": [ - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "HR-LAP-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "fieldname": "employee", - "fieldtype": "Link", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Employee", - "options": "Employee", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "employee_name", - "fieldtype": "Data", - "in_global_search": 1, - "label": "Employee Name", - "read_only": 1 - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "fieldname": "leave_type", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "in_standard_filter": 1, - "label": "Leave Type", - "options": "Leave Type", - "reqd": 1, - "search_index": 1 - }, - { - "fetch_from": "employee.department", - "fieldname": "department", - "fieldtype": "Link", - "label": "Department", - "options": "Department", - "read_only": 1 - }, - { - "fieldname": "leave_balance", - "fieldtype": "Float", - "label": "Leave Balance Before Application", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "section_break_5", - "fieldtype": "Section Break" - }, - { - "fieldname": "from_date", - "fieldtype": "Date", - "in_list_view": 1, - "label": "From Date", - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "to_date", - "fieldtype": "Date", - "label": "To Date", - "reqd": 1, - "search_index": 1 - }, - { - "default": "0", - "fieldname": "half_day", - "fieldtype": "Check", - "label": "Half Day" - }, - { - "depends_on": "eval:doc.half_day && (doc.from_date != doc.to_date)", - "fieldname": "half_day_date", - "fieldtype": "Date", - "label": "Half Day Date" - }, - { - "fieldname": "total_leave_days", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Total Leave Days", - "no_copy": 1, - "precision": "1", - "read_only": 1 - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "print_width": "50%", - "width": "50%" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Reason" - }, - { - "fieldname": "section_break_7", - "fieldtype": "Section Break" - }, - { - "fieldname": "leave_approver", - "fieldtype": "Link", - "label": "Leave Approver", - "options": "User" - }, - { - "fieldname": "leave_approver_name", - "fieldtype": "Data", - "label": "Leave Approver Name", - "read_only": 1 - }, - { - "fieldname": "column_break_18", - "fieldtype": "Column Break" - }, - { - "default": "Open", - "fieldname": "status", - "fieldtype": "Select", - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "options": "Open\nApproved\nRejected\nCancelled" - }, - { - "fieldname": "salary_slip", - "fieldtype": "Link", - "label": "Salary Slip", - "options": "Salary Slip", - "print_hide": 1 - }, - { - "fieldname": "sb10", - "fieldtype": "Section Break" - }, - { - "default": "Today", - "fieldname": "posting_date", - "fieldtype": "Date", - "label": "Posting Date", - "no_copy": 1, - "reqd": 1 - }, - { - "allow_on_submit": 1, - "default": "1", - "fieldname": "follow_via_email", - "fieldtype": "Check", - "label": "Follow via Email", - "print_hide": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "color", - "fieldtype": "Color", - "label": "Color", - "print_hide": 1 - }, - { - "fieldname": "column_break_17", - "fieldtype": "Column Break" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "letter_head", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Letter Head", - "options": "Letter Head", - "print_hide": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "options": "Leave Application", - "print_hide": 1, - "read_only": 1 - } - ], - "icon": "fa fa-calendar", - "idx": 1, - "is_submittable": 1, - "max_attachments": 3, - "modified": "2019-08-11 19:13:53.603011", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Application", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "share": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "role": "All" - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Leave Approver", - "share": 1, - "submit": 1, - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "report": 1, - "role": "HR User", - "write": 1 - }, - { - "permlevel": 1, - "read": 1, - "report": 1, - "role": "Leave Approver", - "write": 1 - } - ], - "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "employee", - "title_field": "employee_name" -} \ No newline at end of file + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-02-20 11:18:11", + "description": "Apply / Approve Leaves", + "doctype": "DocType", + "document_type": "Document", + "engine": "InnoDB", + "field_order": [ + "naming_series", + "employee", + "employee_name", + "column_break_4", + "leave_type", + "department", + "leave_balance", + "section_break_5", + "from_date", + "to_date", + "half_day", + "half_day_date", + "total_leave_days", + "column_break1", + "description", + "section_break_7", + "leave_approver", + "leave_approver_name", + "column_break_18", + "status", + "salary_slip", + "sb10", + "posting_date", + "follow_via_email", + "color", + "column_break_17", + "company", + "letter_head", + "amended_from" + ], + "fields": [ + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "HR-LAP-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "fieldname": "employee", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "employee_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Employee Name", + "read_only": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "leave_type", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "in_standard_filter": 1, + "label": "Leave Type", + "options": "Leave Type", + "reqd": 1, + "search_index": 1 + }, + { + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, + { + "fieldname": "leave_balance", + "fieldtype": "Float", + "label": "Leave Balance Before Application", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "From Date", + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "reqd": 1, + "search_index": 1 + }, + { + "default": "0", + "fieldname": "half_day", + "fieldtype": "Check", + "label": "Half Day" + }, + { + "depends_on": "eval:doc.half_day && (doc.from_date != doc.to_date)", + "fieldname": "half_day_date", + "fieldtype": "Date", + "label": "Half Day Date" + }, + { + "fieldname": "total_leave_days", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Leave Days", + "no_copy": 1, + "precision": "1", + "read_only": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "print_width": "50%", + "width": "50%" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Reason" + }, + { + "fieldname": "section_break_7", + "fieldtype": "Section Break" + }, + { + "fieldname": "leave_approver", + "fieldtype": "Link", + "label": "Leave Approver", + "options": "User" + }, + { + "fieldname": "leave_approver_name", + "fieldtype": "Data", + "label": "Leave Approver Name", + "read_only": 1 + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "options": "Open\nApproved\nRejected\nCancelled" + }, + { + "fieldname": "sb10", + "fieldtype": "Section Break" + }, + { + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Posting Date", + "no_copy": 1, + "reqd": 1 + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "allow_on_submit": 1, + "default": "1", + "fieldname": "follow_via_email", + "fieldtype": "Check", + "label": "Follow via Email", + "print_hide": 1 + }, + { + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, + { + "fieldname": "salary_slip", + "fieldtype": "Link", + "label": "Salary Slip", + "options": "Salary Slip", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Letter Head", + "options": "Letter Head", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "color", + "fieldtype": "Color", + "label": "Color", + "print_hide": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "Leave Application", + "print_hide": 1, + "read_only": 1 + } + ], + "icon": "fa fa-calendar", + "idx": 1, + "is_submittable": 1, + "max_attachments": 3, + "modified": "2019-08-13 13:32:04.860848", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Application", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "role": "All" + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Leave Approver", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "HR User", + "write": 1 + }, + { + "permlevel": 1, + "read": 1, + "report": 1, + "role": "Leave Approver", + "write": 1 + } + ], + "search_fields": "employee,employee_name,leave_type,from_date,to_date,total_leave_days", + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "employee", + "title_field": "employee_name" + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 1ef8ee00ae4..d4da9c1ee6f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -5,11 +5,12 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \ - comma_or, get_fullname, add_days, nowdate + comma_or, get_fullname, add_days, nowdate, get_datetime_str from erpnext.hr.utils import set_employee_name, get_leave_period from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry class LeaveDayBlockedError(frappe.ValidationError): pass class OverlapError(frappe.ValidationError): pass @@ -50,6 +51,7 @@ class LeaveApplication(Document): # notify leave applier about approval self.notify_employee() + self.create_leave_ledger_entry() self.reload() def on_cancel(self): @@ -57,6 +59,7 @@ class LeaveApplication(Document): # notify leave applier about cancellation self.notify_employee() self.cancel_attendance() + self.create_leave_ledger_entry(submit=False) def validate_applicable_after(self): if self.leave_type: @@ -193,9 +196,9 @@ class LeaveApplication(Document): frappe.throw(_("The day(s) on which you are applying for leave are holidays. You need not apply for leave.")) if not is_lwp(self.leave_type): - self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, docname=self.name, + self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, self.from_date, self.to_date, consider_all_leaves_in_the_allocation_period=True) - if self.status != "Rejected" and self.leave_balance < self.total_leave_days: + if self.status != "Rejected" and (self.leave_balance < self.total_leave_days or not self.leave_balance): if frappe.db.get_value("Leave Type", self.leave_type, "allow_negative"): frappe.msgprint(_("Note: There is not enough leave balance for Leave Type {0}") .format(self.leave_type)) @@ -347,6 +350,54 @@ class LeaveApplication(Document): except frappe.OutgoingEmailError: pass + def create_leave_ledger_entry(self, submit=True): + expiry_date = get_allocation_expiry(self.employee, self.leave_type, + self.to_date, self.from_date) + + lwp = frappe.db.get_value("Leave Type", self.leave_type, "is_lwp") + + if expiry_date: + self.create_ledger_entry_for_intermediate_allocation_expiry(expiry_date, submit, lwp) + else: + args = dict( + leaves=self.total_leave_days * -1, + from_date=self.from_date, + to_date=self.to_date, + is_lwp=lwp + ) + create_leave_ledger_entry(self, args, submit) + + def create_ledger_entry_for_intermediate_allocation_expiry(self, expiry_date, submit, lwp): + ''' splits leave application into two ledger entries to consider expiry of allocation ''' + args = dict( + from_date=self.from_date, + to_date=expiry_date, + leaves=(date_diff(expiry_date, self.from_date) + 1) * -1, + is_lwp=lwp + ) + create_leave_ledger_entry(self, args, submit) + + if getdate(expiry_date) != getdate(self.to_date): + start_date = add_days(expiry_date, 1) + args.update(dict( + from_date=start_date, + to_date=self.to_date, + leaves=date_diff(self.to_date, expiry_date) * -1 + )) + create_leave_ledger_entry(self, args, submit) + +def get_allocation_expiry(employee, leave_type, to_date, from_date): + ''' Returns expiry of carry forward allocation in leave ledger entry ''' + expiry = frappe.get_all("Leave Ledger Entry", + filters={ + 'employee': employee, + 'leave_type': leave_type, + 'is_carry_forward': 1, + 'transaction_type': 'Leave Allocation', + 'to_date': ['between', (from_date, to_date)] + },fields=['to_date']) + return expiry[0]['to_date'] if expiry else None + @frappe.whitelist() def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day = None, half_day_date = None): number_of_days = 0 @@ -364,14 +415,16 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day @frappe.whitelist() def get_leave_details(employee, date): - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + allocation_records = get_leave_allocation_records(employee, date) leave_allocation = {} for d in allocation_records: allocation = allocation_records.get(d, frappe._dict()) - date = allocation.to_date - leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved") - leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open") - remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending + remaining_leaves = get_leave_balance_on(employee, d, date, to_date = allocation.to_date, + consider_all_leaves_in_the_allocation_period=True) + end_date = allocation.to_date + leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, end_date) * -1 + leaves_pending = get_pending_leaves_for_period(employee, d, allocation.from_date, end_date) + leave_allocation[d] = { "total_leaves": allocation.total_leaves_allocated, "leaves_taken": leaves_taken, @@ -386,27 +439,131 @@ def get_leave_details(employee, date): return ret @frappe.whitelist() -def get_leave_balance_on(employee, leave_type, date, allocation_records=None, docname=None, - consider_all_leaves_in_the_allocation_period=False, consider_encashed_leaves=True): +def get_leave_balance_on(employee, leave_type, date, to_date=nowdate(), consider_all_leaves_in_the_allocation_period=False): + ''' + Returns leave balance till date + :param employee: employee name + :param leave_type: leave type + :param date: date to check balance on + :param to_date: future date to check for allocation expiry + :param consider_all_leaves_in_the_allocation_period: consider all leaves taken till the allocation end date + ''' - if allocation_records == None: - allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + allocation_records = get_leave_allocation_records(employee, date, leave_type) allocation = allocation_records.get(leave_type, frappe._dict()) - if consider_all_leaves_in_the_allocation_period: - date = allocation.to_date - leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status="Approved", docname=docname) - leaves_encashed = 0 - if frappe.db.get_value("Leave Type", leave_type, 'allow_encashment') and consider_encashed_leaves: - leaves_encashed = flt(allocation.total_leaves_encashed) - return flt(allocation.total_leaves_allocated) - (flt(leaves_taken) + flt(leaves_encashed)) + end_date = allocation.to_date if consider_all_leaves_in_the_allocation_period else date + expiry = get_allocation_expiry(employee, leave_type, to_date, date) -def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docname=None): - leave_applications = frappe.db.sql(""" - select name, employee, leave_type, from_date, to_date, total_leave_days - from `tabLeave Application` + leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, end_date) + + return get_remaining_leaves(allocation, leaves_taken, date, expiry) + +def get_leave_allocation_records(employee, date, leave_type=None): + ''' returns the total allocated leaves and carry forwarded leaves based on ledger entries ''' + + conditions = ("and leave_type='%s'" % leave_type) if leave_type else "" + allocation_details = frappe.db.sql(""" + SELECT + SUM(CASE WHEN is_carry_forward = 1 THEN leaves ELSE 0 END) as cf_leaves, + SUM(CASE WHEN is_carry_forward = 0 THEN leaves ELSE 0 END) as new_leaves, + MIN(from_date) as from_date, + MAX(to_date) as to_date, + leave_type + FROM `tabLeave Ledger Entry` + WHERE + from_date <= %(date)s + AND to_date >= %(date)s + AND docstatus=1 + AND transaction_type="Leave Allocation" + AND employee=%(employee)s + AND is_expired=0 + AND is_lwp=0 + {0} + GROUP BY employee, leave_type + """.format(conditions), dict(date=date, employee=employee), as_dict=1) #nosec + + allocated_leaves = frappe._dict() + for d in allocation_details: + allocated_leaves.setdefault(d.leave_type, frappe._dict({ + "from_date": d.from_date, + "to_date": d.to_date, + "total_leaves_allocated": flt(d.cf_leaves) + flt(d.new_leaves), + "unused_leaves": d.cf_leaves, + "new_leaves_allocated": d.new_leaves, + "leave_type": d.leave_type + })) + return allocated_leaves + +def get_pending_leaves_for_period(employee, leave_type, from_date, to_date): + ''' Returns leaves that are pending approval ''' + return frappe.db.get_value("Leave Application", + filters={ + "employee": employee, + "leave_type": leave_type, + "from_date": ("<=", from_date), + "to_date": (">=", to_date), + "status": "Open" + }, fieldname=['SUM(total_leave_days)']) or flt(0) + +def get_remaining_leaves(allocation, leaves_taken, date, expiry): + ''' Returns minimum leaves remaining after comparing with remaining days for allocation expiry ''' + def _get_remaining_leaves(allocated_leaves, end_date): + remaining_leaves = flt(allocated_leaves) + flt(leaves_taken) + + if remaining_leaves > 0: + remaining_days = date_diff(end_date, date) + 1 + remaining_leaves = min(remaining_days, remaining_leaves) + + return remaining_leaves + + total_leaves = allocation.total_leaves_allocated + + if expiry and allocation.unused_leaves: + remaining_leaves = _get_remaining_leaves(allocation.unused_leaves, expiry) + + total_leaves = flt(allocation.new_leaves_allocated) + flt(remaining_leaves) + + return _get_remaining_leaves(total_leaves, allocation.to_date) + +def get_leaves_for_period(employee, leave_type, from_date, to_date): + leave_entries = get_leave_entries(employee, leave_type, from_date, to_date) + leave_days = 0 + + for leave_entry in leave_entries: + inclusive_period = leave_entry.from_date >= getdate(from_date) and leave_entry.to_date <= getdate(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 not skip_expiry_leaves(leave_entry, to_date): + leave_days += leave_entry.leaves + + else: + if leave_entry.from_date < getdate(from_date): + leave_entry.from_date = from_date + if leave_entry.to_date > getdate(to_date): + leave_entry.to_date = to_date + + leave_days += get_number_of_leave_days(employee, leave_type, + leave_entry.from_date, leave_entry.to_date) * -1 + + return leave_days + +def skip_expiry_leaves(leave_entry, date): + ''' Checks whether the expired leaves coincide with the to_date of leave balance check ''' + end_date = frappe.db.get_value("Leave Allocation", {'name': leave_entry.transaction_name}, ['to_date']) + return True if end_date == date and not leave_entry.is_carry_forward else False + +def get_leave_entries(employee, leave_type, from_date, to_date): + ''' Returns leave entries between from_date and to_date ''' + return frappe.db.sql(""" + select employee, leave_type, from_date, to_date, leaves, transaction_type, is_carry_forward + from `tabLeave Ledger Entry` where employee=%(employee)s and leave_type=%(leave_type)s - and status = %(status)s and docstatus != 2 + and docstatus=1 + and leaves<0 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)) @@ -414,43 +571,8 @@ def get_leaves_for_period(employee, leave_type, from_date, to_date, status, docn "from_date": from_date, "to_date": to_date, "employee": employee, - "status": status, "leave_type": leave_type }, as_dict=1) - leave_days = 0 - for leave_app in leave_applications: - if docname and leave_app.name == docname: - continue - if leave_app.from_date >= getdate(from_date) and leave_app.to_date <= getdate(to_date): - leave_days += leave_app.total_leave_days - else: - if leave_app.from_date < getdate(from_date): - leave_app.from_date = from_date - if leave_app.to_date > getdate(to_date): - leave_app.to_date = to_date - - leave_days += get_number_of_leave_days(employee, leave_type, - leave_app.from_date, leave_app.to_date) - - return leave_days - -def get_leave_allocation_records(date, employee=None): - conditions = (" and employee='%s'" % employee) if employee else "" - - leave_allocation_records = frappe.db.sql(""" - select employee, leave_type, total_leaves_allocated, total_leaves_encashed, from_date, to_date - from `tabLeave Allocation` - where %s between from_date and to_date and docstatus=1 {0}""".format(conditions), (date), as_dict=1) - - allocated_leaves = frappe._dict() - for d in leave_allocation_records: - allocated_leaves.setdefault(d.employee, frappe._dict()).setdefault(d.leave_type, frappe._dict({ - "from_date": d.from_date, - "to_date": d.to_date, - "total_leaves_allocated": d.total_leaves_allocated, - "total_leaves_encashed":d.total_leaves_encashed - })) - return allocated_leaves @frappe.whitelist() def get_holidays(employee, from_date, to_date): @@ -623,10 +745,12 @@ def get_approved_leaves_for_period(employee, leave_type, from_date, to_date): return leave_days @frappe.whitelist() -def get_leave_approver(employee, department=None): - if not department: - department = frappe.db.get_value('Employee', employee, 'department') +def get_leave_approver(employee): + leave_approver, department = frappe.db.get_value("Employee", + employee, ["leave_approver", "department"]) - if department: - return frappe.db.get_value('Department Approver', {'parent': department, + if not leave_approver and department: + leave_approver = frappe.db.get_value('Department Approver', {'parent': department, 'parentfield': 'leave_approvers', 'idx': 1}, 'approver') + + return leave_approver \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.py b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py new file mode 100644 index 00000000000..8075b7b5c57 --- /dev/null +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'reports': [ + { + 'label': _('Reports'), + 'items': ['Employee Leave Balance'] + } + ] + } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_application/leave_application_list.js b/erpnext/hr/doctype/leave_application/leave_application_list.js index f69b1827373..cbb4b73227e 100644 --- a/erpnext/hr/doctype/leave_application/leave_application_list.js +++ b/erpnext/hr/doctype/leave_application/leave_application_list.js @@ -5,6 +5,8 @@ frappe.listview_settings['Leave Application'] = { return [__("Approved"), "green", "status,=,Approved"]; } else if (doc.status === "Rejected") { return [__("Rejected"), "red", "status,=,Rejected"]; + } else { + return [__("Open"), "red", "status,=,Open"]; } } }; diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index d3dcca1da05..ad141a57482 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -7,7 +7,9 @@ import unittest from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on from frappe.permissions import clear_user_permissions_for_doctype -from frappe.utils import add_days, nowdate, now_datetime, getdate +from frappe.utils import add_days, nowdate, now_datetime, getdate, add_months +from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type +from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation test_dependencies = ["Leave Allocation", "Leave Block List"] @@ -17,6 +19,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00001", "from_date": "2013-05-01", + "description": "_Test Reason", "leave_type": "_Test Leave Type", "posting_date": "2013-01-02", "to_date": "2013-05-05" @@ -26,6 +29,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00002", "from_date": "2013-05-01", + "description": "_Test Reason", "leave_type": "_Test Leave Type", "posting_date": "2013-01-02", "to_date": "2013-05-05" @@ -35,6 +39,7 @@ _test_records = [ "doctype": "Leave Application", "employee": "_T-Employee-00001", "from_date": "2013-01-15", + "description": "_Test Reason", "leave_type": "_Test Leave Type LWP", "posting_date": "2013-01-02", "to_date": "2013-01-15" @@ -44,8 +49,8 @@ _test_records = [ class TestLeaveApplication(unittest.TestCase): def setUp(self): - for dt in ["Leave Application", "Leave Allocation", "Salary Slip"]: - frappe.db.sql("delete from `tab%s`" % dt) + for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]: + frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec @classmethod def setUpClass(cls): @@ -268,13 +273,14 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, company = '_Test Company', + description = "_Test Reason", leave_type = leave_type, from_date = date, to_date = date, )) # can only apply on optional holidays - self.assertTrue(NotAnOptionalHoliday, leave_application.insert) + self.assertRaises(NotAnOptionalHoliday, leave_application.insert) leave_application.from_date = today leave_application.to_date = today @@ -285,7 +291,6 @@ class TestLeaveApplication(unittest.TestCase): # check leave balance is reduced self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9) - def test_leaves_allowed(self): employee = get_employee() leave_period = get_leave_period() @@ -301,24 +306,25 @@ class TestLeaveApplication(unittest.TestCase): allocate_leaves(employee, leave_period, leave_type.name, 5) leave_application = frappe.get_doc(dict( - doctype = 'Leave Application', + doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 2), company = "_Test Company", docstatus = 1, status = "Approved" )) - - self.assertTrue(leave_application.insert()) + leave_application.submit() leave_application = frappe.get_doc(dict( doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = add_days(date, 4), - to_date = add_days(date, 7), + to_date = add_days(date, 8), company = "_Test Company", docstatus = 1, status = "Approved" @@ -342,6 +348,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -363,6 +370,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type_1.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -392,6 +400,7 @@ class TestLeaveApplication(unittest.TestCase): doctype = 'Leave Application', employee = employee.name, leave_type = leave_type.name, + description = "_Test Reason", from_date = date, to_date = add_days(date, 4), company = "_Test Company", @@ -401,6 +410,18 @@ class TestLeaveApplication(unittest.TestCase): self.assertRaises(frappe.ValidationError, leave_application.insert) + def test_leave_balance_near_allocaton_expiry(self): + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + self.assertEqual(get_leave_balance_on(employee.name, leave_type.name, nowdate(), add_days(nowdate(), 8)), 21) + def test_earned_leave(self): leave_period = get_leave_period() employee = get_employee() @@ -444,9 +465,10 @@ class TestLeaveApplication(unittest.TestCase): allocation.insert(ignore_permissions=True) allocation.submit() leave_application = frappe.get_doc(dict( - doctype = 'Leave Application', + doctype = 'Leave Application', employee = employee.name, leave_type = leave_type, + description = "_Test Reason", from_date = '2018-10-02', to_date = '2018-10-02', company = '_Test Company', @@ -457,9 +479,103 @@ class TestLeaveApplication(unittest.TestCase): leave_application.submit() self.assertEqual(leave_application.docstatus, 1) -def make_allocation_record(employee=None, leave_type=None): - frappe.db.sql("delete from `tabLeave Allocation`") + def test_creation_of_leave_ledger_entry_on_submit(self): + employee = get_employee() + leave_type = create_leave_type(leave_type_name = 'Test Leave Type 1') + leave_type.save() + + leave_allocation = create_leave_allocation(employee=employee.name, employee_name=employee.employee_name, + leave_type=leave_type.name) + leave_allocation.submit() + + leave_application = frappe.get_doc(dict( + doctype = 'Leave Application', + employee = employee.name, + leave_type = leave_type.name, + from_date = add_days(nowdate(), 1), + to_date = add_days(nowdate(), 4), + description = "_Test Reason", + company = "_Test Company", + docstatus = 1, + status = "Approved" + )) + leave_application.submit() + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name)) + + self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_application.total_leave_days * -1) + + # check if leave ledger entry is deleted on cancellation + leave_application.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_application.name})) + + def test_ledger_entry_creation_on_intermediate_allocation_expiry(self): + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + leave_application = frappe.get_doc(dict( + doctype = 'Leave Application', + employee = employee.name, + leave_type = leave_type.name, + from_date = add_days(nowdate(), -3), + to_date = add_days(nowdate(), 7), + description = "_Test Reason", + company = "_Test Company", + docstatus = 1, + status = "Approved" + )) + leave_application.submit() + + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', '*', filters=dict(transaction_name=leave_application.name)) + + self.assertEquals(len(leave_ledger_entry), 2) + self.assertEquals(leave_ledger_entry[0].employee, leave_application.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_application.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, -9) + self.assertEquals(leave_ledger_entry[1].leaves, -2) + + def test_leave_application_creation_after_expiry(self): + # test leave balance for carry forwarded allocation + employee = get_employee() + leave_type = create_leave_type( + leave_type_name="_Test_CF_leave_expiry", + is_carry_forward=1, + expire_carry_forwarded_leaves_after_days=90) + leave_type.submit() + + create_carry_forwarded_allocation(employee, leave_type) + + self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0) + +def create_carry_forwarded_allocation(employee, leave_type): + # initial leave allocation + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + employee=employee.name, + employee_name=employee.employee_name, + from_date=add_months(nowdate(), -24), + to_date=add_months(nowdate(), -12), + carry_forward=0) + leave_allocation.submit() + + leave_allocation = create_leave_allocation( + leave_type="_Test_CF_leave_expiry", + employee=employee.name, + employee_name=employee.employee_name, + from_date=add_days(nowdate(), -84), + to_date=add_days(nowdate(), 100), + carry_forward=1) + leave_allocation.submit() + +def make_allocation_record(employee=None, leave_type=None): allocation = frappe.get_doc({ "doctype": "Leave Allocation", "employee": employee or "_T-Employee-00001", @@ -513,4 +629,4 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el "docstatus": 1 }).insert() - allocate_leave.submit() + allocate_leave.submit() \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py index 9944bc53806..42f0179baf5 100644 --- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py @@ -10,6 +10,8 @@ from frappe.utils import getdate, nowdate, flt from erpnext.hr.utils import set_employee_name from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on from erpnext.hr.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure +from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry +from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves class LeaveEncashment(Document): def validate(self): @@ -25,7 +27,7 @@ class LeaveEncashment(Document): def on_submit(self): if not self.leave_allocation: - self.leave_allocation = self.get_leave_allocation() + self.leave_allocation = self.get_leave_allocation().get('name') additional_salary = frappe.new_doc("Additional Salary") additional_salary.company = frappe.get_value("Employee", self.employee, "company") additional_salary.employee = self.employee @@ -40,6 +42,8 @@ class LeaveEncashment(Document): frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed", frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') + self.encashable_days) + self.create_leave_ledger_entry() + def on_cancel(self): if self.additional_salary: frappe.get_doc("Additional Salary", self.additional_salary).cancel() @@ -48,6 +52,7 @@ class LeaveEncashment(Document): if self.leave_allocation: frappe.db.set_value("Leave Allocation", self.leave_allocation, "total_leaves_encashed", frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days) + self.create_leave_ledger_entry(submit=False) def get_leave_details_for_encashment(self): salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate())) @@ -57,8 +62,10 @@ class LeaveEncashment(Document): if not frappe.db.get_value("Leave Type", self.leave_type, 'allow_encashment'): frappe.throw(_("Leave Type {0} is not encashable").format(self.leave_type)) - self.leave_balance = get_leave_balance_on(self.employee, self.leave_type, - self.encashment_date or getdate(nowdate()), consider_all_leaves_in_the_allocation_period=True) + allocation = self.get_leave_allocation() + + self.leave_balance = allocation.total_leaves_allocated - allocation.carry_forwarded_leaves_count\ + - get_unused_leaves(self.employee, self.leave_type, allocation.from_date, self.encashment_date) encashable_days = self.leave_balance - frappe.db.get_value('Leave Type', self.leave_type, 'encashment_threshold_days') self.encashable_days = encashable_days if encashable_days > 0 else 0 @@ -66,12 +73,47 @@ class LeaveEncashment(Document): per_day_encashment = frappe.db.get_value('Salary Structure', salary_structure , 'leave_encashment_amount_per_day') self.encashment_amount = self.encashable_days * per_day_encashment if per_day_encashment > 0 else 0 - self.leave_allocation = self.get_leave_allocation() + self.leave_allocation = allocation.name return True def get_leave_allocation(self): - leave_allocation = frappe.db.sql("""select name from `tabLeave Allocation` where '{0}' + leave_allocation = frappe.db.sql("""select name, to_date, total_leaves_allocated, carry_forwarded_leaves_count from `tabLeave Allocation` where '{0}' between from_date and to_date and docstatus=1 and leave_type='{1}' - and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee)) + and employee= '{2}'""".format(self.encashment_date or getdate(nowdate()), self.leave_type, self.employee), as_dict=1) #nosec - return leave_allocation[0][0] if leave_allocation else None + return leave_allocation[0] if leave_allocation else None + + def create_leave_ledger_entry(self, submit=True): + args = frappe._dict( + leaves=self.encashable_days * -1, + from_date=self.encashment_date, + to_date=self.encashment_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args, submit) + + # create reverse entry for expired leaves + to_date = self.get_leave_allocation().get('to_date') + if to_date < getdate(nowdate()): + args = frappe._dict( + leaves=self.encashable_days, + from_date=to_date, + to_date=to_date, + is_carry_forward=0 + ) + create_leave_ledger_entry(self, args, submit) + + +def create_leave_encashment(leave_allocation): + ''' Creates leave encashment for the given allocations ''' + for allocation in leave_allocation: + if not get_assigned_salary_structure(allocation.employee, allocation.to_date): + continue + leave_encashment = frappe.get_doc(dict( + doctype="Leave Encashment", + leave_period=allocation.leave_period, + employee=allocation.employee, + leave_type=allocation.leave_type, + encashment_date=allocation.to_date + )) + leave_encashment.insert(ignore_permissions=True) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py index ef5c2aa6f39..e5bd170bc4a 100644 --- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py +++ b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.py @@ -9,42 +9,43 @@ from frappe.utils import today, add_months from erpnext.hr.doctype.employee.test_employee import make_employee from erpnext.hr.doctype.salary_structure.test_salary_structure import make_salary_structure from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period +from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy\ test_dependencies = ["Leave Type"] class TestLeaveEncashment(unittest.TestCase): def setUp(self): frappe.db.sql('''delete from `tabLeave Period`''') - def test_leave_balance_value_and_amount(self): - employee = "test_employee_encashment@salary.com" - leave_type = "_Test Leave Type Encashment" + frappe.db.sql('''delete from `tabLeave Allocation`''') + frappe.db.sql('''delete from `tabLeave Ledger Entry`''') + frappe.db.sql('''delete from `tabAdditional Salary`''') # create the leave policy - leave_policy = frappe.get_doc({ - "doctype": "Leave Policy", - "leave_policy_details": [{ - "leave_type": leave_type, - "annual_allocation": 10 - }] - }).insert() + leave_policy = create_leave_policy( + leave_type="_Test Leave Type Encashment", + annual_allocation=10) leave_policy.submit() # create employee, salary structure and assignment - employee = make_employee(employee) - frappe.db.set_value("Employee", employee, "leave_policy", leave_policy.name) - salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", employee, + self.employee = make_employee("test_employee_encashment@example.com") + + frappe.db.set_value("Employee", self.employee, "leave_policy", leave_policy.name) + + salary_structure = make_salary_structure("Salary Structure for Encashment", "Monthly", self.employee, other_details={"leave_encashment_amount_per_day": 50}) # create the leave period and assign the leaves - leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) - leave_period.grant_leave_allocation(employee=employee) + self.leave_period = create_leave_period(add_months(today(), -3), add_months(today(), 3)) + self.leave_period.grant_leave_allocation(employee=self.employee) + def test_leave_balance_value_and_amount(self): + frappe.db.sql('''delete from `tabLeave Encashment`''') leave_encashment = frappe.get_doc(dict( - doctype = 'Leave Encashment', - employee = employee, - leave_type = leave_type, - leave_period = leave_period.name, - payroll_date = today() + doctype='Leave Encashment', + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + payroll_date=today() )).insert() self.assertEqual(leave_encashment.leave_balance, 10) @@ -53,3 +54,26 @@ class TestLeaveEncashment(unittest.TestCase): leave_encashment.submit() self.assertTrue(frappe.db.get_value("Leave Encashment", leave_encashment.name, "additional_salary")) + + def test_creation_of_leave_ledger_entry_on_submit(self): + frappe.db.sql('''delete from `tabLeave Encashment`''') + leave_encashment = frappe.get_doc(dict( + doctype='Leave Encashment', + employee=self.employee, + leave_type="_Test Leave Type Encashment", + leave_period=self.leave_period.name, + payroll_date=today() + )).insert() + + leave_encashment.submit() + + leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_encashment.name)) + + self.assertEquals(len(leave_ledger_entry), 1) + self.assertEquals(leave_ledger_entry[0].employee, leave_encashment.employee) + self.assertEquals(leave_ledger_entry[0].leave_type, leave_encashment.leave_type) + self.assertEquals(leave_ledger_entry[0].leaves, leave_encashment.encashable_days * -1) + + # check if leave ledger entry is deleted on cancellation + leave_encashment.cancel() + self.assertFalse(frappe.db.exists("Leave Ledger Entry", {'transaction_name':leave_encashment.name})) diff --git a/erpnext/hr/doctype/leave_ledger_entry/__init__.py b/erpnext/hr/doctype/leave_ledger_entry/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js new file mode 100644 index 00000000000..c68d518f670 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.js @@ -0,0 +1,8 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Leave Ledger Entry', { + // refresh: function(frm) { + + // } +}); diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json new file mode 100644 index 00000000000..771e706bbb3 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.json @@ -0,0 +1,169 @@ +{ + "creation": "2019-05-09 15:47:39.760406", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "employee", + "employee_name", + "leave_type", + "transaction_type", + "transaction_name", + "leaves", + "column_break_7", + "from_date", + "to_date", + "is_carry_forward", + "is_expired", + "is_lwp", + "amended_from" + ], + "fields": [ + { + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Employee", + "options": "Employee" + }, + { + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name" + }, + { + "fieldname": "leave_type", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Leave Type", + "options": "Leave Type" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Leave Ledger Entry", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "transaction_type", + "fieldtype": "Link", + "label": "Transaction Type", + "options": "DocType" + }, + { + "fieldname": "transaction_name", + "fieldtype": "Dynamic Link", + "label": "Transaction Name", + "options": "transaction_type" + }, + { + "fieldname": "leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Leaves" + }, + { + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date" + }, + { + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date" + }, + { + "default": "0", + "fieldname": "is_carry_forward", + "fieldtype": "Check", + "label": "Is Carry Forward" + }, + { + "default": "0", + "fieldname": "is_expired", + "fieldtype": "Check", + "label": "Is Expired" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "is_lwp", + "fieldtype": "Check", + "label": "Is Leave Without Pay" + } + ], + "in_create": 1, + "is_submittable": 1, + "modified": "2019-08-20 14:40:04.130799", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Ledger Entry", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "employee" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py new file mode 100644 index 00000000000..bfc6d95d172 --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe import _ +from frappe.utils import add_days, today, flt, DATE_FORMAT, getdate + +class LeaveLedgerEntry(Document): + def validate(self): + if getdate(self.from_date) > getdate(self.to_date): + frappe.throw(_("To date needs to be before from date")) + + def on_cancel(self): + # allow cancellation of expiry leaves + if self.is_expired: + frappe.db.set_value("Leave Allocation", self.transaction_name, "expired", 0) + else: + frappe.throw(_("Only expired allocation can be cancelled")) + +def validate_leave_allocation_against_leave_application(ledger): + ''' Checks that leave allocation has no leave application against it ''' + leave_application_records = frappe.db.sql_list(""" + SELECT transaction_name + FROM `tabLeave Ledger Entry` + WHERE + employee=%s + AND leave_type=%s + AND transaction_type='Leave Application' + AND from_date>=%s + AND to_date<=%s + """, (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date)) + + if leave_application_records: + frappe.throw(_("Leave allocation %s is linked with leave application %s" + % (ledger.transaction_name, ', '.join(leave_application_records)))) + +def create_leave_ledger_entry(ref_doc, args, submit=True): + ledger = frappe._dict( + doctype='Leave Ledger Entry', + employee=ref_doc.employee, + employee_name=ref_doc.employee_name, + leave_type=ref_doc.leave_type, + transaction_type=ref_doc.doctype, + transaction_name=ref_doc.name, + is_carry_forward=0, + is_expired=0, + is_lwp=0 + ) + ledger.update(args) + + if submit: + frappe.get_doc(ledger).submit() + else: + delete_ledger_entry(ledger) + +def delete_ledger_entry(ledger): + ''' Delete ledger entry on cancel of leave application/allocation/encashment ''' + if ledger.transaction_type == "Leave Allocation": + validate_leave_allocation_against_leave_application(ledger) + + expired_entry = get_previous_expiry_ledger_entry(ledger) + frappe.db.sql("""DELETE + FROM `tabLeave Ledger Entry` + WHERE + `transaction_name`=%s + OR `name`=%s""", (ledger.transaction_name, expired_entry)) + +def get_previous_expiry_ledger_entry(ledger): + ''' Returns the expiry ledger entry having same creation date as the ledger entry to be cancelled ''' + creation_date = frappe.db.get_value("Leave Ledger Entry", filters={ + 'transaction_name': ledger.transaction_name, + 'is_expired': 0, + 'transaction_type': 'Leave Allocation' + }, fieldname=['creation']) + + creation_date = creation_date.strftime(DATE_FORMAT) if creation_date else '' + + return frappe.db.get_value("Leave Ledger Entry", filters={ + 'creation': ('like', creation_date+"%"), + 'employee': ledger.employee, + 'leave_type': ledger.leave_type, + 'is_expired': 1, + 'docstatus': 1, + 'is_carry_forward': 0 + }, fieldname=['name']) + +def process_expired_allocation(): + ''' Check if a carry forwarded allocation has expired and create a expiry ledger entry ''' + + # 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] + + 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) + }) + + if expire_allocation: + create_expiry_ledger_entry(expire_allocation) + +def create_expiry_ledger_entry(allocations): + ''' Create ledger entry for expired allocation ''' + for allocation in allocations: + if allocation.is_carry_forward: + expire_carried_forward_allocation(allocation) + else: + expire_allocation(allocation) + +def get_remaining_leaves(allocation): + ''' Returns remaining leaves from the given allocation ''' + return frappe.db.get_value("Leave Ledger Entry", + filters={ + 'employee': allocation.employee, + 'leave_type': allocation.leave_type, + 'to_date': ('<=', allocation.to_date), + }, fieldname=['SUM(leaves)']) + +@frappe.whitelist() +def expire_allocation(allocation, expiry_date=None): + ''' expires non-carry forwarded allocation ''' + leaves = get_remaining_leaves(allocation) + expiry_date = expiry_date if expiry_date else allocation.to_date + + if leaves: + args = dict( + leaves=flt(leaves) * -1, + transaction_name=allocation.name, + transaction_type='Leave Allocation', + from_date=expiry_date, + to_date=expiry_date, + is_carry_forward=0, + is_expired=1 + ) + create_leave_ledger_entry(allocation, args) + + frappe.db.set_value("Leave Allocation", allocation.name, "expired", 1) + +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 = flt(allocation.leaves) + flt(leaves_taken) + if leaves > 0: + args = frappe._dict( + transaction_name=allocation.name, + transaction_type="Leave Allocation", + leaves=allocation.leaves * -1, + is_carry_forward=allocation.is_carry_forward, + is_expired=1, + from_date=allocation.to_date, + to_date=allocation.to_date + ) + create_leave_ledger_entry(allocation, args) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py new file mode 100644 index 00000000000..6f7725c254e --- /dev/null +++ b/erpnext/hr/doctype/leave_ledger_entry/test_leave_ledger_entry.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestLeaveLedgerEntry(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js index b8c5f716f5f..bad2b8766c8 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.js +++ b/erpnext/hr/doctype/leave_period/leave_period.js @@ -68,7 +68,7 @@ frappe.ui.form.on('Leave Period', { }, { "label": "Add unused leaves from previous allocations", - "fieldname": "carry_forward_leaves", + "fieldname": "carry_forward", "fieldtype": "Check" } ], diff --git a/erpnext/hr/doctype/leave_period/leave_period.json b/erpnext/hr/doctype/leave_period/leave_period.json index df28763c79c..9e895c34fb2 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.json +++ b/erpnext/hr/doctype/leave_period/leave_period.json @@ -1,294 +1,294 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "HR-LPR-.YYYY.-.#####", - "beta": 0, - "creation": "2018-04-13 15:20:52.864288", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "HR-LPR-.YYYY.-.#####", + "beta": 0, + "creation": "2018-04-13 15:20:52.864288", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_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": "From Date", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "to_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": "To Date", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Date", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_active", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Is Active", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "fieldtype": "Column Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_active", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Active", - "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, + "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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "optional_holiday_list", - "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": "Holiday List for Optional Leave", - "length": 0, - "no_copy": 0, - "options": "Holiday List", - "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, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "optional_holiday_list", + "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": "Holiday List for Optional Leave", + "length": 0, + "no_copy": 0, + "options": "Holiday List", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-21 16:15:43.305502", - "modified_by": "Administrator", - "module": "HR", - "name": "Leave Period", - "name_case": "", - "owner": "Administrator", + ], + "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": "2019-05-30 16:15:43.305502", + "modified_by": "Administrator", + "module": "HR", + "name": "Leave Period", + "name_case": "", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "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, + ], + "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, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.py b/erpnext/hr/doctype/leave_period/leave_period.py index 15fa8d6f8cf..0973ac71985 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.py +++ b/erpnext/hr/doctype/leave_period/leave_period.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import getdate, cstr +from frappe.utils import getdate, cstr, add_days, date_diff, getdate, ceil from frappe.model.document import Document from erpnext.hr.utils import validate_overlap, get_employee_leave_policy from frappe.utils.background_jobs import enqueue @@ -21,8 +21,8 @@ class LeavePeriod(Document): condition_str = " and " + " and ".join(conditions) if len(conditions) else "" - employees = frappe.db.sql_list("select name from tabEmployee where status='Active' {condition}" - .format(condition=condition_str), tuple(values)) + employees = frappe._dict(frappe.db.sql("select name, date_of_joining from tabEmployee where status='Active' {condition}" #nosec + .format(condition=condition_str), tuple(values))) return employees @@ -36,29 +36,29 @@ class LeavePeriod(Document): def grant_leave_allocation(self, grade=None, department=None, designation=None, - employee=None, carry_forward_leaves=0): - employees = self.get_employees({ + employee=None, carry_forward=0): + employee_records = self.get_employees({ "grade": grade, - "department": department, - "designation": designation, + "department": department, + "designation": designation, "name": employee }) - if employees: - if len(employees) > 20: + if employee_records: + if len(employee_records) > 20: frappe.enqueue(grant_leave_alloc_for_employees, timeout=600, - employees=employees, leave_period=self, carry_forward_leaves=carry_forward_leaves) + employee_records=employee_records, leave_period=self, carry_forward=carry_forward) else: - grant_leave_alloc_for_employees(employees, self, carry_forward_leaves) + grant_leave_alloc_for_employees(employee_records, self, carry_forward) else: frappe.msgprint(_("No Employee Found")) -def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leaves=0): +def grant_leave_alloc_for_employees(employee_records, leave_period, carry_forward=0): leave_allocations = [] - existing_allocations_for = get_existing_allocations(employees, leave_period.name) + existing_allocations_for = get_existing_allocations(list(employee_records.keys()), leave_period.name) leave_type_details = get_leave_type_details() - count=0 - for employee in employees: + count = 0 + for employee in employee_records.keys(): if employee in existing_allocations_for: continue count +=1 @@ -67,18 +67,24 @@ def grant_leave_alloc_for_employees(employees, leave_period, carry_forward_leave for leave_policy_detail in leave_policy.leave_policy_details: if not leave_type_details.get(leave_policy_detail.leave_type).is_lwp: leave_allocation = create_leave_allocation(employee, leave_policy_detail.leave_type, - leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward_leaves) + leave_policy_detail.annual_allocation, leave_type_details, leave_period, carry_forward, employee_records.get(employee)) leave_allocations.append(leave_allocation) frappe.db.commit() - frappe.publish_progress(count*100/len(set(employees) - set(existing_allocations_for)), title = _("Allocating leaves...")) + frappe.publish_progress(count*100/len(set(employee_records.keys()) - set(existing_allocations_for)), title = _("Allocating leaves...")) if leave_allocations: frappe.msgprint(_("Leaves has been granted sucessfully")) def get_existing_allocations(employees, leave_period): leave_allocations = frappe.db.sql_list(""" - select distinct employee from `tabLeave Allocation` - where leave_period=%s and employee in (%s) and docstatus=1 + SELECT DISTINCT + employee + FROM `tabLeave Allocation` + WHERE + leave_period=%s + AND employee in (%s) + AND carry_forward=0 + AND docstatus=1 """ % ('%s', ', '.join(['%s']*len(employees))), [leave_period] + employees) if leave_allocations: frappe.msgprint(_("Skipping Leave Allocation for the following employees, as Leave Allocation records already exists against them. {0}") @@ -87,28 +93,36 @@ def get_existing_allocations(employees, leave_period): def get_leave_type_details(): leave_type_details = frappe._dict() - leave_types = frappe.get_all("Leave Type", fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward"]) + leave_types = frappe.get_all("Leave Type", + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details -def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward_leaves): - allocation = frappe.new_doc("Leave Allocation") - allocation.employee = employee - allocation.leave_type = leave_type - allocation.from_date = leave_period.from_date - allocation.to_date = leave_period.to_date +def create_leave_allocation(employee, leave_type, new_leaves_allocated, leave_type_details, leave_period, carry_forward, date_of_joining): + ''' Creates leave allocation for the given employee in the provided leave period ''' + if carry_forward and not leave_type_details.get(leave_type).is_carry_forward: + carry_forward = 0 + + # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period + if getdate(date_of_joining) > getdate(leave_period.from_date): + remaining_period = ((date_diff(leave_period.to_date, date_of_joining) + 1) / (date_diff(leave_period.to_date, leave_period.from_date) + 1)) + new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) + # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1: new_leaves_allocated = 0 - allocation.new_leaves_allocated = new_leaves_allocated - allocation.leave_period = leave_period.name - if carry_forward_leaves: - if leave_type_details.get(leave_type).is_carry_forward: - allocation.carry_forward = carry_forward_leaves + allocation = frappe.get_doc(dict( + doctype="Leave Allocation", + employee=employee, + leave_type=leave_type, + from_date=leave_period.from_date, + to_date=leave_period.to_date, + new_leaves_allocated=new_leaves_allocated, + leave_period=leave_period.name, + carry_forward=carry_forward + )) allocation.save(ignore_permissions = True) allocation.submit() - return allocation.name - - + return allocation.name \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py index f97d2855a4f..48a204596c3 100644 --- a/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py +++ b/erpnext/hr/doctype/leave_policy/leave_policy_dashboard.py @@ -12,6 +12,9 @@ def get_data(): }, { 'items': ['Employee Grade'] - } + }, + { + 'items': ['Leave Allocation'] + }, ] } \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py index 2c6f1d0a21a..fc868ea15a6 100644 --- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py +++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py @@ -12,16 +12,20 @@ class TestLeavePolicy(unittest.TestCase): if random_leave_type: random_leave_type = random_leave_type[0] leave_type = frappe.get_doc("Leave Type", random_leave_type.name) - old_max_leaves_allowed = leave_type.max_leaves_allowed leave_type.max_leaves_allowed = 2 leave_type.save() - leave_policy_details = { - "doctype": "Leave Policy", - "leave_policy_details": [{ - "leave_type": leave_type.name, - "annual_allocation": leave_type.max_leaves_allowed + 1 - }] - } + leave_policy = create_leave_policy(leave_type=leave_type.name, annual_allocation=leave_type.max_leaves_allowed + 1) - self.assertRaises(frappe.ValidationError, frappe.get_doc(leave_policy_details).insert) + self.assertRaises(frappe.ValidationError, leave_policy.insert) + +def create_leave_policy(**args): + ''' Returns an object of leave policy ''' + args = frappe._dict(args) + return frappe.get_doc({ + "doctype": "Leave Policy", + "leave_policy_details": [{ + "leave_type": args.leave_type or "_Test Leave Type", + "annual_allocation": args.annual_allocation or 10 + }] + }) \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 6a7a80a7845..550d536c8df 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -1,694 +1,214 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:leave_type_name", - "beta": 0, "creation": "2013-02-21 09:55:58", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "engine": "InnoDB", + "field_order": [ + "leave_type_name", + "max_leaves_allowed", + "applicable_after", + "max_continuous_days_allowed", + "column_break_3", + "is_carry_forward", + "is_lwp", + "is_optional_leave", + "allow_negative", + "include_holiday", + "is_compensatory", + "carry_forward_section", + "maximum_carry_forwarded_leaves", + "expire_carry_forwarded_leaves_after_days", + "encashment", + "allow_encashment", + "encashment_threshold_days", + "earning_component", + "earned_leave", + "is_earned_leave", + "earned_leave_frequency", + "rounding" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "leave_type_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Leave Type Name", - "length": 0, - "no_copy": 0, "oldfieldname": "leave_type_name", "oldfieldtype": "Data", - "permlevel": 0, - "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 + "unique": 1 }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "max_leaves_allowed", "fieldtype": "Int", - "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": "Max Leaves Allowed", - "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": "Max Leaves Allowed" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "applicable_after", "fieldtype": "Int", - "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": "Applicable After (Working Days)", - "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": "Applicable After (Working Days)" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "max_continuous_days_allowed", "fieldtype": "Int", - "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": "Maximum Continuous Days Applicable", - "length": 0, - "no_copy": 0, "oldfieldname": "max_days_allowed", - "oldfieldtype": "Data", - "permlevel": 0, - "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 + "oldfieldtype": "Data" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "column_break_3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldtype": "Column Break" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "is_carry_forward", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Is Carry Forward", - "length": 0, - "no_copy": 0, "oldfieldname": "is_carry_forward", - "oldfieldtype": "Check", - "permlevel": 0, - "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 + "oldfieldtype": "Check" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "is_lwp", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Leave Without Pay", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Is Leave Without Pay" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "is_optional_leave", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Optional Leave", - "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": "Is Optional Leave" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "allow_negative", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Negative Balance", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Allow Negative Balance" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "include_holiday", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Include holidays within leaves as leaves", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Include holidays within leaves as leaves" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "is_compensatory", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Compensatory", - "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": "Is Compensatory" + }, + { + "collapsible": 1, + "depends_on": "eval: doc.is_carry_forward == 1", + "fieldname": "carry_forward_section", + "fieldtype": "Section Break", + "label": "Carry Forward" + }, + { + "description": "Calculated in days", + "fieldname": "expire_carry_forwarded_leaves_after_days", + "fieldtype": "Int", + "label": "Expire Carry Forwarded Leaves (Days)" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "encashment", "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": "Encashment", - "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": "Encashment" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "allow_encashment", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Encashment", - "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": "Allow Encashment" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "allow_encashment", "fieldname": "encashment_threshold_days", "fieldtype": "Int", - "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": "Encashment Threshold Days", - "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": "Encashment Threshold Days" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "allow_encashment", "fieldname": "earning_component", "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": "Earning Component", - "length": 0, - "no_copy": 0, - "options": "Salary Component", - "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": "Salary Component" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, "fieldname": "earned_leave", "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": "Earned Leave", - "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": "Earned Leave" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, + "default": "0", "fieldname": "is_earned_leave", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Earned Leave", - "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": "Is Earned Leave" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "is_earned_leave", "fieldname": "earned_leave_frequency", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Earned Leave Frequency", - "length": 0, - "no_copy": 0, - "options": "Monthly\nQuarterly\nHalf-Yearly\nYearly", - "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": "Monthly\nQuarterly\nHalf-Yearly\nYearly" }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0.5", "depends_on": "is_earned_leave", "fieldname": "rounding", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Rounding", - "length": 0, - "no_copy": 0, - "options": "0.5\n1.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 + "options": "0.5\n1.0" + }, + { + "depends_on": "is_carry_forward", + "fieldname": "maximum_carry_forwarded_leaves", + "fieldtype": "Float", + "label": "Maximum Carry Forwarded Leaves" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-flag", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-06-03 18:32:51.803472", + "modified": "2019-09-06 18:48:48.946074", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "HR Manager", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, "read": 1, - "report": 0, - "role": "Employee", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Employee" } ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 0, - "track_seen": 0 -} + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_type/leave_type.py b/erpnext/hr/doctype/leave_type/leave_type.py index e0127e5e973..c0d12968416 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.py +++ b/erpnext/hr/doctype/leave_type/leave_type.py @@ -2,9 +2,22 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals +import calendar import frappe +from datetime import datetime +from frappe.utils import today +from frappe import _ from frappe.model.document import Document class LeaveType(Document): - pass \ No newline at end of file + def validate(self): + if self.is_lwp: + leave_allocation = frappe.get_all("Leave Allocation", filters={ + 'leave_type': self.name, + 'from_date': ("<=", today()), + 'to_date': (">=", today()) + }, fields=['name']) + leave_allocation = [l['name'] for l in leave_allocation] + if leave_allocation: + frappe.throw(_('Leave application is linked with leave allocations {0}. Leave application cannot be set as leave without pay').format(", ".join(leave_allocation))) #nosec diff --git a/erpnext/hr/doctype/leave_type/test_leave_type.py b/erpnext/hr/doctype/leave_type/test_leave_type.py index b844e49e7c4..0c4f435860a 100644 --- a/erpnext/hr/doctype/leave_type/test_leave_type.py +++ b/erpnext/hr/doctype/leave_type/test_leave_type.py @@ -2,6 +2,25 @@ # License: GNU General Public License v3. See license.txt from __future__ import unicode_literals - import frappe -test_records = frappe.get_test_records('Leave Type') \ No newline at end of file +from frappe import _ + +test_records = frappe.get_test_records('Leave Type') + +def create_leave_type(**args): + args = frappe._dict(args) + if frappe.db.exists("Leave Type", args.leave_type_name): + return frappe.get_doc("Leave Type", args.leave_type_name) + leave_type = frappe.get_doc({ + "doctype": "Leave Type", + "leave_type_name": args.leave_type_name or "_Test Leave Type", + "include_holiday": args.include_holidays or 1, + "allow_encashment": args.allow_encashment or 0, + "is_earned_leave": args.is_earned_leave or 0, + "is_lwp": args.is_lwp or 0, + "is_carry_forward": args.is_carry_forward or 0, + "expire_carry_forwarded_leaves_after_days": args.expire_carry_forwarded_leaves_after_days or 0, + "encashment_threshold_days": args.encashment_threshold_days or 5, + "earning_component": "Leave Encashment" + }) + return leave_type \ No newline at end of file diff --git a/erpnext/hr/doctype/loan_application/loan_application.py b/erpnext/hr/doctype/loan_application/loan_application.py index 28d9c43f8e3..582bf48bf0a 100644 --- a/erpnext/hr/doctype/loan_application/loan_application.py +++ b/erpnext/hr/doctype/loan_application/loan_application.py @@ -30,7 +30,7 @@ class LoanApplication(Document): monthly_interest_rate = flt(self.rate_of_interest) / (12 *100) if monthly_interest_rate: min_repayment_amount = self.loan_amount*monthly_interest_rate - if self.repayment_amount - min_repayment_amount <= 0: + if (self.repayment_amount - min_repayment_amount) <= 0: frappe.throw(_("Repayment Amount must be greater than " \ + str(flt(min_repayment_amount, 2)))) self.repayment_periods = math.ceil((math.log(self.repayment_amount) - @@ -58,10 +58,13 @@ def make_loan(source_name, target_doc = None): doclist = get_mapped_doc("Loan Application", source_name, { "Loan Application": { "doctype": "Loan", + "field_map": { + "repayment_amount": "monthly_repayment_amount" + }, "validation": { "docstatus": ["=", 1] } } }, target_doc) - return doclist \ No newline at end of file + return doclist diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index e4ab68068c4..adc06712ef0 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -69,7 +69,7 @@ frappe.ui.form.on('Payroll Entry', { }, add_context_buttons: function(frm) { - if(frm.doc.salary_slips_submitted) { + if(frm.doc.salary_slips_submitted || (frm.doc.__onload && frm.doc.__onload.submitted_ss)) { frm.events.add_bank_entry_button(frm); } else if(frm.doc.salary_slips_created) { frm.add_custom_button(__("Submit Salary Slip"), function() { diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 8803a9a84e2..97cfc841598 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -14,13 +14,12 @@ from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee class PayrollEntry(Document): def onload(self): if not self.docstatus==1 or self.salary_slips_submitted: - return + return # check if salary slips were manually submitted entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name']) - if cint(entries) == len(self.employees) and not self.salary_slips_submitted: - self.db_set("salary_slips_submitted", 1) - self.reload() + if cint(entries) == len(self.employees): + self.set_onload("submitted_ss", True) def on_submit(self): self.create_salary_slips() @@ -429,7 +428,6 @@ def get_start_end_dates(payroll_frequency, start_date=None, company=None): 'start_date': start_date, 'end_date': end_date }) - def get_frequency_kwargs(frequency_name): frequency_dict = { 'monthly': {'months': 1}, diff --git a/erpnext/hr/doctype/retention_bonus/retention_bonus.js b/erpnext/hr/doctype/retention_bonus/retention_bonus.js index 58f6b536042..64e726db857 100644 --- a/erpnext/hr/doctype/retention_bonus/retention_bonus.js +++ b/erpnext/hr/doctype/retention_bonus/retention_bonus.js @@ -10,8 +10,13 @@ frappe.ui.form.on('Retention Bonus', { } }; }); - }, - refresh: function(frm) { + frm.set_query("salary_component", function() { + return { + filters: { + "type": "Earning" + } + }; + }); } }); diff --git a/erpnext/hr/doctype/retention_bonus/retention_bonus.json b/erpnext/hr/doctype/retention_bonus/retention_bonus.json index 5a92f07bd67..7781053e137 100644 --- a/erpnext/hr/doctype/retention_bonus/retention_bonus.json +++ b/erpnext/hr/doctype/retention_bonus/retention_bonus.json @@ -1,415 +1,165 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "HR-RTB-.YYYY.-.#####", - "beta": 0, - "creation": "2018-05-13 14:59:42.038964", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "allow_import": 1, + "allow_rename": 1, + "autoname": "HR-RTB-.YYYY.-.#####", + "creation": "2018-05-13 14:59:42.038964", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "employee", + "bonus_payment_date", + "bonus_amount", + "salary_component", + "amended_from", + "column_break_6", + "employee_name", + "department", + "date_of_joining", + "additional_salary" + ], "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": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "employee", - "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": "Employee", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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 - }, + "fieldname": "employee", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Employee", + "options": "Employee", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bonus_payment_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": "Bonus Payment Date", - "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 - }, + "fieldname": "bonus_payment_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Bonus Payment Date", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bonus_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bonus Amount", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "bonus_amount", + "fieldtype": "Currency", + "label": "Bonus Amount", + "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": "Retention Bonus", - "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 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Retention Bonus", + "print_hide": 1, + "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_6", - "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 - }, + "fieldname": "column_break_6", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.employee_name", - "fieldname": "employee_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": "Employee Name", - "length": 0, - "no_copy": 0, - "options": "", - "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 - }, + "fetch_from": "employee.employee_name", + "fieldname": "employee_name", + "fieldtype": "Data", + "label": "Employee Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.department", - "fieldname": "department", - "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": "Department", - "length": 0, - "no_copy": 0, - "options": "Department", - "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 - }, + "fetch_from": "employee.department", + "fieldname": "department", + "fieldtype": "Link", + "label": "Department", + "options": "Department", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "employee.date_of_joining", - "fieldname": "date_of_joining", - "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": "Date of Joining", - "length": 0, - "no_copy": 0, - "options": "", - "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 + "fetch_from": "employee.date_of_joining", + "fieldname": "date_of_joining", + "fieldtype": "Data", + "label": "Date of Joining", + "read_only": 1 + }, + { + "fieldname": "additional_salary", + "fieldtype": "Link", + "label": "Additional Salary", + "no_copy": 1, + "options": "Additional Salary", + "read_only": 1 + }, + { + "fieldname": "salary_component", + "fieldtype": "Link", + "label": "Salary Component", + "options": "Salary Component", + "reqd": 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, - "modified": "2018-08-21 16:15:38.710684", - "modified_by": "Administrator", - "module": "HR", - "name": "Retention Bonus", - "name_case": "", - "owner": "Administrator", + ], + "is_submittable": 1, + "modified": "2019-09-03 16:47:24.210422", + "modified_by": "Administrator", + "module": "HR", + "name": "Retention Bonus", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 1, - "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": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "HR User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "HR User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Employee", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Employee", + "share": 1 } - ], - "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 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/hr/doctype/retention_bonus/retention_bonus.py b/erpnext/hr/doctype/retention_bonus/retention_bonus.py index 20d4c13f197..48637a350cd 100644 --- a/erpnext/hr/doctype/retention_bonus/retention_bonus.py +++ b/erpnext/hr/doctype/retention_bonus/retention_bonus.py @@ -10,7 +10,42 @@ from frappe.utils import getdate class RetentionBonus(Document): def validate(self): - if frappe.get_value("Employee", self.employee, "status") == "Left": - frappe.throw(_("Cannot create Retention Bonus for left Employees")) + if frappe.get_value('Employee', self.employee, 'status') == 'Left': + frappe.throw(_('Cannot create Retention Bonus for left Employees')) if getdate(self.bonus_payment_date) < getdate(): - frappe.throw(_("Bonus Payment Date cannot be a past date")) + frappe.throw(_('Bonus Payment Date cannot be a past date')) + + def on_submit(self): + company = frappe.db.get_value('Employee', self.employee, 'company') + additional_salary = frappe.db.exists('Additional Salary', { + 'employee': self.employee, + 'salary_component': self.salary_component, + 'payroll_date': self.bonus_payment_date, + 'company': company, + 'docstatus': 1 + }) + + if not additional_salary: + additional_salary = frappe.new_doc('Additional Salary') + additional_salary.employee = self.employee + additional_salary.salary_component = self.salary_component + additional_salary.amount = self.bonus_amount + additional_salary.payroll_date = self.bonus_payment_date + additional_salary.company = company + additional_salary.submit() + self.db_set('additional_salary', additional_salary.name) + + else: + bonus_added = frappe.db.get_value('Additional Salary', additional_salary, 'amount') + self.bonus_amount + frappe.db.set_value('Additional Salary', additional_salary, 'amount', bonus_added) + self.db_set('additional_salary', additional_salary) + + def on_cancel(self): + if self.additional_salary: + bonus_removed = frappe.db.get_value('Additional Salary', self.additional_salary, 'amount') - self.bonus_amount + if bonus_removed == 0: + frappe.get_doc('Additional Salary', self.additional_salary).cancel() + else: + frappe.db.set_value('Additional Salary', self.additional_salary, 'amount', bonus_removed) + + self.db_set('additional_salary', '') \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_type/shift_type.json b/erpnext/hr/doctype/shift_type/shift_type.json index 86039deebd4..61f3d2c2798 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.json +++ b/erpnext/hr/doctype/shift_type/shift_type.json @@ -23,14 +23,9 @@ "grace_period_settings_auto_attendance_section", "enable_entry_grace_period", "late_entry_grace_period", - "consequence_after", - "consequence", "column_break_18", "enable_exit_grace_period", - "enable_different_consequence_for_early_exit", - "early_exit_grace_period", - "early_exit_consequence_after", - "early_exit_consequence" + "early_exit_grace_period" ], "fields": [ { @@ -107,21 +102,6 @@ "fieldtype": "Int", "label": "Late Entry Grace Period" }, - { - "depends_on": "enable_entry_grace_period", - "description": "The number of occurrence after which the consequence is executed.", - "fieldname": "consequence_after", - "fieldtype": "Int", - "label": "Consequence after" - }, - { - "default": "Half Day", - "depends_on": "enable_entry_grace_period", - "fieldname": "consequence", - "fieldtype": "Select", - "label": "Consequence", - "options": "Half Day\nAbsent" - }, { "fieldname": "column_break_18", "fieldtype": "Column Break" @@ -132,13 +112,6 @@ "fieldtype": "Check", "label": "Enable Exit Grace Period" }, - { - "default": "0", - "depends_on": "enable_exit_grace_period", - "fieldname": "enable_different_consequence_for_early_exit", - "fieldtype": "Check", - "label": "Enable Different Consequence for Early Exit" - }, { "depends_on": "eval:doc.enable_exit_grace_period", "description": "The time before the shift end time when check-out is considered as early (in minutes).", @@ -146,21 +119,6 @@ "fieldtype": "Int", "label": "Early Exit Grace Period" }, - { - "depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit", - "description": "The number of occurrence after which the consequence is executed.", - "fieldname": "early_exit_consequence_after", - "fieldtype": "Int", - "label": "Early Exit Consequence after" - }, - { - "default": "Half Day", - "depends_on": "eval:doc.enable_exit_grace_period && doc.enable_different_consequence_for_early_exit", - "fieldname": "early_exit_consequence", - "fieldtype": "Select", - "label": "Early Exit Consequence", - "options": "Half Day\nAbsent" - }, { "default": "60", "description": "Time after the end of shift during which check-out is considered for attendance.", @@ -178,7 +136,6 @@ "depends_on": "enable_auto_attendance", "fieldname": "grace_period_settings_auto_attendance_section", "fieldtype": "Section Break", - "hidden": 1, "label": "Grace Period Settings For Auto Attendance" }, { @@ -201,7 +158,7 @@ "label": "Last Sync of Checkin" } ], - "modified": "2019-06-10 06:02:44.272036", + "modified": "2019-07-30 01:05:24.660666", "modified_by": "Administrator", "module": "HR", "name": "Shift Type", diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py index b98f445c0f3..8de92b2761a 100644 --- a/erpnext/hr/doctype/shift_type/shift_type.py +++ b/erpnext/hr/doctype/shift_type/shift_type.py @@ -28,8 +28,8 @@ class ShiftType(Document): logs = frappe.db.get_list('Employee Checkin', fields="*", filters=filters, order_by="employee,time") for key, group in itertools.groupby(logs, key=lambda x: (x['employee'], x['shift_actual_start'])): single_shift_logs = list(group) - attendance_status, working_hours = self.get_attendance(single_shift_logs) - mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, self.name) + attendance_status, working_hours, late_entry, early_exit = self.get_attendance(single_shift_logs) + mark_attendance_and_link_log(single_shift_logs, attendance_status, key[1].date(), working_hours, late_entry, early_exit, self.name) for employee in self.get_assigned_employee(self.process_attendance_after, True): self.mark_absent_for_dates_with_no_attendance(employee) @@ -39,12 +39,19 @@ class ShiftType(Document): 1. These logs belongs to an single shift, single employee and is not in a holiday date. 2. Logs are in chronological order """ - total_working_hours = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on) + late_entry = early_exit = False + total_working_hours, in_time, out_time = calculate_working_hours(logs, self.determine_check_in_and_check_out, self.working_hours_calculation_based_on) + if cint(self.enable_entry_grace_period) and in_time and in_time > logs[0].shift_start + timedelta(minutes=cint(self.late_entry_grace_period)): + late_entry = True + + if cint(self.enable_exit_grace_period) and out_time and out_time < logs[0].shift_end - timedelta(minutes=cint(self.early_exit_grace_period)): + early_exit = True + if self.working_hours_threshold_for_absent and total_working_hours < self.working_hours_threshold_for_absent: - return 'Absent', total_working_hours + return 'Absent', total_working_hours, late_entry, early_exit if self.working_hours_threshold_for_half_day and total_working_hours < self.working_hours_threshold_for_half_day: - return 'Half Day', total_working_hours - return 'Present', total_working_hours + return 'Half Day', total_working_hours, late_entry, early_exit + return 'Present', total_working_hours, late_entry, early_exit def mark_absent_for_dates_with_no_attendance(self, employee): """Marks Absents for the given employee on working days in this shift which have no attendance marked. diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js index 59c25608c24..05728a297b2 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.js @@ -24,6 +24,18 @@ frappe.query_reports["Employee Leave Balance"] = { "options": "Company", "reqd": 1, "default": frappe.defaults.get_user_default("Company") + }, + { + "fieldname":"department", + "label": __("Department"), + "fieldtype": "Link", + "options": "Department", + }, + { + "fieldname":"employee", + "label": __("Employee"), + "fieldtype": "Link", + "options": "Employee", } ] } diff --git a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py index 18431768528..7717ba0e402 100644 --- a/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py +++ b/erpnext/hr/report/employee_leave_balance/employee_leave_balance.py @@ -4,9 +4,12 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import flt from erpnext.hr.doctype.leave_application.leave_application \ - import get_leave_allocation_records, get_leave_balance_on, get_approved_leaves_for_period + 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") @@ -18,7 +21,7 @@ def execute(filters=None): def get_columns(leave_types): columns = [ - _("Employee") + ":Link/Employee:150", + _("Employee") + ":Link.Employee:150", _("Employee Name") + "::200", _("Department") +"::150" ] @@ -30,57 +33,50 @@ def get_columns(leave_types): return columns +def get_conditions(filters): + conditions = { + "status": "Active", + "company": filters.company, + } + if filters.get("department"): + conditions.update({"department": filters.get("department")}) + if filters.get("employee"): + conditions.update({"employee": filters.get("employee")}) + + return conditions + def get_data(filters, leave_types): user = frappe.session.user - allocation_records_based_on_to_date = get_leave_allocation_records(filters.to_date) - allocation_records_based_on_from_date = get_leave_allocation_records(filters.from_date) + conditions = get_conditions(filters) if filters.to_date <= filters.from_date: frappe.throw(_("From date can not be greater than than To date")) active_employees = frappe.get_all("Employee", - filters = { "status": "Active", "company": filters.company}, - fields = ["name", "employee_name", "department", "user_id"]) + filters=conditions, + fields=["name", "employee_name", "department", "user_id", "leave_approver"]) + + department_approver_map = get_department_leave_approver_map(filters.get('department')) data = [] for employee in active_employees: - leave_approvers = get_approvers(employee.department) + leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver) if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) or ("HR Manager" in frappe.get_roles(user)): row = [employee.name, employee.employee_name, employee.department] for leave_type in leave_types: # leaves taken - leaves_taken = get_approved_leaves_for_period(employee.name, leave_type, - filters.from_date, filters.to_date) + 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, - allocation_records_based_on_to_date.get(employee.name, frappe._dict())) + opening = get_leave_balance_on(employee.name, leave_type, filters.from_date) # closing balance - closing = get_leave_balance_on(employee.name, leave_type, filters.to_date, - allocation_records_based_on_to_date.get(employee.name, frappe._dict())) + closing = get_leave_balance_on(employee.name, leave_type, filters.to_date) row += [opening, leaves_taken, closing] data.append(row) - return data - -def get_approvers(department): - if not department: - return [] - - approvers = [] - # get current department and all its child - department_details = frappe.db.get_value("Department", {"name": department}, ["lft", "rgt"], as_dict=True) - department_list = frappe.db.sql("""select name from `tabDepartment` - where lft >= %s and rgt <= %s order by lft desc - """, (department_details.lft, department_details.rgt), as_list = True) - - # retrieve approvers list from current department and from its subsequent child departments - for d in department_list: - approvers.extend([l.leave_approver for l in frappe.db.sql("""select approver from `tabDepartment Approver` \ - where parent = %s and parentfield = 'leave_approvers'""", (d), as_dict=True)]) - - return approvers + return data \ No newline at end of file diff --git a/erpnext/hr/report/employee_leave_balance_summary/__init__.py b/erpnext/hr/report/employee_leave_balance_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.js b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.js new file mode 100644 index 00000000000..3fb8f6e9c1a --- /dev/null +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.js @@ -0,0 +1,42 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports['Employee Leave Balance Summary'] = { + filters: [ + { + fieldname:'from_date', + label: __('From Date'), + fieldtype: 'Date', + reqd: 1, + default: frappe.defaults.get_default('year_start_date') + }, + { + fieldname:'to_date', + label: __('To Date'), + fieldtype: 'Date', + reqd: 1, + default: frappe.defaults.get_default('year_end_date') + }, + { + fieldname:'company', + label: __('Company'), + fieldtype: 'Link', + options: 'Company', + reqd: 1, + default: frappe.defaults.get_user_default('Company') + }, + { + fieldname:'employee', + label: __('Employee'), + fieldtype: 'Link', + options: 'Employee', + }, + { + fieldname:'department', + label: __('Department'), + fieldtype: 'Link', + options: 'Department', + } + ] +}; diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.json b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.json new file mode 100644 index 00000000000..60fe1ae25ce --- /dev/null +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.json @@ -0,0 +1,34 @@ +{ + "add_total_row": 0, + "creation": "2019-09-05 11:18:06.209397", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "letter_head": "sapcon-old", + "modified": "2019-09-05 11:18:06.209397", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Leave Balance Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Employee", + "report_name": "Employee Leave Balance Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Employee" + }, + { + "role": "HR Manager" + }, + { + "role": "HR User" + }, + { + "role": "Leave Approver" + } + ] +} \ No newline at end of file diff --git a/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py new file mode 100644 index 00000000000..15a5da00f83 --- /dev/null +++ b/erpnext/hr/report/employee_leave_balance_summary/employee_leave_balance_summary.py @@ -0,0 +1,130 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +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 + +def execute(filters=None): + if filters.to_date <= filters.from_date: + frappe.throw(_('From date can not be greater than than To date')) + + columns = get_columns() + data = get_data(filters) + + return columns, data + +def get_columns(): + columns = [{ + 'label': _('Leave Type'), + 'fieldtype': 'Link', + 'fieldname': 'leave_type', + 'width': 300, + 'options': 'Leave Type' + }, { + 'label': _('Employee'), + 'fieldtype': 'Link', + 'fieldname': 'employee', + 'width': 100, + 'options': 'Employee' + }, { + 'label': _('Employee Name'), + 'fieldtype': 'Data', + 'fieldname': 'employee_name', + 'width': 100, + }, { + 'label': _('Opening Balance'), + 'fieldtype': 'float', + 'fieldname': 'opening_balance', + 'width': 160, + }, { + 'label': _('Leaves Taken'), + 'fieldtype': 'float', + 'fieldname': 'leaves_taken', + 'width': 160, + }, { + 'label': _('Closing Balance'), + 'fieldtype': 'float', + 'fieldname': 'closing_balance', + 'width': 160, + }] + + return columns + +def get_data(filters): + leave_types = frappe.db.sql_list("SELECT `name` FROM `tabLeave Type` ORDER BY `name` ASC") + + conditions = get_conditions(filters) + + user = frappe.session.user + department_approver_map = get_department_leave_approver_map(filters.get('department')) + + active_employees = frappe.get_list('Employee', + filters=conditions, + fields=['name', 'employee_name', 'department', 'user_id', 'leave_approver']) + + data = [] + + for leave_type in leave_types: + data.append({ + 'leave_type': leave_type + }) + for employee in active_employees: + + leave_approvers = department_approver_map.get(employee.department_name, []).append(employee.leave_approver) + + if (len(leave_approvers) and user in leave_approvers) or (user in ["Administrator", employee.user_id]) \ + or ("HR Manager" in frappe.get_roles(user)): + row = frappe._dict({ + 'employee': employee.name, + 'employee_name': employee.employee_name + }) + + leaves_taken = get_leaves_for_period(employee.name, leave_type, + filters.from_date, filters.to_date) * -1 + + 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 + +def get_conditions(filters): + conditions={ + 'status': 'Active', + } + if filters.get('employee'): + conditions['name'] = filters.get('employee') + + if filters.get('employee'): + conditions['name'] = filters.get('employee') + + 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 diff --git a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py index e9c702944d0..1e9c83bf3e6 100644 --- a/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py +++ b/erpnext/hr/report/monthly_attendance_sheet/monthly_attendance_sheet.py @@ -25,6 +25,7 @@ def execute(filters=None): leave_types = frappe.db.sql("""select name from `tabLeave Type`""", as_list=True) leave_list = [d[0] for d in leave_types] columns.extend(leave_list) + columns.extend([_("Total Late Entries") + ":Float:120", _("Total Early Exits") + ":Float:120"]) for emp in sorted(att_map): emp_det = emp_map.get(emp) @@ -65,6 +66,10 @@ def execute(filters=None): leave_details = frappe.db.sql("""select leave_type, status, count(*) as count from `tabAttendance`\ where leave_type is not NULL %s group by leave_type, status""" % conditions, filters, as_dict=1) + + time_default_counts = frappe.db.sql("""select (select count(*) from `tabAttendance` where \ + late_entry = 1 %s) as late_entry_count, (select count(*) from tabAttendance where \ + early_exit = 1 %s) as early_exit_count""" % (conditions, conditions), filters) leaves = {} for d in leave_details: @@ -80,7 +85,8 @@ def execute(filters=None): row.append(leaves[d]) else: row.append("0.0") - + + row.extend([time_default_counts[0][0],time_default_counts[0][1]]) data.append(row) return columns, data diff --git a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py index e2989e29c07..e5622b7ae12 100644 --- a/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py +++ b/erpnext/hr/report/vehicle_expenses/vehicle_expenses.py @@ -23,7 +23,8 @@ def get_columns(): _("Model") + ":data:50", _("Location") + ":data:100", _("Log") + ":Link/Vehicle Log:100", _("Odometer") + ":Int:80", _("Date") + ":Date:100", _("Fuel Qty") + ":Float:80", - _("Fuel Price") + ":Float:100",_("Service Expense") + ":Float:100" + _("Fuel Price") + ":Float:100",_("Fuel Expense") + ":Float:100", + _("Service Expense") + ":Float:100" ] return columns @@ -32,7 +33,8 @@ def get_log_data(filters): data = frappe.db.sql("""select vhcl.license_plate as "License", vhcl.make as "Make", vhcl.model as "Model", vhcl.location as "Location", log.name as "Log", log.odometer as "Odometer", - log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price" + log.date as "Date", log.fuel_qty as "Fuel Qty", log.price as "Fuel Price", + log.fuel_qty * log.price as "Fuel Expense" from `tabVehicle` vhcl,`tabVehicle Log` log where @@ -58,7 +60,7 @@ def get_chart_data(data,period_list): total_ser_exp=0 for row in data: if row["Date"] <= period.to_date and row["Date"] >= period.from_date: - total_fuel_exp+=flt(row["Fuel Price"]) + total_fuel_exp+=flt(row["Fuel Expense"]) total_ser_exp+=flt(row["Service Expense"]) fueldata.append([period.key,total_fuel_exp]) servicedata.append([period.key,total_ser_exp]) @@ -84,4 +86,4 @@ def get_chart_data(data,period_list): } } chart["type"] = "line" - return chart \ No newline at end of file + return chart diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index de2b0909dbd..1464a779368 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe, erpnext from frappe import _ -from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr +from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate, flt, cstr, add_days, today from frappe.model.document import Document from frappe.desk.form import assign_to from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee @@ -270,6 +270,21 @@ def get_leave_period(from_date, to_date, company): if leave_period: return leave_period +def generate_leave_encashment(): + ''' Generates a draft leave encashment on allocation expiry ''' + from erpnext.hr.doctype.leave_encashment.leave_encashment import create_leave_encashment + + if frappe.db.get_single_value('HR Settings', 'auto_leave_encashment'): + leave_type = frappe.get_all('Leave Type', filters={'allow_encashment': 1}, fields=['name']) + leave_type=[l['name'] for l in leave_type] + + leave_allocation = frappe.get_all("Leave Allocation", filters={ + 'to_date': add_days(today(), -1), + 'leave_type': ('in', leave_type) + }, fields=['employee', 'leave_period', 'leave_type', 'to_date', 'total_leaves_allocated', 'new_leaves_allocated']) + + create_leave_encashment(leave_allocation=leave_allocation) + def allocate_earned_leaves(): '''Allocate earned leaves to Employees''' e_leave_types = frappe.get_all("Leave Type", @@ -277,31 +292,43 @@ def allocate_earned_leaves(): filters={'is_earned_leave' : 1}) today = getdate() divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} - if e_leave_types: - for e_leave_type in e_leave_types: - leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' - between from_date and to_date and docstatus=1 and leave_type='{1}'""" - .format(today, e_leave_type.name), as_dict=1) - for allocation in leave_allocations: - leave_policy = get_employee_leave_policy(allocation.employee) - if not leave_policy: - continue - if not e_leave_type.earned_leave_frequency == "Monthly": - if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): - continue - annual_allocation = frappe.db.sql("""select annual_allocation from `tabLeave Policy Detail` - where parent=%s and leave_type=%s""", (leave_policy.name, e_leave_type.name)) - if annual_allocation and annual_allocation[0]: - earned_leaves = flt(annual_allocation[0][0]) / divide_by_frequency[e_leave_type.earned_leave_frequency] - if e_leave_type.rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) - allocated_leaves = frappe.db.get_value('Leave Allocation', allocation.name, 'total_leaves_allocated') - new_allocation = flt(allocated_leaves) + flt(earned_leaves) - new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed - frappe.db.set_value('Leave Allocation', allocation.name, 'total_leaves_allocated', new_allocation) + for e_leave_type in e_leave_types: + leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where %s + between from_date and to_date and docstatus=1 and leave_type=%s""", (today, e_leave_type.name), as_dict=1) + for allocation in leave_allocations: + leave_policy = get_employee_leave_policy(allocation.employee) + if not leave_policy: + continue + if not e_leave_type.earned_leave_frequency == "Monthly": + if not check_frequency_hit(allocation.from_date, today, e_leave_type.earned_leave_frequency): + continue + annual_allocation = frappe.db.get_value("Leave Policy Detail", filters={ + 'parent': leave_policy.name, + 'leave_type': e_leave_type.name + }, fieldname=['annual_allocation']) + if annual_allocation: + earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency] + if e_leave_type.rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) + + allocation = frappe.get_doc('Leave Allocation', allocation.name) + new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) + new_allocation = new_allocation if new_allocation <= e_leave_type.max_leaves_allowed else e_leave_type.max_leaves_allowed + + if new_allocation == allocation.total_leaves_allocated: + continue + allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False) + create_earned_leave_ledger_entry(allocation, earned_leaves, today) + +def create_earned_leave_ledger_entry(allocation, earned_leaves, date): + ''' Create leave ledger entry based on the earned leave frequency ''' + allocation.new_leaves_allocated = earned_leaves + allocation.from_date = date + allocation.unused_leaves = 0 + allocation.create_leave_ledger_entry() def check_frequency_hit(from_date, to_date, frequency): '''Return True if current date matches frequency''' diff --git a/erpnext/hub_node/legacy.py b/erpnext/hub_node/legacy.py index 95ada76a6a5..85eb1b2bb01 100644 --- a/erpnext/hub_node/legacy.py +++ b/erpnext/hub_node/legacy.py @@ -68,12 +68,13 @@ def make_contact(supplier): contact = frappe.get_doc({ 'doctype': 'Contact', 'first_name': supplier.supplier_name, - 'email_id': supplier.supplier_email, 'is_primary_contact': 1, 'links': [ {'link_doctype': 'Supplier', 'link_name': supplier.supplier_name} ] - }).insert() + }) + contact.add_email(supplier.supplier_email) + contact.insert() else: contact = frappe.get_doc('Contact', contact_name) diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json index 7865a2476f9..a0faeb5fb55 100644 --- a/erpnext/manufacturing/doctype/bom/bom.json +++ b/erpnext/manufacturing/doctype/bom/bom.json @@ -1,2068 +1,528 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "beta": 0, - "creation": "2013-01-22 15:11:38", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_import": 1, + "creation": "2013-01-22 15:11:38", + "doctype": "DocType", + "document_type": "Setup", + "field_order": [ + "item", + "item_name", + "image", + "uom", + "quantity", + "cb0", + "is_active", + "is_default", + "with_operations", + "inspection_required", + "allow_alternative_item", + "allow_same_item_multiple_times", + "set_rate_of_sub_assembly_item_based_on_bom", + "quality_inspection_template", + "currency_detail", + "company", + "transfer_material_against", + "conversion_rate", + "column_break_12", + "currency", + "rm_cost_as_per", + "buying_price_list", + "operations_section", + "routing", + "operations", + "materials_section", + "items", + "scrap_section", + "scrap_items", + "costing", + "operating_cost", + "raw_material_cost", + "scrap_material_cost", + "cb1", + "base_operating_cost", + "base_raw_material_cost", + "base_scrap_material_cost", + "total_cost_of_bom", + "total_cost", + "column_break_26", + "base_total_cost", + "more_info_section", + "project", + "amended_from", + "col_break23", + "section_break_25", + "description", + "column_break_27", + "section_break0", + "exploded_items", + "website_section", + "show_in_website", + "route", + "website_image", + "thumbnail", + "sb_web_spec", + "web_long_description", + "show_items", + "show_operations" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Item to be manufactured or repacked", - "fetch_if_empty": 0, - "fieldname": "item", - "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": 1, - "label": "Item", - "length": 0, - "no_copy": 0, - "oldfieldname": "item", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "description": "Item to be manufactured or repacked", + "fieldname": "item", + "fieldtype": "Link", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item", + "oldfieldname": "item", + "oldfieldtype": "Link", + "options": "Item", + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_from": "item.item_name", - "fetch_if_empty": 0, - "fieldname": "item_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": "Item 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 - }, + "fetch_from": "item.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Item Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "item.image", - "fetch_if_empty": 0, - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image", - "length": 0, - "no_copy": 0, - "options": "image", - "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 - }, + "fetch_from": "item.image", + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "options": "image", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "item.stock_uom", - "fetch_if_empty": 0, - "fieldname": "uom", - "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": "Item UOM", - "length": 0, - "no_copy": 0, - "options": "UOM", - "permlevel": 0, - "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 - }, + "fetch_from": "item.stock_uom", + "fieldname": "uom", + "fieldtype": "Link", + "label": "Item UOM", + "options": "UOM", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials", - "fetch_if_empty": 0, - "fieldname": "quantity", - "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": "Quantity", - "length": 0, - "no_copy": 0, - "oldfieldname": "quantity", - "oldfieldtype": "Currency", - "permlevel": 0, - "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 - }, + "default": "1", + "description": "Quantity of item obtained after manufacturing / repacking from given quantities of raw materials", + "fieldname": "quantity", + "fieldtype": "Float", + "label": "Quantity", + "oldfieldname": "quantity", + "oldfieldtype": "Currency", + "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": "cb0", - "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, - "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": "cb0", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "is_active", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Is Active", - "length": 0, - "no_copy": 1, - "oldfieldname": "is_active", - "oldfieldtype": "Select", - "permlevel": 0, - "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_on_submit": 1, + "default": "1", + "fieldname": "is_active", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Active", + "no_copy": 1, + "oldfieldname": "is_active", + "oldfieldtype": "Select" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "is_default", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Is Default", - "length": 0, - "no_copy": 1, - "oldfieldname": "is_default", - "oldfieldtype": "Check", - "permlevel": 0, - "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_on_submit": 1, + "default": "1", + "fieldname": "is_default", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Is Default", + "no_copy": 1, + "oldfieldname": "is_default", + "oldfieldtype": "Check" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Manage cost of operations", - "fetch_if_empty": 0, - "fieldname": "with_operations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "With Operations", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "default": "0", + "description": "Manage cost of operations", + "fieldname": "with_operations", + "fieldtype": "Check", + "ignore_user_permissions": 1, + "label": "With Operations" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "inspection_required", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Inspection Required", - "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 - }, + "default": "0", + "fieldname": "inspection_required", + "fieldtype": "Check", + "label": "Inspection Required" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_alternative_item", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Alternative Item", - "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 - }, + "default": "0", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "allow_same_item_multiple_times", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Allow Same Item Multiple Times", - "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 - }, + "default": "0", + "fieldname": "allow_same_item_multiple_times", + "fieldtype": "Check", + "label": "Allow Same Item Multiple Times" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fetch_if_empty": 0, - "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Set rate of sub-assembly item based on BOM", - "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_on_submit": 1, + "default": "1", + "fieldname": "set_rate_of_sub_assembly_item_based_on_bom", + "fieldtype": "Check", + "label": "Set rate of sub-assembly item based on BOM" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "inspection_required", - "fetch_if_empty": 0, - "fieldname": "quality_inspection_template", - "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": "Quality Inspection Template", - "length": 0, - "no_copy": 0, - "options": "Quality Inspection Template", - "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 - }, + "depends_on": "inspection_required", + "fieldname": "quality_inspection_template", + "fieldtype": "Link", + "label": "Quality Inspection Template", + "options": "Quality Inspection Template" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "currency_detail", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "currency_detail", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, - "fieldname": "transfer_material_against", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Transfer Material Against", - "length": 0, - "no_copy": 0, - "options": "\nWork Order\nJob Card", - "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": "transfer_material_against", + "fieldtype": "Select", + "label": "Transfer Material Against", + "options": "\nWork Order\nJob Card" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "conversion_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": "Conversion Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "9", - "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 - }, + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Conversion Rate", + "precision": "9", + "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": "column_break_12", - "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 - }, + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "currency", - "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": "Currency", - "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 - }, + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "options": "Currency", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Valuation Rate", - "fetch_if_empty": 0, - "fieldname": "rm_cost_as_per", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rate Of Materials Based On", - "length": 0, - "no_copy": 0, - "options": "Valuation Rate\nLast Purchase Rate\nPrice List", - "permlevel": 0, - "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_on_submit": 1, + "default": "Valuation Rate", + "fieldname": "rm_cost_as_per", + "fieldtype": "Select", + "label": "Rate Of Materials Based On", + "options": "Valuation Rate\nLast Purchase Rate\nPrice List" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.rm_cost_as_per===\"Price List\"", - "fetch_if_empty": 0, - "fieldname": "buying_price_list", - "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": "Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "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_on_submit": 1, + "depends_on": "eval:doc.rm_cost_as_per===\"Price List\"", + "fieldname": "buying_price_list", + "fieldtype": "Link", + "label": "Price List", + "options": "Price List" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, - "fieldname": "operations_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, - "label": "Operations", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "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": "operations_section", + "fieldtype": "Section Break", + "label": "Operations", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "routing", - "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": "Routing", - "length": 0, - "no_copy": 0, - "options": "Routing", - "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": "routing", + "fieldtype": "Link", + "label": "Routing", + "options": "Routing" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "operations", - "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": "Operations", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_operations", - "oldfieldtype": "Table", - "options": "BOM Operation", - "permlevel": 0, - "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": "operations", + "fieldtype": "Table", + "label": "Operations", + "oldfieldname": "bom_operations", + "oldfieldtype": "Table", + "options": "BOM Operation" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "materials_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, - "label": "Materials", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "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": "materials_section", + "fieldtype": "Section Break", + "label": "Materials", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "bom_materials", - "oldfieldtype": "Table", - "options": "BOM Item", - "permlevel": 0, - "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 - }, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "bom_materials", + "oldfieldtype": "Table", + "options": "BOM Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "scrap_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, - "label": "Scrap", - "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 - }, + "collapsible": 1, + "fieldname": "scrap_section", + "fieldtype": "Section Break", + "label": "Scrap" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "scrap_items", - "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": "Scrap Items", - "length": 0, - "no_copy": 0, - "options": "BOM Scrap Item", - "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": "scrap_items", + "fieldtype": "Table", + "label": "Scrap Items", + "options": "BOM Scrap Item" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "costing", - "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": "Costing", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "permlevel": 0, - "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": "costing", + "fieldtype": "Section Break", + "label": "Costing", + "oldfieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "operating_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Operating Cost", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "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 - }, + "fieldname": "operating_cost", + "fieldtype": "Currency", + "label": "Operating Cost", + "options": "currency", + "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": "raw_material_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raw Material Cost", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "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 - }, + "fieldname": "raw_material_cost", + "fieldtype": "Currency", + "label": "Raw Material Cost", + "options": "currency", + "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": "scrap_material_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Scrap Material Cost", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, + "fieldname": "scrap_material_cost", + "fieldtype": "Currency", + "label": "Scrap Material Cost", + "options": "currency", + "print_hide": 1, + "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": "cb1", - "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, - "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": "cb1", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_operating_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Operating Cost (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_operating_cost", + "fieldtype": "Currency", + "label": "Operating Cost (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "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": "base_raw_material_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Raw Material Cost(Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_raw_material_cost", + "fieldtype": "Currency", + "label": "Raw Material Cost (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "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": "base_scrap_material_cost", - "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": "Scrap Material Cost(Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_scrap_material_cost", + "fieldtype": "Data", + "label": "Scrap Material Cost(Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "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": "total_cost_of_bom", - "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 - }, + "fieldname": "total_cost_of_bom", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_cost", - "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": "Total Cost", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "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 - }, + "fieldname": "total_cost", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Total Cost", + "options": "currency", + "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_26", - "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 - }, + "fieldname": "column_break_26", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_total_cost", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Cost(Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_total_cost", + "fieldtype": "Currency", + "label": "Total Cost (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "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": "more_info_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, - "label": "", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "more_info_section", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0, - "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": "project", + "fieldtype": "Link", + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "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": "BOM", - "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 - }, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "options": "BOM", + "print_hide": 1, + "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": "col_break23", - "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, - "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": "col_break23", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_25", - "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 - }, + "fieldname": "section_break_25", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "item.description", - "fetch_if_empty": 0, - "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": 0, - "in_standard_filter": 0, - "label": "Item Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fetch_from": "item.description", + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Item Description", + "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_27", - "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 - }, + "fieldname": "column_break_27", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.__islocal", - "fetch_if_empty": 0, - "fieldname": "section_break0", - "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": "Materials Required (Exploded)", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "depends_on": "eval:!doc.__islocal", + "fieldname": "section_break0", + "fieldtype": "Section Break", + "label": "Materials Required (Exploded)" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "exploded_items", - "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": "Exploded_items", - "length": 0, - "no_copy": 1, - "oldfieldname": "flat_bom_details", - "oldfieldtype": "Table", - "options": "BOM Explosion Item", - "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 - }, + "fieldname": "exploded_items", + "fieldtype": "Table", + "label": "Exploded Items", + "no_copy": 1, + "oldfieldname": "flat_bom_details", + "oldfieldtype": "Table", + "options": "BOM Explosion Item", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "website_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, - "label": "Website", - "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 - }, + "collapsible": 1, + "fieldname": "website_section", + "fieldtype": "Section Break", + "label": "Website" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "show_in_website", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show in Website", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "show_in_website", + "fieldtype": "Check", + "label": "Show in Website" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "route", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Route", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "route", + "fieldtype": "Small Text", + "label": "Route" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "show_in_website", - "description": "Item Image (if not slideshow)", - "fetch_if_empty": 0, - "fieldname": "website_image", - "fieldtype": "Attach Image", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "depends_on": "show_in_website", + "description": "Item Image (if not slideshow)", + "fieldname": "website_image", + "fieldtype": "Attach Image", + "label": "Image" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "thumbnail", - "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": "Thumbnail", - "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 - }, + "allow_on_submit": 1, + "fieldname": "thumbnail", + "fieldtype": "Data", + "label": "Thumbnail", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "website_items", - "columns": 0, - "depends_on": "show_in_website", - "fetch_if_empty": 0, - "fieldname": "sb_web_spec", - "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": "Website Specifications", - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "website_items", + "depends_on": "show_in_website", + "fieldname": "sb_web_spec", + "fieldtype": "Section Break", + "label": "Website Specifications" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "show_in_website", - "fetch_if_empty": 0, - "fieldname": "web_long_description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Website Description", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "depends_on": "show_in_website", + "fieldname": "web_long_description", + "fieldtype": "Text Editor", + "label": "Website Description" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "show_in_website", - "fetch_if_empty": 0, - "fieldname": "show_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Items", - "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_on_submit": 1, + "default": "0", + "depends_on": "show_in_website", + "fieldname": "show_items", + "fieldtype": "Check", + "label": "Show Items" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.show_in_website && doc.with_operations)", - "fetch_if_empty": 0, - "fieldname": "show_operations", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Show Operations", - "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_on_submit": 1, + "default": "0", + "depends_on": "eval:(doc.show_in_website && doc.with_operations)", + "fieldname": "show_operations", + "fieldtype": "Check", + "label": "Show Operations" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-sitemap", - "idx": 1, - "image_field": "image", - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-05-01 16:36:05.197126", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "BOM", - "owner": "Administrator", + ], + "icon": "fa fa-sitemap", + "idx": 1, + "image_field": "image", + "is_submittable": 1, + "modified": "2019-07-30 17:00:09.665068", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "BOM", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "search_fields": "item, item_name", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "search_fields": "item, item_name", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index a7162933bf0..8eb4c9c28c8 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -9,6 +9,7 @@ from erpnext.setup.utils import get_exchange_rate from frappe.website.website_generator import WebsiteGenerator from erpnext.stock.get_item_details import get_conversion_factor from erpnext.stock.get_item_details import get_price_list_rate +from frappe.core.doctype.version.version import get_diff import functools @@ -715,6 +716,8 @@ def get_children(doctype, parent=None, is_root=False, **filters): next(item for item in items if item.get('name') == bom_item.get('item_code')) ) + + bom_item.parent_bom_qty = bom_doc.quantity bom_item.expandable = 0 if bom_item.value in ('', None) else 1 return bom_items @@ -763,3 +766,52 @@ def add_additional_cost(stock_entry, work_order): 'description': name[0], 'amount': items.get(name[0]) }) + +@frappe.whitelist() +def get_bom_diff(bom1, bom2): + from frappe.model import table_fields + + doc1 = frappe.get_doc('BOM', bom1) + doc2 = frappe.get_doc('BOM', bom2) + + out = get_diff(doc1, doc2) + out.row_changed = [] + out.added = [] + out.removed = [] + + meta = doc1.meta + + identifiers = { + 'operations': 'operation', + 'items': 'item_code', + 'scrap_items': 'item_code', + 'exploded_items': 'item_code' + } + + for df in meta.fields: + old_value, new_value = doc1.get(df.fieldname), doc2.get(df.fieldname) + + if df.fieldtype in table_fields: + identifier = identifiers[df.fieldname] + # make maps + old_row_by_identifier, new_row_by_identifier = {}, {} + for d in old_value: + old_row_by_identifier[d.get(identifier)] = d + for d in new_value: + new_row_by_identifier[d.get(identifier)] = d + + # check rows for additions, changes + for i, d in enumerate(new_value): + if d.get(identifier) in old_row_by_identifier: + diff = get_diff(old_row_by_identifier[d.get(identifier)], d, for_child=True) + if diff and diff.changed: + out.row_changed.append((df.fieldname, i, d.get(identifier), diff.changed)) + else: + out.added.append([df.fieldname, d.as_dict()]) + + # check for deletions + for d in old_value: + if not d.get(identifier) in new_row_by_identifier: + out.removed.append([df.fieldname, d.as_dict()]) + + return out diff --git a/erpnext/manufacturing/doctype/production_order/production_order.py b/erpnext/manufacturing/doctype/production_order/production_order.py deleted file mode 100644 index 67930629f56..00000000000 --- a/erpnext/manufacturing/doctype/production_order/production_order.py +++ /dev/null @@ -1,648 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt - -from __future__ import unicode_literals -import frappe -import json -from six import text_type -from frappe import _ -from frappe.utils import flt, get_datetime, getdate, date_diff, cint, nowdate -from frappe.model.document import Document -from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, get_bom_items_as_dict -from dateutil.relativedelta import relativedelta -from erpnext.stock.doctype.item.item import validate_end_of_life -from erpnext.manufacturing.doctype.workstation.workstation import WorkstationHolidayError -from erpnext.projects.doctype.timesheet.timesheet import OverlapError -from erpnext.stock.doctype.stock_entry.stock_entry import get_additional_costs -from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations -from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty -from frappe.utils.csvutils import getlink -from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty -from erpnext.utilities.transaction_base import validate_uom_is_integer - -class OverProductionError(frappe.ValidationError): pass -class StockOverProductionError(frappe.ValidationError): pass -class OperationTooLongError(frappe.ValidationError): pass -class ItemHasVariantError(frappe.ValidationError): pass - -form_grid_templates = { - "operations": "templates/form_grid/production_order_grid.html" -} - -class ProductionOrder(Document): - def validate(self): - self.validate_production_item() - if self.bom_no: - validate_bom_no(self.production_item, self.bom_no) - - self.validate_sales_order() - self.set_default_warehouse() - self.validate_warehouse_belongs_to_company() - self.calculate_operating_cost() - self.validate_qty() - self.validate_operation_time() - self.status = self.get_status() - - validate_uom_is_integer(self, "stock_uom", ["qty", "produced_qty"]) - - self.set_required_items(reset_only_qty = len(self.get("required_items"))) - - def validate_sales_order(self): - if self.sales_order: - so = frappe.db.sql(""" - select so.name, so_item.delivery_date, so.project - from `tabSales Order` so - inner join `tabSales Order Item` so_item on so_item.parent = so.name - left join `tabProduct Bundle Item` pk_item on so_item.item_code = pk_item.parent - where so.name=%s and so.docstatus = 1 and ( - so_item.item_code=%s or - pk_item.item_code=%s ) - """, (self.sales_order, self.production_item, self.production_item), as_dict=1) - - if not so: - so = frappe.db.sql(""" - select - so.name, so_item.delivery_date, so.project - from - `tabSales Order` so, `tabSales Order Item` so_item, `tabPacked Item` packed_item - where so.name=%s - and so.name=so_item.parent - and so.name=packed_item.parent - and so_item.item_code = packed_item.parent_item - and so.docstatus = 1 and packed_item.item_code=%s - """, (self.sales_order, self.production_item), as_dict=1) - - if len(so): - if not self.expected_delivery_date: - self.expected_delivery_date = so[0].delivery_date - - if so[0].project: - self.project = so[0].project - - if not self.material_request: - self.validate_production_order_against_so() - else: - frappe.throw(_("Sales Order {0} is not valid").format(self.sales_order)) - - def set_default_warehouse(self): - if not self.wip_warehouse: - self.wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_wip_warehouse") - if not self.fg_warehouse: - self.fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_fg_warehouse") - - def validate_warehouse_belongs_to_company(self): - warehouses = [self.fg_warehouse, self.wip_warehouse] - for d in self.get("required_items"): - if d.source_warehouse not in warehouses: - warehouses.append(d.source_warehouse) - - for wh in warehouses: - validate_warehouse_company(wh, self.company) - - def calculate_operating_cost(self): - self.planned_operating_cost, self.actual_operating_cost = 0.0, 0.0 - for d in self.get("operations"): - d.planned_operating_cost = flt(d.hour_rate) * (flt(d.time_in_mins) / 60.0) - d.actual_operating_cost = flt(d.hour_rate) * (flt(d.actual_operation_time) / 60.0) - - self.planned_operating_cost += flt(d.planned_operating_cost) - self.actual_operating_cost += flt(d.actual_operating_cost) - - variable_cost = self.actual_operating_cost if self.actual_operating_cost \ - else self.planned_operating_cost - self.total_operating_cost = flt(self.additional_operating_cost) + flt(variable_cost) - - def validate_production_order_against_so(self): - # already ordered qty - ordered_qty_against_so = frappe.db.sql("""select sum(qty) from `tabProduction Order` - where production_item = %s and sales_order = %s and docstatus < 2 and name != %s""", - (self.production_item, self.sales_order, self.name))[0][0] - - total_qty = flt(ordered_qty_against_so) + flt(self.qty) - - # get qty from Sales Order Item table - so_item_qty = frappe.db.sql("""select sum(stock_qty) from `tabSales Order Item` - where parent = %s and item_code = %s""", - (self.sales_order, self.production_item))[0][0] - # get qty from Packing Item table - dnpi_qty = frappe.db.sql("""select sum(qty) from `tabPacked Item` - where parent = %s and parenttype = 'Sales Order' and item_code = %s""", - (self.sales_order, self.production_item))[0][0] - # total qty in SO - so_qty = flt(so_item_qty) + flt(dnpi_qty) - - allowance_percentage = flt(frappe.db.get_single_value("Manufacturing Settings", - "over_production_allowance_percentage")) - - if total_qty > so_qty + (allowance_percentage/100 * so_qty): - frappe.throw(_("Cannot produce more Item {0} than Sales Order quantity {1}") - .format(self.production_item, so_qty), OverProductionError) - - def update_status(self, status=None): - '''Update status of production order if unknown''' - if status != "Stopped": - status = self.get_status(status) - - if status != self.status: - self.db_set("status", status) - - self.update_required_items() - - return status - - def get_status(self, status=None): - '''Return the status based on stock entries against this production order''' - if not status: - status = self.status - - if self.docstatus==0: - status = 'Draft' - elif self.docstatus==1: - if status != 'Stopped': - stock_entries = frappe._dict(frappe.db.sql("""select purpose, sum(fg_completed_qty) - from `tabStock Entry` where production_order=%s and docstatus=1 - group by purpose""", self.name)) - - status = "Not Started" - if stock_entries: - status = "In Process" - produced_qty = stock_entries.get("Manufacture") - if flt(produced_qty) == flt(self.qty): - status = "Completed" - else: - status = 'Cancelled' - - return status - - def update_production_order_qty(self): - """Update **Manufactured Qty** and **Material Transferred for Qty** in Production Order - based on Stock Entry""" - - for purpose, fieldname in (("Manufacture", "produced_qty"), - ("Material Transfer for Manufacture", "material_transferred_for_manufacturing")): - qty = flt(frappe.db.sql("""select sum(fg_completed_qty) - from `tabStock Entry` where production_order=%s and docstatus=1 - and purpose=%s""", (self.name, purpose))[0][0]) - - if qty > self.qty: - frappe.throw(_("{0} ({1}) cannot be greater than planned quanitity ({2}) in Production Order {3}").format(\ - self.meta.get_label(fieldname), qty, self.qty, self.name), StockOverProductionError) - - self.db_set(fieldname, qty) - - def before_submit(self): - self.make_time_logs() - - def on_submit(self): - if not self.wip_warehouse: - frappe.throw(_("Work-in-Progress Warehouse is required before Submit")) - if not self.fg_warehouse: - frappe.throw(_("For Warehouse is required before Submit")) - - self.update_reserved_qty_for_production() - self.update_completed_qty_in_material_request() - self.update_planned_qty() - - def on_cancel(self): - self.validate_cancel() - - frappe.db.set(self,'status', 'Cancelled') - self.delete_timesheet() - self.update_completed_qty_in_material_request() - self.update_planned_qty() - self.update_reserved_qty_for_production() - - def validate_cancel(self): - if self.status == "Stopped": - frappe.throw(_("Stopped Production Order cannot be cancelled, Unstop it first to cancel")) - - # Check whether any stock entry exists against this Production Order - stock_entry = frappe.db.sql("""select name from `tabStock Entry` - where production_order = %s and docstatus = 1""", self.name) - if stock_entry: - frappe.throw(_("Cannot cancel because submitted Stock Entry {0} exists").format(stock_entry[0][0])) - - def update_planned_qty(self): - update_bin_qty(self.production_item, self.fg_warehouse, { - "planned_qty": get_planned_qty(self.production_item, self.fg_warehouse) - }) - - if self.material_request: - mr_obj = frappe.get_doc("Material Request", self.material_request) - mr_obj.update_requested_qty([self.material_request_item]) - - def update_completed_qty_in_material_request(self): - if self.material_request: - frappe.get_doc("Material Request", self.material_request).update_completed_qty([self.material_request_item]) - - def set_production_order_operations(self): - """Fetch operations from BOM and set in 'Production Order'""" - self.set('operations', []) - - if not self.bom_no \ - or cint(frappe.db.get_single_value("Manufacturing Settings", "disable_capacity_planning")): - return - - if self.use_multi_level_bom: - bom_list = frappe.get_doc("BOM", self.bom_no).traverse_tree() - else: - bom_list = [self.bom_no] - - operations = frappe.db.sql(""" - select - operation, description, workstation, idx, - base_hour_rate as hour_rate, time_in_mins, - "Pending" as status, parent as bom - from - `tabBOM Operation` - where - parent in (%s) order by idx - """ % ", ".join(["%s"]*len(bom_list)), tuple(bom_list), as_dict=1) - - self.set('operations', operations) - self.calculate_time() - - def calculate_time(self): - bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity") - - for d in self.get("operations"): - d.time_in_mins = flt(d.time_in_mins) / flt(bom_qty) * flt(self.qty) - - self.calculate_operating_cost() - - def get_holidays(self, workstation): - holiday_list = frappe.db.get_value("Workstation", workstation, "holiday_list") - - holidays = {} - - if holiday_list not in holidays: - holiday_list_days = [getdate(d[0]) for d in frappe.get_all("Holiday", fields=["holiday_date"], - filters={"parent": holiday_list}, order_by="holiday_date", limit_page_length=0, as_list=1)] - - holidays[holiday_list] = holiday_list_days - - return holidays[holiday_list] - - def make_time_logs(self, open_new=False): - """Capacity Planning. Plan time logs based on earliest availablity of workstation after - Planned Start Date. Time logs will be created and remain in Draft mode and must be submitted - before manufacturing entry can be made.""" - - if not self.operations: - return - - timesheets = [] - plan_days = frappe.db.get_single_value("Manufacturing Settings", "capacity_planning_for_days") or 30 - - timesheet = make_timesheet(self.name, self.company) - timesheet.set('time_logs', []) - - for i, d in enumerate(self.operations): - - if d.status != 'Completed': - self.set_start_end_time_for_workstation(d, i) - - args = self.get_operations_data(d) - - add_timesheet_detail(timesheet, args) - original_start_time = d.planned_start_time - - # validate operating hours if workstation [not mandatory] is specified - try: - timesheet.validate_time_logs() - except OverlapError: - if frappe.message_log: frappe.message_log.pop() - timesheet.schedule_for_production_order(d.idx) - except WorkstationHolidayError: - if frappe.message_log: frappe.message_log.pop() - timesheet.schedule_for_production_order(d.idx) - - from_time, to_time = self.get_start_end_time(timesheet, d.name) - - if date_diff(from_time, original_start_time) > cint(plan_days): - frappe.throw(_("Unable to find Time Slot in the next {0} days for Operation {1}").format(plan_days, d.operation)) - break - - d.planned_start_time = from_time - d.planned_end_time = to_time - d.db_update() - - if timesheet and open_new: - return timesheet - - if timesheet and timesheet.get("time_logs"): - timesheet.save() - timesheets.append(getlink("Timesheet", timesheet.name)) - - self.planned_end_date = self.operations[-1].planned_end_time - if timesheets: - frappe.local.message_log = [] - frappe.msgprint(_("Timesheet created:") + "\n" + "\n".join(timesheets)) - - def get_operations_data(self, data): - return { - 'from_time': get_datetime(data.planned_start_time), - 'hours': data.time_in_mins / 60.0, - 'to_time': get_datetime(data.planned_end_time), - 'project': self.project, - 'operation': data.operation, - 'operation_id': data.name, - 'workstation': data.workstation, - 'completed_qty': flt(self.qty) - flt(data.completed_qty) - } - - def set_start_end_time_for_workstation(self, data, index): - """Set start and end time for given operation. If first operation, set start as - `planned_start_date`, else add time diff to end time of earlier operation.""" - - if index == 0: - data.planned_start_time = self.planned_start_date - else: - data.planned_start_time = get_datetime(self.operations[index-1].planned_end_time)\ - + get_mins_between_operations() - - data.planned_end_time = get_datetime(data.planned_start_time) + relativedelta(minutes = data.time_in_mins) - - if data.planned_start_time == data.planned_end_time: - frappe.throw(_("Capacity Planning Error")) - - def get_start_end_time(self, timesheet, operation_id): - for data in timesheet.time_logs: - if data.operation_id == operation_id: - return data.from_time, data.to_time - - def check_operation_fits_in_working_hours(self, d): - """Raises expection if operation is longer than working hours in the given workstation.""" - from erpnext.manufacturing.doctype.workstation.workstation import check_if_within_operating_hours - check_if_within_operating_hours(d.workstation, d.operation, d.planned_start_time, d.planned_end_time) - - def update_operation_status(self): - for d in self.get("operations"): - if not d.completed_qty: - d.status = "Pending" - elif flt(d.completed_qty) < flt(self.qty): - d.status = "Work in Progress" - elif flt(d.completed_qty) == flt(self.qty): - d.status = "Completed" - else: - frappe.throw(_("Completed Qty can not be greater than 'Qty to Manufacture'")) - - def set_actual_dates(self): - self.actual_start_date = None - self.actual_end_date = None - if self.get("operations"): - actual_start_dates = [d.actual_start_time for d in self.get("operations") if d.actual_start_time] - if actual_start_dates: - self.actual_start_date = min(actual_start_dates) - - actual_end_dates = [d.actual_end_time for d in self.get("operations") if d.actual_end_time] - if actual_end_dates: - self.actual_end_date = max(actual_end_dates) - - def delete_timesheet(self): - for timesheet in frappe.get_all("Timesheet", ["name"], {"production_order": self.name}): - frappe.delete_doc("Timesheet", timesheet.name) - - def validate_production_item(self): - if frappe.db.get_value("Item", self.production_item, "has_variants"): - frappe.throw(_("Production Order cannot be raised against a Item Template"), ItemHasVariantError) - - if self.production_item: - validate_end_of_life(self.production_item) - - def validate_qty(self): - if not self.qty > 0: - frappe.throw(_("Quantity to Manufacture must be greater than 0.")) - - def validate_operation_time(self): - for d in self.operations: - if not d.time_in_mins > 0: - frappe.throw(_("Operation Time must be greater than 0 for Operation {0}".format(d.operation))) - - def update_required_items(self): - ''' - update bin reserved_qty_for_production - called from Stock Entry for production, after submit, cancel - ''' - if self.docstatus==1: - # calculate transferred qty based on submitted stock entries - self.update_transaferred_qty_for_required_items() - - # update in bin - self.update_reserved_qty_for_production() - - def update_reserved_qty_for_production(self, items=None): - '''update reserved_qty_for_production in bins''' - for d in self.required_items: - if d.source_warehouse: - stock_bin = get_bin(d.item_code, d.source_warehouse) - stock_bin.update_reserved_qty_for_production() - - def get_items_and_operations_from_bom(self): - self.set_required_items() - self.set_production_order_operations() - - return check_if_scrap_warehouse_mandatory(self.bom_no) - - def set_available_qty(self): - for d in self.get("required_items"): - if d.source_warehouse: - d.available_qty_at_source_warehouse = get_latest_stock_qty(d.item_code, d.source_warehouse) - - if self.wip_warehouse: - d.available_qty_at_wip_warehouse = get_latest_stock_qty(d.item_code, self.wip_warehouse) - - def set_required_items(self, reset_only_qty=False): - '''set required_items for production to keep track of reserved qty''' - if not reset_only_qty: - self.required_items = [] - - if self.bom_no and self.qty: - item_dict = get_bom_items_as_dict(self.bom_no, self.company, qty=self.qty, - fetch_exploded = self.use_multi_level_bom) - - if reset_only_qty: - for d in self.get("required_items"): - if item_dict.get(d.item_code): - d.required_qty = item_dict.get(d.item_code).get("qty") - else: - for item in sorted(item_dict.values(), key=lambda d: d['idx']): - self.append('required_items', { - 'item_code': item.item_code, - 'item_name': item.item_name, - 'description': item.description, - 'required_qty': item.qty, - 'source_warehouse': item.source_warehouse or item.default_warehouse - }) - - self.set_available_qty() - - def update_transaferred_qty_for_required_items(self): - '''update transferred qty from submitted stock entries for that item against - the production order''' - - for d in self.required_items: - transferred_qty = frappe.db.sql('''select sum(qty) - from `tabStock Entry` entry, `tabStock Entry Detail` detail - where - entry.production_order = %s - and entry.purpose = "Material Transfer for Manufacture" - and entry.docstatus = 1 - and detail.parent = entry.name - and detail.item_code = %s''', (self.name, d.item_code))[0][0] - - d.db_set('transferred_qty', flt(transferred_qty), update_modified = False) - - -@frappe.whitelist() -def get_item_details(item, project = None): - res = frappe.db.sql(""" - select stock_uom, description - from `tabItem` - where disabled=0 - and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %s) - and name=%s - """, (nowdate(), item), as_dict=1) - - if not res: - return {} - - res = res[0] - - filters = {"item": item, "is_default": 1} - - if project: - filters = {"item": item, "project": project} - - res["bom_no"] = frappe.db.get_value("BOM", filters = filters) - - if not res["bom_no"]: - variant_of= frappe.db.get_value("Item", item, "variant_of") - - if variant_of: - res["bom_no"] = frappe.db.get_value("BOM", filters={"item": variant_of, "is_default": 1}) - - if not res["bom_no"]: - if project: - res = get_item_details(item) - frappe.msgprint(_("Default BOM not found for Item {0} and Project {1}").format(item, project), alert=1) - else: - frappe.throw(_("Default BOM for {0} not found").format(item)) - - res['project'] = project or frappe.db.get_value('BOM', res['bom_no'], 'project') - res.update(check_if_scrap_warehouse_mandatory(res["bom_no"])) - - return res - -@frappe.whitelist() -def check_if_scrap_warehouse_mandatory(bom_no): - res = {"set_scrap_wh_mandatory": False } - if bom_no: - bom = frappe.get_doc("BOM", bom_no) - - if len(bom.scrap_items) > 0: - res["set_scrap_wh_mandatory"] = True - - return res - -@frappe.whitelist() -def set_production_order_ops(name): - po = frappe.get_doc('Production Order', name) - po.set_production_order_operations() - po.save() - -@frappe.whitelist() -def make_stock_entry(production_order_id, purpose, qty=None): - production_order = frappe.get_doc("Production Order", production_order_id) - if not frappe.db.get_value("Warehouse", production_order.wip_warehouse, "is_group") \ - and not production_order.skip_transfer: - wip_warehouse = production_order.wip_warehouse - else: - wip_warehouse = None - - stock_entry = frappe.new_doc("Stock Entry") - stock_entry.purpose = purpose - stock_entry.production_order = production_order_id - stock_entry.company = production_order.company - stock_entry.from_bom = 1 - stock_entry.bom_no = production_order.bom_no - stock_entry.use_multi_level_bom = production_order.use_multi_level_bom - stock_entry.fg_completed_qty = qty or (flt(production_order.qty) - flt(production_order.produced_qty)) - stock_entry.set_stock_entry_type() - - if purpose=="Material Transfer for Manufacture": - stock_entry.to_warehouse = wip_warehouse - stock_entry.project = production_order.project - else: - stock_entry.from_warehouse = wip_warehouse - stock_entry.to_warehouse = production_order.fg_warehouse - additional_costs = get_additional_costs(production_order, fg_qty=stock_entry.fg_completed_qty) - stock_entry.project = production_order.project - stock_entry.set("additional_costs", additional_costs) - - stock_entry.get_items() - return stock_entry.as_dict() - -@frappe.whitelist() -def make_timesheet(production_order, company): - timesheet = frappe.new_doc("Timesheet") - timesheet.employee = "" - timesheet.production_order = production_order - timesheet.company = company - return timesheet - -@frappe.whitelist() -def add_timesheet_detail(timesheet, args): - if isinstance(timesheet, text_type): - timesheet = frappe.get_doc('Timesheet', timesheet) - - if isinstance(args, text_type): - args = json.loads(args) - - timesheet.append('time_logs', args) - return timesheet - -@frappe.whitelist() -def get_default_warehouse(): - wip_warehouse = frappe.db.get_single_value("Manufacturing Settings", - "default_wip_warehouse") - fg_warehouse = frappe.db.get_single_value("Manufacturing Settings", - "default_fg_warehouse") - return {"wip_warehouse": wip_warehouse, "fg_warehouse": fg_warehouse} - -@frappe.whitelist() -def make_new_timesheet(source_name, target_doc=None): - po = frappe.get_doc('Production Order', source_name) - ts = po.make_time_logs(open_new=True) - - if not ts or not ts.get('time_logs'): - frappe.throw(_("Already completed")) - - return ts - -@frappe.whitelist() -def stop_unstop(production_order, status): - """ Called from client side on Stop/Unstop event""" - - if not frappe.has_permission("Production Order", "write"): - frappe.throw(_("Not permitted"), frappe.PermissionError) - - pro_order = frappe.get_doc("Production Order", production_order) - pro_order.update_status(status) - pro_order.update_planned_qty() - frappe.msgprint(_("Production Order has been {0}").format(status)) - pro_order.notify_update() - - return pro_order.status - -@frappe.whitelist() -def query_sales_order(production_item): - out = frappe.db.sql_list(""" - select distinct so.name from `tabSales Order` so, `tabSales Order Item` so_item - where so_item.parent=so.name and so_item.item_code=%s and so.docstatus=1 - union - select distinct so.name from `tabSales Order` so, `tabPacked Item` pi_item - where pi_item.parent=so.name and pi_item.item_code=%s and so.docstatus=1 - """, (production_item, production_item)) - - return out diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 8334f6b869a..b51420ffdbe 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -99,7 +99,7 @@ class ProductionPlan(Document): self.get_mr_items() def get_so_items(self): - so_list = [d.sales_order for d in self.sales_orders if d.sales_order] + so_list = [d.sales_order for d in self.get("sales_orders", []) if d.sales_order] if not so_list: msgprint(_("Please enter Sales Orders in the above table")) return [] @@ -134,7 +134,7 @@ class ProductionPlan(Document): self.calculate_total_planned_qty() def get_mr_items(self): - mr_list = [d.material_request for d in self.material_requests if d.material_request] + mr_list = [d.material_request for d in self.get("material_requests", []) if d.material_request] if not mr_list: msgprint(_("Please enter Material Requests in the above table")) return [] @@ -301,7 +301,6 @@ class ProductionPlan(Document): wo_list.extend(work_orders) frappe.flags.mute_messages = False - if wo_list: wo_list = ["""%s""" % \ (p, p) for p in wo_list] @@ -309,18 +308,20 @@ class ProductionPlan(Document): else : msgprint(_("No Work Orders created")) + def make_work_order_for_sub_assembly_items(self, item): work_orders = [] bom_data = {} - get_sub_assembly_items(item.get("bom_no"), bom_data) + get_sub_assembly_items(item.get("bom_no"), bom_data, item.get("qty")) for key, data in bom_data.items(): data.update({ - 'qty': data.get("stock_qty") * item.get("qty"), + 'qty': data.get("stock_qty"), 'production_plan': self.name, 'company': self.company, - 'fg_warehouse': item.get("fg_warehouse") + 'fg_warehouse': item.get("fg_warehouse"), + 'update_consumed_material_cost_in_project': 0 }) work_order = self.create_work_order(data) @@ -707,7 +708,7 @@ def get_item_data(item_code): "description": item_details.get("description") } -def get_sub_assembly_items(bom_no, bom_data): +def get_sub_assembly_items(bom_no, bom_data, qty): data = get_children('BOM', parent = bom_no) for d in data: if d.expandable: @@ -724,6 +725,6 @@ def get_sub_assembly_items(bom_no, bom_data): }) bom_item = bom_data.get(key) - bom_item["stock_qty"] += d.stock_qty + bom_item["stock_qty"] += ((d.stock_qty * qty) / d.parent_bom_qty) - get_sub_assembly_items(bom_item.get("bom_no"), bom_data) + get_sub_assembly_items(bom_item.get("bom_no"), bom_data, bom_item["stock_qty"]) diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 22613cc8a47..ce7b4f9425b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -4,16 +4,17 @@ frappe.ui.form.on("Work Order", { setup: function(frm) { frm.custom_make_buttons = { - 'Stock Entry': 'Make Stock Entry', - } + 'Stock Entry': 'Start', + 'Pick List': 'Create Pick List', + }; // Set query for warehouses - frm.set_query("wip_warehouse", function(doc) { + frm.set_query("wip_warehouse", function() { return { filters: { 'company': frm.doc.company, } - } + }; }); frm.set_query("source_warehouse", function() { @@ -21,7 +22,7 @@ frappe.ui.form.on("Work Order", { filters: { 'company': frm.doc.company, } - } + }; }); frm.set_query("source_warehouse", "required_items", function() { @@ -29,7 +30,7 @@ frappe.ui.form.on("Work Order", { filters: { 'company': frm.doc.company, } - } + }; }); frm.set_query("sales_order", function() { @@ -37,7 +38,7 @@ frappe.ui.form.on("Work Order", { filters: { "status": ["not in", ["Closed", "On Hold"]] } - } + }; }); frm.set_query("fg_warehouse", function() { @@ -46,7 +47,7 @@ frappe.ui.form.on("Work Order", { 'company': frm.doc.company, 'is_group': 0 } - } + }; }); frm.set_query("scrap_warehouse", function() { @@ -55,17 +56,19 @@ frappe.ui.form.on("Work Order", { 'company': frm.doc.company, 'is_group': 0 } - } + }; }); // Set query for BOM frm.set_query("bom_no", function() { if (frm.doc.production_item) { - return{ + return { query: "erpnext.controllers.queries.bom", filters: {item: cstr(frm.doc.production_item)} - } - } else msgprint(__("Please enter Production Item first")); + }; + } else { + frappe.msgprint(__("Please enter Production Item first")); + } }); // Set query for FG Item @@ -76,7 +79,7 @@ frappe.ui.form.on("Work Order", { ['is_stock_item', '=',1], ['default_bom', '!=', ''] ] - } + }; }); // Set query for FG Item @@ -85,12 +88,12 @@ frappe.ui.form.on("Work Order", { filters:[ ['Project', 'status', 'not in', 'Completed, Cancelled'] ] - } + }; }); // formatter for work order operation frm.set_indicator_formatter('operation', - function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange" }); + function(doc) { return (frm.doc.qty==doc.completed_qty) ? "green" : "orange"; }); }, onload: function(frm) { @@ -133,7 +136,7 @@ frappe.ui.form.on("Work Order", { if(not_completed && not_completed.length) { frm.add_custom_button(__('Create Job Card'), () => { - frm.trigger("make_job_card") + frm.trigger("make_job_card"); }).addClass('btn-primary'); } } @@ -151,7 +154,7 @@ frappe.ui.form.on("Work Order", { condition: (d) => { if (d.allow_alternative_item) {return true;} } - }) + }); }); } } @@ -285,13 +288,13 @@ frappe.ui.form.on("Work Order", { if(!frm.doc.skip_transfer){ var pending_complete = frm.doc.material_transferred_for_manufacturing - frm.doc.produced_qty; if(pending_complete) { - var title = __('{0} items in progress', [pending_complete]); var width = ((pending_complete / frm.doc.qty * 100) - added_min); + title = __('{0} items in progress', [pending_complete]); bars.push({ 'title': title, 'width': (width > 100 ? "99.5" : width) + '%', 'progress_class': 'progress-bar-warning' - }) + }); message = message + '. ' + title; } } @@ -377,7 +380,7 @@ frappe.ui.form.on("Work Order", { filters: [ ["Sales Order","name", "in", r.message] ] - } + }; }); } }); @@ -401,10 +404,10 @@ frappe.ui.form.on("Work Order Item", { frappe.model.set_value(row.doctype, row.name, "available_qty_at_source_warehouse", r.message); } - }) + }); } } -}) +}); frappe.ui.form.on("Work Order Operation", { workstation: function(frm, cdt, cdn) { @@ -421,7 +424,7 @@ frappe.ui.form.on("Work Order Operation", { erpnext.work_order.calculate_cost(frm.doc); erpnext.work_order.calculate_total_cost(frm); } - }) + }); } }, time_in_mins: function(frm, cdt, cdn) { @@ -447,10 +450,13 @@ erpnext.work_order = { const show_start_btn = (frm.doc.skip_transfer || frm.doc.transfer_material_against == 'Job Card') ? 0 : 1; - if (show_start_btn){ + if (show_start_btn) { if ((flt(doc.material_transferred_for_manufacturing) < flt(doc.qty)) && frm.doc.status != 'Stopped') { frm.has_start_btn = true; + frm.add_custom_button(__('Create Pick List'), function() { + erpnext.work_order.create_pick_list(frm); + }); var start_btn = frm.add_custom_button(__('Start'), function() { erpnext.work_order.make_se(frm, 'Material Transfer for Manufacture'); }); @@ -519,8 +525,8 @@ erpnext.work_order = { calculate_total_cost: function(frm) { var variable_cost = frm.doc.actual_operating_cost ? - flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost) - frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)) + flt(frm.doc.actual_operating_cost) : flt(frm.doc.planned_operating_cost); + frm.set_value("total_operating_cost", (flt(frm.doc.additional_operating_cost) + variable_cost)); }, set_default_warehouse: function(frm) { @@ -528,45 +534,72 @@ erpnext.work_order = { frappe.call({ method: "erpnext.manufacturing.doctype.work_order.work_order.get_default_warehouse", callback: function(r) { - if(!r.exe) { + if (!r.exe) { frm.set_value("wip_warehouse", r.message.wip_warehouse); - frm.set_value("fg_warehouse", r.message.fg_warehouse) + frm.set_value("fg_warehouse", r.message.fg_warehouse); } } }); } }, - make_se: function(frm, purpose) { - if(!frm.doc.skip_transfer){ - var max = (purpose === "Manufacture") ? - flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty) : - flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing); + get_max_transferable_qty: (frm, purpose) => { + let max = 0; + if (frm.doc.skip_transfer) return max; + if (purpose === 'Manufacture') { + max = flt(frm.doc.material_transferred_for_manufacturing) - flt(frm.doc.produced_qty); } else { - var max = flt(frm.doc.qty) - flt(frm.doc.produced_qty); + max = flt(frm.doc.qty) - flt(frm.doc.material_transferred_for_manufacturing); } + return flt(max, precision('qty')); + }, - max = flt(max, precision("qty")); - frappe.prompt({fieldtype:"Float", label: __("Qty for {0}", [purpose]), fieldname:"qty", - description: __("Max: {0}", [max]), 'default': max }, function(data) - { - if(data.qty > max) { - frappe.msgprint(__("Quantity must not be more than {0}", [max])); - return; - } - frappe.call({ - method:"erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry", - args: { - "work_order_id": frm.doc.name, - "purpose": purpose, - "qty": data.qty - }, - callback: function(r) { - var doclist = frappe.model.sync(r.message); - frappe.set_route("Form", doclist[0].doctype, doclist[0].name); + show_prompt_for_qty_input: function(frm, purpose) { + let max = this.get_max_transferable_qty(frm, purpose); + return new Promise((resolve, reject) => { + frappe.prompt({ + fieldtype: 'Float', + label: __('Qty for {0}', [purpose]), + fieldname: 'qty', + description: __('Max: {0}', [max]), + default: max + }, data => { + if (data.qty > max) { + frappe.msgprint(__('Quantity must not be more than {0}', [max])); + reject(); } + data.purpose = purpose; + resolve(data); + }, __('Select Quantity'), __('Create')); + }); + }, + + make_se: function(frm, purpose) { + this.show_prompt_for_qty_input(frm, purpose) + .then(data => { + return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.make_stock_entry', { + 'work_order_id': frm.doc.name, + 'purpose': purpose, + 'qty': data.qty + }); + }).then(stock_entry => { + frappe.model.sync(stock_entry); + frappe.set_route('Form', stock_entry.doctype, stock_entry.name); + }); + + }, + + create_pick_list: function(frm, purpose='Material Transfer for Manufacture') { + this.show_prompt_for_qty_input(frm, purpose) + .then(data => { + return frappe.xcall('erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', { + 'source_name': frm.doc.name, + 'for_qty': data.qty + }); + }).then(pick_list => { + frappe.model.sync(pick_list); + frappe.set_route('Form', pick_list.doctype, pick_list.name); }); - }, __("Select Quantity"), __('Create')); }, make_consumption_se: function(frm, backflush_raw_materials_based_on) { @@ -606,6 +639,6 @@ erpnext.work_order = { frm.reload_doc(); } } - }) + }); } -} +}; diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json index 1534b5929dc..0d073a2312b 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.json +++ b/erpnext/manufacturing/doctype/work_order/work_order.json @@ -1,484 +1,505 @@ { - "allow_import": 1, - "autoname": "naming_series:", - "creation": "2013-01-10 16:34:16", - "doctype": "DocType", - "document_type": "Setup", - "field_order": [ - "item", - "naming_series", - "status", - "production_item", - "item_name", - "image", - "bom_no", - "allow_alternative_item", - "use_multi_level_bom", - "skip_transfer", - "column_break1", - "company", - "qty", - "material_transferred_for_manufacturing", - "produced_qty", - "sales_order", - "project", - "from_wip_warehouse", - "warehouses", - "wip_warehouse", - "fg_warehouse", - "column_break_12", - "scrap_warehouse", - "required_items_section", - "required_items", - "time", - "planned_start_date", - "actual_start_date", - "column_break_13", - "planned_end_date", - "actual_end_date", - "expected_delivery_date", - "operations_section", - "transfer_material_against", - "operations", - "section_break_22", - "planned_operating_cost", - "actual_operating_cost", - "additional_operating_cost", - "column_break_24", - "total_operating_cost", - "more_info", - "description", - "stock_uom", - "column_break2", - "material_request", - "material_request_item", - "sales_order_item", - "production_plan", - "production_plan_item", - "product_bundle_item", - "amended_from" - ], - "fields": [ - { - "fieldname": "item", - "fieldtype": "Section Break", - "options": "fa fa-gift" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "options": "MFG-WO-.YYYY.-", - "print_hide": 1, - "reqd": 1, - "set_only_once": 1 - }, - { - "default": "Draft", - "depends_on": "eval:!doc.__islocal", - "fieldname": "status", - "fieldtype": "Select", - "label": "Status", - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", - "read_only": 1, - "reqd": 1, - "search_index": 1 - }, - { - "fieldname": "production_item", - "fieldtype": "Link", - "in_global_search": 1, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Item To Manufacture", - "oldfieldname": "production_item", - "oldfieldtype": "Link", - "options": "Item", - "reqd": 1 - }, - { - "depends_on": "eval:doc.production_item", - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name", - "read_only": 1 - }, - { - "fetch_from": "production_item.image", - "fieldname": "image", - "fieldtype": "Attach Image", - "hidden": 1, - "label": "Image", - "options": "image", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "bom_no", - "fieldtype": "Link", - "label": "BOM No", - "oldfieldname": "bom_no", - "oldfieldtype": "Link", - "options": "BOM", - "reqd": 1 - }, - { - "default": "0", - "fieldname": "allow_alternative_item", - "fieldtype": "Check", - "label": "Allow Alternative Item" - }, - { - "default": "1", - "description": "Plan material for sub-assemblies", - "fieldname": "use_multi_level_bom", - "fieldtype": "Check", - "label": "Use Multi-Level BOM", - "print_hide": 1 - }, - { - "default": "0", - "description": "Check if material transfer entry is not required", - "fieldname": "skip_transfer", - "fieldtype": "Check", - "label": "Skip Material Transfer to WIP Warehouse" - }, - { - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "width": "50%" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "remember_last_selected_value": 1, - "reqd": 1 - }, - { - "fieldname": "qty", - "fieldtype": "Float", - "label": "Qty To Manufacture", - "oldfieldname": "qty", - "oldfieldtype": "Currency", - "reqd": 1 - }, - { - "default": "0", - "depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0", - "fieldname": "material_transferred_for_manufacturing", - "fieldtype": "Float", - "label": "Material Transferred for Manufacturing", - "no_copy": 1, - "read_only": 1 - }, - { - "default": "0", - "depends_on": "eval:doc.docstatus==1", - "fieldname": "produced_qty", - "fieldtype": "Float", - "label": "Manufactured Qty", - "no_copy": 1, - "oldfieldname": "produced_qty", - "oldfieldtype": "Currency", - "read_only": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "sales_order", - "fieldtype": "Link", - "in_global_search": 1, - "label": "Sales Order", - "options": "Sales Order" - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project" - }, - { - "default": "0", - "depends_on": "skip_transfer", - "fieldname": "from_wip_warehouse", - "fieldtype": "Check", - "label": "Backflush Raw Materials From Work-in-Progress Warehouse" - }, - { - "fieldname": "warehouses", - "fieldtype": "Section Break", - "label": "Warehouses", - "options": "fa fa-building" - }, - { - "fieldname": "wip_warehouse", - "fieldtype": "Link", - "label": "Work-in-Progress Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "fg_warehouse", - "fieldtype": "Link", - "label": "Target Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "column_break_12", - "fieldtype": "Column Break" - }, - { - "fieldname": "scrap_warehouse", - "fieldtype": "Link", - "label": "Scrap Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "required_items_section", - "fieldtype": "Section Break", - "label": "Required Items" - }, - { - "fieldname": "required_items", - "fieldtype": "Table", - "label": "Required Items", - "no_copy": 1, - "options": "Work Order Item", - "print_hide": 1 - }, - { - "fieldname": "time", - "fieldtype": "Section Break", - "label": "Time", - "options": "fa fa-time" - }, - { - "allow_on_submit": 1, - "default": "now", - "fieldname": "planned_start_date", - "fieldtype": "Datetime", - "label": "Planned Start Date", - "reqd": 1 - }, - { - "fieldname": "actual_start_date", - "fieldtype": "Datetime", - "label": "Actual Start Date", - "read_only": 1 - }, - { - "fieldname": "column_break_13", - "fieldtype": "Column Break" - }, - { - "fieldname": "planned_end_date", - "fieldtype": "Datetime", - "label": "Planned End Date", - "no_copy": 1, - "read_only": 1 - }, - { - "fieldname": "actual_end_date", - "fieldtype": "Datetime", - "label": "Actual End Date", - "read_only": 1 - }, - { - "allow_on_submit": 1, - "fieldname": "expected_delivery_date", - "fieldtype": "Date", - "label": "Expected Delivery Date" - }, - { - "fieldname": "operations_section", - "fieldtype": "Section Break", - "label": "Operations", - "options": "fa fa-wrench" - }, - { - "default": "Work Order", - "depends_on": "operations", - "fieldname": "transfer_material_against", - "fieldtype": "Select", - "label": "Transfer Material Against", - "options": "\nWork Order\nJob Card" - }, - { - "fieldname": "operations", - "fieldtype": "Table", - "label": "Operations", - "options": "Work Order Operation", - "read_only": 1 - }, - { - "depends_on": "operations", - "fieldname": "section_break_22", - "fieldtype": "Section Break", - "label": "Operation Cost" - }, - { - "fieldname": "planned_operating_cost", - "fieldtype": "Currency", - "label": "Planned Operating Cost", - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "actual_operating_cost", - "fieldtype": "Currency", - "label": "Actual Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "fieldname": "additional_operating_cost", - "fieldtype": "Currency", - "label": "Additional Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency" - }, - { - "fieldname": "column_break_24", - "fieldtype": "Column Break" - }, - { - "fieldname": "total_operating_cost", - "fieldtype": "Currency", - "label": "Total Operating Cost", - "no_copy": 1, - "options": "Company:company:default_currency", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "more_info", - "fieldtype": "Section Break", - "label": "More Information", - "options": "fa fa-file-text" - }, - { - "fieldname": "description", - "fieldtype": "Small Text", - "label": "Item Description", - "read_only": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "oldfieldname": "stock_uom", - "oldfieldtype": "Data", - "options": "UOM", - "read_only": 1 - }, - { - "fieldname": "column_break2", - "fieldtype": "Column Break", - "width": "50%" - }, - { - "description": "Manufacture against Material Request", - "fieldname": "material_request", - "fieldtype": "Link", - "label": "Material Request", - "options": "Material Request" - }, - { - "fieldname": "material_request_item", - "fieldtype": "Data", - "hidden": 1, - "label": "Material Request Item", - "read_only": 1 - }, - { - "fieldname": "sales_order_item", - "fieldtype": "Data", - "hidden": 1, - "label": "Sales Order Item", - "read_only": 1 - }, - { - "fieldname": "production_plan", - "fieldtype": "Link", - "label": "Production Plan", - "no_copy": 1, - "options": "Production Plan", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "production_plan_item", - "fieldtype": "Data", - "label": "Production Plan Item", - "no_copy": 1, - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "product_bundle_item", - "fieldtype": "Link", - "label": "Product Bundle Item", - "no_copy": 1, - "options": "Item", - "print_hide": 1, - "read_only": 1 - }, - { - "fieldname": "amended_from", - "fieldtype": "Link", - "ignore_user_permissions": 1, - "label": "Amended From", - "no_copy": 1, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Work Order", - "read_only": 1 - } - ], - "icon": "fa fa-cogs", - "idx": 1, - "image_field": "image", - "is_submittable": 1, - "modified": "2019-05-27 09:36:16.707719", - "modified_by": "Administrator", - "module": "Manufacturing", - "name": "Work Order", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "import": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Manufacturing User", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "read": 1, - "report": 1, - "role": "Stock User" - } - ], - "sort_order": "ASC", - "title_field": "production_item", - "track_changes": 1, - "track_seen": 1 - } \ No newline at end of file + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-01-10 16:34:16", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "item", + "naming_series", + "status", + "production_item", + "item_name", + "image", + "bom_no", + "column_break1", + "company", + "qty", + "material_transferred_for_manufacturing", + "produced_qty", + "sales_order", + "project", + "settings_section", + "allow_alternative_item", + "use_multi_level_bom", + "column_break_18", + "skip_transfer", + "from_wip_warehouse", + "update_consumed_material_cost_in_project", + "warehouses", + "wip_warehouse", + "fg_warehouse", + "column_break_12", + "scrap_warehouse", + "required_items_section", + "required_items", + "time", + "planned_start_date", + "actual_start_date", + "column_break_13", + "planned_end_date", + "actual_end_date", + "expected_delivery_date", + "operations_section", + "transfer_material_against", + "operations", + "section_break_22", + "planned_operating_cost", + "actual_operating_cost", + "additional_operating_cost", + "column_break_24", + "total_operating_cost", + "more_info", + "description", + "stock_uom", + "column_break2", + "material_request", + "material_request_item", + "sales_order_item", + "production_plan", + "production_plan_item", + "product_bundle_item", + "amended_from" + ], + "fields": [ + { + "fieldname": "item", + "fieldtype": "Section Break", + "options": "fa fa-gift" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "MFG-WO-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "default": "Draft", + "depends_on": "eval:!doc.__islocal", + "fieldname": "status", + "fieldtype": "Select", + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nSubmitted\nNot Started\nIn Process\nCompleted\nStopped\nCancelled", + "read_only": 1, + "reqd": 1, + "search_index": 1 + }, + { + "fieldname": "production_item", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Item To Manufacture", + "oldfieldname": "production_item", + "oldfieldtype": "Link", + "options": "Item", + "reqd": 1 + }, + { + "depends_on": "eval:doc.production_item", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fetch_from": "production_item.image", + "fieldname": "image", + "fieldtype": "Attach Image", + "hidden": 1, + "label": "Image", + "options": "image", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "bom_no", + "fieldtype": "Link", + "label": "BOM No", + "oldfieldname": "bom_no", + "oldfieldtype": "Link", + "options": "BOM", + "reqd": 1 + }, + { + "default": "0", + "fieldname": "allow_alternative_item", + "fieldtype": "Check", + "label": "Allow Alternative Item" + }, + { + "default": "1", + "description": "Plan material for sub-assemblies", + "fieldname": "use_multi_level_bom", + "fieldtype": "Check", + "label": "Use Multi-Level BOM", + "print_hide": 1 + }, + { + "default": "0", + "description": "Check if material transfer entry is not required", + "fieldname": "skip_transfer", + "fieldtype": "Check", + "label": "Skip Material Transfer to WIP Warehouse" + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "remember_last_selected_value": 1, + "reqd": 1 + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "label": "Qty To Manufacture", + "oldfieldname": "qty", + "oldfieldtype": "Currency", + "reqd": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.docstatus==1 && doc.skip_transfer==0", + "fieldname": "material_transferred_for_manufacturing", + "fieldtype": "Float", + "label": "Material Transferred for Manufacturing", + "no_copy": 1, + "read_only": 1 + }, + { + "default": "0", + "depends_on": "eval:doc.docstatus==1", + "fieldname": "produced_qty", + "fieldtype": "Float", + "label": "Manufactured Qty", + "no_copy": 1, + "oldfieldname": "produced_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "sales_order", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Sales Order", + "options": "Sales Order" + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project" + }, + { + "default": "0", + "depends_on": "skip_transfer", + "fieldname": "from_wip_warehouse", + "fieldtype": "Check", + "label": "Backflush Raw Materials From Work-in-Progress Warehouse" + }, + { + "fieldname": "warehouses", + "fieldtype": "Section Break", + "label": "Warehouses", + "options": "fa fa-building" + }, + { + "fieldname": "wip_warehouse", + "fieldtype": "Link", + "label": "Work-in-Progress Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "fg_warehouse", + "fieldtype": "Link", + "label": "Target Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "column_break_12", + "fieldtype": "Column Break" + }, + { + "fieldname": "scrap_warehouse", + "fieldtype": "Link", + "label": "Scrap Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "required_items_section", + "fieldtype": "Section Break", + "label": "Required Items" + }, + { + "fieldname": "required_items", + "fieldtype": "Table", + "label": "Required Items", + "no_copy": 1, + "options": "Work Order Item", + "print_hide": 1 + }, + { + "fieldname": "time", + "fieldtype": "Section Break", + "label": "Time", + "options": "fa fa-time" + }, + { + "allow_on_submit": 1, + "default": "now", + "fieldname": "planned_start_date", + "fieldtype": "Datetime", + "label": "Planned Start Date", + "reqd": 1 + }, + { + "fieldname": "actual_start_date", + "fieldtype": "Datetime", + "label": "Actual Start Date", + "read_only": 1 + }, + { + "fieldname": "column_break_13", + "fieldtype": "Column Break" + }, + { + "fieldname": "planned_end_date", + "fieldtype": "Datetime", + "label": "Planned End Date", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "actual_end_date", + "fieldtype": "Datetime", + "label": "Actual End Date", + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "expected_delivery_date", + "fieldtype": "Date", + "label": "Expected Delivery Date" + }, + { + "fieldname": "operations_section", + "fieldtype": "Section Break", + "label": "Operations", + "options": "fa fa-wrench" + }, + { + "default": "Work Order", + "depends_on": "operations", + "fieldname": "transfer_material_against", + "fieldtype": "Select", + "label": "Transfer Material Against", + "options": "\nWork Order\nJob Card" + }, + { + "fieldname": "operations", + "fieldtype": "Table", + "label": "Operations", + "options": "Work Order Operation", + "read_only": 1 + }, + { + "depends_on": "operations", + "fieldname": "section_break_22", + "fieldtype": "Section Break", + "label": "Operation Cost" + }, + { + "fieldname": "planned_operating_cost", + "fieldtype": "Currency", + "label": "Planned Operating Cost", + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "actual_operating_cost", + "fieldtype": "Currency", + "label": "Actual Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "fieldname": "additional_operating_cost", + "fieldtype": "Currency", + "label": "Additional Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency" + }, + { + "fieldname": "column_break_24", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_operating_cost", + "fieldtype": "Currency", + "label": "Total Operating Cost", + "no_copy": 1, + "options": "Company:company:default_currency", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "options": "fa fa-file-text" + }, + { + "fieldname": "description", + "fieldtype": "Small Text", + "label": "Item Description", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "oldfieldname": "stock_uom", + "oldfieldtype": "Data", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "description": "Manufacture against Material Request", + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "options": "Material Request" + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Material Request Item", + "read_only": 1 + }, + { + "fieldname": "sales_order_item", + "fieldtype": "Data", + "hidden": 1, + "label": "Sales Order Item", + "read_only": 1 + }, + { + "fieldname": "production_plan", + "fieldtype": "Link", + "label": "Production Plan", + "no_copy": 1, + "options": "Production Plan", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "production_plan_item", + "fieldtype": "Data", + "label": "Production Plan Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "product_bundle_item", + "fieldtype": "Link", + "label": "Product Bundle Item", + "no_copy": 1, + "options": "Item", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Work Order", + "read_only": 1 + }, + { + "fieldname": "settings_section", + "fieldtype": "Section Break", + "label": "Settings" + }, + { + "fieldname": "column_break_18", + "fieldtype": "Column Break" + }, + { + "default": "1", + "fieldname": "update_consumed_material_cost_in_project", + "fieldtype": "Check", + "label": "Update Consumed Material Cost In Project" + } + ], + "icon": "fa fa-cogs", + "idx": 1, + "image_field": "image", + "is_submittable": 1, + "modified": "2019-08-28 12:29:35.315239", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Work Order", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "read": 1, + "report": 1, + "role": "Stock User" + } + ], + "sort_field": "modified", + "sort_order": "ASC", + "title_field": "production_item", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py index 2b70325d9f1..a636b871e2e 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.py +++ b/erpnext/manufacturing/doctype/work_order/work_order.py @@ -19,6 +19,7 @@ from erpnext.stock.stock_balance import get_planned_qty, update_bin_qty from frappe.utils.csvutils import getlink from erpnext.stock.utils import get_bin, validate_warehouse_company, get_latest_stock_qty from erpnext.utilities.transaction_base import validate_uom_is_integer +from frappe.model.mapper import get_mapped_doc class OverProductionError(frappe.ValidationError): pass class StockOverProductionError(frappe.ValidationError): pass @@ -707,3 +708,46 @@ def get_work_order_operation_data(work_order, operation, workstation): for d in work_order.operations: if d.operation == operation and d.workstation == workstation: return d + +@frappe.whitelist() +def create_pick_list(source_name, target_doc=None, for_qty=None): + for_qty = for_qty or json.loads(target_doc).get('for_qty') + max_finished_goods_qty = frappe.db.get_value('Work Order', source_name, 'qty') + def update_item_quantity(source, target, source_parent): + pending_to_issue = flt(source.required_qty) - flt(source.transferred_qty) + desire_to_transfer = flt(source.required_qty) / max_finished_goods_qty * flt(for_qty) + + qty = 0 + if desire_to_transfer <= pending_to_issue: + qty = desire_to_transfer + elif pending_to_issue > 0: + qty = pending_to_issue + + if qty: + target.qty = qty + target.stock_qty = qty + target.uom = frappe.get_value('Item', source.item_code, 'stock_uom') + target.stock_uom = target.uom + target.conversion_factor = 1 + else: + target.delete() + + doc = get_mapped_doc('Work Order', source_name, { + 'Work Order': { + 'doctype': 'Pick List', + 'validation': { + 'docstatus': ['=', 1] + } + }, + 'Work Order Item': { + 'doctype': 'Pick List Item', + 'postprocess': update_item_quantity, + 'condition': lambda doc: abs(doc.transferred_qty) < abs(doc.required_qty) + }, + }, target_doc) + + doc.for_qty = for_qty + + doc.set_item_locations() + + return doc \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py index 3fe5282582e..0d3c30ea6eb 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py +++ b/erpnext/manufacturing/doctype/work_order/work_order_dashboard.py @@ -6,7 +6,7 @@ def get_data(): 'fieldname': 'work_order', 'transactions': [ { - 'items': ['Stock Entry', 'Job Card'] + 'items': ['Pick List', 'Stock Entry', 'Job Card'] } ] } \ No newline at end of file diff --git a/erpnext/manufacturing/page/bom_comparison_tool/__init__.py b/erpnext/manufacturing/page/bom_comparison_tool/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.js b/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.js new file mode 100644 index 00000000000..7152d3dff61 --- /dev/null +++ b/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.js @@ -0,0 +1,213 @@ +frappe.pages['bom-comparison-tool'].on_page_load = function(wrapper) { + var page = frappe.ui.make_app_page({ + parent: wrapper, + title: __('BOM Comparison Tool'), + single_column: true + }); + + new erpnext.BOMComparisonTool(page); +} + +erpnext.BOMComparisonTool = class BOMComparisonTool { + constructor(page) { + this.page = page; + this.make_form(); + } + + make_form() { + this.form = new frappe.ui.FieldGroup({ + fields: [ + { + label: __('BOM 1'), + fieldname: 'name1', + fieldtype: 'Link', + options: 'BOM', + change: () => this.fetch_and_render() + }, + { + fieldtype: 'Column Break' + }, + { + label: __('BOM 2'), + fieldname: 'name2', + fieldtype: 'Link', + options: 'BOM', + change: () => this.fetch_and_render() + }, + { + fieldtype: 'Section Break' + }, + { + fieldtype: 'HTML', + fieldname: 'preview' + } + ], + body: this.page.body + }); + this.form.make(); + } + + fetch_and_render() { + let { name1, name2 } = this.form.get_values(); + if (!(name1 && name2)) { + this.form.get_field('preview').html(''); + return; + } + + // set working state + this.form.get_field('preview').html(` +
    + ${__("Fetching...")} +
    + `); + + frappe.call('erpnext.manufacturing.doctype.bom.bom.get_bom_diff', { + bom1: name1, + bom2: name2 + }).then(r => { + let diff = r.message; + frappe.model.with_doctype('BOM', () => { + this.render('BOM', name1, name2, diff); + }); + }); + } + + render(doctype, name1, name2, diff) { + + let change_html = (title, doctype, changed) => { + let values_changed = this.get_changed_values(doctype, changed) + .map(change => { + let [fieldname, value1, value2] = change; + return ` + + ${frappe.meta.get_label(doctype, fieldname)} + ${value1} + ${value2} + + `; + }) + .join(''); + + return ` +

    ${title}

    +
    + + + + + + + ${values_changed} +
    ${__('Field')}${name1}${name2}
    +
    + `; + } + + let value_changes = change_html(__('Values Changed'), doctype, diff.changed); + + let row_changes_by_fieldname = group_items(diff.row_changed, change => change[0]); + + let table_changes = Object.keys(row_changes_by_fieldname).map(fieldname => { + let changes = row_changes_by_fieldname[fieldname]; + let df = frappe.meta.get_docfield(doctype, fieldname); + + let html = changes.map(change => { + let [fieldname,, item_code, changes] = change; + let df = frappe.meta.get_docfield(doctype, fieldname); + let child_doctype = df.options; + let values_changed = this.get_changed_values(child_doctype, changes); + + return values_changed.map((change, i) => { + let [fieldname, value1, value2] = change; + let th = i === 0 + ? `${item_code}` + : ''; + return ` + + ${th} + ${frappe.meta.get_label(child_doctype, fieldname)} + ${value1} + ${value2} + + `; + }).join(''); + }).join(''); + + return ` +

    ${__('Changes in {0}', [df.label])}

    + + + + + + + + ${html} +
    ${__('Item Code')}${__('Field')}${name1}${name2}
    + `; + }).join(''); + + let get_added_removed_html = (title, grouped_items) => { + return Object.keys(grouped_items).map(fieldname => { + let rows = grouped_items[fieldname]; + let df = frappe.meta.get_docfield(doctype, fieldname); + let fields = frappe.meta.get_docfields(df.options) + .filter(df => df.in_list_view); + + let html = rows.map(row => { + let [, doc] = row; + let cells = fields + .map(df => `${doc[df.fieldname]}`) + .join(''); + return `${cells}`; + }).join(''); + + let header = fields.map(df => `${df.label}`).join(''); + return ` +

    ${$.format(title, [df.label])}

    + + ${header} + ${html} +
    + `; + }).join(''); + }; + + let added_by_fieldname = group_items(diff.added, change => change[0]); + let removed_by_fieldname = group_items(diff.removed, change => change[0]); + + let added_html = get_added_removed_html(__('Rows Added in {0}'), added_by_fieldname); + let removed_html = get_added_removed_html(__('Rows Removed in {0}'), removed_by_fieldname); + + let html = ` + ${value_changes} + ${table_changes} + ${added_html} + ${removed_html} + `; + + this.form.get_field('preview').html(html); + } + + get_changed_values(doctype, changed) { + return changed.filter(change => { + let [fieldname, value1, value2] = change; + if (!value1) value1 = ''; + if (!value2) value2 = ''; + if (value1 === value2) return false; + let df = frappe.meta.get_docfield(doctype, fieldname); + if (!df) return false; + if (df.hidden) return false; + return true; + }); + } +}; + +function group_items(array, fn) { + return array.reduce((acc, item) => { + let key = fn(item); + acc[key] = acc[key] || []; + acc[key].push(item); + return acc; + }, {}); +} diff --git a/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.json b/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.json new file mode 100644 index 00000000000..067a1061b89 --- /dev/null +++ b/erpnext/manufacturing/page/bom_comparison_tool/bom_comparison_tool.json @@ -0,0 +1,30 @@ +{ + "content": null, + "creation": "2019-07-29 13:24:38.201981", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2019-07-29 13:24:38.201981", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "bom-comparison-tool", + "owner": "Administrator", + "page_name": "BOM Comparison Tool", + "restrict_to_domain": "Manufacturing", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Manufacturing Manager" + } + ], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0, + "title": "BOM Comparison Tool" +} \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b6ea542554e..7c1d8a034d2 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -605,7 +605,6 @@ erpnext.patches.v11_1.rename_depends_on_lwp execute:frappe.delete_doc("Report", "Inactive Items") erpnext.patches.v11_1.delete_scheduling_tool erpnext.patches.v12_0.rename_tolerance_fields -erpnext.patches.v12_0.make_custom_fields_for_bank_remittance #14-06-2019 execute:frappe.delete_doc_if_exists("Page", "support-analytics") erpnext.patches.v12_0.remove_patient_medical_record_page erpnext.patches.v11_1.move_customer_lead_to_dynamic_column @@ -626,3 +625,13 @@ erpnext.patches.v12_0.add_default_buying_selling_terms_in_company erpnext.patches.v12_0.update_ewaybill_field_position erpnext.patches.v12_0.create_accounting_dimensions_in_missing_doctypes erpnext.patches.v11_1.set_status_for_material_request_type_manufacture +erpnext.patches.v12_0.move_plaid_settings_to_doctype +execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_link') +execute:frappe.reload_doc('desk', 'doctype','dashboard') +execute:frappe.reload_doc('desk', 'doctype','dashboard_chart_source') +execute:frappe.reload_doc('desk', 'doctype','dashboard_chart') +erpnext.patches.v12_0.add_default_dashboards +erpnext.patches.v12_0.remove_bank_remittance_custom_fields +erpnext.patches.v12_0.generate_leave_ledger_entries +erpnext.patches.v12_0.move_credit_limit_to_customer_credit_limit +erpnext.patches.v12_0.add_variant_of_in_item_attribute_table diff --git a/erpnext/patches/v12_0/add_default_dashboards.py b/erpnext/patches/v12_0/add_default_dashboards.py new file mode 100644 index 00000000000..ab92fbaa696 --- /dev/null +++ b/erpnext/patches/v12_0/add_default_dashboards.py @@ -0,0 +1,8 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +import frappe +from erpnext.setup.setup_wizard.operations.install_fixtures import add_dashboards + +def execute(): + add_dashboards() diff --git a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py index bc6119067cf..893f7a4909e 100644 --- a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py +++ b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py @@ -1,8 +1,9 @@ import frappe def execute(): - frappe.db.sql(''' - UPDATE `tabItem Variant Attribute` t1 - INNER JOIN `tabItem` t2 ON t2.name = t1.parent - SET t1.variant_of = t2.variant_of - ''') + frappe.reload_doc('stock', 'doctype', 'item_variant_attribute') + frappe.db.sql(''' + UPDATE `tabItem Variant Attribute` t1 + INNER JOIN `tabItem` t2 ON t2.name = t1.parent + SET t1.variant_of = t2.variant_of + ''') diff --git a/erpnext/patches/v12_0/generate_leave_ledger_entries.py b/erpnext/patches/v12_0/generate_leave_ledger_entries.py new file mode 100644 index 00000000000..5e91449c3ed --- /dev/null +++ b/erpnext/patches/v12_0/generate_leave_ledger_entries.py @@ -0,0 +1,87 @@ +# Copyright (c) 2018, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils import getdate, today + +def execute(): + """ Generates leave ledger entries for leave allocation/application/encashment + for last allocation """ + frappe.reload_doc("HR", "doctype", "Leave Ledger Entry") + frappe.reload_doc("HR", "doctype", "Leave Encashment") + if frappe.db.a_row_exists("Leave Ledger Entry"): + return + + if not frappe.get_meta("Leave Allocation").has_field("unused_leaves"): + frappe.reload_doc("HR", "doctype", "Leave Allocation") + update_leave_allocation_fieldname() + + generate_allocation_ledger_entries() + generate_application_leave_ledger_entries() + generate_encashment_leave_ledger_entries() + generate_expiry_allocation_ledger_entries() + +def update_leave_allocation_fieldname(): + ''' maps data from old field to the new field ''' + frappe.db.sql(""" + UPDATE `tabLeave Allocation` + SET `unused_leaves` = `carry_forwarded_leaves` + """) + +def generate_allocation_ledger_entries(): + ''' fix ledger entries for missing leave allocation transaction ''' + allocation_list = get_allocation_records() + + for allocation in allocation_list: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name}): + allocation.update(dict(doctype="Leave Allocation")) + allocation_obj = frappe.get_doc(allocation) + allocation_obj.create_leave_ledger_entry() + +def generate_application_leave_ledger_entries(): + ''' fix ledger entries for missing leave application transaction ''' + leave_applications = get_leaves_application_records() + + for application in leave_applications: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Application', 'transaction_name': application.name}): + application.update(dict(doctype="Leave Application")) + frappe.get_doc(application).create_leave_ledger_entry() + +def generate_encashment_leave_ledger_entries(): + ''' fix ledger entries for missing leave encashment transaction ''' + leave_encashments = get_leave_encashment_records() + + for encashment in leave_encashments: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Encashment', 'transaction_name': encashment.name}): + encashment.update(dict(doctype="Leave Encashment")) + frappe.get_doc(encashment).create_leave_ledger_entry() + +def generate_expiry_allocation_ledger_entries(): + ''' fix ledger entries for missing leave allocation transaction ''' + from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import expire_allocation + allocation_list = get_allocation_records() + + for allocation in allocation_list: + if not frappe.db.exists("Leave Ledger Entry", {'transaction_type': 'Leave Allocation', 'transaction_name': allocation.name, 'is_expired': 1}): + allocation.update(dict(doctype="Leave Allocation")) + allocation_obj = frappe.get_doc(allocation) + if allocation_obj.to_date <= getdate(today()): + expire_allocation(allocation_obj) + +def get_allocation_records(): + return frappe.get_all("Leave Allocation", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'new_leaves_allocated', + 'unused_leaves', 'from_date', 'to_date', 'carry_forward' + ], order_by='to_date ASC') + +def get_leaves_application_records(): + return frappe.get_all("Leave Application", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'total_leave_days', 'from_date', 'to_date']) + +def get_leave_encashment_records(): + return frappe.get_all("Leave Encashment", filters={ + "docstatus": 1 + }, fields=['name', 'employee', 'leave_type', 'encashable_days', 'encashment_date']) \ No newline at end of file diff --git a/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py b/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py deleted file mode 100644 index 3d4a9952c77..00000000000 --- a/erpnext/patches/v12_0/make_custom_fields_for_bank_remittance.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import unicode_literals -import frappe -from erpnext.regional.india.setup import make_custom_fields - -def execute(): - frappe.reload_doc("accounts", "doctype", "tax_category") - frappe.reload_doc("stock", "doctype", "item_manufacturer") - company = frappe.get_all('Company', filters = {'country': 'India'}) - if not company: - return - - make_custom_fields() \ No newline at end of file diff --git a/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py new file mode 100644 index 00000000000..c9293b9b63c --- /dev/null +++ b/erpnext/patches/v12_0/move_credit_limit_to_customer_credit_limit.py @@ -0,0 +1,46 @@ +# Copyright (c) 2019, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + ''' Move credit limit and bypass credit limit to the child table of customer credit limit ''' + frappe.reload_doc("Selling", "doctype", "Customer Credit Limit") + frappe.reload_doc("Selling", "doctype", "Customer") + frappe.reload_doc("Setup", "doctype", "Customer Group") + + if frappe.db.a_row_exists("Customer Credit Limit"): + return + + move_credit_limit_to_child_table() + +def move_credit_limit_to_child_table(): + ''' maps data from old field to the new field in the child table ''' + + companies = frappe.get_all("Company", 'name') + for doctype in ("Customer", "Customer Group"): + fields = "" + if doctype == "Customer" \ + and frappe.db.has_column('Customer', 'bypass_credit_limit_check_at_sales_order'): + fields = ", bypass_credit_limit_check_at_sales_order" + + credit_limit_records = frappe.db.sql(''' + SELECT name, credit_limit {0} + FROM `tab{1}` where credit_limit > 0 + '''.format(fields, doctype), as_dict=1) #nosec + + for record in credit_limit_records: + doc = frappe.get_doc(doctype, record.name) + for company in companies: + row = frappe._dict({ + 'credit_limit': record.credit_limit, + 'company': company.name + }) + if doctype == "Customer": + row.bypass_credit_limit_check = record.bypass_credit_limit_check_at_sales_order + + doc.append("credit_limits", row) + + for row in doc.credit_limits: + row.db_insert() diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py index 02203d29d40..9f4c4453651 100644 --- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py +++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py @@ -82,7 +82,7 @@ def get_item_tax_template(item_tax_templates, rename_template_to_untitled, item_ account_name = " - ".join(parts[:-1]) company = frappe.db.get_value("Company", filters={"abbr": parts[-1]}) parent_account = frappe.db.get_value("Account", - filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0}, fieldname="parent_account") + filters={"account_type": "Tax", "root_type": "Liability", "is_group": 0, "company": company}, fieldname="parent_account") frappe.get_doc({ "doctype": "Account", diff --git a/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py new file mode 100644 index 00000000000..8e60d33f850 --- /dev/null +++ b/erpnext/patches/v12_0/move_plaid_settings_to_doctype.py @@ -0,0 +1,22 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc("erpnext_integrations", "doctype", "plaid_settings") + plaid_settings = frappe.get_single("Plaid Settings") + if plaid_settings.enabled: + if not (frappe.conf.plaid_client_id and frappe.conf.plaid_env \ + and frappe.conf.plaid_public_key and frappe.conf.plaid_secret): + plaid_settings.enabled = 0 + else: + plaid_settings.update({ + "plaid_client_id": frappe.conf.plaid_client_id, + "plaid_public_key": frappe.conf.plaid_public_key, + "plaid_env": frappe.conf.plaid_env, + "plaid_secret": frappe.conf.plaid_secret + }) + plaid_settings.flags.ignore_mandatory = True + plaid_settings.save() diff --git a/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py new file mode 100644 index 00000000000..d1446b3227d --- /dev/null +++ b/erpnext/patches/v12_0/remove_bank_remittance_custom_fields.py @@ -0,0 +1,14 @@ +from __future__ import unicode_literals +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + frappe.reload_doc("accounts", "doctype", "tax_category") + frappe.reload_doc("stock", "doctype", "item_manufacturer") + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + if frappe.db.exists("Custom Field", "Company-bank_remittance_section"): + deprecated_fields = ['bank_remittance_section', 'client_code', 'remittance_column_break', 'product_code'] + for i in range(len(deprecated_fields)): + frappe.delete_doc("Custom Field", 'Company-'+deprecated_fields[i]) \ No newline at end of file diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js index 101d903bed7..3eea390ff31 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.js +++ b/erpnext/projects/doctype/timesheet/timesheet.js @@ -147,6 +147,15 @@ frappe.ui.form.on("Timesheet Detail", { calculate_time_and_amount(frm); }, + task: (frm, cdt, cdn) => { + let row = frm.selected_doc; + if (row.task) { + frappe.db.get_value("Task", row.task, "project", (r) => { + frappe.model.set_value(cdt, cdn, "project", r.project); + }); + } + }, + from_time: function(frm, cdt, cdn) { calculate_end_time(frm, cdt, cdn); }, @@ -200,9 +209,6 @@ frappe.ui.form.on("Timesheet Detail", { }, activity_type: function(frm, cdt, cdn) { - frm.script_manager.copy_from_first_row('time_logs', frm.selected_doc, - 'project'); - frappe.call({ method: "erpnext.projects.doctype.timesheet.timesheet.get_activity_cost", args: { diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index df9a6baf382..9ee292796c3 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -96,7 +96,7 @@ class Timesheet(Document): for time in self.time_logs: if time.from_time and time.to_time: - if flt(std_working_hours) > 0: + if flt(std_working_hours) and date_diff(time.to_time, time.from_time): time.hours = flt(std_working_hours) * date_diff(time.to_time, time.from_time) else: if not time.hours: @@ -145,12 +145,17 @@ class Timesheet(Document): def validate_time_logs(self): for data in self.get('time_logs'): self.validate_overlap(data) + self.validate_task_project() def validate_overlap(self, data): settings = frappe.get_single('Projects Settings') self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap) self.validate_overlap_for("employee", data, self.employee, settings.ignore_employee_time_overlap) + def validate_task_project(self): + for log in self.time_logs: + log.project = log.project or frappe.db.get_value("Task", log.task, "project") + def validate_overlap_for(self, fieldname, args, value, ignore_validation=False): if not value or ignore_validation: return diff --git a/erpnext/public/js/call_popup/call_popup.js b/erpnext/public/js/call_popup/call_popup.js index 5278b322a4e..5e4d4a585fa 100644 --- a/erpnext/public/js/call_popup/call_popup.js +++ b/erpnext/public/js/call_popup/call_popup.js @@ -28,12 +28,12 @@ class CallPopup { 'depends_on': () => this.call_log.lead }, { 'fieldtype': 'Button', - 'label': __('Make New Contact'), + 'label': __('Create New Contact'), 'click': () => frappe.new_doc('Contact', { 'mobile_no': this.caller_number }), 'depends_on': () => !this.get_caller_name() }, { 'fieldtype': 'Button', - 'label': __('Make New Lead'), + 'label': __('Create New Lead'), 'click': () => frappe.new_doc('Lead', { 'mobile_no': this.caller_number }), 'depends_on': () => !this.get_caller_name() }, { diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 7cf2181e421..2ece7110406 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -44,6 +44,12 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ this.calculate_contribution(); } + // Update paid amount on return/debit note creation + if(this.frm.doc.doctype === "Purchase Invoice" && this.frm.doc.is_return + && (this.frm.doc.grand_total > this.frm.doc.paid_amount)) { + this.frm.doc.paid_amount = flt(this.frm.doc.grand_total, precision("grand_total")); + } + this.frm.refresh_fields(); }, diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js index 595a138f12d..cd767f5d167 100644 --- a/erpnext/public/js/purchase_trends_filters.js +++ b/erpnext/public/js/purchase_trends_filters.js @@ -3,6 +3,14 @@ erpnext.get_purchase_trends_filters = function() { return [ + { + "fieldname":"company", + "label": __("Company"), + "fieldtype": "Link", + "options": "Company", + "reqd": 1, + "default": frappe.defaults.get_user_default("Company") + }, { "fieldname":"period", "label": __("Period"), @@ -15,6 +23,23 @@ erpnext.get_purchase_trends_filters = function() { ], "default": "Monthly" }, + { + "fieldname":"fiscal_year", + "label": __("Fiscal Year"), + "fieldtype": "Link", + "options":'Fiscal Year', + "default": frappe.sys_defaults.fiscal_year + }, + { + "fieldname":"period_based_on", + "label": __("Period based On"), + "fieldtype": "Select", + "options": [ + { "value": "posting_date", "label": __("Posting Date") }, + { "value": "bill_date", "label": __("Billing Date") }, + ], + "default": "posting_date" + }, { "fieldname":"based_on", "label": __("Based On"), @@ -39,19 +64,5 @@ erpnext.get_purchase_trends_filters = function() { ], "default": "" }, - { - "fieldname":"fiscal_year", - "label": __("Fiscal Year"), - "fieldtype": "Link", - "options":'Fiscal Year', - "default": frappe.sys_defaults.fiscal_year - }, - { - "fieldname":"company", - "label": __("Company"), - "fieldtype": "Link", - "options": "Company", - "default": frappe.defaults.get_user_default("Company") - }, ]; } diff --git a/erpnext/public/js/templates/contact_list.html b/erpnext/public/js/templates/contact_list.html index 893b4e0ec20..50fbfd9f12e 100644 --- a/erpnext/public/js/templates/contact_list.html +++ b/erpnext/public/js/templates/contact_list.html @@ -14,20 +14,33 @@ style="margin-top:-3px; margin-right: -5px;"> {%= __("Edit") %}

    - {% if (contact_list[i].phone || contact_list[i].mobile_no || - contact_list[i].email_id) { %} + {% if (contact_list[i].phones || contact_list[i].email_ids) { %}

    - {% if(contact_list[i].phone) { %} - {%= __("Phone") %}: {%= contact_list[i].phone %}
    - {% } %} - {% if(contact_list[i].mobile_no) { %} - {%= __("Mobile No.") %}: {%= contact_list[i].mobile_no %}
    - {% } %} - {% if(contact_list[i].email_id) { %} - {%= __("Email Address") %}: {%= contact_list[i].email_id %} - {% } %} + {% if(contact_list[i].phone) { %} + {%= __("Phone") %}: {%= contact_list[i].phone %} ({%= __("Primary") %})
    + {% endif %} + {% if(contact_list[i].phone_nos) { %} + {% for(var j=0, k=contact_list[i].phone_nos.length; j + {% } %} + {% endif %} +

    +

    + {% if(contact_list[i].email_id) { %} + {%= __("Email") %}: {%= contact_list[i].email_id %} ({%= __("Primary") %})
    + {% endif %} + {% if(contact_list[i].email_ids) { %} + {% for(var j=0, k=contact_list[i].email_ids.length; j + {% } %} + {% endif %}

    {% endif %} +

    + {% if (contact_list[i].address) { %} + {%= __("Address") %}: {%= contact_list[i].address %}
    + {% endif %} +

    {% } %} {% if(!contact_list.length) { %} diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js index 0a363a04fde..ffc5e6ad365 100755 --- a/erpnext/public/js/utils.js +++ b/erpnext/public/js/utils.js @@ -60,7 +60,15 @@ $.extend(erpnext, { var me = this; $btn.on("click", function() { - me.show_serial_batch_selector(grid_row.frm, grid_row.doc); + let callback = ''; + let on_close = ''; + + if (grid_row.doc.serial_no) { + grid_row.doc.has_serial_no = true; + } + + me.show_serial_batch_selector(grid_row.frm, grid_row.doc, + callback, on_close, true); }); }, }); diff --git a/erpnext/public/js/utils/customer_quick_entry.js b/erpnext/public/js/utils/customer_quick_entry.js index f454ba057b4..ac9a7828cbb 100644 --- a/erpnext/public/js/utils/customer_quick_entry.js +++ b/erpnext/public/js/utils/customer_quick_entry.js @@ -37,7 +37,8 @@ frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ { label: __("Address Line 1"), fieldname: "address_line1", - fieldtype: "Data" + fieldtype: "Data", + reqd: 1 }, { label: __("Address Line 2"), @@ -55,7 +56,8 @@ frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ { label: __("City"), fieldname: "city", - fieldtype: "Data" + fieldtype: "Data", + reqd: 1, }, { label: __("State"), @@ -66,7 +68,8 @@ frappe.ui.form.CustomerQuickEntryForm = frappe.ui.form.QuickEntryForm.extend({ label: __("Country"), fieldname: "country", fieldtype: "Link", - options: "Country" + options: "Country", + reqd: 1 }, { label: __("Customer POS Id"), diff --git a/erpnext/public/js/utils/dimension_tree_filter.js b/erpnext/public/js/utils/dimension_tree_filter.js index f1c92091a84..7abe4a7aab1 100644 --- a/erpnext/public/js/utils/dimension_tree_filter.js +++ b/erpnext/public/js/utils/dimension_tree_filter.js @@ -1,12 +1,13 @@ frappe.provide('frappe.ui.form'); erpnext.doctypes_with_dimensions = ["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", - "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"]; + "Expense Claim", "Stock Entry", "Budget", "Payroll Entry", "Delivery Note", "Shipping Rule", "Loyalty Program", + "Fee Schedule", "Fee Structure", "Stock Reconciliation", "Travel Request", "Fees", "POS Profile", "Opening Invoice Creation Tool", + "Subscription", "Purchase Order", "Journal Entry", "Material Request", "Purchase Receipt", "Landed Cost Item", "Asset"]; + +erpnext.child_docs = ["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", + "Landed Cost Item", "Asset Value Adjustment", "Opening Invoice Creation Tool Item", "Subscription Plan"]; frappe.call({ method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimension_filters", @@ -26,10 +27,51 @@ erpnext.doctypes_with_dimensions.forEach((doctype) => { "is_group": 0 }); } - if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { + + if (Object.keys(erpnext.default_dimensions).length > 0) { + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { + if (frm.is_new() && frappe.meta.has_field(doctype, 'company') && frm.doc.company) { + frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); + } + } + + if (frm.doc.items && frm.doc.items.length) { + frm.doc.items[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + } + + if (frm.doc.accounts && frm.doc.accounts.length) { + frm.doc.accounts[0][dimension['fieldname']] = erpnext.default_dimensions[frm.doc.company][dimension['document_type']]; + } + } + }); + }); + }, + + company: function(frm) { + if(frm.doc.company && (Object.keys(erpnext.default_dimensions).length > 0)) { + erpnext.dimension_filters.forEach((dimension) => { + if (frappe.meta.has_field(doctype, dimension['fieldname'])) { frm.set_value(dimension['fieldname'], erpnext.default_dimensions[frm.doc.company][dimension['document_type']]); } }); + } + }, + }); +}); + +erpnext.child_docs.forEach((doctype) => { + frappe.ui.form.on(doctype, { + items_add: function(frm, cdt, cdn) { + erpnext.dimension_filters.forEach((dimension) => { + var row = frappe.get_doc(cdt, cdn); + frm.script_manager.copy_from_first_row("items", row, [dimension['fieldname']]); + }); + }, + + accounts_add: function(frm, cdt, cdn) { + erpnext.dimension_filters.forEach((dimension) => { + var row = frappe.get_doc(cdt, cdn); + frm.script_manager.copy_from_first_row("accounts", row, [dimension['fieldname']]); }); }, diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py index 4d3c5229563..b4de03e1e4f 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure.py @@ -52,7 +52,19 @@ class QualityProcedure(NestedSet): def get_children(doctype, parent=None, parent_quality_procedure=None, is_root=False): if parent is None or parent == "All Quality Procedures": parent = "" - return frappe.get_all(doctype, fields=["name as value", "is_group as expandable"], filters={"parent_quality_procedure": parent}) + + return frappe.db.sql(""" + select + name as value, + is_group as expandable + from + `tab{doctype}` + where + ifnull(parent_quality_procedure, "")={parent} + """.format( + doctype = doctype, + parent=frappe.db.escape(parent) + ), as_dict=1) @frappe.whitelist() def add_node(): diff --git a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js index 8fd785f2057..dbdbbab3925 100644 --- a/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js +++ b/erpnext/quality_management/doctype/quality_procedure/quality_procedure_tree.js @@ -1,5 +1,3 @@ -frappe.provide("frappe.treeview_settings"); - frappe.treeview_settings["Quality Procedure"] = { ignore_fields:["parent_quality_procedure"], get_tree_nodes: 'erpnext.quality_management.doctype.quality_procedure.quality_procedure.get_children', @@ -19,7 +17,7 @@ frappe.treeview_settings["Quality Procedure"] = { ], breadcrumb: "Setup", root_label: "All Quality Procedures", - get_tree_root: true, + get_tree_root: false, menu_items: [ { label: __("New Quality Procedure"), diff --git a/erpnext/regional/india/bank_remittance.py b/erpnext/regional/india/bank_remittance.py deleted file mode 100644 index 85c95647225..00000000000 --- a/erpnext/regional/india/bank_remittance.py +++ /dev/null @@ -1,190 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2018, Frappe and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document -from frappe.utils import cint,cstr, today -from frappe import _ -import re -import datetime -from collections import OrderedDict - -def create_bank_remittance_txt(name): - payment_order = frappe.get_cached_doc("Payment Order", name) - - no_of_records = len(payment_order.get("references")) - total_amount = sum(entry.get("amount") for entry in payment_order.get("references")) - - product_code, client_code, company_email = frappe.db.get_value("Company", - filters={'name' : payment_order.company}, - fieldname=['product_code', 'client_code', 'email']) - - header, file_name = get_header_row(payment_order, client_code) - batch = get_batch_row(payment_order, no_of_records, total_amount, product_code) - - detail = [] - for ref_doc in payment_order.get("references"): - detail += get_detail_row(ref_doc, payment_order, company_email) - - trailer = get_trailer_row(no_of_records, total_amount) - detail_records = "\n".join(detail) - - return "\n".join([header, batch, detail_records, trailer]), file_name - -@frappe.whitelist() -def generate_report(name): - data, file_name = create_bank_remittance_txt(name) - - f = frappe.get_doc({ - 'doctype': 'File', - 'file_name': file_name, - 'content': data, - "attached_to_doctype": 'Payment Order', - "attached_to_name": name, - 'is_private': True - }) - f.save() - return { - 'file_url': f.file_url, - 'file_name': file_name - } - -def generate_file_name(name, company_account, date): - ''' generate file name with format (account_code)_mmdd_(payment_order_no) ''' - bank, acc_no = frappe.db.get_value("Bank Account", {"name": company_account}, ['bank', 'bank_account_no']) - return bank[:1]+str(acc_no)[-4:]+'_'+date.strftime("%m%d")+sanitize_data(name, '')[4:]+'.txt' - -def get_header_row(doc, client_code): - ''' Returns header row and generated file name ''' - file_name = generate_file_name(doc.name, doc.company_bank_account, doc.posting_date) - header = ["H"] - header.append(validate_field_size(client_code, "Client Code", 20)) - header += [''] * 3 - header.append(validate_field_size(file_name, "File Name", 20)) - return "~".join(header), file_name - -def get_batch_row(doc, no_of_records, total_amount, product_code): - batch = ["B"] - batch.append(validate_field_size(no_of_records, "No Of Records", 5)) - batch.append(validate_amount(format(total_amount, '0.2f'), 17)) - batch.append(sanitize_data(doc.name, '_')[:20]) - batch.append(format_date(doc.posting_date)) - batch.append(validate_field_size(product_code,"Product Code", 20)) - return "~".join(batch) - -def get_detail_row(ref_doc, payment_entry, company_email): - - payment_date = format_date(payment_entry.posting_date) - payment_entry = frappe.get_cached_doc('Payment Entry', ref_doc.payment_entry) - supplier_bank_details = frappe.get_cached_doc('Bank Account', ref_doc.bank_account) - company_bank_acc_no = frappe.db.get_value("Bank Account", {'name': payment_entry.bank_account}, ['bank_account_no']) - - addr_link = frappe.db.get_value('Dynamic Link', - { - 'link_doctype': 'Supplier', - 'link_name': 'Sample Supplier', - 'parenttype':'Address', - 'parent': ('like', '%-Billing') - }, 'parent') - - supplier_billing_address = frappe.get_cached_doc('Address', addr_link) - email = ','.join(filter(None, [supplier_billing_address.email_id, company_email])) - - detail = OrderedDict( - record_identifier='D', - payment_ref_no=sanitize_data(ref_doc.payment_entry), - payment_type=cstr(payment_entry.mode_of_payment)[:10], - amount=str(validate_amount(format(ref_doc.amount, '.2f'),13)), - payment_date=payment_date, - instrument_date=payment_date, - instrument_number='', - dr_account_no_client=str(validate_field_size(company_bank_acc_no, "Company Bank Account", 20)), - dr_description='', - dr_ref_no='', - cr_ref_no='', - bank_code_indicator='M', - beneficiary_code='', - beneficiary_name=sanitize_data(validate_information(payment_entry, "party", 160), ' '), - beneficiary_bank=sanitize_data(validate_information(supplier_bank_details, "bank", 10)), - beneficiary_branch_code=cstr(validate_information(supplier_bank_details, "branch_code", 11)), - beneficiary_acc_no=validate_information(supplier_bank_details, "bank_account_no", 20), - location='', - print_location='', - beneficiary_address_1=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line1), ' '), " Beneficiary Address 1", 50), - beneficiary_address_2=validate_field_size(sanitize_data(cstr(supplier_billing_address.address_line2), ' '), " Beneficiary Address 2", 50), - beneficiary_address_3='', - beneficiary_address_4='', - beneficiary_address_5='', - beneficiary_city=validate_field_size(cstr(supplier_billing_address.city), "Beneficiary City", 20), - beneficiary_zipcode=validate_field_size(cstr(supplier_billing_address.pincode), "Pin Code", 6), - beneficiary_state=validate_field_size(cstr(supplier_billing_address.state), "Beneficiary State", 20), - beneficiary_email=cstr(email)[:255], - beneficiary_mobile=validate_field_size(cstr(supplier_billing_address.phone), "Beneficiary Mobile", 10), - payment_details_1='', - payment_details_2='', - payment_details_3='', - payment_details_4='', - delivery_mode='' - ) - detail_record = ["~".join(list(detail.values()))] - - detail_record += get_advice_rows(payment_entry) - return detail_record - -def get_advice_rows(payment_entry): - ''' Returns multiple advice rows for a single detail entry ''' - payment_entry_date = payment_entry.posting_date.strftime("%b%y%d%m").upper() - mode_of_payment = payment_entry.mode_of_payment - advice_rows = [] - for record in payment_entry.references: - advice = ['E'] - advice.append(cstr(mode_of_payment)) - advice.append(cstr(record.total_amount)) - advice.append('') - advice.append(cstr(record.outstanding_amount)) - advice.append(record.reference_name) - advice.append(format_date(record.due_date)) - advice.append(payment_entry_date) - advice_rows.append("~".join(advice)) - return advice_rows - -def get_trailer_row(no_of_records, total_amount): - ''' Returns trailer row ''' - trailer = ["T"] - trailer.append(validate_field_size(no_of_records, "No of Records", 5)) - trailer.append(validate_amount(format(total_amount, "0.2f"), 17)) - return "~".join(trailer) - -def sanitize_data(val, replace_str=''): - ''' Remove all the non-alphanumeric characters from string ''' - pattern = re.compile('[\W_]+') - return pattern.sub(replace_str, val) - -def format_date(val): - ''' Convert a datetime object to DD/MM/YYYY format ''' - return val.strftime("%d/%m/%Y") - -def validate_amount(val, max_int_size): - ''' Validate amount to be within the allowed limits ''' - int_size = len(str(val).split('.')[0]) - - if int_size > max_int_size: - frappe.throw(_("Amount for a single transaction exceeds maximum allowed amount, create a separate payment order by splitting the transactions")) - - return val - -def validate_information(obj, attr, max_size): - ''' Checks if the information is not set in the system and is within the size ''' - if hasattr(obj, attr): - return validate_field_size(getattr(obj, attr), frappe.unscrub(attr), max_size) - - else: - frappe.throw(_("{0} is mandatory for generating remittance payments, set the field and try again".format(frappe.unscrub(attr)))) - -def validate_field_size(val, label, max_size): - ''' check the size of the val ''' - if len(cstr(val)) > max_size: - frappe.throw(_("{0} field is limited to size {1}".format(label, max_size))) - return cstr(val) \ No newline at end of file diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 40b98ed19aa..756c17dc3b3 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -407,14 +407,6 @@ def make_custom_fields(update=True): fieldtype='Link', options='Salary Component', insert_after='basic_component'), dict(fieldname='arrear_component', label='Arrear Component', fieldtype='Link', options='Salary Component', insert_after='hra_component'), - dict(fieldname='bank_remittance_section', label='Bank Remittance Settings', - fieldtype='Section Break', collapsible=1, insert_after='arrear_component'), - dict(fieldname='client_code', label='Client Code', fieldtype='Data', - insert_after='bank_remittance_section'), - dict(fieldname='remittance_column_break', fieldtype='Column Break', - insert_after='client_code'), - dict(fieldname='product_code', label='Product Code', fieldtype='Data', - insert_after='remittance_column_break'), ], 'Employee Tax Exemption Declaration':[ dict(fieldname='hra_section', label='HRA Exemption', diff --git a/erpnext/regional/italy/e-invoice.xml b/erpnext/regional/italy/e-invoice.xml index 9a588d1666b..9978dc0da2e 100644 --- a/erpnext/regional/italy/e-invoice.xml +++ b/erpnext/regional/italy/e-invoice.xml @@ -1,5 +1,5 @@ -{%- macro format_float(value) -%} -{{ "%.2f" % value|abs }} +{%- macro format_float(value, precision=2) -%} +{{ value|round(frappe.utils.cint(precision)) }} {%- endmacro -%} {%- macro render_address(address) %} @@ -182,10 +182,10 @@ {{ html2text(item.description or '') or item.item_name }} {{ format_float(item.qty) }} {{ item.stock_uom }} - {{ format_float(item.price_list_rate or item.rate) }} + {{ format_float(item.price_list_rate or item.rate, item_meta.get_field("rate").precision) }} {{ render_discount_or_margin(item) }} - {{ format_float(item.amount) }} - {{ format_float(item.tax_rate) }} + {{ format_float(item.amount, item_meta.get_field("amount").precision) }} + {{ format_float(item.tax_rate, item_meta.get_field("tax_rate").precision) }} {%- if item.tax_exemption_reason %} {{ item.tax_exemption_reason.split("-")[0] }} {%- endif %} @@ -197,8 +197,8 @@ {%- if data.tax_exemption_reason %} {{ data.tax_exemption_reason.split("-")[0] }} {%- endif %} - {{ format_float(data.taxable_amount) }} - {{ format_float(data.tax_amount) }} + {{ format_float(data.taxable_amount, item_meta.get_field("tax_amount").precision) }} + {{ format_float(data.tax_amount, item_meta.get_field("tax_amount").precision) }} {{ doc.vat_collectability.split("-")[0] }} {%- if data.tax_exemption_law %} {{ data.tax_exemption_law }} diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py index 407677f9767..12f5762f441 100644 --- a/erpnext/regional/italy/utils.py +++ b/erpnext/regional/italy/utils.py @@ -151,7 +151,8 @@ def get_invoice_summary(items, taxes): tax_rate=tax.rate, tax_amount=(reference_row.tax_amount * tax.rate) / 100, net_amount=reference_row.tax_amount, - taxable_amount=reference_row.tax_amount, + taxable_amount=(reference_row.tax_amount if tax.charge_type == 'On Previous Row Amount' + else reference_row.total), item_tax_rate={tax.account_head: tax.rate}, charges=True ) @@ -278,7 +279,11 @@ def prepare_and_attach_invoice(doc, replace=False): progressive_name, progressive_number = get_progressive_name_and_number(doc, replace) invoice = prepare_invoice(doc, progressive_number) - invoice_xml = frappe.render_template('erpnext/regional/italy/e-invoice.xml', context={"doc": invoice}, is_path=True) + item_meta = frappe.get_meta("Sales Invoice Item") + + invoice_xml = frappe.render_template('erpnext/regional/italy/e-invoice.xml', + context={"doc": invoice, "item_meta": item_meta}, is_path=True) + invoice_xml = invoice_xml.replace("&", "&") xml_filename = progressive_name + ".xml" diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 2da10857324..922619cc421 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -156,34 +156,21 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - customers = frappe.get_all("Customer", - filters={ - "gst_category": ["in", ["Registered Regular", "Deemed Export", "SEZ"]] - }) - - if customers: - conditions += """ and ifnull(gst_category, '') != 'Overseas' and is_return != 1 - and customer in ({0})""".format(", ".join([frappe.db.escape(c.name) for c in customers])) + conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') if not b2c_limit: frappe.throw(_("Please set B2C Limit in GST Settings.")) - customers = frappe.get_all("Customer", - filters={ - "gst_category": ["in", ["Unregistered"]] - }) - - if self.filters.get("type_of_business") == "B2C Large" and customers: + if self.filters.get("type_of_business") == "B2C Large": conditions += """ and SUBSTR(place_of_supply, 1, 2) != SUBSTR(company_gstin, 1, 2) - and grand_total > {0} and is_return != 1 and customer in ({1})""".\ - format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers])) - elif self.filters.get("type_of_business") == "B2C Small" and customers: + and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + + elif self.filters.get("type_of_business") == "B2C Small": conditions += """ and ( SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2) - or grand_total <= {0}) and is_return != 1 and customer in ({1})""".\ - format(flt(b2c_limit), ", ".join([frappe.db.escape(c.name) for c in customers])) + or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "CDNR": conditions += """ and is_return = 1 """ @@ -735,7 +722,7 @@ def get_company_gstin_number(company): if gstin: return gstin[0]["gstin"] else: - frappe.throw(_("No GST No. found for the Company.")) + frappe.throw(_("Please set valid GSTIN No. in Company Address")) def download_json_file(filename, report_type, data): ''' download json content in a file ''' diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json index 8c305289608..c2c8c1971a9 100644 --- a/erpnext/selling/doctype/customer/customer.json +++ b/erpnext/selling/doctype/customer/customer.json @@ -1,2036 +1,475 @@ { - "allow_copy": 0, "allow_events_in_timeline": 1, - "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "naming_series:", - "beta": 0, "creation": "2013-06-11 14:26:44", - "custom": 0, "description": "Buyer of Goods and Services.", - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, "engine": "InnoDB", + "field_order": [ + "basic_info", + "naming_series", + "salutation", + "customer_name", + "gender", + "customer_type", + "default_bank_account", + "lead_name", + "image", + "column_break0", + "account_manager", + "customer_group", + "territory", + "tax_id", + "tax_category", + "disabled", + "is_internal_customer", + "represents_company", + "allowed_to_transact_section", + "companies", + "currency_and_price_list", + "default_currency", + "default_price_list", + "column_break_14", + "language", + "address_contacts", + "address_html", + "website", + "column_break1", + "contact_html", + "primary_address_and_contact_detail", + "customer_primary_contact", + "mobile_no", + "email_id", + "column_break_26", + "customer_primary_address", + "primary_address", + "default_receivable_accounts", + "accounts", + "credit_limit_section", + "payment_terms", + "credit_limits", + "more_info", + "customer_details", + "column_break_45", + "market_segment", + "industry", + "is_frozen", + "column_break_38", + "loyalty_program", + "loyalty_program_tier", + "sales_team_section_break", + "default_sales_partner", + "default_commission_rate", + "sales_team_section", + "sales_team", + "customer_pos_id" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "basic_info", "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": "Name and Type", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-user", - "permlevel": 0, - "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": "fa fa-user" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, "fieldname": "naming_series", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Series", - "length": 0, "no_copy": 1, "options": "CUST-.YYYY.-", - "permlevel": 0, - "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": 1, - "translatable": 0, - "unique": 0 + "set_only_once": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.customer_type!='Company'", - "fetch_if_empty": 0, "fieldname": "salutation", "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": "Salutation", - "length": 0, - "no_copy": 0, - "options": "Salutation", - "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": "Salutation" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "customer_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": "Full Name", - "length": 0, "no_copy": 1, "oldfieldname": "customer_name", "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:doc.customer_type != 'Company'", - "fetch_if_empty": 0, "fieldname": "gender", "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": "Gender", - "length": 0, - "no_copy": 0, - "options": "Gender", - "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": "Gender" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "Company", - "fetch_if_empty": 0, "fieldname": "customer_type", "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Type", - "length": 0, - "no_copy": 0, "oldfieldname": "customer_type", "oldfieldtype": "Select", "options": "Company\nIndividual", - "permlevel": 0, - "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": "default_bank_account", "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": "Default Bank Account", - "length": 0, - "no_copy": 0, - "options": "Bank Account", - "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": "Bank Account" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "lead_name", "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": "From Lead", - "length": 0, "no_copy": 1, "oldfieldname": "lead_name", "oldfieldtype": "Link", "options": "Lead", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 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": "image", "fieldtype": "Attach Image", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Image", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 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 + "print_hide": 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_break0", "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, - "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, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "account_manager", "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Account Manager", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "options": "User" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "customer_group", "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": 1, "label": "Customer Group", - "length": 0, - "no_copy": 0, "oldfieldname": "customer_group", "oldfieldtype": "Link", "options": "Customer Group", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, "fieldname": "territory", "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": 1, "label": "Territory", - "length": 0, - "no_copy": 0, "oldfieldname": "territory", "oldfieldtype": "Link", "options": "Territory", - "permlevel": 0, "print_hide": 1, - "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": "tax_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax ID", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "label": "Tax ID" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "tax_category", "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": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "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": "Tax Category" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "disabled", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disabled", - "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": "Disabled" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "default": "0", - "fetch_if_empty": 0, "fieldname": "is_internal_customer", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Internal Customer", - "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": "Is Internal Customer" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "is_internal_customer", - "fetch_if_empty": 0, "fieldname": "represents_company", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Represents 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": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "represents_company", - "fetch_if_empty": 0, "fieldname": "allowed_to_transact_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, - "label": "Allowed To Transact With", - "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": "Allowed To Transact With" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "represents_company", - "fetch_if_empty": 0, "fieldname": "companies", "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": "Allowed To Transact With", - "length": 0, - "no_copy": 0, - "options": "Allowed To Transact With", - "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": "Allowed To Transact With" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "currency_and_price_list", "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": "Currency and Price List", - "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": "Currency and Price List" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_currency", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Billing Currency", - "length": 0, "no_copy": 1, - "options": "Currency", - "permlevel": 0, - "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": "Currency" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_price_list", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "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": "Price List" }, { - "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_14", - "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_if_empty": 0, "fieldname": "language", "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": "Print Language", - "length": 0, - "no_copy": 0, - "options": "Language", - "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": "Language" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.__islocal", - "fetch_if_empty": 0, "fieldname": "address_contacts", "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-map-marker", - "permlevel": 0, - "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": "fa fa-map-marker" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "address_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Address HTML", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "website", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Website", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Website" }, { - "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_break1", "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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, "width": "50%" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "contact_html", "fieldtype": "HTML", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Contact HTML", - "length": 0, - "no_copy": 0, "oldfieldtype": "HTML", - "permlevel": 0, - "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, "description": "Select, to make the customer searchable with these fields", - "fetch_if_empty": 0, "fieldname": "primary_address_and_contact_detail", "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": "Primary Address and Contact Detail", - "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": "Primary Address and Contact Detail" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Reselect, if the chosen contact is edited after save", - "fetch_if_empty": 0, "fieldname": "customer_primary_contact", "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": "Customer Primary Contact", - "length": 0, - "no_copy": 0, - "options": "Contact", - "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": "Contact" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "customer_primary_contact.mobile_no", - "fetch_if_empty": 0, "fieldname": "mobile_no", "fieldtype": "Read Only", - "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": "Mobile No", - "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": "Mobile No" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "customer_primary_contact.email_id", - "fetch_if_empty": 0, "fieldname": "email_id", "fieldtype": "Read Only", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Email Id", - "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": "Email Id" }, { - "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_26", - "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, "description": "Reselect, if the chosen address is edited after save", - "fetch_if_empty": 0, "fieldname": "customer_primary_address", "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": "Customer Primary Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "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": "Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, "fieldname": "primary_address", "fieldtype": "Read Only", - "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": "Primary Address", - "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": "Primary Address" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_receivable_accounts", "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": "Accounting", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Accounting" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "description": "Mention if non-standard receivable account", - "fetch_if_empty": 0, "fieldname": "accounts", "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": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Party Account", - "permlevel": 0, - "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": "Party Account" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "fetch_if_empty": 0, "fieldname": "credit_limit_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, - "label": "Credit Limit and Payment Terms", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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, - "width": "" + "label": "Credit Limit and Payment Terms" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "credit_limit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Credit Limit", - "length": 0, - "no_copy": 0, - "oldfieldname": "credit_limit", - "oldfieldtype": "Currency", - "options": "", - "permlevel": 1, - "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, - "default": "0", - "fetch_if_empty": 0, - "fieldname": "bypass_credit_limit_check_at_sales_order", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Bypass credit limit check at Sales Order", - "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, - "fetch_if_empty": 0, - "fieldname": "column_break_34", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fetch_if_empty": 0, "fieldname": "payment_terms", "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": "Default Payment Terms Template", - "length": 0, - "no_copy": 0, - "options": "Payment Terms Template", - "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": "Payment Terms Template" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, "collapsible_depends_on": "customer_details", - "columns": 0, - "fetch_if_empty": 0, "fieldname": "more_info", "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": "More Information", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "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": "fa fa-file-text" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "description": "Additional information regarding the customer.", - "fetch_if_empty": 0, "fieldname": "customer_details", "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Customer Details", - "length": 0, - "no_copy": 0, "oldfieldname": "customer_details", - "oldfieldtype": "Code", - "permlevel": 0, - "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 + "oldfieldtype": "Code" }, { - "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_45", - "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_if_empty": 0, "fieldname": "market_segment", "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": "Market Segment", - "length": 0, - "no_copy": 0, - "options": "Market Segment", - "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": "Market Segment" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "industry", "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": "Industry", - "length": 0, - "no_copy": 0, - "options": "Industry Type", - "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": "Industry Type" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, + "default": "0", "fieldname": "is_frozen", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Frozen", - "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": "Is Frozen" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "column_break_38", "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": "Loyalty Points", - "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": "Loyalty Points" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "loyalty_program", "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": "Loyalty Program", - "length": 0, "no_copy": 1, - "options": "Loyalty 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": "Loyalty Program" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "loyalty_program_tier", "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": "Loyalty Program Tier", - "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": 1, "collapsible_depends_on": "default_sales_partner", - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sales_team_section_break", "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": "Sales Partner and Commission", - "length": 0, - "no_copy": 0, "oldfieldtype": "Section Break", - "options": "fa fa-group", - "permlevel": 0, - "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": "fa fa-group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_sales_partner", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Sales Partner", - "length": 0, - "no_copy": 0, "oldfieldname": "default_sales_partner", "oldfieldtype": "Link", - "options": "Sales Partner", - "permlevel": 0, - "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": "Sales Partner" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "default_commission_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": "Commission Rate", - "length": 0, - "no_copy": 0, "oldfieldname": "default_commission_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "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 + "oldfieldtype": "Currency" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, "collapsible": 1, "collapsible_depends_on": "sales_team", - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sales_team_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, - "label": "Sales Team", - "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": "Sales Team" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "sales_team", "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": "Sales Team Details", - "length": 0, - "no_copy": 0, "oldfieldname": "sales_team", "oldfieldtype": "Table", - "options": "Sales Team", - "permlevel": 0, - "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": "Sales Team" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, "fieldname": "customer_pos_id", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Customer POS id", - "length": 0, "no_copy": 1, - "permlevel": 0, - "precision": "", "print_hide": 1, - "print_hide_if_no_value": 0, "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 + }, + { + "default": "0", + "fieldname": "credit_limits", + "fieldtype": "Table", + "label": "Credit Limit", + "options": "Customer Credit Limit" } ], - "has_web_view": 0, - "hide_toolbar": 0, "icon": "fa fa-user", "idx": 363, "image_field": "image", - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-12 08:45:39.357491", + "modified": "2019-09-06 12:40:31.801424", "modified_by": "Administrator", "module": "Selling", "name": "Customer", @@ -2038,184 +477,81 @@ "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, "create": 1, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales User", - "set_user_permissions": 0, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales Manager" }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Master Manager", "set_user_permissions": 1, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, "role": "Sales Master Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Stock Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Accounts User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Accounts Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Accounts Manager" } ], "quick_entry": 1, - "read_only": 0, "search_fields": "customer_name,customer_group,territory, mobile_no,primary_address", "show_name_in_global_search": 1, + "sort_field": "modified", "sort_order": "ASC", "title_field": "customer_name", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + "track_changes": 1 } \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index c946c47c598..4ca5af51a4d 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -167,13 +167,18 @@ class Customer(TransactionBase): frappe.throw(_("A Customer Group exists with same name please change the Customer name or rename the Customer Group"), frappe.NameError) def validate_credit_limit_on_change(self): - if self.get("__islocal") or not self.credit_limit \ - or self.credit_limit == frappe.db.get_value("Customer", self.name, "credit_limit"): + if self.get("__islocal") or not self.credit_limits: return - for company in frappe.get_all("Company"): - outstanding_amt = get_customer_outstanding(self.name, company.name) - if flt(self.credit_limit) < outstanding_amt: + company_record = [] + for limit in self.credit_limits: + if limit.company in company_record: + frappe.throw(_("Credit limit is already defined for the Company {0}").format(limit.company, self.name)) + else: + company_record.append(limit.company) + + outstanding_amt = get_customer_outstanding(self.name, limit.company) + if flt(limit.credit_limit) < outstanding_amt: frappe.throw(_("""New credit limit is less than current outstanding amount for the customer. Credit limit has to be atleast {0}""").format(outstanding_amt)) def on_trash(self): @@ -322,11 +327,13 @@ def get_credit_limit(customer, company): credit_limit = None if customer: - credit_limit, customer_group = frappe.get_cached_value("Customer", - customer, ["credit_limit", "customer_group"]) + credit_limit = frappe.db.get_value("Customer Credit Limit", + {'parent': customer, 'parenttype': 'Customer', 'company': company}, 'credit_limit') if not credit_limit: - credit_limit = frappe.get_cached_value("Customer Group", customer_group, "credit_limit") + customer_group = frappe.get_cached_value("Customer", customer, 'customer_group') + credit_limit = frappe.db.get_value("Customer Credit Limit", + {'parent': customer_group, 'parenttype': 'Customer Group', 'company': company}, 'credit_limit') if not credit_limit: credit_limit = frappe.get_cached_value('Company', company, "credit_limit") @@ -337,14 +344,15 @@ def make_contact(args, is_primary_contact=1): contact = frappe.get_doc({ 'doctype': 'Contact', 'first_name': args.get('name'), - 'mobile_no': args.get('mobile_no'), - 'email_id': args.get('email_id'), 'is_primary_contact': is_primary_contact, 'links': [{ 'link_doctype': args.get('doctype'), 'link_name': args.get('name') }] - }).insert() + }) + contact.add_email(args.get('email_id')) + contact.add_phone(args.get('mobile_no')) + contact.insert() return contact @@ -371,7 +379,7 @@ def get_customer_primary_contact(doctype, txt, searchfield, start, page_len, fil return frappe.db.sql(""" select `tabContact`.name from `tabContact`, `tabDynamic Link` where `tabContact`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s - and `tabDynamic Link`.link_doctype = 'Customer' and `tabContact`.is_primary_contact = 1 + and `tabDynamic Link`.link_doctype = 'Customer' and `tabContact`.name like %(txt)s """, { 'customer': customer, @@ -383,7 +391,7 @@ def get_customer_primary_address(doctype, txt, searchfield, start, page_len, fil return frappe.db.sql(""" select `tabAddress`.name from `tabAddress`, `tabDynamic Link` where `tabAddress`.name = `tabDynamic Link`.parent and `tabDynamic Link`.link_name = %(customer)s - and `tabDynamic Link`.link_doctype = 'Customer' and `tabAddress`.is_primary_address = 1 + and `tabDynamic Link`.link_doctype = 'Customer' and `tabAddress`.name like %(txt)s """, { 'customer': customer, diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index 8e790bf9ce3..654dd48c669 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -9,7 +9,7 @@ def get_data(): 'heatmap_message': _('This is based on transactions against this Customer. See timeline below for details'), 'fieldname': 'customer', 'non_standard_fieldnames': { - 'Payment Entry': 'party_name', + 'Payment Entry': 'party', 'Quotation': 'party_name', 'Opportunity': 'party_name' }, diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py index f261ef37fb9..87fdaa366f1 100644 --- a/erpnext/selling/doctype/customer/test_customer.py +++ b/erpnext/selling/doctype/customer/test_customer.py @@ -25,7 +25,7 @@ class TestCustomer(unittest.TestCase): make_test_records('Item') def tearDown(self): - frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', 0.0) + set_credit_limit('_Test Customer', '_Test Company', 0) def test_party_details(self): from erpnext.accounts.party import get_party_details @@ -225,8 +225,8 @@ class TestCustomer(unittest.TestCase): item_qty = int((abs(outstanding_amt) + 200)/100) make_sales_order(qty=item_qty) - if credit_limit == 0.0: - frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', outstanding_amt - 50.0) + if not credit_limit: + set_credit_limit('_Test Customer', '_Test Company', outstanding_amt - 50) # Sales Order so = make_sales_order(do_not_submit=True) @@ -241,7 +241,7 @@ class TestCustomer(unittest.TestCase): self.assertRaises(frappe.ValidationError, si.submit) if credit_limit > outstanding_amt: - frappe.db.set_value("Customer", '_Test Customer', 'credit_limit', credit_limit) + set_credit_limit('_Test Customer', '_Test Company', credit_limit) # Makes Sales invoice from Sales Order so.save(ignore_permissions=True) @@ -252,7 +252,10 @@ class TestCustomer(unittest.TestCase): def test_customer_credit_limit_on_change(self): outstanding_amt = self.get_customer_outstanding_amount() customer = frappe.get_doc("Customer", '_Test Customer') - customer.credit_limit = flt(outstanding_amt - 100) + customer.append('credit_limits', {'credit_limit': flt(outstanding_amt - 100), 'company': '_Test Company'}) + + ''' define new credit limit for same company ''' + customer.append('credit_limits', {'credit_limit': flt(outstanding_amt - 100), 'company': '_Test Company'}) self.assertRaises(frappe.ValidationError, customer.save) def test_customer_payment_terms(self): @@ -292,3 +295,20 @@ def get_customer_dict(customer_name): "doctype": "Customer", "territory": "_Test Territory" } + +def set_credit_limit(customer, company, credit_limit): + customer = frappe.get_doc("Customer", customer) + existing_row = None + for d in customer.credit_limits: + if d.company == company: + existing_row = d + d.credit_limit = credit_limit + d.db_update() + break + + if not existing_row: + customer.append('credit_limits', { + 'company': company, + 'credit_limit': credit_limit + }) + customer.credit_limits[-1].db_insert() diff --git a/erpnext/selling/doctype/customer_credit_limit/__init__.py b/erpnext/selling/doctype/customer_credit_limit/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.json b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.json new file mode 100644 index 00000000000..e26f7989788 --- /dev/null +++ b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.json @@ -0,0 +1,50 @@ +{ + "creation": "2019-08-28 17:29:42.115592", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "column_break_2", + "credit_limit", + "bypass_credit_limit_check" + ], + "fields": [ + { + "columns": 4, + "fieldname": "credit_limit", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Credit Limit" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "columns": 4, + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company" + }, + { + "default": "0", + "fieldname": "bypass_credit_limit_check", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Bypass credit limit_check" + } + ], + "istable": 1, + "modified": "2019-08-29 20:46:36.073953", + "modified_by": "Administrator", + "module": "Selling", + "name": "Customer Credit Limit", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py new file mode 100644 index 00000000000..60a4a9a5d25 --- /dev/null +++ b/erpnext/selling/doctype/customer_credit_limit/customer_credit_limit.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class CustomerCreditLimit(Document): + pass diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py index cc73e76bf3f..46009f45e42 100644 --- a/erpnext/selling/doctype/quotation/quotation.py +++ b/erpnext/selling/doctype/quotation/quotation.py @@ -34,7 +34,7 @@ class Quotation(SellingController): self.with_items = 1 def validate_valid_till(self): - if self.valid_till and self.valid_till < self.transaction_date: + if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date): frappe.throw(_("Valid till date cannot be before transaction date")) def has_sales_order(self): diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 2e5f255a900..e86caddb87c 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -7,6 +7,7 @@ frappe.ui.form.on("Sales Order", { setup: function(frm) { frm.custom_make_buttons = { 'Delivery Note': 'Delivery', + 'Pick List': 'Pick List', 'Sales Invoice': 'Invoice', 'Material Request': 'Material Request', 'Purchase Order': 'Purchase Order', @@ -109,7 +110,9 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( this._super(); let allow_delivery = false; - if(doc.docstatus==1) { + if (doc.docstatus==1) { + this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create')); + if(this.frm.has_perm("submit")) { if(doc.status === 'On Hold') { // un-hold @@ -233,6 +236,13 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( this.order_type(doc); }, + create_pick_list() { + frappe.model.open_mapped_doc({ + method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list", + frm: this.frm + }) + }, + make_work_order() { var me = this; this.frm.call({ diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json index 2481f31ffd2..e537495d946 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.json +++ b/erpnext/selling/doctype/sales_order/sales_order.json @@ -1,4472 +1,1256 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-06-18 12:39:59", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "customer_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, - "label": "", - "length": 0, - "no_copy": 0, - "options": "fa fa-user", - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "column_break0", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "{customer_name}", - "fetch_if_empty": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fetch_if_empty": 0, - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "SAL-ORD-.YYYY.-", - "permlevel": 0, - "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_from": "customer.customer_name", - "fetch_if_empty": 0, - "fieldname": "customer_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": "Customer Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Sales", - "depends_on": "", - "fetch_if_empty": 0, - "fieldname": "order_type", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Order Type", - "length": 0, - "no_copy": 0, - "oldfieldname": "order_type", - "oldfieldtype": "Select", - "options": "\nSales\nMaintenance\nShopping Cart", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 1, - "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, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Sales Order", - "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, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fetch_if_empty": 0, - "fieldname": "transaction_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": 1, - "label": "Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "transaction_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "160px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "delivery_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": "Delivery 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": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, - "fieldname": "po_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": "Customer's Purchase Order", - "length": 0, - "no_copy": 0, - "oldfieldname": "po_no", - "oldfieldtype": "Data", - "permlevel": 0, - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.po_no", - "description": "", - "fetch_if_empty": 0, - "fieldname": "po_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": "Customer's Purchase Order Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "po_date", - "oldfieldtype": "Date", - "permlevel": 0, - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "tax_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax Id", - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "", - "columns": 0, - "depends_on": "customer", - "fetch_if_empty": 0, - "fieldname": "contact_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-bullhorn", - "permlevel": 0, - "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": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "customer_address", - "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": "Customer Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "contact_person", - "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": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "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": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "contact_mobile", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "company_address_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "company_address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "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, - "fetch_if_empty": 0, - "fieldname": "col_break46", - "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, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "shipping_address_name", - "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": "Shipping Address Name", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "shipping_address", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Address", - "length": 0, - "no_copy": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "territory", - "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": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "currency_and_price_list", - "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": "Currency and Price List", - "length": 0, - "no_copy": 0, - "options": "fa fa-tag", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "currency", - "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": "Currency", - "length": 0, - "no_copy": 0, - "oldfieldname": "currency", - "oldfieldtype": "Select", - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which customer's currency is converted to company's base currency", - "fetch_if_empty": 0, - "fieldname": "conversion_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, - "oldfieldname": "conversion_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "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, - "width": "100px" - }, - { - "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_break2", - "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, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "selling_price_list", - "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": "Price List", - "length": 0, - "no_copy": 0, - "oldfieldname": "price_list_name", - "oldfieldtype": "Select", - "options": "Price List", - "permlevel": 0, - "print_hide": 1, - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "price_list_currency", - "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": "Price List Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which Price list currency is converted to company's base currency", - "fetch_if_empty": 0, - "fieldname": "plc_conversion_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": "Price List Exchange Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "ignore_pricing_rule", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore Pricing Rule", - "length": 0, - "no_copy": 1, - "permlevel": 1, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sec_warehouse", - "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, - "fetch_if_empty": 0, - "fieldname": "set_warehouse", - "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": "Set Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "items_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, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "scan_barcode", - "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": "Scan Barcode", - "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": 1, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_order_details", - "oldfieldtype": "Table", - "options": "Sales Order Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "pricing_rule_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Pricing Rules", - "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, - "fetch_if_empty": 0, - "fieldname": "pricing_rules", - "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": "Pricing Rule Detail", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Detail", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_31", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "column_break_33a", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "total_qty", - "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": "Total Quantity", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "net_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "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, - "width": "150px" - }, - { - "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_33", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_net_weight", - "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": "Total Net Weight", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "taxes_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, - "label": "Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "tax_category", - "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": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "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 - }, - { - "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_38", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "shipping_rule", - "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": "Shipping Rule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Button", - "options": "Shipping Rule", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_40", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "taxes_and_charges", - "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": "Sales Taxes and Charges Template", - "length": 0, - "no_copy": 0, - "oldfieldname": "charge", - "oldfieldtype": "Link", - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "taxes", - "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": "Sales Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges", - "oldfieldtype": "Table", - "options": "Sales Taxes and Charges", - "permlevel": 0, - "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": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sec_tax_breakup", - "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": "Tax Breakup", - "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, - "fetch_if_empty": 0, - "fieldname": "other_charges_calculation", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes and Charges Calculation", - "length": 0, - "no_copy": 1, - "oldfieldtype": "HTML", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_43", - "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, - "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, - "fetch_if_empty": 0, - "fieldname": "base_total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "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, - "width": "150px" - }, - { - "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_46", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "loyalty_points_redemption", - "fieldtype": "Section Break", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Loyalty Points Redemption", - "length": 0, - "no_copy": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "loyalty_points", - "fieldtype": "Int", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Loyalty Points", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "loyalty_amount", - "fieldtype": "Currency", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Loyalty 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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "discount_amount", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_48", - "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": "Additional Discount", - "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, - "default": "Grand Total", - "fetch_if_empty": 0, - "fieldname": "apply_discount_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Additional Discount On", - "length": 0, - "no_copy": 0, - "options": "\nGrand Total\nNet Total", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, - { - "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_50", - "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, - "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": "", - "fetch_if_empty": 0, - "fieldname": "additional_discount_percentage", - "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": "Additional Discount Percentage", - "length": 0, - "no_copy": 0, - "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 - }, - { - "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": "discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "totals", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grand Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "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, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment (Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "base_rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "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, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "In Words will be visible once you save the Sales Order.", - "fetch_if_empty": 0, - "fieldname": "base_in_words", - "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": "In Words (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words", - "oldfieldtype": "Data", - "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, - "width": "200px" - }, - { - "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_break3", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "grand_total", - "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": "Grand Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "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, - "width": "150px" - }, - { - "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": "rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment", - "length": 0, - "no_copy": 1, - "options": "currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "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, - "width": "150px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "in_words", - "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": "In Words", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words_export", - "oldfieldtype": "Data", - "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, - "width": "200px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "advance_paid", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Advance Paid", - "length": 0, - "no_copy": 1, - "options": "party_account_currency", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "packed_items", - "columns": 0, - "description": "", - "fetch_if_empty": 0, - "fieldname": "packing_list", - "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": "Packing List", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-suitcase", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "packed_items", - "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": "Packed Items", - "length": 0, - "no_copy": 0, - "options": "Packed Item", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "payment_schedule_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, - "label": "Payment Terms", - "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, - "fetch_if_empty": 0, - "fieldname": "payment_terms_template", - "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": "Payment Terms Template", - "length": 0, - "no_copy": 0, - "options": "Payment Terms Template", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "payment_schedule", - "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 Schedule", - "length": 0, - "no_copy": 1, - "options": "Payment Schedule", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "terms", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "terms_section_break", - "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": "Terms and Conditions", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-legal", - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "tc_name", - "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": "Terms", - "length": 0, - "no_copy": 0, - "oldfieldname": "tc_name", - "oldfieldtype": "Link", - "options": "Terms and Conditions", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "terms", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms and Conditions Details", - "length": 0, - "no_copy": 0, - "oldfieldname": "terms", - "oldfieldtype": "Text Editor", - "permlevel": 0, - "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": 1, - "collapsible_depends_on": "project", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "more_info", - "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": "More Information", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "inter_company_order_reference", - "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": "Inter Company Order Reference", - "length": 0, - "no_copy": 0, - "options": "Purchase Order", - "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, - "description": "Track this Sales Order against any Project", - "fetch_if_empty": 0, - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "party_account_currency", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Party Account Currency", - "length": 0, - "no_copy": 1, - "options": "Currency", - "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 - }, - { - "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_77", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "source", - "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": "Source", - "length": 0, - "no_copy": 0, - "oldfieldname": "source", - "oldfieldtype": "Select", - "options": "Lead Source", - "permlevel": 0, - "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 - }, - { - "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": "campaign", - "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": "Campaign", - "length": 0, - "no_copy": 0, - "oldfieldname": "campaign", - "oldfieldtype": "Link", - "options": "Campaign", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "printing_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Printing Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "language", - "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": "Print Language", - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "letter_head", - "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": "Letter Head", - "length": 0, - "no_copy": 0, - "oldfieldname": "letter_head", - "oldfieldtype": "Select", - "options": "Letter Head", - "permlevel": 0, - "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 - }, - { - "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_break4", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "select_print_heading", - "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": "Print Heading", - "length": 0, - "no_copy": 1, - "oldfieldname": "select_print_heading", - "oldfieldtype": "Link", - "options": "Print Heading", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "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": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "group_same_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Group same items", - "length": 0, - "no_copy": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break_78", - "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": "Billing and Delivery Status", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fetch_if_empty": 0, - "fieldname": "status", - "fieldtype": "Select", - "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": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "delivery_status", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Delivery Status", - "length": 0, - "no_copy": 1, - "options": "Not Delivered\nFully Delivered\nPartly Delivered\nClosed\nNot Applicable", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.__islocal", - "description": "% of materials delivered against this Sales Order", - "fetch_if_empty": 0, - "fieldname": "per_delivered", - "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": "% Delivered", - "length": 0, - "no_copy": 1, - "oldfieldname": "per_delivered", - "oldfieldtype": "Currency", - "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, - "width": "100px" - }, - { - "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_81", - "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, - "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": "eval:!doc.__islocal", - "description": "% of materials billed against this Sales Order", - "fetch_if_empty": 0, - "fieldname": "per_billed", - "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": "% Amount Billed", - "length": 0, - "no_copy": 1, - "oldfieldname": "per_billed", - "oldfieldtype": "Currency", - "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, - "width": "100px" - }, - { - "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": "billing_status", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Billing Status", - "length": 0, - "no_copy": 1, - "options": "Not Billed\nFully Billed\nPartly Billed\nClosed", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "commission_rate", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_team_section_break", - "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": "Commission", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-group", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_partner", - "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": "Sales Partner", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_partner", - "oldfieldtype": "Link", - "options": "Sales Partner", - "permlevel": 0, - "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, - "width": "150px" - }, - { - "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_break7", - "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, - "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, - "width": "50%" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "commission_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": "Commission Rate", - "length": 0, - "no_copy": 0, - "oldfieldname": "commission_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "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, - "width": "100px" - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "total_commission", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Commission", - "length": 0, - "no_copy": 0, - "oldfieldname": "total_commission", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "sales_team", - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "section_break1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Team", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "sales_team", - "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": "Sales Team1", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_team", - "oldfieldtype": "Table", - "options": "Sales Team", - "permlevel": 0, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "subscription_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, - "label": "Auto Repeat Section", - "length": 0, - "no_copy": 1, - "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 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, - "fieldname": "from_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "From Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "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": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fetch_if_empty": 0, - "fieldname": "to_date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "To Date", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "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, - "fetch_if_empty": 0, - "fieldname": "column_break_108", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "auto_repeat", - "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": "Auto Repeat", - "length": 0, - "no_copy": 0, - "options": "Auto Repeat", - "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": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval: doc.auto_repeat", - "fetch_if_empty": 0, - "fieldname": "update_auto_repeat_reference", - "fieldtype": "Button", - "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": "Update Auto Repeat Reference", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_toolbar": 0, - "icon": "fa fa-file-text", - "idx": 105, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-04-18 12:05:23.464968", - "modified_by": "Administrator", - "module": "Selling", - "name": "Sales Order", - "owner": "Administrator", - "permissions": [ - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales Manager", - "set_user_permissions": 1, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Maintenance User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 0, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "search_fields": "status,transaction_date,customer,customer_name, territory,order_type,company", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "customer", - "title_field": "title", - "track_changes": 1, - "track_seen": 1, - "track_views": 0 - } \ No newline at end of file + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-06-18 12:39:59", + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "customer_section", + "column_break0", + "title", + "naming_series", + "customer", + "customer_name", + "order_type", + "column_break1", + "amended_from", + "company", + "transaction_date", + "delivery_date", + "po_no", + "po_date", + "tax_id", + "contact_info", + "customer_address", + "address_display", + "contact_person", + "contact_display", + "contact_phone", + "contact_mobile", + "contact_email", + "company_address_display", + "company_address", + "col_break46", + "shipping_address_name", + "shipping_address", + "customer_group", + "territory", + "currency_and_price_list", + "currency", + "conversion_rate", + "column_break2", + "selling_price_list", + "price_list_currency", + "plc_conversion_rate", + "ignore_pricing_rule", + "sec_warehouse", + "set_warehouse", + "items_section", + "scan_barcode", + "items", + "pricing_rule_details", + "pricing_rules", + "section_break_31", + "column_break_33a", + "total_qty", + "base_total", + "base_net_total", + "column_break_33", + "total", + "net_total", + "total_net_weight", + "taxes_section", + "tax_category", + "column_break_38", + "shipping_rule", + "section_break_40", + "taxes_and_charges", + "taxes", + "sec_tax_breakup", + "other_charges_calculation", + "section_break_43", + "base_total_taxes_and_charges", + "column_break_46", + "total_taxes_and_charges", + "loyalty_points_redemption", + "loyalty_points", + "loyalty_amount", + "section_break_48", + "apply_discount_on", + "base_discount_amount", + "column_break_50", + "additional_discount_percentage", + "discount_amount", + "totals", + "base_grand_total", + "base_rounding_adjustment", + "base_rounded_total", + "base_in_words", + "column_break3", + "grand_total", + "rounding_adjustment", + "rounded_total", + "in_words", + "advance_paid", + "packing_list", + "packed_items", + "payment_schedule_section", + "payment_terms_template", + "payment_schedule", + "terms_section_break", + "tc_name", + "terms", + "more_info", + "inter_company_order_reference", + "project", + "party_account_currency", + "column_break_77", + "source", + "campaign", + "printing_details", + "language", + "letter_head", + "column_break4", + "select_print_heading", + "group_same_items", + "section_break_78", + "status", + "delivery_status", + "per_delivered", + "column_break_81", + "per_billed", + "billing_status", + "sales_team_section_break", + "sales_partner", + "column_break7", + "commission_rate", + "total_commission", + "section_break1", + "sales_team", + "subscription_section", + "from_date", + "to_date", + "column_break_108", + "auto_repeat", + "update_auto_repeat_reference" + ], + "fields": [ + { + "fieldname": "customer_section", + "fieldtype": "Section Break", + "options": "fa fa-user" + }, + { + "fieldname": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "allow_on_submit": 1, + "default": "{customer_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "SAL-ORD-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, + { + "bold": 1, + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Customer Name", + "read_only": 1 + }, + { + "default": "Sales", + "fieldname": "order_type", + "fieldtype": "Select", + "label": "Order Type", + "oldfieldname": "order_type", + "oldfieldtype": "Select", + "options": "\nSales\nMaintenance\nShopping Cart", + "print_hide": 1, + "reqd": 1 + }, + { + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Sales Order", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "print_hide": 1, + "remember_last_selected_value": 1, + "reqd": 1, + "width": "150px" + }, + { + "default": "Today", + "fieldname": "transaction_date", + "fieldtype": "Date", + "in_standard_filter": 1, + "label": "Date", + "no_copy": 1, + "oldfieldname": "transaction_date", + "oldfieldtype": "Date", + "reqd": 1, + "search_index": 1, + "width": "160px" + }, + { + "allow_on_submit": 1, + "fieldname": "delivery_date", + "fieldtype": "Date", + "in_list_view": 1, + "label": "Delivery Date", + "no_copy": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "po_no", + "fieldtype": "Data", + "label": "Customer's Purchase Order", + "oldfieldname": "po_no", + "oldfieldtype": "Data", + "width": "100px" + }, + { + "allow_on_submit": 1, + "depends_on": "eval:doc.po_no", + "fieldname": "po_date", + "fieldtype": "Date", + "label": "Customer's Purchase Order Date", + "oldfieldname": "po_date", + "oldfieldtype": "Date", + "width": "100px" + }, + { + "fieldname": "tax_id", + "fieldtype": "Data", + "label": "Tax Id", + "read_only": 1, + "width": "100px" + }, + { + "collapsible": 1, + "depends_on": "customer", + "fieldname": "contact_info", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-bullhorn" + }, + { + "allow_on_submit": 1, + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Customer Address", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Address", + "read_only": 1 + }, + { + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, + { + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, + { + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "label": "Mobile No", + "read_only": 1 + }, + { + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "company_address_display", + "fieldtype": "Small Text", + "read_only": 1 + }, + { + "fieldname": "company_address", + "fieldtype": "Link", + "label": "Company Address", + "options": "Address" + }, + { + "fieldname": "col_break46", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "allow_on_submit": 1, + "fieldname": "shipping_address_name", + "fieldtype": "Link", + "label": "Shipping Address Name", + "options": "Address", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "shipping_address", + "fieldtype": "Small Text", + "label": "Shipping Address", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, + { + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "currency_and_price_list", + "fieldtype": "Section Break", + "label": "Currency and Price List", + "options": "fa fa-tag", + "print_hide": 1 + }, + { + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "oldfieldname": "currency", + "oldfieldtype": "Select", + "options": "Currency", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "description": "Rate at which customer's currency is converted to company's base currency", + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "oldfieldname": "conversion_rate", + "oldfieldtype": "Currency", + "precision": "9", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "column_break2", + "fieldtype": "Column Break", + "width": "50%" + }, + { + "fieldname": "selling_price_list", + "fieldtype": "Link", + "label": "Price List", + "oldfieldname": "price_list_name", + "oldfieldtype": "Select", + "options": "Price List", + "print_hide": 1, + "reqd": 1, + "width": "100px" + }, + { + "fieldname": "price_list_currency", + "fieldtype": "Link", + "label": "Price List Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, + { + "description": "Rate at which Price list currency is converted to company's base currency", + "fieldname": "plc_conversion_rate", + "fieldtype": "Float", + "label": "Price List Exchange Rate", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, + { + "default": "0", + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, + { + "fieldname": "sec_warehouse", + "fieldtype": "Section Break" + }, + { + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Source Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, + { + "fieldname": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, + { + "fieldname": "scan_barcode", + "fieldtype": "Data", + "label": "Scan Barcode" + }, + { + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "sales_order_details", + "oldfieldtype": "Table", + "options": "Sales Order Item", + "reqd": 1 + }, + { + "fieldname": "pricing_rule_details", + "fieldtype": "Section Break", + "label": "Pricing Rules" + }, + { + "fieldname": "pricing_rules", + "fieldtype": "Table", + "label": "Pricing Rule Detail", + "options": "Pricing Rule Detail", + "read_only": 1 + }, + { + "fieldname": "section_break_31", + "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_33a", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, + { + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_net_total", + "fieldtype": "Currency", + "label": "Net Total (Company Currency)", + "oldfieldname": "net_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "column_break_33", + "fieldtype": "Column Break" + }, + { + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, + { + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "total_net_weight", + "fieldtype": "Float", + "label": "Total Net Weight", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "taxes_section", + "fieldtype": "Section Break", + "label": "Taxes and Charges", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category", + "print_hide": 1 + }, + { + "fieldname": "column_break_38", + "fieldtype": "Column Break" + }, + { + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Shipping Rule", + "oldfieldtype": "Button", + "options": "Shipping Rule", + "print_hide": 1 + }, + { + "fieldname": "section_break_40", + "fieldtype": "Section Break" + }, + { + "fieldname": "taxes_and_charges", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "oldfieldname": "charge", + "oldfieldtype": "Link", + "options": "Sales Taxes and Charges Template", + "print_hide": 1 + }, + { + "fieldname": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, + { + "collapsible": 1, + "fieldname": "sec_tax_breakup", + "fieldtype": "Section Break", + "label": "Tax Breakup" + }, + { + "fieldname": "other_charges_calculation", + "fieldtype": "Text", + "label": "Taxes and Charges Calculation", + "no_copy": 1, + "oldfieldtype": "HTML", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "section_break_43", + "fieldtype": "Section Break" + }, + { + "fieldname": "base_total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges (Company Currency)", + "oldfieldname": "other_charges_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "column_break_46", + "fieldtype": "Column Break" + }, + { + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "loyalty_points_redemption", + "fieldtype": "Section Break", + "hidden": 1, + "label": "Loyalty Points Redemption", + "print_hide": 1 + }, + { + "fieldname": "loyalty_points", + "fieldtype": "Int", + "hidden": 1, + "label": "Loyalty Points", + "read_only": 1 + }, + { + "fieldname": "loyalty_amount", + "fieldtype": "Currency", + "hidden": 1, + "label": "Loyalty Amount", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "discount_amount", + "fieldname": "section_break_48", + "fieldtype": "Section Break", + "label": "Additional Discount" + }, + { + "default": "Grand Total", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Additional Discount On", + "options": "\nGrand Total\nNet Total", + "print_hide": 1 + }, + { + "fieldname": "base_discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_50", + "fieldtype": "Column Break" + }, + { + "fieldname": "additional_discount_percentage", + "fieldtype": "Float", + "label": "Additional Discount Percentage", + "print_hide": 1 + }, + { + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount", + "options": "currency", + "print_hide": 1 + }, + { + "fieldname": "totals", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-money", + "print_hide": 1 + }, + { + "fieldname": "base_grand_total", + "fieldtype": "Currency", + "label": "Grand Total (Company Currency)", + "oldfieldname": "grand_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "base_rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "base_rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total (Company Currency)", + "oldfieldname": "rounded_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1, + "width": "150px" + }, + { + "description": "In Words will be visible once you save the Sales Order.", + "fieldname": "base_in_words", + "fieldtype": "Data", + "label": "In Words (Company Currency)", + "oldfieldname": "in_words", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "column_break3", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Grand Total", + "oldfieldname": "grand_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, + { + "bold": 1, + "fieldname": "rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total", + "oldfieldname": "rounded_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "read_only": 1, + "width": "150px" + }, + { + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "oldfieldname": "in_words_export", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1, + "width": "200px" + }, + { + "fieldname": "advance_paid", + "fieldtype": "Currency", + "label": "Advance Paid", + "no_copy": 1, + "options": "party_account_currency", + "print_hide": 1, + "read_only": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "packed_items", + "fieldname": "packing_list", + "fieldtype": "Section Break", + "label": "Packing List", + "oldfieldtype": "Section Break", + "options": "fa fa-suitcase", + "print_hide": 1 + }, + { + "fieldname": "packed_items", + "fieldtype": "Table", + "label": "Packed Items", + "options": "Packed Item", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "payment_schedule_section", + "fieldtype": "Section Break", + "label": "Payment Terms" + }, + { + "fieldname": "payment_terms_template", + "fieldtype": "Link", + "label": "Payment Terms Template", + "options": "Payment Terms Template", + "print_hide": 1 + }, + { + "fieldname": "payment_schedule", + "fieldtype": "Table", + "label": "Payment Schedule", + "no_copy": 1, + "options": "Payment Schedule", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "terms", + "fieldname": "terms_section_break", + "fieldtype": "Section Break", + "label": "Terms and Conditions", + "oldfieldtype": "Section Break", + "options": "fa fa-legal" + }, + { + "fieldname": "tc_name", + "fieldtype": "Link", + "label": "Terms", + "oldfieldname": "tc_name", + "oldfieldtype": "Link", + "options": "Terms and Conditions", + "print_hide": 1 + }, + { + "fieldname": "terms", + "fieldtype": "Text Editor", + "label": "Terms and Conditions Details", + "oldfieldname": "terms", + "oldfieldtype": "Text Editor" + }, + { + "collapsible": 1, + "collapsible_depends_on": "project", + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 + }, + { + "fieldname": "inter_company_order_reference", + "fieldtype": "Link", + "label": "Inter Company Order Reference", + "options": "Purchase Order" + }, + { + "description": "Track this Sales Order against any Project", + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project" + }, + { + "fieldname": "party_account_currency", + "fieldtype": "Link", + "hidden": 1, + "label": "Party Account Currency", + "no_copy": 1, + "options": "Currency", + "print_hide": 1, + "read_only": 1 + }, + { + "fieldname": "column_break_77", + "fieldtype": "Column Break" + }, + { + "fieldname": "source", + "fieldtype": "Link", + "label": "Source", + "oldfieldname": "source", + "oldfieldtype": "Select", + "options": "Lead Source", + "print_hide": 1 + }, + { + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "oldfieldname": "campaign", + "oldfieldtype": "Link", + "options": "Campaign", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "printing_details", + "fieldtype": "Section Break", + "label": "Printing Details" + }, + { + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "oldfieldname": "letter_head", + "oldfieldtype": "Select", + "options": "Letter Head", + "print_hide": 1 + }, + { + "fieldname": "column_break4", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "oldfieldname": "select_print_heading", + "oldfieldtype": "Link", + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, + { + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group same items", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_78", + "fieldtype": "Section Break", + "label": "Billing and Delivery Status", + "oldfieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nOn Hold\nTo Deliver and Bill\nTo Bill\nTo Deliver\nCompleted\nCancelled\nClosed", + "print_hide": 1, + "read_only": 1, + "reqd": 1, + "search_index": 1, + "width": "100px" + }, + { + "fieldname": "delivery_status", + "fieldtype": "Select", + "hidden": 1, + "in_standard_filter": 1, + "label": "Delivery Status", + "no_copy": 1, + "options": "Not Delivered\nFully Delivered\nPartly Delivered\nClosed\nNot Applicable", + "print_hide": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "description": "% of materials delivered against this Sales Order", + "fieldname": "per_delivered", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Delivered", + "no_copy": 1, + "oldfieldname": "per_delivered", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1, + "width": "100px" + }, + { + "fieldname": "column_break_81", + "fieldtype": "Column Break" + }, + { + "depends_on": "eval:!doc.__islocal", + "description": "% of materials billed against this Sales Order", + "fieldname": "per_billed", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Amount Billed", + "no_copy": 1, + "oldfieldname": "per_billed", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1, + "width": "100px" + }, + { + "fieldname": "billing_status", + "fieldtype": "Select", + "hidden": 1, + "in_standard_filter": 1, + "label": "Billing Status", + "no_copy": 1, + "options": "Not Billed\nFully Billed\nPartly Billed\nClosed", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "commission_rate", + "fieldname": "sales_team_section_break", + "fieldtype": "Section Break", + "label": "Commission", + "oldfieldtype": "Section Break", + "options": "fa fa-group", + "print_hide": 1 + }, + { + "fieldname": "sales_partner", + "fieldtype": "Link", + "label": "Sales Partner", + "oldfieldname": "sales_partner", + "oldfieldtype": "Link", + "options": "Sales Partner", + "print_hide": 1, + "width": "150px" + }, + { + "fieldname": "column_break7", + "fieldtype": "Column Break", + "print_hide": 1, + "width": "50%" + }, + { + "fieldname": "commission_rate", + "fieldtype": "Float", + "label": "Commission Rate", + "oldfieldname": "commission_rate", + "oldfieldtype": "Currency", + "print_hide": 1, + "width": "100px" + }, + { + "fieldname": "total_commission", + "fieldtype": "Currency", + "label": "Total Commission", + "oldfieldname": "total_commission", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1 + }, + { + "collapsible": 1, + "collapsible_depends_on": "sales_team", + "fieldname": "section_break1", + "fieldtype": "Section Break", + "label": "Sales Team", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "sales_team", + "fieldtype": "Table", + "label": "Sales Team1", + "oldfieldname": "sales_team", + "oldfieldtype": "Table", + "options": "Sales Team", + "print_hide": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "label": "Auto Repeat Section", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "from_date", + "fieldtype": "Date", + "label": "From Date", + "no_copy": 1 + }, + { + "allow_on_submit": 1, + "fieldname": "to_date", + "fieldtype": "Date", + "label": "To Date", + "no_copy": 1 + }, + { + "fieldname": "column_break_108", + "fieldtype": "Column Break" + }, + { + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "options": "Auto Repeat" + }, + { + "allow_on_submit": 1, + "depends_on": "eval: doc.auto_repeat", + "fieldname": "update_auto_repeat_reference", + "fieldtype": "Button", + "label": "Update Auto Repeat Reference" + }, + { + "fieldname": "contact_phone", + "fieldtype": "Data", + "label": "Phone", + "read_only": 1 + } + ], + "icon": "fa fa-file-text", + "idx": 105, + "is_submittable": 1, + "modified": "2019-09-12 02:13:56.308839", + "modified_by": "Administrator", + "module": "Selling", + "name": "Sales Order", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales Manager", + "set_user_permissions": 1, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Maintenance User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "email": 1, + "print": 1, + "read": 1, + "role": "Accounts User" + }, + { + "read": 1, + "report": 1, + "role": "Stock User" + }, + { + "permlevel": 1, + "read": 1, + "role": "Sales Manager", + "write": 1 + } + ], + "search_fields": "status,transaction_date,customer,customer_name, territory,order_type,company", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 09dc9a99327..12b9a8e96d2 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -72,9 +72,7 @@ class SalesOrder(SellingController): frappe.msgprint(_("Warning: Sales Order {0} already exists against Customer's Purchase Order {1}").format(so[0][0], self.po_no)) def validate_for_items(self): - check_list = [] for d in self.get('items'): - check_list.append(cstr(d.item_code)) # used for production plan d.transaction_date = self.transaction_date @@ -83,13 +81,6 @@ class SalesOrder(SellingController): where item_code = %s and warehouse = %s", (d.item_code, d.warehouse)) d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0 - # check for same entry multiple times - unique_chk_list = set(check_list) - if len(unique_chk_list) != len(check_list) and \ - not cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")): - frappe.msgprint(_("Same item has been entered multiple times"), - title=_("Warning"), indicator='orange') - def product_bundle_has_stock_item(self, product_bundle): """Returns true if product bundle has stock item""" ret = len(frappe.db.sql("""select i.name from tabItem i, `tabProduct Bundle Item` pbi @@ -217,7 +208,9 @@ class SalesOrder(SellingController): def check_credit_limit(self): # if bypass credit limit check is set to true (1) at sales order level, # then we need not to check credit limit and vise versa - if not cint(frappe.get_cached_value("Customer", self.customer, "bypass_credit_limit_check_at_sales_order")): + if not cint(frappe.db.get_value("Customer Credit Limit", + {'parent': self.customer, 'parenttype': 'Customer', 'company': self.company}, + "bypass_credit_limit_check")): check_credit_limit(self.customer, self.company) def check_nextdoc_docstatus(self): @@ -527,7 +520,7 @@ def make_material_request(source_name, target_doc=None): "doctype": "Material Request Item", "field_map": { "parent": "sales_order", - "stock_uom": "uom" + "uom": "stock_uom" }, "postprocess": update_item }, @@ -568,7 +561,7 @@ def make_project(source_name, target_doc=None): return doc @frappe.whitelist() -def make_delivery_note(source_name, target_doc=None): +def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False): def set_missing_values(source, target): target.ignore_pricing_rule = 1 target.run_method("set_missing_values") @@ -593,23 +586,13 @@ def make_delivery_note(source_name, target_doc=None): or item.get("buying_cost_center") \ or item_group.get("buying_cost_center") - target_doc = get_mapped_doc("Sales Order", source_name, { + mapper = { "Sales Order": { "doctype": "Delivery Note", "validation": { "docstatus": ["=", 1] } }, - "Sales Order Item": { - "doctype": "Delivery Note Item", - "field_map": { - "rate": "rate", - "name": "so_detail", - "parent": "against_sales_order", - }, - "postprocess": update_item, - "condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 - }, "Sales Taxes and Charges": { "doctype": "Sales Taxes and Charges", "add_if_empty": True @@ -618,7 +601,21 @@ def make_delivery_note(source_name, target_doc=None): "doctype": "Sales Team", "add_if_empty": True } - }, target_doc, set_missing_values) + } + + if not skip_item_mapping: + mapper["Sales Order Item"] = { + "doctype": "Delivery Note Item", + "field_map": { + "rate": "rate", + "name": "so_detail", + "parent": "against_sales_order", + }, + "postprocess": update_item, + "condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 + } + + target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values) return target_doc @@ -996,3 +993,33 @@ def make_raw_material_request(items, company, sales_order, project=None): def make_inter_company_purchase_order(source_name, target_doc=None): from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction return make_inter_company_transaction("Sales Order", source_name, target_doc) + +@frappe.whitelist() +def create_pick_list(source_name, target_doc=None): + def update_item_quantity(source, target, source_parent): + target.qty = flt(source.qty) - flt(source.delivered_qty) + target.stock_qty = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.conversion_factor) + + doc = get_mapped_doc('Sales Order', source_name, { + 'Sales Order': { + 'doctype': 'Pick List', + 'validation': { + 'docstatus': ['=', 1] + } + }, + 'Sales Order Item': { + 'doctype': 'Pick List Item', + 'field_map': { + 'parent': 'sales_order', + 'name': 'sales_order_item' + }, + 'postprocess': update_item_quantity, + 'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 + }, + }, target_doc) + + doc.purpose = 'Delivery against Sales Order' + + doc.set_item_locations() + + return doc diff --git a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py index aab6db2584f..4126bc6a704 100644 --- a/erpnext/selling/doctype/sales_order/sales_order_dashboard.py +++ b/erpnext/selling/doctype/sales_order/sales_order_dashboard.py @@ -17,7 +17,7 @@ def get_data(): 'transactions': [ { 'label': _('Fulfillment'), - 'items': ['Sales Invoice', 'Delivery Note'] + 'items': ['Sales Invoice', 'Pick List', 'Delivery Note'] }, { 'label': _('Purchasing'), diff --git a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_bypass_credit_limit_check.js b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_bypass_credit_limit_check.js index dbd58c19c56..79d798b9443 100644 --- a/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_bypass_credit_limit_check.js +++ b/erpnext/selling/doctype/sales_order/tests/test_sales_order_with_bypass_credit_limit_check.js @@ -1,7 +1,7 @@ QUnit.module('Sales Order'); QUnit.test("test_sales_order_with_bypass_credit_limit_check", function(assert) { -//#PR : 10861, Author : ashish-greycube & jigneshpshah, Email:mr.ashish.shah@gmail.com +//#PR : 10861, Author : ashish-greycube & jigneshpshah, Email:mr.ashish.shah@gmail.com assert.expect(2); let done = assert.async(); frappe.run_serially([ @@ -10,8 +10,10 @@ QUnit.test("test_sales_order_with_bypass_credit_limit_check", function(assert) { () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), () => frappe.timeout(1), () => cur_frm.set_value("customer_name", "Test Customer 10"), - () => cur_frm.set_value("credit_limit", 100.00), - () => cur_frm.set_value("bypass_credit_limit_check_at_sales_order", 1), + () => cur_frm.add_child('credit_limits', { + 'company': cur_frm.doc.company || '_Test Company' + 'credit_limit': 1000, + 'bypass_credit_limit_check': 1}), // save form () => cur_frm.save(), () => frappe.timeout(1), @@ -22,10 +24,10 @@ QUnit.test("test_sales_order_with_bypass_credit_limit_check", function(assert) { () => frappe.timeout(1), () => cur_frm.set_value("item_code", "Test Product 10"), () => cur_frm.set_value("item_group", "Products"), - () => cur_frm.set_value("standard_rate", 100), + () => cur_frm.set_value("standard_rate", 100), // save form () => cur_frm.save(), - () => frappe.timeout(1), + () => frappe.timeout(1), () => { return frappe.tests.make('Sales Order', [ @@ -46,11 +48,11 @@ QUnit.test("test_sales_order_with_bypass_credit_limit_check", function(assert) { () => frappe.tests.click_button('Yes'), () => frappe.timeout(3), () => { - + assert.ok(cur_frm.doc.status=="To Deliver and Bill", "It is submited. Credit limit is NOT checked for sales order"); - }, + }, () => done() ]); }); diff --git a/erpnext/selling/doctype/sales_order/tests/test_sales_order_without_bypass_credit_limit_check.js b/erpnext/selling/doctype/sales_order/tests/test_sales_order_without_bypass_credit_limit_check.js index 4e81fb065f6..8de39f9aa30 100644 --- a/erpnext/selling/doctype/sales_order/tests/test_sales_order_without_bypass_credit_limit_check.js +++ b/erpnext/selling/doctype/sales_order/tests/test_sales_order_without_bypass_credit_limit_check.js @@ -1,7 +1,7 @@ QUnit.module('Sales Order'); QUnit.test("test_sales_order_without_bypass_credit_limit_check", function(assert) { -//#PR : 10861, Author : ashish-greycube & jigneshpshah, Email:mr.ashish.shah@gmail.com +//#PR : 10861, Author : ashish-greycube & jigneshpshah, Email:mr.ashish.shah@gmail.com assert.expect(2); let done = assert.async(); frappe.run_serially([ @@ -10,8 +10,10 @@ QUnit.test("test_sales_order_without_bypass_credit_limit_check", function(assert () => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(), () => frappe.timeout(1), () => cur_frm.set_value("customer_name", "Test Customer 11"), - () => cur_frm.set_value("credit_limit", 100.00), - () => cur_frm.set_value("bypass_credit_limit_check_at_sales_order", 0), + () => cur_frm.add_child('credit_limits', { + 'credit_limit': 1000, + 'company': '_Test Company', + 'bypass_credit_limit_check': 1}), // save form () => cur_frm.save(), () => frappe.timeout(1), @@ -21,10 +23,10 @@ QUnit.test("test_sales_order_without_bypass_credit_limit_check", function(assert () => frappe.click_link('Edit in full page'), () => cur_frm.set_value("item_code", "Test Product 11"), () => cur_frm.set_value("item_group", "Products"), - () => cur_frm.set_value("standard_rate", 100), + () => cur_frm.set_value("standard_rate", 100), // save form () => cur_frm.save(), - () => frappe.timeout(1), + () => frappe.timeout(1), () => { return frappe.tests.make('Sales Order', [ @@ -45,14 +47,14 @@ QUnit.test("test_sales_order_without_bypass_credit_limit_check", function(assert () => frappe.tests.click_button('Yes'), () => frappe.timeout(3), () => { - - if (cur_dialog.body.innerText.match(/^Credit limit has been crossed for customer.*$/)) - { + + if (cur_dialog.body.innerText.match(/^Credit limit has been crossed for customer.*$/)) + { /*Match found */ assert.ok(true, "Credit Limit crossed message received"); } - - + + }, () => cur_dialog.cancel(), () => done() diff --git a/erpnext/selling/doctype/sms_center/sms_center.py b/erpnext/selling/doctype/sms_center/sms_center.py index bb6ba1ffce4..289b045e1c1 100644 --- a/erpnext/selling/doctype/sms_center/sms_center.py +++ b/erpnext/selling/doctype/sms_center/sms_center.py @@ -31,7 +31,7 @@ class SMSCenter(Document): self.sales_partner.replace("'", "\'") or " and ifnull(dl.link_name, '') != ''" if self.send_to in ['All Contact', 'All Customer Contact', 'All Supplier Contact', 'All Sales Partner Contact']: rec = frappe.db.sql("""select CONCAT(ifnull(c.first_name,''), ' ', ifnull(c.last_name,'')), - c.mobile_no from `tabContact` c, `tabDynamic Link` dl where ifnull(c.mobile_no,'')!='' and + c.phone from `tabContact` c, `tabDynamic Link` dl where ifnull(c.phone,'')!='' and c.docstatus != 2 and dl.parent = c.name%s""" % where_clause) elif self.send_to == 'All Lead (Open)': diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.js b/erpnext/selling/page/point_of_sale/point_of_sale.js index d233b41787d..d2c2d70dbe7 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -4,7 +4,7 @@ frappe.provide('erpnext.pos'); frappe.pages['point-of-sale'].on_page_load = function(wrapper) { frappe.ui.make_app_page({ parent: wrapper, - title: 'Point of Sale', + title: __('Point of Sale'), single_column: true }); diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index ee0d72be7bb..cd50568cf98 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -29,7 +29,7 @@ def execute(filters=None): if customer_naming_type == "Naming Series": row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal, - d.bypass_credit_limit_check_at_sales_order, d.is_frozen, + d.bypass_credit_limit_check, d.is_frozen, d.disabled] else: row = [d.name, credit_limit, outstanding_amt, bal, @@ -60,9 +60,15 @@ def get_details(filters): conditions = "" if filters.get("customer"): - conditions += " where name = %(customer)s" - - return frappe.db.sql("""select name, customer_name, - bypass_credit_limit_check_at_sales_order, is_frozen, disabled from `tabCustomer` %s - """ % conditions, filters, as_dict=1) + conditions += " AND c.name = " + filters.get("customer") + return frappe.db.sql("""SELECT + c.name, c.customer_name, + ccl.bypass_credit_limit_check, + c.is_frozen, c.disabled + FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl + WHERE + c.name = ccl.parent + AND ccl.company = %s + {0} + """.format(conditions), (filters.get("company")), as_dict=1) #nosec diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py index 670b4e98bf3..14d80315823 100644 --- a/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py +++ b/erpnext/selling/report/pending_so_items_for_purchase_request/pending_so_items_for_purchase_request.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import cint,cstr +from frappe.utils import flt def execute(filters=None): columns = get_columns() @@ -100,7 +100,7 @@ def get_data(): so.transaction_date, so.customer, so.territory, - sum(so_item.qty) as net_qty, + sum(so_item.qty) as total_qty, so.company FROM `tabSales Order` so, `tabSales Order Item` so_item WHERE @@ -111,10 +111,15 @@ def get_data(): so.name,so_item.item_code """, as_dict = 1) + sales_orders = [row.name for row in sales_order_entry] mr_records = frappe.get_all("Material Request Item", - {"sales_order_item": ("!=",""), "docstatus": 1}, + {"sales_order": ("in", sales_orders), "docstatus": 1}, ["parent", "qty", "sales_order", "item_code"]) + bundled_item_map = get_packed_items(sales_orders) + + item_with_product_bundle = get_items_with_product_bundle([row.item_code for row in sales_order_entry]) + materials_request_dict = {} for record in mr_records: @@ -131,27 +136,64 @@ def get_data(): if record.parent not in details.get('material_requests'): details['material_requests'].append(record.parent) - pending_so=[] + pending_so = [] for so in sales_order_entry: - # fetch all the material request records for a sales order item - key = (so.name, so.item_code) - materials_request = materials_request_dict.get(key) or {} + if so.item_code not in item_with_product_bundle: + material_requests_against_so = materials_request_dict.get((so.name, so.item_code)) or {} + # check for pending sales order + if flt(so.total_qty) > flt(material_requests_against_so.get('qty')): + so_record = { + "item_code": so.item_code, + "item_name": so.item_name, + "description": so.description, + "sales_order_no": so.name, + "date": so.transaction_date, + "material_request": ','.join(material_requests_against_so.get('material_requests', [])), + "customer": so.customer, + "territory": so.territory, + "so_qty": so.total_qty, + "requested_qty": material_requests_against_so.get('qty'), + "pending_qty": so.total_qty - flt(material_requests_against_so.get('qty')), + "company": so.company + } + pending_so.append(so_record) + else: + for item in bundled_item_map.get((so.name, so.item_code)): + material_requests_against_so = materials_request_dict.get((so.name, item.item_code)) or {} + if flt(item.qty) > flt(material_requests_against_so.get('qty')): + so_record = { + "item_code": item.item_code, + "item_name": item.item_name, + "description": item.description, + "sales_order_no": so.name, + "date": so.transaction_date, + "material_request": ','.join(material_requests_against_so.get('material_requests', [])), + "customer": so.customer, + "territory": so.territory, + "so_qty": item.qty, + "requested_qty": material_requests_against_so.get('qty', 0), + "pending_qty": item.qty - flt(material_requests_against_so.get('qty', 0)), + "company": so.company + } + pending_so.append(so_record) - # check for pending sales order - if cint(so.net_qty) > cint(materials_request.get('qty')): - so_record = { - "item_code": so.item_code, - "item_name": so.item_name, - "description": so.description, - "sales_order_no": so.name, - "date": so.transaction_date, - "material_request": ','.join(materials_request.get('material_requests', [])), - "customer": so.customer, - "territory": so.territory, - "so_qty": so.net_qty, - "requested_qty": cint(materials_request.get('qty')), - "pending_qty": so.net_qty - cint(materials_request.get('qty')), - "company": so.company - } - pending_so.append(so_record) - return pending_so \ No newline at end of file + + return pending_so + +def get_items_with_product_bundle(item_list): + bundled_items = frappe.get_all("Product Bundle", filters = [ + ("new_item_code", "IN", item_list) + ], fields = ["new_item_code"]) + + return [d.new_item_code for d in bundled_items] + +def get_packed_items(sales_order_list): + packed_items = frappe.get_all("Packed Item", filters = [ + ("parent", "IN", sales_order_list) + ], fields = ["parent_item", "item_code", "qty", "item_name", "description", "parent"]) + + bundled_item_map = frappe._dict() + for d in packed_items: + bundled_item_map.setdefault((d.parent, d.parent_item), []).append(d) + + return bundled_item_map diff --git a/erpnext/selling/report/sales_analytics/sales_analytics.py b/erpnext/selling/report/sales_analytics/sales_analytics.py index 8a5e50a61e7..72767f06890 100644 --- a/erpnext/selling/report/sales_analytics/sales_analytics.py +++ b/erpnext/selling/report/sales_analytics/sales_analytics.py @@ -40,6 +40,16 @@ class Analytics(object): "fieldtype": "Data", "width": 140 }) + + if self.filters.tree_type == "Item": + self.columns.append({ + "label": _("UOM"), + "fieldname": 'stock_uom', + "fieldtype": "Link", + "options": "UOM", + "width": 100 + }) + for end_date in self.periodic_daterange: period = self.get_period(end_date) self.columns.append({ @@ -129,7 +139,7 @@ class Analytics(object): value_field = 'qty' self.entries = frappe.db.sql(""" - select i.item_code as entity, i.item_name as entity_name, i.{value_field} as value_field, s.{date_field} + select i.item_code as entity, i.item_name as entity_name, i.stock_uom, i.{value_field} as value_field, s.{date_field} from `tab{doctype} Item` i , `tab{doctype}` s where s.name = i.parent and i.docstatus = 1 and s.company = %s and s.{date_field} between %s and %s @@ -198,6 +208,10 @@ class Analytics(object): total += amount row["total"] = total + + if self.filters.tree_type == "Item": + row["stock_uom"] = period_data.get("stock_uom") + self.data.append(row) def get_rows_by_group(self): @@ -232,6 +246,9 @@ class Analytics(object): self.entity_periodic_data.setdefault(d.entity, frappe._dict()).setdefault(period, 0.0) self.entity_periodic_data[d.entity][period] += flt(d.value_field) + if self.filters.tree_type == "Item": + self.entity_periodic_data[d.entity]['stock_uom'] = d.stock_uom + def get_period(self, posting_date): if self.filters.range == 'Weekly': period = "Week " + str(posting_date.isocalendar()[1]) + " " + str(posting_date.year) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index da29d20503d..584391e1e01 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -252,7 +252,7 @@ class Company(NestedSet): def set_mode_of_payment_account(self): cash = frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name') if cash and self.default_cash_account \ - and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': 'Cash'}): + and not frappe.db.get_value('Mode of Payment Account', {'company': self.name, 'parent': cash}): mode_of_payment = frappe.get_doc('Mode of Payment', cash) mode_of_payment.append('accounts', { 'company': self.name, diff --git a/erpnext/setup/doctype/customer_group/customer_group.json b/erpnext/setup/doctype/customer_group/customer_group.json index c130e795415..3565b4b38a9 100644 --- a/erpnext/setup/doctype/customer_group/customer_group.json +++ b/erpnext/setup/doctype/customer_group/customer_group.json @@ -1,553 +1,194 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, + "_comments": "[]", "allow_import": 1, "allow_rename": 1, "autoname": "field:customer_group_name", - "beta": 0, "creation": "2013-01-10 16:34:23", - "custom": 0, - "docstatus": 0, "doctype": "DocType", "document_type": "Setup", - "editable_grid": 0, + "field_order": [ + "customer_group_name", + "parent_customer_group", + "is_group", + "cb0", + "default_price_list", + "payment_terms", + "lft", + "rgt", + "old_parent", + "default_receivable_account", + "accounts", + "credit_limit_section", + "credit_limits" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "customer_group_name", "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Customer Group Name", - "length": 0, "no_copy": 1, "oldfieldname": "customer_group_name", "oldfieldtype": "Data", - "permlevel": 0, - "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": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "parent_customer_group", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Parent Customer Group", - "length": 0, - "no_copy": 0, "oldfieldname": "parent_customer_group", "oldfieldtype": "Link", - "options": "Customer Group", - "permlevel": 0, - "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": "Customer Group" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, "bold": 1, - "collapsible": 0, - "columns": 0, + "default": "0", "description": "Only leaf nodes are allowed in transaction", "fieldname": "is_group", "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, "in_list_view": 1, - "in_standard_filter": 0, "label": "Is Group", - "length": 0, - "no_copy": 0, "oldfieldname": "is_group", - "oldfieldtype": "Select", - "options": "", - "permlevel": 0, - "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 + "oldfieldtype": "Select" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "cb0", - "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, - "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": "default_price_list", "fieldtype": "Link", - "hidden": 0, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "Default Price List", - "length": 0, - "no_copy": 0, - "options": "Price List", - "permlevel": 0, - "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": "Price List" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", "fieldname": "payment_terms", "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": "Default Payment Terms Template", - "length": 0, - "no_copy": 0, - "options": "Payment Terms Template", - "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": "Payment Terms Template" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "credit_limit", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Credit Limit", - "length": 0, - "no_copy": 0, - "permlevel": 1, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "lft", "fieldtype": "Int", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "lft", - "length": 0, "no_copy": 1, "oldfieldname": "lft", "oldfieldtype": "Int", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, "report_hide": 1, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "rgt", "fieldtype": "Int", "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "rgt", - "length": 0, "no_copy": 1, "oldfieldname": "rgt", "oldfieldtype": "Int", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, "report_hide": 1, - "reqd": 0, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "search_index": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", "fieldname": "old_parent", "fieldtype": "Link", "hidden": 1, "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, "label": "old_parent", - "length": 0, "no_copy": 1, "oldfieldname": "old_parent", "oldfieldtype": "Data", "options": "Customer Group", - "permlevel": 0, "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "report_hide": 1 }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fieldname": "default_receivable_account", "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": "Default Receivable Account", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "Default Receivable Account" }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "depends_on": "eval:!doc.__islocal", "description": "Mention if non-standard receivable account applicable", "fieldname": "accounts", "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": "Accounts", - "length": 0, - "no_copy": 0, - "options": "Party Account", - "permlevel": 0, - "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": "Party Account" + }, + { + "fieldname": "credit_limit_section", + "fieldtype": "Section Break", + "label": "Credit Limits" + }, + { + "fieldname": "credit_limits", + "fieldtype": "Table", + "label": "Credit Limit", + "options": "Customer Credit Limit" } ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, "icon": "fa fa-sitemap", "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:26:05.935871", + "modified": "2019-09-06 12:40:14.954697", "modified_by": "Administrator", "module": "Setup", "name": "Customer Group", "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales Manager" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" }, { - "amend": 0, - "cancel": 0, "create": 1, "delete": 1, "email": 1, "export": 1, - "if_owner": 0, "import": 1, - "permlevel": 0, "print": 1, "read": 1, "report": 1, "role": "Sales Master Manager", "set_user_permissions": 1, "share": 1, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, "role": "Sales Master Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, "write": 1 }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales User" }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, "permlevel": 1, - "print": 0, "read": 1, - "report": 0, - "role": "Sales Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 + "role": "Sales Manager" } ], "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, "search_fields": "parent_customer_group", "show_name_in_global_search": 1, - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + "sort_order": "DESC" } \ No newline at end of file diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py index a39e246c683..fa7bc504b68 100644 --- a/erpnext/setup/doctype/global_defaults/global_defaults.py +++ b/erpnext/setup/doctype/global_defaults/global_defaults.py @@ -58,7 +58,7 @@ class GlobalDefaults(Document): # Make property setters to hide rounded total fields for doctype in ("Quotation", "Sales Order", "Sales Invoice", "Delivery Note", - "Supplier Quotation", "Purchase Order"): + "Supplier Quotation", "Purchase Order", "Purchase Invoice"): make_property_setter(doctype, "base_rounded_total", "hidden", self.disable_rounded_total, "Check") make_property_setter(doctype, "base_rounded_total", "print_hide", 1, "Check") diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 33ab992568b..5603f17a54b 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe import copy +from frappe import _ from frappe.utils import nowdate, cint, cstr from frappe.utils.nestedset import NestedSet from frappe.website.website_generator import WebsiteGenerator @@ -26,6 +27,11 @@ class ItemGroup(NestedSet, WebsiteGenerator): def validate(self): super(ItemGroup, self).validate() + + if not self.parent_item_group and not frappe.flags.in_test: + if frappe.db.exists("Item Group", _('All Item Groups')): + self.parent_item_group = _('All Item Groups') + self.make_route() def on_update(self): diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py index cb7ad380142..b2cffbbf0d8 100644 --- a/erpnext/setup/doctype/naming_series/naming_series.py +++ b/erpnext/setup/doctype/naming_series/naming_series.py @@ -151,7 +151,7 @@ class NamingSeries(Document): def insert_series(self, series): """insert series if missing""" - if not frappe.db.get_value('Series', series, 'name', order_by="name"): + if frappe.db.get_value('Series', series, 'name', order_by="name") == None: frappe.db.sql("insert into tabSeries (name, current) values (%s, 0)", (series)) def update_series_start(self): diff --git a/erpnext/setup/setup_wizard/data/dashboard_charts.py b/erpnext/setup/setup_wizard/data/dashboard_charts.py new file mode 100644 index 00000000000..bb8c1319bf3 --- /dev/null +++ b/erpnext/setup/setup_wizard/data/dashboard_charts.py @@ -0,0 +1,117 @@ +from __future__ import unicode_literals +from frappe import _ +import frappe +import json + +def get_company_for_dashboards(): + company = frappe.defaults.get_defaults().company + if company: + return company + else: + company_list = frappe.get_list("Company") + if company_list: + return company_list[0].name + return None + +def get_default_dashboards(): + company = frappe.get_doc("Company", get_company_for_dashboards()) + income_account = company.default_income_account or get_account("Income Account", company.name) + expense_account = company.default_expense_account or get_account("Expense Account", company.name) + bank_account = company.default_bank_account or get_account("Bank", company.name) + + return { + "Dashboards": [ + { + "doctype": "Dashboard", + "dashboard_name": "Accounts", + "charts": [ + { "chart": "Outgoing Bills (Sales Invoice)" }, + { "chart": "Incoming Bills (Purchase Invoice)" }, + { "chart": "Bank Balance" }, + { "chart": "Income" }, + { "chart": "Expenses" } + ] + } + ], + "Charts": [ + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Income", + "timespan": "Last Year", + "color": None, + "filters_json": json.dumps({"company": company.name, "account": income_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Expenses", + "timespan": "Last Year", + "color": None, + "filters_json": json.dumps({"company": company.name, "account": expense_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Quarterly", + "chart_name": "Bank Balance", + "timespan": "Last Year", + "color": "#ffb868", + "filters_json": json.dumps({"company": company.name, "account": bank_account}), + "source": "Account Balance Timeline", + "chart_type": "Custom", + "timeseries": 1, + "owner": "Administrator", + "type": "Line", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "chart_name": "Incoming Bills (Purchase Invoice)", + "timespan": "Last Year", + "color": "#a83333", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Purchase Invoice", + "type": "Bar", + "width": "Half" + }, + { + "doctype": "Dashboard Chart", + "time_interval": "Monthly", + "chart_name": "Outgoing Bills (Sales Invoice)", + "timespan": "Last Year", + "color": "#7b933d", + "value_based_on": "base_grand_total", + "filters_json": json.dumps({}), + "chart_type": "Sum", + "timeseries": 1, + "based_on": "posting_date", + "owner": "Administrator", + "document_type": "Sales Invoice", + "type": "Bar", + "width": "Half" + } + ] + } + +def get_account(account_type, company): + accounts = frappe.get_list("Account", filters={"account_type": account_type, "company": company}) + if accounts: + return accounts[0].name diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 4b734df61d5..b65716536d9 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -475,13 +475,14 @@ def install_defaults(args=None): frappe.db.set_value("Company", args.company_name, "default_bank_account", bank_account.name, update_modified=False) - return doc except RootNotEditable: frappe.throw(_("Bank account cannot be named as {0}").format(args.bank_account)) except frappe.DuplicateEntryError: # bank account same as a CoA entry pass + add_dashboards() + # Now, with fixtures out of the way, onto concrete stuff records = [ @@ -499,6 +500,27 @@ def install_defaults(args=None): make_records(records) +def add_dashboards(): + from erpnext.setup.setup_wizard.data.dashboard_charts import get_company_for_dashboards + + if not get_company_for_dashboards(): + return + + from erpnext.setup.setup_wizard.data.dashboard_charts import get_default_dashboards + from frappe.modules.import_file import import_file_by_path + + dashboard_data = get_default_dashboards() + + # create account balance timeline before creating dashbaord charts + doctype = "dashboard_chart_source" + docname = "account_balance_timeline" + folder = os.path.dirname(frappe.get_module("erpnext.accounts").__file__) + doc_path = os.path.join(folder, doctype, docname, docname) + ".json" + import_file_by_path(doc_path, force=0, for_sync=True) + + make_records(dashboard_data["Charts"]) + make_records(dashboard_data["Dashboards"]) + def get_fy_details(fy_start_date, fy_end_date): start_year = getdate(fy_start_date).year diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index 0922f3d1a07..db2c3277d0f 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -336,19 +336,20 @@ def set_price_list_and_rate(quotation, cart_settings): def _set_price_list(quotation, cart_settings): """Set price list based on customer or shopping cart default""" - if quotation.selling_price_list: - return + from erpnext.accounts.party import get_default_price_list # check if customer price list exists selling_price_list = None if quotation.party_name: - from erpnext.accounts.party import get_default_price_list - selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name)) + selling_price_list = frappe.db.get_value('Customer', quotation.party_name, 'default_price_list') # else check for territory based price list if not selling_price_list: selling_price_list = cart_settings.price_list + if not selling_price_list and quotation.party_name: + selling_price_list = get_default_price_list(frappe.get_doc("Customer", quotation.party_name)) + quotation.selling_price_list = selling_price_list def set_taxes(quotation, cart_settings): diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json index b5f2c3404ee..1116273aced 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.json +++ b/erpnext/stock/doctype/delivery_note/delivery_note.json @@ -1,4514 +1,1308 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2013-05-24 19:29:09", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 0, + "allow_import": 1, + "autoname": "naming_series:", + "creation": "2013-05-24 19:29:09", + "doctype": "DocType", + "document_type": "Document", + "field_order": [ + "delivery_to_section", + "column_break0", + "title", + "naming_series", + "customer", + "customer_name", + "column_break1", + "amended_from", + "company", + "posting_date", + "posting_time", + "set_posting_time", + "is_return", + "issue_credit_note", + "return_against", + "customer_po_details", + "po_no", + "section_break_18", + "pick_list", + "column_break_17", + "po_date", + "contact_info", + "shipping_address_name", + "shipping_address", + "contact_person", + "contact_display", + "contact_mobile", + "contact_email", + "col_break21", + "customer_address", + "tax_id", + "address_display", + "company_address", + "company_address_display", + "currency_and_price_list", + "currency", + "conversion_rate", + "col_break23", + "selling_price_list", + "price_list_currency", + "plc_conversion_rate", + "ignore_pricing_rule", + "sec_warehouse", + "set_warehouse", + "col_break_warehouse", + "to_warehouse", + "items_section", + "scan_barcode", + "items", + "pricing_rule_details", + "pricing_rules", + "packing_list", + "packed_items", + "product_bundle_help", + "section_break_31", + "total_qty", + "base_total", + "base_net_total", + "column_break_33", + "total", + "net_total", + "total_net_weight", + "taxes_section", + "tax_category", + "column_break_39", + "shipping_rule", + "section_break_41", + "taxes_and_charges", + "taxes", + "sec_tax_breakup", + "other_charges_calculation", + "section_break_44", + "base_total_taxes_and_charges", + "column_break_47", + "total_taxes_and_charges", + "section_break_49", + "apply_discount_on", + "base_discount_amount", + "column_break_51", + "additional_discount_percentage", + "discount_amount", + "totals", + "base_grand_total", + "base_rounding_adjustment", + "base_rounded_total", + "base_in_words", + "column_break3", + "grand_total", + "rounding_adjustment", + "rounded_total", + "in_words", + "terms_section_break", + "tc_name", + "terms", + "transporter_info", + "transporter", + "driver", + "lr_no", + "vehicle_no", + "col_break34", + "transporter_name", + "driver_name", + "lr_date", + "more_info", + "project", + "campaign", + "source", + "column_break5", + "per_billed", + "customer_group", + "territory", + "printing_details", + "letter_head", + "select_print_heading", + "language", + "column_break_88", + "print_without_amount", + "group_same_items", + "section_break_83", + "status", + "per_installed", + "installation_status", + "column_break_89", + "excise_page", + "instructions", + "subscription_section", + "auto_repeat", + "sales_team_section_break", + "sales_partner", + "column_break7", + "commission_rate", + "total_commission", + "section_break1", + "sales_team" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "delivery_to_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, - "label": "Delivery To", - "length": 0, - "no_copy": 0, - "options": "fa fa-user", - "permlevel": 0, - "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": "delivery_to_section", + "fieldtype": "Section Break", + "label": "Delivery To", + "options": "fa fa-user" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break0", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "50%", - "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": "column_break0", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_width": "50%", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "{customer_name}", - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "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 - }, + "allow_on_submit": 1, + "default": "{customer_name}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, + "label": "Title", + "no_copy": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Series", - "length": 0, - "no_copy": 1, - "oldfieldname": "naming_series", - "oldfieldtype": "Select", - "options": "MAT-DN-.YYYY.-", - "permlevel": 0, - "print_hide": 1, - "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": 1, - "translatable": 0, - "unique": 0 - }, + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "oldfieldname": "naming_series", + "oldfieldtype": "Select", + "options": "MAT-DN-.YYYY.-", + "print_hide": 1, + "reqd": 1, + "set_only_once": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "customer", - "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": "Customer", - "length": 0, - "no_copy": 0, - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "reqd": 1, + "search_index": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fetch_from": "customer.customer_name", - "fieldname": "customer_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": "Customer Name", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "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 - }, + "bold": 1, + "depends_on": "customer", + "fetch_from": "customer.customer_name", + "fieldname": "customer_name", + "fieldtype": "Data", + "in_global_search": 1, + "label": "Customer Name", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break1", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "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": 1, - "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, - "oldfieldname": "amended_from", - "oldfieldtype": "Data", - "options": "Delivery Note", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "ignore_user_permissions": 1, + "label": "Amended From", + "no_copy": 1, + "oldfieldname": "amended_from", + "oldfieldtype": "Data", + "options": "Delivery Note", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "company", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "oldfieldname": "company", - "oldfieldtype": "Link", - "options": "Company", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "company", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Company", + "oldfieldname": "company", + "oldfieldtype": "Link", + "options": "Company", + "print_hide": 1, + "print_width": "150px", + "remember_last_selected_value": 1, + "reqd": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "depends_on": "", - "fieldname": "posting_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": "Date", - "length": 0, - "no_copy": 1, - "oldfieldname": "posting_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", + "label": "Date", + "no_copy": 1, + "oldfieldname": "posting_date", + "oldfieldtype": "Date", + "print_width": "100px", + "reqd": 1, + "search_index": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "description": "", - "fieldname": "posting_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": "Posting Time", - "length": 0, - "no_copy": 0, - "oldfieldname": "posting_time", - "oldfieldtype": "Time", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "posting_time", + "fieldtype": "Time", + "label": "Posting Time", + "oldfieldname": "posting_time", + "oldfieldtype": "Time", + "print_hide": 1, + "print_width": "100px", + "reqd": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.docstatus==0", - "fieldname": "set_posting_time", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Edit Posting Date and Time", - "length": 0, - "no_copy": 0, - "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 - }, + "default": "0", + "depends_on": "eval:doc.docstatus==0", + "fieldname": "set_posting_time", + "fieldtype": "Check", + "label": "Edit Posting Date and Time", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_return", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Is Return", - "length": 0, - "no_copy": 1, - "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 - }, + "default": "0", + "fieldname": "is_return", + "fieldtype": "Check", + "label": "Is Return", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "is_return", - "fieldname": "issue_credit_note", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Issue Credit Note", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "depends_on": "is_return", + "fieldname": "issue_credit_note", + "fieldtype": "Check", + "label": "Issue Credit Note" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "is_return", - "fieldname": "return_against", - "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": "Return Against Delivery Note", - "length": 0, - "no_copy": 1, - "options": "Delivery Note", - "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 - }, + "depends_on": "is_return", + "fieldname": "return_against", + "fieldtype": "Link", + "label": "Return Against Delivery Note", + "no_copy": 1, + "options": "Delivery Note", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "po_no", - "columns": 0, - "fieldname": "customer_po_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer PO Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "collapsible_depends_on": "po_no", + "fieldname": "customer_po_details", + "fieldtype": "Section Break", + "label": "Customer PO Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "po_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": "Customer's Purchase Order No", - "length": 0, - "no_copy": 1, - "oldfieldname": "po_no", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "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_on_submit": 1, + "fieldname": "po_no", + "fieldtype": "Data", + "label": "Customer's Purchase Order No", + "no_copy": 1, + "oldfieldname": "po_no", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_17", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "column_break_17", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.po_no", - "fieldname": "po_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": "Customer's Purchase Order Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "po_date", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "depends_on": "eval:doc.po_no", + "fieldname": "po_date", + "fieldtype": "Date", + "label": "Customer's Purchase Order Date", + "oldfieldname": "po_date", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "100px", + "read_only": 1, "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "depends_on": "customer", - "fieldname": "contact_info", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Address and Contact", - "length": 0, - "no_copy": 0, - "options": "fa fa-bullhorn", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "depends_on": "customer", + "fieldname": "contact_info", + "fieldtype": "Section Break", + "label": "Address and Contact", + "options": "fa fa-bullhorn" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_address_name", - "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": "Shipping Address", - "length": 0, - "no_copy": 0, - "options": "Address", - "permlevel": 0, - "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 - }, + "fieldname": "shipping_address_name", + "fieldtype": "Link", + "label": "Shipping Address", + "options": "Address", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_address", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Shipping Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "shipping_address", + "fieldtype": "Small Text", + "label": "Shipping Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_person", - "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": "Contact Person", - "length": 0, - "no_copy": 0, - "options": "Contact", - "permlevel": 0, - "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 - }, + "fieldname": "contact_person", + "fieldtype": "Link", + "label": "Contact Person", + "options": "Contact", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_display", - "fieldtype": "Small Text", - "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": "Contact", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "contact_display", + "fieldtype": "Small Text", + "in_global_search": 1, + "label": "Contact", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_mobile", - "fieldtype": "Small Text", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Mobile No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "contact_mobile", + "fieldtype": "Small Text", + "hidden": 1, + "label": "Mobile No", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "contact_email", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Contact Email", - "length": 0, - "no_copy": 0, - "options": "Email", - "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 - }, + "fieldname": "contact_email", + "fieldtype": "Data", + "hidden": 1, + "label": "Contact Email", + "options": "Email", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break21", - "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, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "50%", - "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": "col_break21", + "fieldtype": "Column Break", + "print_width": "50%", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "customer", - "fieldname": "customer_address", - "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": "Billing Address Name", - "length": 0, - "no_copy": 0, - "options": "Address", - "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 - }, + "depends_on": "customer", + "fieldname": "customer_address", + "fieldtype": "Link", + "label": "Billing Address Name", + "options": "Address", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_id", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Tax Id", - "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 - }, + "fieldname": "tax_id", + "fieldtype": "Data", + "label": "Tax Id", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Billing Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "address_display", + "fieldtype": "Small Text", + "label": "Billing Address", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_address", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company Address Name", - "length": 0, - "no_copy": 0, - "options": "Address", - "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_address", + "fieldtype": "Link", + "label": "Company Address Name", + "options": "Address" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "company_address_display", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company Address", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "company_address_display", + "fieldtype": "Small Text", + "label": "Company Address" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "currency_and_price_list", - "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": "Currency and Price List", - "length": 0, - "no_copy": 0, - "options": "fa fa-tag", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "fieldname": "currency_and_price_list", + "fieldtype": "Section Break", + "label": "Currency and Price List", + "options": "fa fa-tag" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "currency", - "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": "Currency", - "length": 0, - "no_copy": 0, - "oldfieldname": "currency", - "oldfieldtype": "Select", - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "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 - }, + "fieldname": "currency", + "fieldtype": "Link", + "label": "Currency", + "oldfieldname": "currency", + "oldfieldtype": "Select", + "options": "Currency", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which customer's currency is converted to company's base currency", - "fieldname": "conversion_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, - "oldfieldname": "conversion_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "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 - }, + "description": "Rate at which customer's currency is converted to company's base currency", + "fieldname": "conversion_rate", + "fieldtype": "Float", + "label": "Exchange Rate", + "oldfieldname": "conversion_rate", + "oldfieldtype": "Currency", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break23", - "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, - "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": "col_break23", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "selling_price_list", - "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": "Price List", - "length": 0, - "no_copy": 0, - "oldfieldname": "price_list_name", - "oldfieldtype": "Select", - "options": "Price List", - "permlevel": 0, - "print_hide": 1, - "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 - }, + "fieldname": "selling_price_list", + "fieldtype": "Link", + "label": "Price List", + "oldfieldname": "price_list_name", + "oldfieldtype": "Select", + "options": "Price List", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "price_list_currency", - "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": "Price List Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "price_list_currency", + "fieldtype": "Link", + "label": "Price List Currency", + "options": "Currency", + "print_hide": 1, + "read_only": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Rate at which Price list currency is converted to company's base currency", - "fieldname": "plc_conversion_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": "Price List Exchange Rate", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "9", - "print_hide": 1, - "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 - }, + "description": "Rate at which Price list currency is converted to company's base currency", + "fieldname": "plc_conversion_rate", + "fieldtype": "Float", + "label": "Price List Exchange Rate", + "precision": "9", + "print_hide": 1, + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "ignore_pricing_rule", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Ignore Pricing Rule", - "length": 0, - "no_copy": 1, - "permlevel": 1, - "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 - }, + "default": "0", + "fieldname": "ignore_pricing_rule", + "fieldtype": "Check", + "label": "Ignore Pricing Rule", + "no_copy": 1, + "permlevel": 1, + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sec_warehouse", - "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 - }, + "fieldname": "sec_warehouse", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "set_warehouse", - "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": "Set Source Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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 - }, + "fieldname": "set_warehouse", + "fieldtype": "Link", + "label": "Set Source Warehouse", + "options": "Warehouse", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break_warehouse", - "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 - }, + "fieldname": "col_break_warehouse", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Required only for sample item.", - "fieldname": "to_warehouse", - "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": "To Warehouse", - "length": 0, - "no_copy": 1, - "oldfieldname": "to_warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "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 - }, + "description": "Required only for sample item.", + "fieldname": "to_warehouse", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "To Warehouse", + "no_copy": 1, + "oldfieldname": "to_warehouse", + "oldfieldtype": "Link", + "options": "Warehouse", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items_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, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-shopping-cart", - "permlevel": 0, - "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": "items_section", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-shopping-cart" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "scan_barcode", - "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": "Scan Barcode", - "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 - }, + "fieldname": "scan_barcode", + "fieldtype": "Data", + "label": "Scan Barcode" + }, { - "allow_bulk_edit": 1, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "items", - "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": "Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "delivery_note_details", - "oldfieldtype": "Table", - "options": "Delivery Note Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_bulk_edit": 1, + "fieldname": "items", + "fieldtype": "Table", + "label": "Items", + "oldfieldname": "delivery_note_details", + "oldfieldtype": "Table", + "options": "Delivery Note Item", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "fieldname": "pricing_rule_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Pricing Rules", - "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 - }, + "fieldname": "pricing_rule_details", + "fieldtype": "Section Break", + "label": "Pricing Rules" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", - "columns": 0, - "fieldname": "pricing_rules", - "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": "Pricing Rule Detail", - "length": 0, - "no_copy": 0, - "options": "Pricing Rule Detail", - "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 - }, + "fieldname": "pricing_rules", + "fieldtype": "Table", + "label": "Pricing Rule Detail", + "options": "Pricing Rule Detail", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "packed_items", - "columns": 0, - "fieldname": "packing_list", - "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": "Packing List", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-suitcase", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "packed_items", + "fieldname": "packing_list", + "fieldtype": "Section Break", + "label": "Packing List", + "oldfieldtype": "Section Break", + "options": "fa fa-suitcase", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "packed_items", - "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": "Packed Items", - "length": 0, - "no_copy": 0, - "oldfieldname": "packing_details", - "oldfieldtype": "Table", - "options": "Packed Item", - "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 - }, + "fieldname": "packed_items", + "fieldtype": "Table", + "label": "Packed Items", + "oldfieldname": "packing_details", + "oldfieldtype": "Table", + "options": "Packed Item", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "product_bundle_help", - "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": "Product Bundle Help", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "product_bundle_help", + "fieldtype": "HTML", + "label": "Product Bundle Help", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_31", - "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, - "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": "section_break_31", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_qty", - "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": "Total Quantity", - "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 - }, + "fieldname": "total_qty", + "fieldtype": "Float", + "label": "Total Quantity", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_total", + "fieldtype": "Currency", + "label": "Total (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "net_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "base_net_total", + "fieldtype": "Currency", + "label": "Net Total (Company Currency)", + "oldfieldname": "net_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "column_break_33", - "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, - "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": "column_break_33", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, + "fieldname": "total", + "fieldtype": "Currency", + "label": "Total", + "options": "currency", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "net_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Net Total", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, + "fieldname": "net_total", + "fieldtype": "Currency", + "label": "Net Total", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_net_weight", - "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": "Total Net Weight", - "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 - }, + "fieldname": "total_net_weight", + "fieldtype": "Float", + "label": "Total Net Weight", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes_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, - "label": "Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "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": "taxes_section", + "fieldtype": "Section Break", + "label": "Taxes and Charges", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_category", - "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": "Tax Category", - "length": 0, - "no_copy": 0, - "options": "Tax Category", - "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 - }, + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_39", - "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, - "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": "column_break_39", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "shipping_rule", - "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": "Shipping Rule", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Button", - "options": "Shipping Rule", - "permlevel": 0, - "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 - }, + "fieldname": "shipping_rule", + "fieldtype": "Link", + "label": "Shipping Rule", + "oldfieldtype": "Button", + "options": "Shipping Rule", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_41", - "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, - "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": "section_break_41", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.", - "fieldname": "taxes_and_charges", - "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": "Sales Taxes and Charges Template", - "length": 0, - "no_copy": 0, - "oldfieldname": "charge", - "oldfieldtype": "Link", - "options": "Sales Taxes and Charges Template", - "permlevel": 0, - "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 - }, + "description": "If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.", + "fieldname": "taxes_and_charges", + "fieldtype": "Link", + "label": "Sales Taxes and Charges Template", + "oldfieldname": "charge", + "oldfieldtype": "Link", + "options": "Sales Taxes and Charges Template", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "taxes", - "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": "Sales Taxes and Charges", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges", - "oldfieldtype": "Table", - "options": "Sales Taxes and Charges", - "permlevel": 0, - "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": "taxes", + "fieldtype": "Table", + "label": "Sales Taxes and Charges", + "oldfieldname": "other_charges", + "oldfieldtype": "Table", + "options": "Sales Taxes and Charges" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "sec_tax_breakup", - "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": "Tax Breakup", - "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 - }, + "collapsible": 1, + "fieldname": "sec_tax_breakup", + "fieldtype": "Section Break", + "label": "Tax Breakup" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "other_charges_calculation", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Taxes and Charges Calculation", - "length": 0, - "no_copy": 1, - "oldfieldtype": "HTML", - "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 - }, + "fieldname": "other_charges_calculation", + "fieldtype": "Text", + "label": "Taxes and Charges Calculation", + "no_copy": 1, + "oldfieldtype": "HTML", + "print_hide": 1, + "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_44", - "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, - "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": "section_break_44", + "fieldtype": "Section Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "other_charges_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "base_total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges (Company Currency)", + "oldfieldname": "other_charges_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_47", - "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 - }, + "fieldname": "column_break_47", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_taxes_and_charges", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Taxes and Charges", - "length": 0, - "no_copy": 0, - "options": "currency", - "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 - }, + "fieldname": "total_taxes_and_charges", + "fieldtype": "Currency", + "label": "Total Taxes and Charges", + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "discount_amount", - "columns": 0, - "fieldname": "section_break_49", - "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": "Additional Discount", - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "discount_amount", + "fieldname": "section_break_49", + "fieldtype": "Section Break", + "label": "Additional Discount" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Grand Total", - "fieldname": "apply_discount_on", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Apply Additional Discount On", - "length": 0, - "no_copy": 0, - "options": "\nGrand Total\nNet Total", - "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 - }, + "default": "Grand Total", + "fieldname": "apply_discount_on", + "fieldtype": "Select", + "label": "Apply Additional Discount On", + "options": "\nGrand Total\nNet Total", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount (Company Currency)", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount (Company Currency)", + "options": "Company:company:default_currency", + "print_hide": 1, + "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_51", - "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, - "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": "column_break_51", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "additional_discount_percentage", - "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": "Additional Discount Percentage", - "length": 0, - "no_copy": 0, - "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 - }, + "fieldname": "additional_discount_percentage", + "fieldtype": "Float", + "label": "Additional Discount Percentage", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "discount_amount", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Additional Discount Amount", - "length": 0, - "no_copy": 0, - "options": "currency", - "permlevel": 0, - "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 - }, + "fieldname": "discount_amount", + "fieldtype": "Currency", + "label": "Additional Discount Amount", + "options": "currency", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "totals", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-money", - "permlevel": 0, - "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": "totals", + "fieldtype": "Section Break", + "oldfieldtype": "Section Break", + "options": "fa fa-money" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_grand_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Grand Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "base_grand_total", + "fieldtype": "Currency", + "label": "Grand Total (Company Currency)", + "oldfieldname": "grand_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment (Company Currency)", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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 - }, + "fieldname": "base_rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment (Company Currency)", + "no_copy": 1, + "options": "Company:company:default_currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "base_rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "base_rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total (Company Currency)", + "oldfieldname": "rounded_total", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "In Words will be visible once you save the Delivery Note.", - "fieldname": "base_in_words", - "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": "In Words (Company Currency)", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "200px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "description": "In Words will be visible once you save the Delivery Note.", + "fieldname": "base_in_words", + "fieldtype": "Data", + "label": "In Words (Company Currency)", + "oldfieldname": "in_words", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "200px", + "read_only": 1, "width": "200px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break3", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "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": "column_break3", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "grand_total", - "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": "Grand Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "grand_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "fieldname": "grand_total", + "fieldtype": "Currency", + "in_list_view": 1, + "label": "Grand Total", + "oldfieldname": "grand_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "rounding_adjustment", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounding Adjustment", - "length": 0, - "no_copy": 1, - "options": "currency", - "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 - }, + "fieldname": "rounding_adjustment", + "fieldtype": "Currency", + "label": "Rounding Adjustment", + "no_copy": 1, + "options": "currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "rounded_total", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Rounded Total", - "length": 0, - "no_copy": 0, - "oldfieldname": "rounded_total_export", - "oldfieldtype": "Currency", - "options": "currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "bold": 1, + "fieldname": "rounded_total", + "fieldtype": "Currency", + "label": "Rounded Total", + "oldfieldname": "rounded_total_export", + "oldfieldtype": "Currency", + "options": "currency", + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "In Words (Export) will be visible once you save the Delivery Note.", - "fieldname": "in_words", - "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": "In Words", - "length": 0, - "no_copy": 0, - "oldfieldname": "in_words_export", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "description": "In Words (Export) will be visible once you save the Delivery Note.", + "fieldname": "in_words", + "fieldtype": "Data", + "label": "In Words", + "oldfieldname": "in_words_export", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "terms", - "columns": 0, - "fieldname": "terms_section_break", - "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": "Terms and Conditions", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-legal", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "terms", + "fieldname": "terms_section_break", + "fieldtype": "Section Break", + "label": "Terms and Conditions", + "oldfieldtype": "Section Break", + "options": "fa fa-legal" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tc_name", - "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": "Terms", - "length": 0, - "no_copy": 0, - "oldfieldname": "tc_name", - "oldfieldtype": "Link", - "options": "Terms and Conditions", - "permlevel": 0, - "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 - }, + "fieldname": "tc_name", + "fieldtype": "Link", + "label": "Terms", + "oldfieldname": "tc_name", + "oldfieldtype": "Link", + "options": "Terms and Conditions", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "terms", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Terms and Conditions Details", - "length": 0, - "no_copy": 0, - "oldfieldname": "terms", - "oldfieldtype": "Text Editor", - "permlevel": 0, - "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": "terms", + "fieldtype": "Text Editor", + "label": "Terms and Conditions Details", + "oldfieldname": "terms", + "oldfieldtype": "Text Editor" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "transporter", - "columns": 0, - "fieldname": "transporter_info", - "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": "Transporter Info", - "length": 0, - "no_copy": 0, - "options": "fa fa-truck", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "transporter", + "fieldname": "transporter_info", + "fieldtype": "Section Break", + "label": "Transporter Info", + "options": "fa fa-truck", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "transporter", - "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": "Transporter", - "length": 0, - "no_copy": 0, - "options": "Supplier", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "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": "transporter", + "fieldtype": "Link", + "label": "Transporter", + "options": "Supplier", + "print_hide": 1, + "print_width": "150px", "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "driver", - "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": "Driver", - "length": 0, - "no_copy": 0, - "options": "Driver", - "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 - }, + "fieldname": "driver", + "fieldtype": "Link", + "label": "Driver", + "options": "Driver", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "lr_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": "Transport Receipt No", - "length": 0, - "no_copy": 0, - "oldfieldname": "lr_no", - "oldfieldtype": "Data", - "options": "", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "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": "lr_no", + "fieldtype": "Data", + "label": "Transport Receipt No", + "oldfieldname": "lr_no", + "oldfieldtype": "Data", + "print_hide": 1, + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "vehicle_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": "Vehicle No", - "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 - }, + "fieldname": "vehicle_no", + "fieldtype": "Data", + "label": "Vehicle No", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "col_break34", - "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, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "50%", - "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": "col_break34", + "fieldtype": "Column Break", + "print_width": "50%", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "transporter.name", - "fieldname": "transporter_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": "Transporter Name", - "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 - }, + "fetch_from": "transporter.name", + "fieldname": "transporter_name", + "fieldtype": "Data", + "label": "Transporter Name", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "driver.full_name", - "fieldname": "driver_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": "Driver Name", - "length": 0, - "no_copy": 0, - "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 - }, + "fetch_from": "driver.full_name", + "fieldname": "driver_name", + "fieldtype": "Data", + "label": "Driver Name", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "description": "", - "fieldname": "lr_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": "Transport Receipt Date", - "length": 0, - "no_copy": 0, - "oldfieldname": "lr_date", - "oldfieldtype": "Date", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "default": "Today", + "fieldname": "lr_date", + "fieldtype": "Date", + "label": "Transport Receipt Date", + "oldfieldname": "lr_date", + "oldfieldtype": "Date", + "print_hide": 1, + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "more_info", - "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": "More Information", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-file-text", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "fieldname": "more_info", + "fieldtype": "Section Break", + "label": "More Information", + "oldfieldtype": "Section Break", + "options": "fa fa-file-text", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Track this Delivery Note against any Project", - "fieldname": "project", - "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": "Project", - "length": 0, - "no_copy": 0, - "oldfieldname": "project", - "oldfieldtype": "Link", - "options": "Project", - "permlevel": 0, - "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 - }, + "description": "Track this Delivery Note against any Project", + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "oldfieldname": "project", + "oldfieldtype": "Link", + "options": "Project" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "campaign", - "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": "Campaign", - "length": 0, - "no_copy": 0, - "oldfieldname": "campaign", - "oldfieldtype": "Link", - "options": "Campaign", - "permlevel": 0, - "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 - }, + "fieldname": "campaign", + "fieldtype": "Link", + "label": "Campaign", + "oldfieldname": "campaign", + "oldfieldtype": "Link", + "options": "Campaign", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "source", - "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": "Source", - "length": 0, - "no_copy": 0, - "oldfieldname": "source", - "oldfieldtype": "Select", - "options": "Lead Source", - "permlevel": 0, - "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 - }, + "fieldname": "source", + "fieldtype": "Link", + "label": "Source", + "oldfieldname": "source", + "oldfieldtype": "Select", + "options": "Lead Source", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break5", - "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, - "oldfieldtype": "Column Break", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "50%", - "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": "column_break5", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "print_hide": 1, + "print_width": "50%", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "per_billed", - "fieldtype": "Percent", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "% Amount Billed", - "length": 0, - "no_copy": 1, - "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 - }, + "fieldname": "per_billed", + "fieldtype": "Percent", + "label": "% Amount Billed", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "customer_group", - "fieldtype": "Link", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Customer Group", - "length": 0, - "no_copy": 0, - "options": "Customer Group", - "permlevel": 0, - "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 - }, + "fieldname": "customer_group", + "fieldtype": "Link", + "hidden": 1, + "label": "Customer Group", + "options": "Customer Group", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "territory", - "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": "Territory", - "length": 0, - "no_copy": 0, - "options": "Territory", - "permlevel": 0, - "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 - }, + "fieldname": "territory", + "fieldtype": "Link", + "label": "Territory", + "options": "Territory", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "printing_details", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Printing Details", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "collapsible": 1, + "fieldname": "printing_details", + "fieldtype": "Section Break", + "label": "Printing Details" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "letter_head", - "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": "Letter Head", - "length": 0, - "no_copy": 0, - "oldfieldname": "letter_head", - "oldfieldtype": "Link", - "options": "Letter Head", - "permlevel": 0, - "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 - }, + "allow_on_submit": 1, + "fieldname": "letter_head", + "fieldtype": "Link", + "label": "Letter Head", + "oldfieldname": "letter_head", + "oldfieldtype": "Link", + "options": "Letter Head", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "select_print_heading", - "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": "Print Heading", - "length": 0, - "no_copy": 1, - "oldfieldname": "select_print_heading", - "oldfieldtype": "Link", - "options": "Print Heading", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 1, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "select_print_heading", + "fieldtype": "Link", + "label": "Print Heading", + "no_copy": 1, + "oldfieldname": "select_print_heading", + "oldfieldtype": "Link", + "options": "Print Heading", + "print_hide": 1, + "report_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "language", - "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": "Print Language", - "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 - }, + "fieldname": "language", + "fieldtype": "Data", + "label": "Print Language", + "print_hide": 1, + "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_88", - "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 - }, + "fieldname": "column_break_88", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "print_without_amount", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Print Without Amount", - "length": 0, - "no_copy": 0, - "oldfieldname": "print_without_amount", - "oldfieldtype": "Check", - "permlevel": 0, - "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 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "print_without_amount", + "fieldtype": "Check", + "label": "Print Without Amount", + "oldfieldname": "print_without_amount", + "oldfieldtype": "Check", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "group_same_items", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Group same items", - "length": 0, - "no_copy": 0, - "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 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "group_same_items", + "fieldtype": "Check", + "label": "Group same items", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "columns": 0, - "fieldname": "section_break_83", - "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": "Status", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "collapsible": 1, + "fieldname": "section_break_83", + "fieldtype": "Section Break", + "label": "Status" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 1, - "set_only_once": 0, - "translatable": 0, - "unique": 0, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "\nDraft\nTo Bill\nCompleted\nCancelled\nClosed", + "print_hide": 1, + "print_width": "150px", + "read_only": 1, + "reqd": 1, + "search_index": 1, "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:!doc.__islocal", - "description": "% of materials delivered against this Delivery Note", - "fieldname": "per_installed", - "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": "% Installed", - "length": 0, - "no_copy": 1, - "oldfieldname": "per_installed", - "oldfieldtype": "Currency", - "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 - }, + "depends_on": "eval:!doc.__islocal", + "description": "% of materials delivered against this Delivery Note", + "fieldname": "per_installed", + "fieldtype": "Percent", + "in_list_view": 1, + "label": "% Installed", + "no_copy": 1, + "oldfieldname": "per_installed", + "oldfieldtype": "Currency", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "installation_status", - "fieldtype": "Select", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Installation Status", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "fieldname": "installation_status", + "fieldtype": "Select", + "hidden": 1, + "label": "Installation Status", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_89", - "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, - "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": "column_break_89", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "excise_page", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Excise Page Number", - "length": 0, - "no_copy": 0, - "oldfieldname": "excise_page", - "oldfieldtype": "Data", - "permlevel": 0, - "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 - }, + "fieldname": "excise_page", + "fieldtype": "Data", + "hidden": 1, + "label": "Excise Page Number", + "oldfieldname": "excise_page", + "oldfieldtype": "Data", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "instructions", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Instructions", - "length": 0, - "no_copy": 0, - "oldfieldname": "instructions", - "oldfieldtype": "Text", - "permlevel": 0, - "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": "instructions", + "fieldtype": "Text", + "label": "Instructions", + "oldfieldname": "instructions", + "oldfieldtype": "Text" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "subscription_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, - "label": "Subscription Section", - "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 - }, + "fieldname": "subscription_section", + "fieldtype": "Section Break", + "label": "Subscription Section" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "auto_repeat", - "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": "Auto Repeat", - "length": 0, - "no_copy": 1, - "options": "Auto Repeat", - "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 - }, + "fieldname": "auto_repeat", + "fieldtype": "Link", + "label": "Auto Repeat", + "no_copy": 1, + "options": "Auto Repeat", + "print_hide": 1, + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "total_commission", - "columns": 0, - "fieldname": "sales_team_section_break", - "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": "Commission", - "length": 0, - "no_copy": 0, - "oldfieldtype": "Section Break", - "options": "fa fa-group", - "permlevel": 0, - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "total_commission", + "fieldname": "sales_team_section_break", + "fieldtype": "Section Break", + "label": "Commission", + "oldfieldtype": "Section Break", + "options": "fa fa-group", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_partner", - "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": "Sales Partner", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_partner", - "oldfieldtype": "Link", - "options": "Sales Partner", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "150px", - "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": "sales_partner", + "fieldtype": "Link", + "label": "Sales Partner", + "oldfieldname": "sales_partner", + "oldfieldtype": "Link", + "options": "Sales Partner", + "print_hide": 1, + "print_width": "150px", "width": "150px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break7", - "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, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "50%", - "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": "column_break7", + "fieldtype": "Column Break", + "print_hide": 1, + "print_width": "50%", "width": "50%" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "commission_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": "Commission Rate (%)", - "length": 0, - "no_copy": 0, - "oldfieldname": "commission_rate", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": "100px", - "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": "commission_rate", + "fieldtype": "Float", + "label": "Commission Rate (%)", + "oldfieldname": "commission_rate", + "oldfieldtype": "Currency", + "print_hide": 1, + "print_width": "100px", "width": "100px" - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_commission", - "fieldtype": "Currency", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Total Commission", - "length": 0, - "no_copy": 0, - "oldfieldname": "total_commission", - "oldfieldtype": "Currency", - "options": "Company:company:default_currency", - "permlevel": 0, - "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 - }, + "fieldname": "total_commission", + "fieldtype": "Currency", + "label": "Total Commission", + "oldfieldname": "total_commission", + "oldfieldtype": "Currency", + "options": "Company:company:default_currency", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 1, - "collapsible_depends_on": "sales_team", - "columns": 0, - "fieldname": "section_break1", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Sales Team", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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 - }, + "collapsible": 1, + "collapsible_depends_on": "sales_team", + "fieldname": "section_break1", + "fieldtype": "Section Break", + "label": "Sales Team", + "print_hide": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sales_team", - "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": "Sales Team1", - "length": 0, - "no_copy": 0, - "oldfieldname": "sales_team", - "oldfieldtype": "Table", - "options": "Sales Team", - "permlevel": 0, - "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 + "allow_on_submit": 1, + "fieldname": "sales_team", + "fieldtype": "Table", + "label": "Sales Team", + "oldfieldname": "sales_team", + "oldfieldtype": "Table", + "options": "Sales Team", + "print_hide": 1 + }, + { + "fieldname": "pick_list", + "fieldtype": "Link", + "hidden": 1, + "label": "Pick List", + "options": "Pick List", + "read_only": 1 + }, + { + "fieldname": "section_break_18", + "fieldtype": "Section Break" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-truck", - "idx": 146, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-02-13 01:06:29.783590", - "modified_by": "Administrator", - "module": "Stock", - "name": "Delivery Note", - "owner": "Administrator", + ], + "icon": "fa fa-truck", + "idx": 146, + "is_submittable": 1, + "modified": "2019-08-26 07:37:39.766014", + "modified_by": "Administrator", + "module": "Stock", + "name": "Delivery Note", + "owner": "Administrator", "permissions": [ { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 1, - "cancel": 1, - "create": 1, - "delete": 1, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 1, - "submit": 1, + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "share": 1, + "submit": 1, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 0, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 0 - }, + "read": 1, + "report": 1, + "role": "Accounts User" + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 0, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 1, - "print": 0, - "read": 1, - "report": 0, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "permlevel": 1, + "read": 1, + "role": "Stock Manager", "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 1, - "search_fields": "status,customer,customer_name, territory,base_grand_total", - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "timeline_field": "customer", - "title_field": "title", - "track_changes": 1, - "track_seen": 1, - "track_views": 0 -} + ], + "search_fields": "status,customer,customer_name, territory,base_grand_total", + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "timeline_field": "customer", + "title_field": "title", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index 2de9b975dac..bdb49e4919c 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -166,24 +166,7 @@ class DeliveryNote(SellingController): frappe.throw(_("Customer {0} does not belong to project {1}").format(self.customer, self.project)) def validate_for_items(self): - check_list, chk_dupl_itm = [], [] - if cint(frappe.db.get_single_value("Selling Settings", "allow_multiple_items")): - return - for d in self.get('items'): - e = [d.item_code, d.description, d.warehouse, d.against_sales_order or d.against_sales_invoice, d.batch_no or ''] - f = [d.item_code, d.description, d.against_sales_order or d.against_sales_invoice] - - if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1: - if e in check_list: - frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code)) - else: - check_list.append(e) - else: - if f in chk_dupl_itm: - frappe.msgprint(_("Note: Item {0} entered multiple times").format(d.item_code)) - else: - chk_dupl_itm.append(f) #Customer Provided parts will have zero valuation rate if frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item'): d.allow_zero_valuation_rate = 1 @@ -251,8 +234,10 @@ class DeliveryNote(SellingController): extra_amount = 0 validate_against_credit_limit = False - bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer", self.customer, - "bypass_credit_limit_check_at_sales_order")) + bypass_credit_limit_check_at_sales_order = cint(frappe.db.get_value("Customer Credit Limit", + filters={'parent': self.customer, 'parenttype': 'Customer', 'company': self.company}, + fieldname="bypass_credit_limit_check")) + if bypass_credit_limit_check_at_sales_order: validate_against_credit_limit = True extra_amount = self.base_grand_total diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py index bc95c965bc5..91b6f4c6063 100644 --- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py @@ -701,6 +701,7 @@ def create_delivery_note(**args): "qty": args.qty or 1, "rate": args.rate or 100, "conversion_factor": 1.0, + "allow_zero_valuation_rate": args.allow_zero_valuation_rate or 1, "expense_account": "Cost of Goods Sold - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC", "serial_no": args.serial_no, diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py index a253811b278..77d322ed28d 100644 --- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py +++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py @@ -238,7 +238,7 @@ class DeliveryTrip(Document): try: directions = maps_client.directions(**directions_data) except Exception as e: - frappe.throw(_(e.message)) + frappe.throw(_(e)) return directions[0] if directions else False diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 383fb61f519..46efd4ee26f 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -904,7 +904,7 @@ "description": "Item Image (if not slideshow)", "fieldname": "website_image", "fieldtype": "Attach", - "label": "Image" + "label": "Website Image" }, { "fieldname": "thumbnail", @@ -1040,7 +1040,7 @@ "idx": 2, "image_field": "image", "max_attachments": 1, - "modified": "2019-07-12 12:18:13.977931", + "modified": "2019-09-03 18:34:13.977931", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 15687293447..518fe748894 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -124,6 +124,7 @@ class Item(WebsiteGenerator): self.update_defaults_from_item_group() self.validate_auto_reorder_enabled_in_stock_settings() self.cant_change() + self.update_show_in_website() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -476,6 +477,10 @@ class Item(WebsiteGenerator): [self.remove(d) for d in to_remove] + def update_show_in_website(self): + if self.disabled: + self.show_in_website = False + def update_template_tables(self): template = frappe.get_doc("Item", self.variant_of) diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py index 30675b54b3e..4c496cb59a7 100644 --- a/erpnext/stock/doctype/item_price/item_price.py +++ b/erpnext/stock/doctype/item_price/item_price.py @@ -32,10 +32,16 @@ class ItemPrice(Document): def update_price_list_details(self): if self.price_list: - self.buying, self.selling, self.currency = \ - frappe.db.get_value("Price List", - {"name": self.price_list, "enabled": 1}, - ["buying", "selling", "currency"]) + price_list_details = frappe.db.get_value("Price List", + {"name": self.price_list, "enabled": 1}, + ["buying", "selling", "currency"]) + + if not price_list_details: + link = frappe.utils.get_link_to_form('Price List', self.price_list) + frappe.throw("The price list {0} does not exists or disabled". + format(link)) + + self.buying, self.selling, self.currency = price_list_details def update_item_details(self): if self.item_code: diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js index 96e31ff6fff..99195c300da 100644 --- a/erpnext/stock/doctype/material_request/material_request.js +++ b/erpnext/stock/doctype/material_request/material_request.js @@ -8,6 +8,7 @@ frappe.ui.form.on('Material Request', { setup: function(frm) { frm.custom_make_buttons = { 'Stock Entry': 'Issue Material', + 'Pick List': 'Pick List', 'Purchase Order': 'Purchase Order', 'Request for Quotation': 'Request for Quotation', 'Supplier Quotation': 'Supplier Quotation', @@ -55,8 +56,13 @@ frappe.ui.form.on('Material Request', { if (frm.doc.docstatus == 1 && frm.doc.status != 'Stopped') { if (flt(frm.doc.per_ordered, 2) < 100) { - // make + let add_create_pick_list_button = () => { + frm.add_custom_button(__('Pick List'), + () => frm.events.create_pick_list(frm), __('Create')); + } + if (frm.doc.material_request_type === "Material Transfer") { + add_create_pick_list_button(); frm.add_custom_button(__("Transfer Material"), () => frm.events.make_stock_entry(frm), __('Create')); } @@ -258,6 +264,13 @@ frappe.ui.form.on('Material Request', { }); }, + create_pick_list: (frm) => { + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.material_request.material_request.create_pick_list", + frm: frm + }); + }, + raise_work_orders: function(frm) { frappe.call({ method:"erpnext.stock.doctype.material_request.material_request.raise_work_orders", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index f2fe44879db..44e890cc50e 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -502,3 +502,28 @@ def raise_work_orders(material_request): frappe.throw(_("Productions Orders cannot be raised for:") + '\n' + new_line_sep(errors)) return work_orders + +@frappe.whitelist() +def create_pick_list(source_name, target_doc=None): + doc = get_mapped_doc('Material Request', source_name, { + 'Material Request': { + 'doctype': 'Pick List', + 'field_map': { + 'material_request_type': 'purpose' + }, + 'validation': { + 'docstatus': ['=', 1] + } + }, + 'Material Request Item': { + 'doctype': 'Pick List Item', + 'field_map': { + 'name': 'material_request_item', + 'qty': 'stock_qty' + }, + }, + }, target_doc) + + doc.set_item_locations() + + return doc \ No newline at end of file diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py index adfab86cc4c..cbd64784c63 100644 --- a/erpnext/stock/doctype/material_request/material_request_dashboard.py +++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py @@ -8,7 +8,7 @@ def get_data(): 'transactions': [ { 'label': _('Related'), - 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', "Stock Entry"] + 'items': ['Request for Quotation', 'Supplier Quotation', 'Purchase Order', 'Stock Entry', 'Pick List'] }, { 'label': _('Manufacturing'), diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json index 6c8b2b1d050..b089e759a0f 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.json +++ b/erpnext/stock/doctype/packed_item/packed_item.json @@ -1,685 +1,203 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-22 01:28:00", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2013-02-22 01:28:00", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "parent_item", + "item_code", + "item_name", + "column_break_5", + "description", + "section_break_6", + "warehouse", + "target_warehouse", + "column_break_9", + "qty", + "section_break_9", + "serial_no", + "column_break_11", + "batch_no", + "section_break_13", + "actual_qty", + "projected_qty", + "column_break_16", + "uom", + "page_break", + "prevdoc_doctype", + "parent_detail_docname" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "parent_item", - "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": "Parent Item", - "length": 0, - "no_copy": 0, - "oldfieldname": "parent_item", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "parent_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Parent Item", + "oldfieldname": "parent_item", + "oldfieldtype": "Link", + "options": "Item", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_code", - "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": 0, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_code", - "oldfieldtype": "Link", - "options": "Item", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_code", + "fieldtype": "Link", + "in_global_search": 1, + "in_list_view": 1, + "label": "Item Code", + "oldfieldname": "item_code", + "oldfieldtype": "Link", + "options": "Item", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "item_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": "Item Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "item_name", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "oldfieldname": "item_name", + "oldfieldtype": "Data", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_5", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_5", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text Editor", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": "300px", - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "in_list_view": 1, + "label": "Description", + "oldfieldname": "description", + "oldfieldtype": "Text", + "print_width": "300px", "width": "300px" - }, + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_6", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "warehouse", - "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": "From Warehouse", - "length": 0, - "no_copy": 0, - "oldfieldname": "warehouse", - "oldfieldtype": "Link", - "options": "Warehouse", - "permlevel": 0, - "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": "warehouse", + "fieldtype": "Link", + "label": "From Warehouse", + "oldfieldname": "warehouse", + "oldfieldtype": "Link", + "options": "Warehouse" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "target_warehouse", - "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": "To Warehouse (Optional)", - "length": 0, - "no_copy": 0, - "options": "Warehouse", - "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, - "unique": 0 - }, + "fieldname": "target_warehouse", + "fieldtype": "Link", + "label": "To Warehouse (Optional)", + "options": "Warehouse", + "print_hide": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_9", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_9", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "qty", - "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": "Qty", - "length": 0, - "no_copy": 0, - "oldfieldname": "qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty", + "oldfieldname": "qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_9", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Serial No", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "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": "serial_no", + "fieldtype": "Text", + "label": "Serial No" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_11", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "batch_no", - "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": "Batch No", - "length": 0, - "no_copy": 0, - "options": "Batch", - "permlevel": 0, - "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": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "section_break_13", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "section_break_13", + "fieldtype": "Section Break" + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_qty", - "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": "Actual Qty", - "length": 0, - "no_copy": 1, - "oldfieldname": "actual_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "actual_qty", + "fieldtype": "Float", + "label": "Actual Qty", + "no_copy": 1, + "oldfieldname": "actual_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "projected_qty", - "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": "Projected Qty", - "length": 0, - "no_copy": 1, - "oldfieldname": "projected_qty", - "oldfieldtype": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "fieldname": "projected_qty", + "fieldtype": "Float", + "label": "Projected Qty", + "no_copy": 1, + "oldfieldname": "projected_qty", + "oldfieldtype": "Currency", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_16", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "uom", - "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": "UOM", - "length": 0, - "no_copy": 0, - "oldfieldname": "uom", - "oldfieldtype": "Link", - "options": "UOM", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "oldfieldname": "uom", + "oldfieldtype": "Link", + "options": "UOM", + "read_only": 1 + }, { - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "page_break", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Page Break", - "length": 0, - "no_copy": 0, - "oldfieldname": "page_break", - "oldfieldtype": "Check", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "allow_on_submit": 1, + "default": "0", + "fieldname": "page_break", + "fieldtype": "Check", + "label": "Page Break", + "oldfieldname": "page_break", + "oldfieldtype": "Check", + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "prevdoc_doctype", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Prevdoc DocType", - "length": 0, - "no_copy": 0, - "oldfieldname": "prevdoc_doctype", - "oldfieldtype": "Data", - "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, - "unique": 0 - }, + "fieldname": "prevdoc_doctype", + "fieldtype": "Data", + "hidden": 1, + "label": "Prevdoc DocType", + "oldfieldname": "prevdoc_doctype", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "parent_detail_docname", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Parent Detail docname", - "length": 0, - "no_copy": 1, - "oldfieldname": "parent_detail_docname", - "oldfieldtype": "Data", - "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, - "unique": 0 + "fieldname": "parent_detail_docname", + "fieldtype": "Data", + "hidden": 1, + "label": "Parent Detail docname", + "no_copy": 1, + "oldfieldname": "parent_detail_docname", + "oldfieldtype": "Data", + "print_hide": 1, + "read_only": 1 } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2017-02-20 13:27:37.569945", - "modified_by": "Administrator", - "module": "Stock", - "name": "Packed Item", - "owner": "Administrator", - "permissions": [], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file + ], + "idx": 1, + "istable": 1, + "modified": "2019-08-27 18:17:37.167512", + "modified_by": "Administrator", + "module": "Stock", + "name": "Packed Item", + "owner": "Administrator", + "permissions": [], + "track_changes": 1 +} diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index cf725272051..2fd651612cf 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -30,14 +30,14 @@ def get_bin_qty(item, warehouse): where item_code = %s and warehouse = %s""", (item, warehouse), as_dict = 1) return det and det[0] or frappe._dict() -def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description): +def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description): item = get_packing_item_details(packing_item_code, doc.company) # check if exists exists = 0 for d in doc.get("packed_items"): if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code and\ - d.parent_detail_docname == main_item_row.name and d.description == description: + d.parent_detail_docname == main_item_row.name: pi, exists = d, 1 break @@ -48,10 +48,10 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip pi.item_code = packing_item_code pi.item_name = item.item_name pi.parent_detail_docname = main_item_row.name - pi.description = item.description pi.uom = item.stock_uom pi.qty = flt(qty) - pi.description = description + if description and not pi.description: + pi.description = description if not pi.warehouse: pi.warehouse = (main_item_row.warehouse if ((doc.get('is_pos') or not item.default_warehouse) and main_item_row.warehouse) else item.default_warehouse) @@ -112,4 +112,4 @@ def get_items_from_product_bundle(args): return items def on_doctype_update(): - frappe.db.add_index("Packed Item", ["item_code", "warehouse"]) \ No newline at end of file + frappe.db.add_index("Packed Item", ["item_code", "warehouse"]) diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js index 9f6b9ff1914..bd14e5f6161 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.js +++ b/erpnext/stock/doctype/packing_slip/packing_slip.js @@ -10,7 +10,7 @@ cur_frm.fields_dict['delivery_note'].get_query = function(doc, cdt, cdn) { cur_frm.fields_dict['items'].grid.get_field('item_code').get_query = function(doc, cdt, cdn) { if(!doc.delivery_note) { - frappe.throw(__("Please Delivery Note first")) + frappe.throw(__("Please select a Delivery Note")); } else { return { query: "erpnext.stock.doctype.packing_slip.packing_slip.item_details", @@ -125,4 +125,4 @@ cur_frm.pformat.gross_weight_pkg= function(doc){ return '' + make_row('Gross Weight', doc.gross_weight_pkg) + '
    ' } -// TODO: validate gross weight field \ No newline at end of file +// TODO: validate gross weight field diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.json b/erpnext/stock/doctype/packing_slip/packing_slip.json index 0ed039a3913..ec8d57c9652 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.json +++ b/erpnext/stock/doctype/packing_slip/packing_slip.json @@ -74,7 +74,7 @@ { "description": "Identification of the package for the delivery (for print)", "fieldname": "from_case_no", - "fieldtype": "Data", + "fieldtype": "Int", "in_list_view": 1, "label": "From Package No.", "no_copy": 1, @@ -88,7 +88,7 @@ { "description": "If more than one package of the same type (for print)", "fieldname": "to_case_no", - "fieldtype": "Data", + "fieldtype": "Int", "in_list_view": 1, "label": "To Package No.", "no_copy": 1, @@ -180,7 +180,7 @@ "icon": "fa fa-suitcase", "idx": 1, "is_submittable": 1, - "modified": "2019-05-31 04:45:08.082862", + "modified": "2019-09-09 04:45:08.082862", "modified_by": "Administrator", "module": "Stock", "name": "Packing Slip", @@ -261,4 +261,4 @@ "show_name_in_global_search": 1, "sort_field": "modified", "sort_order": "DESC" - } \ No newline at end of file + } diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py index b99215c426a..7a5ae317c2b 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.py +++ b/erpnext/stock/doctype/packing_slip/packing_slip.py @@ -49,11 +49,10 @@ class PackingSlip(Document): frappe.msgprint(_("Please specify a valid 'From Case No.'"), raise_exception=1) elif not self.to_case_no: self.to_case_no = self.from_case_no - elif self.from_case_no > self.to_case_no: + elif cint(self.from_case_no) > cint(self.to_case_no): frappe.msgprint(_("'To Case No.' cannot be less than 'From Case No.'"), raise_exception=1) - res = frappe.db.sql("""SELECT name FROM `tabPacking Slip` WHERE delivery_note = %(delivery_note)s AND docstatus = 1 AND ((from_case_no BETWEEN %(from_case_no)s AND %(to_case_no)s) diff --git a/erpnext/stock/doctype/pick_list/__init__.py b/erpnext/stock/doctype/pick_list/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js new file mode 100644 index 00000000000..3f66743f078 --- /dev/null +++ b/erpnext/stock/doctype/pick_list/pick_list.js @@ -0,0 +1,180 @@ +// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Pick List', { + setup: (frm) => { + frm.custom_make_buttons = { + 'Delivery Note': 'Delivery Note', + 'Stock Entry': 'Stock Entry', + }; + frm.set_query('parent_warehouse', () => { + return { + filters: { + 'is_group': 1, + 'company': frm.doc.company + } + }; + }); + frm.set_query('work_order', () => { + return { + query: 'erpnext.stock.doctype.pick_list.pick_list.get_pending_work_orders', + filters: { + 'company': frm.doc.company + } + }; + }); + frm.set_query('material_request', () => { + return { + filters: { + 'material_request_type': ['=', frm.doc.purpose] + } + }; + }); + frm.set_query('item_code', 'locations', () => { + return { + filters: { + is_stock_item: 1 + } + }; + }); + }, + get_item_locations: (frm) => { + if (!frm.doc.locations || !frm.doc.locations.length) { + frappe.msgprint(__('First add items in the Item Locations table')); + } else { + frm.call('set_item_locations'); + } + }, + refresh: (frm) => { + frm.trigger('add_get_items_button'); + if (frm.doc.docstatus === 1) { + frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.target_document_exists', { + 'pick_list_name': frm.doc.name, + 'purpose': frm.doc.purpose + }).then(target_document_exists => { + if (target_document_exists) return; + if (frm.doc.purpose === 'Delivery against Sales Order') { + frm.add_custom_button(__('Delivery Note'), () => frm.trigger('create_delivery_note'), __('Create')); + } else { + frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create')); + } + }); + } + }, + work_order: (frm) => { + frappe.db.get_value('Work Order', + frm.doc.work_order, + ['qty', 'material_transferred_for_manufacturing'] + ).then(data => { + let qty_data = data.message; + let max = qty_data.qty - qty_data.material_transferred_for_manufacturing; + frappe.prompt({ + fieldtype: 'Float', + label: __('Qty of Finished Goods Item'), + fieldname: 'qty', + description: __('Max: {0}', [max]), + default: max + }, (data) => { + frm.set_value('for_qty', data.qty); + if (data.qty > max) { + frappe.msgprint(__('Quantity must not be more than {0}', [max])); + return; + } + frm.clear_table('locations'); + erpnext.utils.map_current_doc({ + method: 'erpnext.manufacturing.doctype.work_order.work_order.create_pick_list', + target: frm, + source_name: frm.doc.work_order + }); + }, __('Select Quantity'), __('Get Items')); + }); + }, + material_request: (frm) => { + erpnext.utils.map_current_doc({ + method: 'erpnext.stock.doctype.material_request.material_request.create_pick_list', + target: frm, + source_name: frm.doc.material_request + }); + }, + purpose: (frm) => { + frm.clear_table('locations'); + frm.trigger('add_get_items_button'); + }, + create_delivery_note: (frm) => { + frappe.model.open_mapped_doc({ + method: 'erpnext.stock.doctype.pick_list.pick_list.create_delivery_note', + frm: frm + }); + }, + create_stock_entry: (frm) => { + frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.create_stock_entry', { + 'pick_list': frm.doc, + }).then(stock_entry => { + frappe.model.sync(stock_entry); + frappe.set_route("Form", 'Stock Entry', stock_entry.name); + }); + }, + add_get_items_button: (frm) => { + let purpose = frm.doc.purpose; + if (purpose != 'Delivery against Sales Order' || frm.doc.docstatus !== 0) return; + let get_query_filters = { + docstatus: 1, + per_delivered: ['<', 100], + status: ['!=', ''], + customer: frm.doc.customer + }; + frm.get_items_btn = frm.add_custom_button(__('Get Items'), () => { + if (!frm.doc.customer) { + frappe.msgprint(__('Please select Customer first')); + return; + } + erpnext.utils.map_current_doc({ + method: 'erpnext.selling.doctype.sales_order.sales_order.create_pick_list', + source_doctype: 'Sales Order', + target: frm, + setters: { + company: frm.doc.company, + customer: frm.doc.customer + }, + date_field: 'transaction_date', + get_query_filters: get_query_filters + }); + }); + } +}); + +frappe.ui.form.on('Pick List Item', { + item_code: (frm, cdt, cdn) => { + let row = frappe.get_doc(cdt, cdn); + if (row.item_code) { + get_item_details(row.item_code).then(data => { + frappe.model.set_value(cdt, cdn, 'uom', data.stock_uom); + frappe.model.set_value(cdt, cdn, 'stock_uom', data.stock_uom); + frappe.model.set_value(cdt, cdn, 'conversion_factor', 1); + }); + } + }, + uom: (frm, cdt, cdn) => { + let row = frappe.get_doc(cdt, cdn); + if (row.uom) { + get_item_details(row.item_code, row.uom).then(data => { + frappe.model.set_value(cdt, cdn, 'conversion_factor', data.conversion_factor); + }); + } + }, + qty: (frm, cdt, cdn) => { + let row = frappe.get_doc(cdt, cdn); + frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor); + }, + conversion_factor: (frm, cdt, cdn) => { + let row = frappe.get_doc(cdt, cdn); + frappe.model.set_value(cdt, cdn, 'stock_qty', row.qty * row.conversion_factor); + } +}); + +function get_item_details(item_code, uom=null) { + return frappe.xcall('erpnext.stock.doctype.pick_list.pick_list.get_item_details', { + item_code, + uom + }); +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list.json b/erpnext/stock/doctype/pick_list/pick_list.json new file mode 100644 index 00000000000..8d5ef3d12ab --- /dev/null +++ b/erpnext/stock/doctype/pick_list/pick_list.json @@ -0,0 +1,184 @@ +{ + "autoname": "naming_series:", + "creation": "2019-07-11 16:03:13.681045", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "naming_series", + "company", + "purpose", + "customer", + "work_order", + "material_request", + "for_qty", + "column_break_4", + "parent_warehouse", + "get_item_locations", + "section_break_6", + "locations", + "amended_from" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_6", + "fieldtype": "Section Break" + }, + { + "description": "Items under this warehouse will be suggested", + "fieldname": "parent_warehouse", + "fieldtype": "Link", + "label": "Parent Warehouse", + "options": "Warehouse" + }, + { + "depends_on": "eval:doc.purpose==='Delivery against Sales Order'", + "fieldname": "customer", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Customer", + "options": "Customer" + }, + { + "depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'", + "fieldname": "work_order", + "fieldtype": "Link", + "label": "Work Order", + "options": "Work Order" + }, + { + "fieldname": "locations", + "fieldtype": "Table", + "label": "Item Locations", + "options": "Pick List Item" + }, + { + "depends_on": "eval:doc.purpose==='Material Transfer for Manufacture'", + "description": "Qty of raw materials will be decided based on the qty of the Finished Goods Item", + "fieldname": "for_qty", + "fieldtype": "Float", + "label": "Qty of Finished Goods Item", + "read_only": 1 + }, + { + "fieldname": "amended_from", + "fieldtype": "Link", + "label": "Amended From", + "no_copy": 1, + "options": "Pick List", + "print_hide": 1, + "read_only": 1 + }, + { + "default": "Material Transfer for Manufacture", + "fieldname": "purpose", + "fieldtype": "Select", + "label": "Purpose", + "options": "Material Transfer for Manufacture\nMaterial Transfer\nDelivery against Sales Order" + }, + { + "depends_on": "eval:['Material Transfer', 'Material Issue'].includes(doc.purpose)", + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "options": "Material Request" + }, + { + "depends_on": "eval:doc.docstatus===0", + "fieldname": "get_item_locations", + "fieldtype": "Button", + "label": "Get Item Locations" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "options": "STO-PICK-.YYYY.-", + "reqd": 1, + "set_only_once": 1 + } + ], + "is_submittable": 1, + "modified": "2019-08-29 21:10:11.572387", + "modified_by": "Administrator", + "module": "Stock", + "name": "Pick List", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing Manager", + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Manufacturing User", + "share": 1, + "submit": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py new file mode 100644 index 00000000000..c4d8c41ebb7 --- /dev/null +++ b/erpnext/stock/doctype/pick_list/pick_list.py @@ -0,0 +1,432 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from six import iteritems +from frappe.model.document import Document +from frappe import _ +from collections import OrderedDict +from frappe.utils import floor, flt, today, cint +from frappe.model.mapper import get_mapped_doc, map_child_doc +from erpnext.stock.get_item_details import get_conversion_factor +from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note as create_delivery_note_from_sales_order + +# TODO: Prioritize SO or WO group warehouse + +class PickList(Document): + def before_save(self): + self.set_item_locations() + + def before_submit(self): + for item in self.locations: + if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'): + continue + if len(item.serial_no.split('\n')) == item.picked_qty: + continue + frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity') + .format(frappe.bold(item.item_code), frappe.bold(item.idx))) + + def set_item_locations(self): + items = self.aggregate_item_qty() + self.item_location_map = frappe._dict() + + from_warehouses = None + if self.parent_warehouse: + from_warehouses = frappe.db.get_descendants('Warehouse', self.parent_warehouse) + + # reset + self.delete_key('locations') + for item_doc in items: + item_code = item_doc.item_code + + self.item_location_map.setdefault(item_code, + get_available_item_locations(item_code, from_warehouses, self.item_count_map.get(item_code))) + + locations = get_items_with_location_and_quantity(item_doc, self.item_location_map) + + item_doc.idx = None + item_doc.name = None + + for row in locations: + row.update({ + 'picked_qty': row.stock_qty + }) + + location = item_doc.as_dict() + location.update(row) + self.append('locations', location) + + def aggregate_item_qty(self): + locations = self.get('locations') + self.item_count_map = {} + # aggregate qty for same item + item_map = OrderedDict() + for item in locations: + item_code = item.item_code + reference = item.sales_order_item or item.material_request_item + key = (item_code, item.uom, reference) + + item.idx = None + item.name = None + + if item_map.get(key): + item_map[key].qty += item.qty + item_map[key].stock_qty += item.stock_qty + else: + item_map[key] = item + + # maintain count of each item (useful to limit get query) + self.item_count_map.setdefault(item_code, 0) + self.item_count_map[item_code] += item.stock_qty + + return item_map.values() + + +def get_items_with_location_and_quantity(item_doc, item_location_map): + available_locations = item_location_map.get(item_doc.item_code) + locations = [] + + remaining_stock_qty = item_doc.stock_qty + while remaining_stock_qty > 0 and available_locations: + item_location = available_locations.pop(0) + item_location = frappe._dict(item_location) + + stock_qty = remaining_stock_qty if item_location.qty >= remaining_stock_qty else item_location.qty + qty = stock_qty / (item_doc.conversion_factor or 1) + + uom_must_be_whole_number = frappe.db.get_value('UOM', item_doc.uom, 'must_be_whole_number') + if uom_must_be_whole_number: + qty = floor(qty) + stock_qty = qty * item_doc.conversion_factor + if not stock_qty: break + + serial_nos = None + if item_location.serial_no: + serial_nos = '\n'.join(item_location.serial_no[0: cint(stock_qty)]) + + locations.append(frappe._dict({ + 'qty': qty, + 'stock_qty': stock_qty, + 'warehouse': item_location.warehouse, + 'serial_no': serial_nos, + 'batch_no': item_location.batch_no + })) + + remaining_stock_qty -= stock_qty + + qty_diff = item_location.qty - stock_qty + # if extra quantity is available push current warehouse to available locations + if qty_diff > 0: + item_location.qty = qty_diff + if item_location.serial_no: + # set remaining serial numbers + item_location.serial_no = item_location.serial_no[-qty_diff:] + available_locations = [item_location] + available_locations + + # update available locations for the item + item_location_map[item_doc.item_code] = available_locations + return locations + +def get_available_item_locations(item_code, from_warehouses, required_qty): + locations = [] + if frappe.get_cached_value('Item', item_code, 'has_serial_no'): + locations = get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty) + elif frappe.get_cached_value('Item', item_code, 'has_batch_no'): + locations = get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty) + else: + locations = get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty) + + total_qty_available = sum(location.get('qty') for location in locations) + + remaining_qty = required_qty - total_qty_available + + if remaining_qty > 0: + frappe.msgprint(_('{0} units of {1} is not available.') + .format(remaining_qty, frappe.get_desk_link('Item', item_code))) + + return locations + + +def get_available_item_locations_for_serialized_item(item_code, from_warehouses, required_qty): + filters = frappe._dict({ + 'item_code': item_code, + 'warehouse': ['!=', ''] + }) + + if from_warehouses: + filters.warehouse = ['in', from_warehouses] + + serial_nos = frappe.get_all('Serial No', + fields=['name', 'warehouse'], + filters=filters, + limit=required_qty, + order_by='purchase_date', + as_list=1) + + warehouse_serial_nos_map = frappe._dict() + for serial_no, warehouse in serial_nos: + warehouse_serial_nos_map.setdefault(warehouse, []).append(serial_no) + + locations = [] + for warehouse, serial_nos in iteritems(warehouse_serial_nos_map): + locations.append({ + 'qty': len(serial_nos), + 'warehouse': warehouse, + 'serial_no': serial_nos + }) + + return locations + +def get_available_item_locations_for_batched_item(item_code, from_warehouses, required_qty): + warehouse_condition = 'and warehouse in %(warehouses)s' if from_warehouses else '' + batch_locations = frappe.db.sql(""" + SELECT + sle.`warehouse`, + sle.`batch_no`, + SUM(sle.`actual_qty`) AS `qty` + FROM + `tabStock Ledger Entry` sle, `tabBatch` batch + WHERE + sle.batch_no = batch.name + and sle.`item_code`=%(item_code)s + and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s + {warehouse_condition} + GROUP BY + `warehouse`, + `batch_no`, + `item_code` + HAVING `qty` > 0 + ORDER BY IFNULL(batch.`expiry_date`, '2200-01-01'), batch.`creation` + """.format(warehouse_condition=warehouse_condition), { #nosec + 'item_code': item_code, + 'today': today(), + 'warehouses': from_warehouses + }, as_dict=1) + + return batch_locations + +def get_available_item_locations_for_other_item(item_code, from_warehouses, required_qty): + # gets all items available in different warehouses + filters = frappe._dict({ + 'item_code': item_code, + 'actual_qty': ['>', 0] + }) + + if from_warehouses: + filters.warehouse = ['in', from_warehouses] + + item_locations = frappe.get_all('Bin', + fields=['warehouse', 'actual_qty as qty'], + filters=filters, + limit=required_qty, + order_by='creation') + + return item_locations + + +@frappe.whitelist() +def create_delivery_note(source_name, target_doc=None): + pick_list = frappe.get_doc('Pick List', source_name) + sales_orders = [d.sales_order for d in pick_list.locations] + sales_orders = set(sales_orders) + + delivery_note = None + for sales_order in sales_orders: + delivery_note = create_delivery_note_from_sales_order(sales_order, + delivery_note, skip_item_mapping=True) + + item_table_mapper = { + 'doctype': 'Delivery Note Item', + 'field_map': { + 'rate': 'rate', + 'name': 'so_detail', + 'parent': 'against_sales_order', + }, + 'condition': lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1 + } + + for location in pick_list.locations: + sales_order_item = frappe.get_cached_doc('Sales Order Item', location.sales_order_item) + dn_item = map_child_doc(sales_order_item, delivery_note, item_table_mapper) + + if dn_item: + dn_item.warehouse = location.warehouse + dn_item.qty = location.picked_qty + dn_item.batch_no = location.batch_no + dn_item.serial_no = location.serial_no + + update_delivery_note_item(sales_order_item, dn_item, delivery_note) + + set_delivery_note_missing_values(delivery_note) + + delivery_note.pick_list = pick_list.name + + return delivery_note + +@frappe.whitelist() +def create_stock_entry(pick_list): + pick_list = frappe.get_doc(json.loads(pick_list)) + + if stock_entry_exists(pick_list.get('name')): + return frappe.msgprint(_('Stock Entry has been already created against this Pick List')) + + stock_entry = frappe.new_doc('Stock Entry') + stock_entry.pick_list = pick_list.get('name') + stock_entry.purpose = pick_list.get('purpose') + stock_entry.set_stock_entry_type() + + if pick_list.get('work_order'): + stock_entry = update_stock_entry_based_on_work_order(pick_list, stock_entry) + elif pick_list.get('material_request'): + stock_entry = update_stock_entry_based_on_material_request(pick_list, stock_entry) + else: + stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry) + + stock_entry.set_incoming_rate() + stock_entry.set_actual_qty() + stock_entry.calculate_rate_and_amount(update_finished_item_rate=False) + + return stock_entry.as_dict() + +@frappe.whitelist() +def get_pending_work_orders(doctype, txt, searchfield, start, page_length, filters, as_dict): + return frappe.db.sql(""" + SELECT + `name`, `company`, `planned_start_date` + FROM + `tabWork Order` + WHERE + `status` not in ('Completed', 'Stopped') + AND `qty` > `material_transferred_for_manufacturing` + AND `docstatus` = 1 + AND `company` = %(company)s + AND `name` like %(txt)s + ORDER BY + if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999), name + LIMIT + %(start)s, %(page_length)s""", + { + 'txt': "%%%s%%" % txt, + '_txt': txt.replace('%', ''), + 'start': start, + 'page_length': frappe.utils.cint(page_length), + 'company': filters.get('company') + }, as_dict=as_dict) + +@frappe.whitelist() +def target_document_exists(pick_list_name, purpose): + if purpose == 'Delivery against Sales Order': + return frappe.db.exists('Delivery Note', { + 'pick_list': pick_list_name + }) + + return stock_entry_exists(pick_list_name) + +@frappe.whitelist() +def get_item_details(item_code, uom=None): + details = frappe.db.get_value('Item', item_code, ['stock_uom', 'name'], as_dict=1) + details.uom = uom or details.stock_uom + if uom: + details.update(get_conversion_factor(item_code, uom)) + + return details + + +def update_delivery_note_item(source, target, delivery_note): + cost_center = frappe.db.get_value('Project', delivery_note.project, 'cost_center') + if not cost_center: + cost_center = get_cost_center(source.item_code, 'Item', delivery_note.company) + + if not cost_center: + cost_center = get_cost_center(source.item_group, 'Item Group', delivery_note.company) + + target.cost_center = cost_center + +def get_cost_center(for_item, from_doctype, company): + '''Returns Cost Center for Item or Item Group''' + return frappe.db.get_value('Item Default', + fieldname=['buying_cost_center'], + filters={ + 'parent': for_item, + 'parenttype': from_doctype, + 'company': company + }) + +def set_delivery_note_missing_values(target): + target.run_method('set_missing_values') + target.run_method('set_po_nos') + target.run_method('calculate_taxes_and_totals') + +def stock_entry_exists(pick_list_name): + return frappe.db.exists('Stock Entry', { + 'pick_list': pick_list_name + }) + +def update_stock_entry_based_on_work_order(pick_list, stock_entry): + work_order = frappe.get_doc("Work Order", pick_list.get('work_order')) + + stock_entry.work_order = work_order.name + stock_entry.company = work_order.company + stock_entry.from_bom = 1 + stock_entry.bom_no = work_order.bom_no + stock_entry.use_multi_level_bom = work_order.use_multi_level_bom + stock_entry.fg_completed_qty = pick_list.for_qty + if work_order.bom_no: + stock_entry.inspection_required = frappe.db.get_value('BOM', + work_order.bom_no, 'inspection_required') + + is_wip_warehouse_group = frappe.db.get_value('Warehouse', work_order.wip_warehouse, 'is_group') + if not (is_wip_warehouse_group and work_order.skip_transfer): + wip_warehouse = work_order.wip_warehouse + else: + wip_warehouse = None + stock_entry.to_warehouse = wip_warehouse + + stock_entry.project = work_order.project + + for location in pick_list.locations: + item = frappe._dict() + update_common_item_properties(item, location) + item.t_warehouse = wip_warehouse + + stock_entry.append('items', item) + + return stock_entry + +def update_stock_entry_based_on_material_request(pick_list, stock_entry): + for location in pick_list.locations: + target_warehouse = None + if location.material_request_item: + target_warehouse = frappe.get_value('Material Request Item', + location.material_request_item, 'warehouse') + item = frappe._dict() + update_common_item_properties(item, location) + item.t_warehouse = target_warehouse + stock_entry.append('items', item) + + return stock_entry + +def update_stock_entry_items_with_no_reference(pick_list, stock_entry): + for location in pick_list.locations: + item = frappe._dict() + update_common_item_properties(item, location) + + stock_entry.append('items', item) + + return stock_entry + +def update_common_item_properties(item, location): + item.item_code = location.item_code + item.s_warehouse = location.warehouse + item.qty = location.picked_qty * location.conversion_factor + item.transfer_qty = location.picked_qty + item.uom = location.uom + item.conversion_factor = location.conversion_factor + item.stock_uom = location.stock_uom + item.material_request = location.material_request + item.serial_no = location.serial_no + item.batch_no = location.batch_no + item.material_request_item = location.material_request_item \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py new file mode 100644 index 00000000000..6e007df5e6b --- /dev/null +++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py @@ -0,0 +1,12 @@ +from __future__ import unicode_literals +from frappe import _ + +def get_data(): + return { + 'fieldname': 'pick_list', + 'transactions': [ + { + 'items': ['Stock Entry', 'Delivery Note'] + }, + ] + } \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py new file mode 100644 index 00000000000..6b4f73b140a --- /dev/null +++ b/erpnext/stock/doctype/pick_list/test_pick_list.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch'] + +from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \ + import EmptyStockReconciliationItemsError + +class TestPickList(unittest.TestCase): + + def test_pick_list_picks_warehouse_for_each_item(self): + try: + frappe.get_doc({ + 'doctype': 'Stock Reconciliation', + 'company': '_Test Company', + 'purpose': 'Opening Stock', + 'expense_account': 'Temporary Opening - _TC', + 'items': [{ + 'item_code': '_Test Item Home Desktop 100', + 'warehouse': '_Test Warehouse - _TC', + 'valuation_rate': 100, + 'qty': 5 + }] + }).submit() + except EmptyStockReconciliationItemsError: + pass + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Item Home Desktop 100', + 'qty': 5, + 'stock_qty': 5, + 'conversion_factor': 1, + 'sales_order': '_T-Sales Order-1', + 'sales_order_item': '_T-Sales Order-1_item', + }] + }) + pick_list.set_item_locations() + + self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC') + self.assertEqual(pick_list.locations[0].qty, 5) + + def test_pick_list_splits_row_according_to_warhouse_availability(self): + try: + frappe.get_doc({ + 'doctype': 'Stock Reconciliation', + 'company': '_Test Company', + 'purpose': 'Opening Stock', + 'expense_account': 'Temporary Opening - _TC', + 'items': [{ + 'item_code': '_Test Item Warehouse Group Wise Reorder', + 'warehouse': '_Test Warehouse Group-C1 - _TC', + 'valuation_rate': 100, + 'qty': 5 + }] + }).submit() + except EmptyStockReconciliationItemsError: + pass + + try: + frappe.get_doc({ + 'doctype': 'Stock Reconciliation', + 'company': '_Test Company', + 'purpose': 'Opening Stock', + 'expense_account': 'Temporary Opening - _TC', + 'items': [{ + 'item_code': '_Test Item Warehouse Group Wise Reorder', + 'warehouse': '_Test Warehouse 2 - _TC', + 'valuation_rate': 400, + 'qty': 10 + }] + }).submit() + except EmptyStockReconciliationItemsError: + pass + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Item Warehouse Group Wise Reorder', + 'qty': 1000, + 'stock_qty': 1000, + 'conversion_factor': 1, + 'sales_order': '_T-Sales Order-1', + 'sales_order_item': '_T-Sales Order-1_item', + }] + }) + + pick_list.set_item_locations() + + self.assertEqual(pick_list.locations[0].item_code, '_Test Item Warehouse Group Wise Reorder') + self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse Group-C1 - _TC') + self.assertEqual(pick_list.locations[0].qty, 5) + + self.assertEqual(pick_list.locations[1].item_code, '_Test Item Warehouse Group Wise Reorder') + self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse 2 - _TC') + self.assertEqual(pick_list.locations[1].qty, 10) + + def test_pick_list_shows_serial_no_for_serialized_item(self): + + stock_reconciliation = frappe.get_doc({ + 'doctype': 'Stock Reconciliation', + 'company': '_Test Company', + 'items': [{ + 'item_code': '_Test Serialized Item', + 'warehouse': '_Test Warehouse - _TC', + 'valuation_rate': 100, + 'qty': 5, + 'serial_no': '123450\n123451\n123452\n123453\n123454' + }] + }) + + stock_reconciliation.submit() + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Serialized Item', + 'qty': 1000, + 'stock_qty': 1000, + 'conversion_factor': 1, + 'sales_order': '_T-Sales Order-1', + 'sales_order_item': '_T-Sales Order-1_item', + }] + }) + + pick_list.set_item_locations() + self.assertEqual(pick_list.locations[0].item_code, '_Test Serialized Item') + self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC') + self.assertEqual(pick_list.locations[0].qty, 5) + self.assertEqual(pick_list.locations[0].serial_no, '123450\n123451\n123452\n123453\n123454') + + def test_pick_list_for_items_from_multiple_sales_orders(self): + try: + frappe.get_doc({ + 'doctype': 'Stock Reconciliation', + 'company': '_Test Company', + 'purpose': 'Opening Stock', + 'expense_account': 'Temporary Opening - _TC', + 'items': [{ + 'item_code': '_Test Item Home Desktop 100', + 'warehouse': '_Test Warehouse - _TC', + 'valuation_rate': 100, + 'qty': 10 + }] + }).submit() + except EmptyStockReconciliationItemsError: + pass + + sales_order = frappe.get_doc({ + 'doctype': "Sales Order", + 'customer': '_Test Customer', + 'company': '_Test Company', + 'items': [{ + 'item_code': '_Test Item Home Desktop 100', + 'qty': 10, + 'delivery_date': frappe.utils.today() + }], + }) + sales_order.submit() + + pick_list = frappe.get_doc({ + 'doctype': 'Pick List', + 'company': '_Test Company', + 'customer': '_Test Customer', + 'items_based_on': 'Sales Order', + 'locations': [{ + 'item_code': '_Test Item Home Desktop 100', + 'qty': 5, + 'stock_qty': 5, + 'conversion_factor': 1, + 'sales_order': '_T-Sales Order-1', + 'sales_order_item': '_T-Sales Order-1_item', + }, { + 'item_code': '_Test Item Home Desktop 100', + 'qty': 5, + 'stock_qty': 5, + 'conversion_factor': 1, + 'sales_order': sales_order.name, + 'sales_order_item': sales_order.items[0].name, + }] + }) + pick_list.set_item_locations() + + self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC') + self.assertEqual(pick_list.locations[0].qty, 5) + self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item') + + self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100') + self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC') + self.assertEqual(pick_list.locations[1].qty, 5) + self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name) + + + # def test_pick_list_skips_items_in_expired_batch(self): + # pass + + # def test_pick_list_from_sales_order(self): + # pass + + # def test_pick_list_from_work_order(self): + # pass + + # def test_pick_list_from_material_request(self): + # pass \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list_item/__init__.py b/erpnext/stock/doctype/pick_list_item/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json new file mode 100644 index 00000000000..c7a35df51f5 --- /dev/null +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json @@ -0,0 +1,182 @@ +{ + "creation": "2019-07-11 16:01:22.832885", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "item_code", + "item_name", + "column_break_2", + "description", + "section_break_5", + "warehouse", + "quantity_section", + "qty", + "stock_qty", + "picked_qty", + "column_break_11", + "uom", + "conversion_factor", + "stock_uom", + "serial_no_and_batch_section", + "serial_no", + "column_break_20", + "batch_no", + "column_break_15", + "sales_order", + "sales_order_item", + "material_request", + "material_request_item" + ], + "fields": [ + { + "default": "1", + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Qty" + }, + { + "fieldname": "picked_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Picked Qty" + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Warehouse", + "options": "Warehouse", + "read_only": 1 + }, + { + "fetch_from": "item_code.item_name", + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name", + "read_only": 1 + }, + { + "fetch_from": "item_code.description", + "fieldname": "description", + "fieldtype": "Text", + "label": "Description", + "read_only": 1 + }, + { + "depends_on": "serial_no", + "fieldname": "serial_no", + "fieldtype": "Small Text", + "label": "Serial No" + }, + { + "depends_on": "batch_no", + "fieldname": "batch_no", + "fieldtype": "Link", + "label": "Batch No", + "options": "Batch" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "section_break_5", + "fieldtype": "Section Break" + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "column_break_11", + "fieldtype": "Column Break" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "UOM Conversion Factor", + "read_only": 1 + }, + { + "fieldname": "stock_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Stock Qty", + "read_only": 1 + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Item", + "options": "Item" + }, + { + "fieldname": "quantity_section", + "fieldtype": "Section Break", + "label": "Quantity" + }, + { + "fieldname": "column_break_15", + "fieldtype": "Section Break", + "label": "Reference" + }, + { + "fieldname": "sales_order", + "fieldtype": "Link", + "label": "Sales Order", + "options": "Sales Order", + "read_only": 1 + }, + { + "fieldname": "sales_order_item", + "fieldtype": "Data", + "label": "Sales Order Item", + "read_only": 1 + }, + { + "fieldname": "serial_no_and_batch_section", + "fieldtype": "Section Break", + "label": "Serial No and Batch" + }, + { + "fieldname": "column_break_20", + "fieldtype": "Column Break" + }, + { + "fieldname": "material_request", + "fieldtype": "Link", + "label": "Material Request", + "options": "Material Request", + "read_only": 1 + }, + { + "fieldname": "material_request_item", + "fieldtype": "Data", + "label": "Material Request Item", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2019-08-29 21:28:39.539007", + "modified_by": "Administrator", + "module": "Stock", + "name": "Pick List Item", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.py b/erpnext/stock/doctype/pick_list_item/pick_list_item.py new file mode 100644 index 00000000000..8797b8dc219 --- /dev/null +++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class PickListItem(Document): + pass diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d124ae4747d..ab9311b4803 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -329,6 +329,11 @@ class TestPurchaseReceipt(unittest.TestCase): location = frappe.db.get_value('Serial No', serial_nos[0].name, 'location') self.assertEquals(location, "Test Location") + frappe.db.set_value("Asset", asset, "purchase_receipt", "") + frappe.db.set_value("Purchase Receipt Item", pr.items[0].name, "asset", "") + + pr.load_from_db() + pr.cancel() serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] self.assertEquals(len(serial_nos), 0) diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.js b/erpnext/stock/doctype/quality_inspection/quality_inspection.js index aa9854aee19..22f29e05b49 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.js +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.js @@ -39,11 +39,12 @@ cur_frm.fields_dict['item_code'].get_query = function(doc, cdt, cdn) { query: "erpnext.stock.doctype.quality_inspection.quality_inspection.item_query", filters: { "from": doctype, - "parent": doc.reference_name + "parent": doc.reference_name, + "inspection_type": doc.inspection_type } - } + }; } -} +}, // Serial No based on item_code cur_frm.fields_dict['item_serial_no'].get_query = function(doc, cdt, cdn) { diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py index e0b738202cc..738c63ca358 100644 --- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py +++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py @@ -63,10 +63,12 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): mcond = get_match_cond(filters["from"]) cond, qi_condition = "", "and (quality_inspection is null or quality_inspection = '')" - if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']: + if filters.get('from') in ['Purchase Invoice Item', 'Purchase Receipt Item']\ + and filters.get("inspection_type") != "In Process": cond = """and item_code in (select name from `tabItem` where inspection_required_before_purchase = 1)""" - elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']: + elif filters.get('from') in ['Sales Invoice Item', 'Delivery Note Item']\ + and filters.get("inspection_type") != "In Process": cond = """and item_code in (select name from `tabItem` where inspection_required_before_delivery = 1)""" elif filters.get('from') == 'Stock Entry Detail': diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index ee563cb4a0d..7bcefa03e49 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -17,6 +17,7 @@ "purchase_order", "delivery_note_no", "sales_invoice_no", + "pick_list", "purchase_receipt_no", "col2", "posting_date", @@ -354,6 +355,7 @@ "label": "Scan Barcode" }, { + "allow_bulk_edit": 1, "fieldname": "items", "fieldtype": "Table", "label": "Items", @@ -613,12 +615,19 @@ { "fieldname": "dimension_col_break", "fieldtype": "Column Break" + }, + { + "fieldname": "pick_list", + "fieldtype": "Link", + "label": "Pick List", + "options": "Pick List", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 1, "is_submittable": 1, - "modified": "2019-07-14 17:41:39.257508", + "modified": "2019-09-09 11:40:05.762003", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index b5303327f67..0825557f183 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -145,6 +145,10 @@ class StockEntry(StockController): self.precision("transfer_qty", item)) def update_cost_in_project(self): + if (self.work_order and not frappe.db.get_value("Work Order", + self.work_order, "update_consumed_material_cost_in_project")): + return + if self.project: amount = frappe.db.sql(""" select ifnull(sum(sed.amount), 0) from @@ -521,15 +525,21 @@ class StockEntry(StockController): backflush_raw_materials_based_on = frappe.db.get_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on") + qty_allowance = flt(frappe.db.get_single_value("Buying Settings", + "over_transfer_allowance")) + if (self.purpose == "Send to Subcontractor" and self.purchase_order and backflush_raw_materials_based_on == 'BOM'): purchase_order = frappe.get_doc("Purchase Order", self.purchase_order) for se_item in self.items: item_code = se_item.original_item or se_item.item_code precision = cint(frappe.db.get_default("float_precision")) or 3 - total_allowed = sum([flt(d.required_qty) for d in purchase_order.supplied_items \ + required_qty = sum([flt(d.required_qty) for d in purchase_order.supplied_items \ if d.rm_item_code == item_code]) - if not total_allowed: + + total_allowed = required_qty + (required_qty * (qty_allowance/100)) + + if not required_qty: frappe.throw(_("Item {0} not found in 'Raw Materials Supplied' table in Purchase Order {1}") .format(se_item.item_code, self.purchase_order)) total_supplied = frappe.db.sql("""select sum(transfer_qty) @@ -1106,6 +1116,7 @@ class StockEntry(StockController): se_child.allow_alternative_item = item_dict[d].get("allow_alternative_item", 0) se_child.subcontracted_item = item_dict[d].get("main_item_code") se_child.original_item = item_dict[d].get("original_item") + se_child.po_detail = item_dict[d].get("po_detail") if item_dict[d].get("idx"): se_child.idx = item_dict[d].get("idx") @@ -1157,7 +1168,14 @@ class StockEntry(StockController): where po.name = poitemsup.parent and po.name = %s""", self.purchase_order)) - #Update reserved sub contracted quantity in bin based on Supplied Item Details + #Update Supplied Qty in PO Supplied Items + + frappe.db.sql("""UPDATE `tabPurchase Order Item Supplied` pos + SET pos.supplied_qty = (SELECT ifnull(sum(transfer_qty), 0) FROM `tabStock Entry Detail` sed + WHERE pos.name = sed.po_detail and sed.docstatus = 1) + WHERE pos.docstatus = 1 and pos.parent = %s""", self.purchase_order) + + #Update reserved sub contracted quantity in bin based on Supplied Item Details and for d in self.get("items"): item_code = d.get('original_item') or d.get('item_code') reserve_warehouse = item_wh.get(item_code) diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json index 912a4651b3c..d86e68b7222 100644 --- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json +++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json @@ -59,6 +59,7 @@ "reference_section", "against_stock_entry", "ste_detail", + "po_detail", "column_break_51", "transferred_qty", "reference_purchase_receipt", @@ -480,11 +481,20 @@ "label": "Project", "options": "Project", "read_only": 1 + }, + { + "fieldname": "po_detail", + "fieldtype": "Data", + "hidden": 1, + "label": "PO Supplied Item", + "no_copy": 1, + "print_hide": 1, + "read_only": 1 } ], "idx": 1, "istable": 1, - "modified": "2019-07-12 11:34:53.190749", + "modified": "2019-08-20 14:01:02.319754", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry Detail", diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index ededc4d8b4f..cd059297436 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -152,7 +152,6 @@ class TestStockReconciliation(unittest.TestCase): for d in to_delete_records: stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() - frappe.delete_doc("Stock Reconciliation", stock_doc.name) for d in serial_nos + serial_nos1: if frappe.db.exists("Serial No", d): @@ -203,9 +202,6 @@ class TestStockReconciliation(unittest.TestCase): stock_doc = frappe.get_doc("Stock Reconciliation", d) stock_doc.cancel() - frappe.delete_doc("Batch", sr.items[0].batch_no) - for d in to_delete_records: - frappe.delete_doc("Stock Reconciliation", d) def insert_existing_sle(): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index f1d784c8d9e..35c0bb61658 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -22,7 +22,7 @@ sales_doctypes = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'] purchase_doctypes = ['Material Request', 'Supplier Quotation', 'Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] @frappe.whitelist() -def get_item_details(args, doc=None): +def get_item_details(args, doc=None, overwrite_warehouse=True): """ args = { "item_code": "", @@ -44,11 +44,12 @@ def get_item_details(args, doc=None): "set_warehouse": "" } """ + args = process_args(args) item = frappe.get_cached_doc("Item", args.item_code) validate_item_details(args, item) - out = get_basic_details(args, item) + out = get_basic_details(args, item, overwrite_warehouse) get_item_tax_template(args, item, out) out["item_tax_rate"] = get_item_tax_map(args.company, args.get("item_tax_template") if out.get("item_tax_template") is None \ @@ -178,7 +179,7 @@ def validate_item_details(args, item): throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) -def get_basic_details(args, item): +def get_basic_details(args, item, overwrite_warehouse=True): """ :param args: { "item_code": "", @@ -225,14 +226,26 @@ def get_basic_details(args, item): item_group_defaults = get_item_group_defaults(item.name, args.company) brand_defaults = get_brand_defaults(item.name, args.company) - warehouse = (args.get("set_warehouse") or item_defaults.get("default_warehouse") or - item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse) + if overwrite_warehouse or not args.warehouse: + warehouse = ( + args.get("set_warehouse") or + item_defaults.get("default_warehouse") or + item_group_defaults.get("default_warehouse") or + brand_defaults.get("default_warehouse") or + args.warehouse + ) - if not warehouse: - defaults = frappe.defaults.get_defaults() or {} - if defaults.get("default_warehouse") and frappe.db.exists("Warehouse", - {'name': defaults.default_warehouse, 'company': args.company}): - warehouse = defaults.default_warehouse + if not warehouse: + defaults = frappe.defaults.get_defaults() or {} + warehouse_exists = frappe.db.exists("Warehouse", { + 'name': defaults.default_warehouse, + 'company': args.company + }) + if defaults.get("default_warehouse") and warehouse_exists: + warehouse = defaults.default_warehouse + + else: + warehouse = args.warehouse if args.get('doctype') == "Material Request" and not args.get('material_request_type'): args['material_request_type'] = frappe.db.get_value('Material Request', @@ -626,7 +639,7 @@ def validate_price_list(args): if not frappe.db.get_value("Price List", {"name": args.price_list, args.transaction_type: 1, "enabled": 1}): throw(_("Price List {0} is disabled or does not exist").format(args.price_list)) - elif not args.get("supplier"): + elif args.get("customer"): throw(_("Price List not selected")) def validate_conversion_rate(args, meta): diff --git a/erpnext/stock/print_format/__init__.py b/erpnext/stock/print_format/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/print_format/pick_list/__init__.py b/erpnext/stock/print_format/pick_list/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/stock/print_format/pick_list/pick_list.json b/erpnext/stock/print_format/pick_list/pick_list.json new file mode 100644 index 00000000000..0e88d24c5ac --- /dev/null +++ b/erpnext/stock/print_format/pick_list/pick_list.json @@ -0,0 +1,23 @@ +{ + "align_labels_right": 1, + "creation": "2019-08-02 07:27:42.533305", + "custom_format": 0, + "disabled": 0, + "doc_type": "Pick List", + "docstatus": 0, + "doctype": "Print Format", + "font": "Default", + "format_data": "[{\"fieldname\": \"print_heading_template\", \"fieldtype\": \"Custom HTML\", \"options\": \"
    \\t\\t\\t\\t

    Pick List
    {{ doc.name }}\\t\\t\\t\\t

    \"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"company\", \"label\": \"Company\"}, {\"print_hide\": 0, \"fieldname\": \"customer\", \"label\": \"Customer\"}, {\"fieldtype\": \"Column Break\"}, {\"print_hide\": 0, \"fieldname\": \"purpose\", \"label\": \"Purpose\"}, {\"fieldtype\": \"Section Break\", \"label\": \"\"}, {\"fieldtype\": \"Column Break\"}, {\"visible_columns\": [{\"print_hide\": 0, \"fieldname\": \"item_name\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"warehouse\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"stock_qty\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"serial_no\", \"print_width\": \"\"}, {\"print_hide\": 0, \"fieldname\": \"batch_no\", \"print_width\": \"\"}], \"print_hide\": 0, \"fieldname\": \"locations\", \"label\": \"Item Locations\"}]", + "idx": 0, + "line_breaks": 1, + "modified": "2019-08-30 15:58:27.807219", + "modified_by": "Administrator", + "module": "Stock", + "name": "Pick List", + "owner": "Administrator", + "print_format_builder": 1, + "print_format_type": "Jinja", + "raw_printing": 0, + "show_section_headings": 1, + "standard": "Yes" +} \ No newline at end of file diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 5fda2a40076..69a4b94b4ea 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -181,10 +181,7 @@ class update_entries_after(object): # rounding as per precision self.stock_value = flt(self.stock_value, self.precision) - if self.prev_stock_value < 0 and self.stock_value >= 0 and sle.voucher_type != 'Stock Reconciliation': - stock_value_difference = sle.actual_qty * self.valuation_rate - else: - stock_value_difference = self.stock_value - self.prev_stock_value + stock_value_difference = self.stock_value - self.prev_stock_value self.prev_stock_value = self.stock_value @@ -227,9 +224,9 @@ class update_entries_after(object): elif actual_qty < 0: # In case of delivery/stock issue, get average purchase rate # of serial nos of current entry - stock_value_change = -1 * flt(frappe.db.sql("""select sum(purchase_rate) - from `tabSerial No` where name in (%s)""" % (", ".join(["%s"]*len(serial_no))), - tuple(serial_no))[0][0]) + stock_value_change = -1 * flt(frappe.get_all("Serial No", + fields=["sum(purchase_rate)"], + filters = {'name': ('in', serial_no)}, as_list=1)[0][0]) new_stock_qty = self.qty_after_transaction + actual_qty @@ -464,16 +461,22 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and warehouse = %s - and valuation_rate >= 0 - order by posting_date desc, posting_time desc, creation desc limit 1""", (item_code, warehouse)) + where + item_code = %s + AND warehouse = %s + AND valuation_rate >= 0 + AND NOT (voucher_no = %s AND voucher_type = %s) + order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type)) if not last_valuation_rate: # Get valuation rate from last sle for the item against any warehouse last_valuation_rate = frappe.db.sql("""select valuation_rate from `tabStock Ledger Entry` - where item_code = %s and valuation_rate > 0 - order by posting_date desc, posting_time desc, creation desc limit 1""", item_code) + where + item_code = %s + AND valuation_rate > 0 + AND NOT(voucher_no = %s AND voucher_type = %s) + order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, voucher_no, voucher_type)) if last_valuation_rate: return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py index 6ea322872ef..aec37d4e945 100644 --- a/erpnext/stock/utils.py +++ b/erpnext/stock/utils.py @@ -15,7 +15,7 @@ def get_stock_value_from_bin(warehouse=None, item_code=None): values = {} conditions = "" if warehouse: - conditions += """ and warehouse in ( + conditions += """ and `tabBin`.warehouse in ( select w2.name from `tabWarehouse` w1 join `tabWarehouse` w2 on w1.name = %(warehouse)s @@ -25,11 +25,12 @@ def get_stock_value_from_bin(warehouse=None, item_code=None): values['warehouse'] = warehouse if item_code: - conditions += " and item_code = %(item_code)s" + conditions += " and `tabBin`.item_code = %(item_code)s" values['item_code'] = item_code - query = "select sum(stock_value) from `tabBin` where 1 = 1 %s" % conditions + query = """select sum(stock_value) from `tabBin`, `tabItem` where 1 = 1 + and `tabItem`.name = `tabBin`.item_code and ifnull(`tabItem`.disabled, 0) = 0 %s""" % conditions stock_value = frappe.db.sql(query, values) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index aec9db9d4ac..bad40cc37fc 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -107,7 +107,7 @@ frappe.ui.form.on("Issue", { }, () => { reset_sla.enable_primary_action(); frm.refresh(); - frappe.msgprint(__("Service Level Agreement Reset.")); + frappe.msgprint(__("Service Level Agreement was reset.")); }); } }); diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 1e5d5f9ea77..222554bda13 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -1,384 +1,392 @@ { - "allow_import": 1, - "allow_rename": 1, - "autoname": "naming_series:", - "creation": "2013-02-01 10:36:25", - "doctype": "DocType", - "document_type": "Setup", - "engine": "InnoDB", - "field_order": [ - "subject_section", - "naming_series", - "subject", - "customer", - "raised_by", - "cb00", - "status", - "priority", - "issue_type", - "sb_details", - "description", - "service_level_section", - "service_level_agreement", - "response_by", - "response_by_variance", - "reset_service_level_agreement", - "cb", - "agreement_fulfilled", - "resolution_by", - "resolution_by_variance", - "service_level_agreement_creation", - "response", - "mins_to_first_response", - "first_responded_on", - "additional_info", - "lead", - "contact", - "email_account", - "column_break_16", - "customer_name", - "project", - "company", - "section_break_19", - "resolution_details", - "column_break1", - "opening_date", - "opening_time", - "resolution_date", - "content_type", - "attachment", - "via_customer_portal" - ], - "fields": [ - { - "fieldname": "subject_section", - "fieldtype": "Section Break", - "label": "Subject", - "options": "fa fa-flag" - }, - { - "fieldname": "naming_series", - "fieldtype": "Select", - "label": "Series", - "no_copy": 1, - "options": "ISS-.YYYY.-", - "print_hide": 1, - "set_only_once": 1 - }, - { - "bold": 1, - "fieldname": "subject", - "fieldtype": "Data", - "in_global_search": 1, - "in_standard_filter": 1, - "label": "Subject", - "reqd": 1 - }, - { - "fieldname": "customer", - "fieldtype": "Link", - "in_global_search": 1, - "label": "Customer", - "oldfieldname": "customer", - "oldfieldtype": "Link", - "options": "Customer", - "print_hide": 1, - "search_index": 1 - }, - { - "bold": 1, - "depends_on": "eval:doc.__islocal", - "fieldname": "raised_by", - "fieldtype": "Data", - "in_global_search": 1, - "in_list_view": 1, - "label": "Raised By (Email)", - "oldfieldname": "raised_by", - "oldfieldtype": "Data", - "options": "Email" - }, - { - "fieldname": "cb00", - "fieldtype": "Column Break" - }, - { - "default": "Open", - "fieldname": "status", - "fieldtype": "Select", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "no_copy": 1, - "oldfieldname": "status", - "oldfieldtype": "Select", - "options": "Open\nReplied\nHold\nClosed", - "search_index": 1 - }, - { - "default": "Medium", - "fieldname": "priority", - "fieldtype": "Link", - "in_standard_filter": 1, - "label": "Priority", - "options": "Issue Priority" - }, - { - "fieldname": "issue_type", - "fieldtype": "Link", - "label": "Issue Type", - "options": "Issue Type" - }, - { - "collapsible": 1, - "collapsible_depends_on": "eval:doc.status!=\"Closed\"", - "fieldname": "sb_details", - "fieldtype": "Section Break", - "label": "Details" - }, - { - "bold": 1, - "fieldname": "description", - "fieldtype": "Text Editor", - "in_global_search": 1, - "label": "Description", - "oldfieldname": "problem_description", - "oldfieldtype": "Text" - }, - { - "collapsible": 1, - "fieldname": "service_level_section", - "fieldtype": "Section Break", - "label": "Service Level" - }, - { - "fieldname": "service_level_agreement", - "fieldtype": "Link", - "label": "Service Level Agreement", - "options": "Service Level Agreement" - }, - { - "fieldname": "response_by", - "fieldtype": "Datetime", - "label": "Response By", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "cb", - "fieldtype": "Column Break", - "options": "fa fa-pushpin", - "read_only": 1 - }, - { - "fieldname": "resolution_by", - "fieldtype": "Datetime", - "label": "Resolution By", - "read_only": 1 - }, - { - "collapsible": 1, - "fieldname": "response", - "fieldtype": "Section Break", - "label": "Response" - }, - { - "bold": 1, - "fieldname": "mins_to_first_response", - "fieldtype": "Float", - "label": "Mins to First Response", - "read_only": 1 - }, - { - "fieldname": "first_responded_on", - "fieldtype": "Datetime", - "label": "First Responded On" - }, - { - "collapsible": 1, - "fieldname": "additional_info", - "fieldtype": "Section Break", - "label": "Reference", - "options": "fa fa-pushpin", - "read_only": 1 - }, - { - "fieldname": "lead", - "fieldtype": "Link", - "label": "Lead", - "options": "Lead" - }, - { - "fieldname": "contact", - "fieldtype": "Link", - "label": "Contact", - "options": "Contact" - }, - { - "fieldname": "email_account", - "fieldtype": "Link", - "label": "Email Account", - "options": "Email Account" - }, - { - "fieldname": "column_break_16", - "fieldtype": "Column Break" - }, - { - "bold": 1, - "fieldname": "customer_name", - "fieldtype": "Data", - "label": "Customer Name", - "oldfieldname": "customer_name", - "oldfieldtype": "Data", - "read_only": 1 - }, - { - "fieldname": "project", - "fieldtype": "Link", - "label": "Project", - "options": "Project" - }, - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "print_hide": 1 - }, - { - "collapsible": 1, - "fieldname": "section_break_19", - "fieldtype": "Section Break", - "label": "Resolution" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_details", - "fieldtype": "Text Editor", - "label": "Resolution Details", - "no_copy": 1, - "oldfieldname": "resolution_details", - "oldfieldtype": "Text" - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "column_break1", - "fieldtype": "Column Break", - "oldfieldtype": "Column Break", - "read_only": 1 - }, - { - "default": "Today", - "fieldname": "opening_date", - "fieldtype": "Date", - "label": "Opening Date", - "no_copy": 1, - "oldfieldname": "opening_date", - "oldfieldtype": "Date", - "read_only": 1 - }, - { - "fieldname": "opening_time", - "fieldtype": "Time", - "label": "Opening Time", - "no_copy": 1, - "oldfieldname": "opening_time", - "oldfieldtype": "Time", - "read_only": 1 - }, - { - "depends_on": "eval:!doc.__islocal", - "fieldname": "resolution_date", - "fieldtype": "Datetime", - "label": "Resolution Date", - "no_copy": 1, - "oldfieldname": "resolution_date", - "oldfieldtype": "Date", - "read_only": 1 - }, - { - "fieldname": "content_type", - "fieldtype": "Data", - "hidden": 1, - "label": "Content Type" - }, - { - "fieldname": "attachment", - "fieldtype": "Attach", - "hidden": 1, - "label": "Attachment" - }, - { - "default": "0", - "fieldname": "via_customer_portal", - "fieldtype": "Check", - "label": "Via Customer Portal" - }, - { - "default": "Ongoing", - "depends_on": "eval: doc.service_level_agreement", - "fieldname": "agreement_fulfilled", - "fieldtype": "Select", - "label": "Service Level Agreement Fulfilled", - "options": "Ongoing\nFulfilled\nFailed", - "read_only": 1 - }, - { - "depends_on": "eval: doc.service_level_agreement", - "description": "in hours", - "fieldname": "response_by_variance", - "fieldtype": "Float", - "label": "Response By Variance", - "read_only": 1 - }, - { - "depends_on": "eval: doc.service_level_agreement", - "description": "in hours", - "fieldname": "resolution_by_variance", - "fieldtype": "Float", - "label": "Resolution By Variance", - "read_only": 1 - }, - { - "fieldname": "service_level_agreement_creation", - "fieldtype": "Datetime", - "hidden": 1, - "label": "Service Level Agreement Creation", - "read_only": 1 - }, - { - "depends_on": "eval: doc.service_level_agreement", - "fieldname": "reset_service_level_agreement", - "fieldtype": "Button", - "label": "Reset Service Level Agreement" - } - ], - "icon": "fa fa-ticket", - "idx": 7, - "modified": "2019-07-11 23:57:22.015881", - "modified_by": "Administrator", - "module": "Support", - "name": "Issue", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Support Team", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "search_fields": "status,customer,subject,raised_by", - "sort_field": "modified", - "sort_order": "ASC", - "timeline_field": "customer", - "title_field": "subject", - "track_changes": 1, - "track_seen": 1 - } \ No newline at end of file + "allow_import": 1, + "allow_rename": 1, + "autoname": "naming_series:", + "creation": "2013-02-01 10:36:25", + "doctype": "DocType", + "document_type": "Setup", + "engine": "InnoDB", + "field_order": [ + "subject_section", + "naming_series", + "subject", + "customer", + "raised_by", + "cb00", + "status", + "priority", + "issue_type", + "issue_split_from", + "sb_details", + "description", + "service_level_section", + "service_level_agreement", + "response_by", + "response_by_variance", + "reset_service_level_agreement", + "cb", + "agreement_fulfilled", + "resolution_by", + "resolution_by_variance", + "service_level_agreement_creation", + "response", + "mins_to_first_response", + "first_responded_on", + "additional_info", + "lead", + "contact", + "email_account", + "column_break_16", + "customer_name", + "project", + "company", + "section_break_19", + "resolution_details", + "column_break1", + "opening_date", + "opening_time", + "resolution_date", + "content_type", + "attachment", + "via_customer_portal" + ], + "fields": [ + { + "fieldname": "subject_section", + "fieldtype": "Section Break", + "label": "Subject", + "options": "fa fa-flag" + }, + { + "fieldname": "naming_series", + "fieldtype": "Select", + "label": "Series", + "no_copy": 1, + "options": "ISS-.YYYY.-", + "print_hide": 1, + "set_only_once": 1 + }, + { + "bold": 1, + "fieldname": "subject", + "fieldtype": "Data", + "in_global_search": 1, + "in_standard_filter": 1, + "label": "Subject", + "reqd": 1 + }, + { + "fieldname": "customer", + "fieldtype": "Link", + "in_global_search": 1, + "label": "Customer", + "oldfieldname": "customer", + "oldfieldtype": "Link", + "options": "Customer", + "print_hide": 1, + "search_index": 1 + }, + { + "bold": 1, + "depends_on": "eval:doc.__islocal", + "fieldname": "raised_by", + "fieldtype": "Data", + "in_global_search": 1, + "in_list_view": 1, + "label": "Raised By (Email)", + "oldfieldname": "raised_by", + "oldfieldtype": "Data", + "options": "Email" + }, + { + "fieldname": "cb00", + "fieldtype": "Column Break" + }, + { + "default": "Open", + "fieldname": "status", + "fieldtype": "Select", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "no_copy": 1, + "oldfieldname": "status", + "oldfieldtype": "Select", + "options": "Open\nReplied\nHold\nClosed", + "search_index": 1 + }, + { + "default": "Medium", + "fieldname": "priority", + "fieldtype": "Link", + "in_standard_filter": 1, + "label": "Priority", + "options": "Issue Priority" + }, + { + "fieldname": "issue_type", + "fieldtype": "Link", + "label": "Issue Type", + "options": "Issue Type" + }, + { + "collapsible": 1, + "collapsible_depends_on": "eval:doc.status!=\"Closed\"", + "fieldname": "sb_details", + "fieldtype": "Section Break", + "label": "Details" + }, + { + "bold": 1, + "fieldname": "description", + "fieldtype": "Text Editor", + "in_global_search": 1, + "label": "Description", + "oldfieldname": "problem_description", + "oldfieldtype": "Text" + }, + { + "collapsible": 1, + "fieldname": "service_level_section", + "fieldtype": "Section Break", + "label": "Service Level" + }, + { + "fieldname": "service_level_agreement", + "fieldtype": "Link", + "label": "Service Level Agreement", + "options": "Service Level Agreement" + }, + { + "fieldname": "response_by", + "fieldtype": "Datetime", + "label": "Response By", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "cb", + "fieldtype": "Column Break", + "options": "fa fa-pushpin", + "read_only": 1 + }, + { + "fieldname": "resolution_by", + "fieldtype": "Datetime", + "label": "Resolution By", + "read_only": 1 + }, + { + "collapsible": 1, + "fieldname": "response", + "fieldtype": "Section Break", + "label": "Response" + }, + { + "bold": 1, + "fieldname": "mins_to_first_response", + "fieldtype": "Float", + "label": "Mins to First Response", + "read_only": 1 + }, + { + "fieldname": "first_responded_on", + "fieldtype": "Datetime", + "label": "First Responded On" + }, + { + "collapsible": 1, + "fieldname": "additional_info", + "fieldtype": "Section Break", + "label": "Reference", + "options": "fa fa-pushpin", + "read_only": 1 + }, + { + "fieldname": "lead", + "fieldtype": "Link", + "label": "Lead", + "options": "Lead" + }, + { + "fieldname": "contact", + "fieldtype": "Link", + "label": "Contact", + "options": "Contact" + }, + { + "fieldname": "email_account", + "fieldtype": "Link", + "label": "Email Account", + "options": "Email Account" + }, + { + "fieldname": "column_break_16", + "fieldtype": "Column Break" + }, + { + "bold": 1, + "fieldname": "customer_name", + "fieldtype": "Data", + "label": "Customer Name", + "oldfieldname": "customer_name", + "oldfieldtype": "Data", + "read_only": 1 + }, + { + "fieldname": "project", + "fieldtype": "Link", + "label": "Project", + "options": "Project" + }, + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "print_hide": 1 + }, + { + "collapsible": 1, + "fieldname": "section_break_19", + "fieldtype": "Section Break", + "label": "Resolution" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "resolution_details", + "fieldtype": "Text Editor", + "label": "Resolution Details", + "no_copy": 1, + "oldfieldname": "resolution_details", + "oldfieldtype": "Text" + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "column_break1", + "fieldtype": "Column Break", + "oldfieldtype": "Column Break", + "read_only": 1 + }, + { + "default": "Today", + "fieldname": "opening_date", + "fieldtype": "Date", + "label": "Opening Date", + "no_copy": 1, + "oldfieldname": "opening_date", + "oldfieldtype": "Date", + "read_only": 1 + }, + { + "fieldname": "opening_time", + "fieldtype": "Time", + "label": "Opening Time", + "no_copy": 1, + "oldfieldname": "opening_time", + "oldfieldtype": "Time", + "read_only": 1 + }, + { + "depends_on": "eval:!doc.__islocal", + "fieldname": "resolution_date", + "fieldtype": "Datetime", + "label": "Resolution Date", + "no_copy": 1, + "oldfieldname": "resolution_date", + "oldfieldtype": "Date", + "read_only": 1 + }, + { + "fieldname": "content_type", + "fieldtype": "Data", + "hidden": 1, + "label": "Content Type" + }, + { + "fieldname": "attachment", + "fieldtype": "Attach", + "hidden": 1, + "label": "Attachment" + }, + { + "default": "0", + "fieldname": "via_customer_portal", + "fieldtype": "Check", + "label": "Via Customer Portal" + }, + { + "default": "Ongoing", + "depends_on": "eval: doc.service_level_agreement", + "fieldname": "agreement_fulfilled", + "fieldtype": "Select", + "label": "Service Level Agreement Fulfilled", + "options": "Ongoing\nFulfilled\nFailed", + "read_only": 1 + }, + { + "depends_on": "eval: doc.service_level_agreement", + "description": "in hours", + "fieldname": "response_by_variance", + "fieldtype": "Float", + "label": "Response By Variance", + "read_only": 1 + }, + { + "depends_on": "eval: doc.service_level_agreement", + "description": "in hours", + "fieldname": "resolution_by_variance", + "fieldtype": "Float", + "label": "Resolution By Variance", + "read_only": 1 + }, + { + "fieldname": "service_level_agreement_creation", + "fieldtype": "Datetime", + "hidden": 1, + "label": "Service Level Agreement Creation", + "read_only": 1 + }, + { + "depends_on": "eval: doc.service_level_agreement", + "fieldname": "reset_service_level_agreement", + "fieldtype": "Button", + "label": "Reset Service Level Agreement" + }, + { + "fieldname": "issue_split_from", + "fieldtype": "Link", + "label": "Issue Split From", + "options": "Issue", + "read_only": 1 + } + ], + "icon": "fa fa-ticket", + "idx": 7, + "modified": "2019-09-11 09:03:57.465623", + "modified_by": "himanshu@erpnext.com", + "module": "Support", + "name": "Issue", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Support Team", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "search_fields": "status,customer,subject,raised_by", + "sort_field": "modified", + "sort_order": "ASC", + "timeline_field": "customer", + "title_field": "subject", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 58e2076858e..b748e3fa46e 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -117,6 +117,9 @@ class Issue(Document): replicated_issue = deepcopy(self) replicated_issue.subject = subject + replicated_issue.issue_split_from = self.name + replicated_issue.mins_to_first_response = 0 + replicated_issue.first_responded_on = None replicated_issue.creation = now_datetime() # Reset SLA @@ -144,6 +147,14 @@ class Issue(Document): doc.reference_name = replicated_issue.name doc.save(ignore_permissions=True) + frappe.get_doc({ + "doctype": "Comment", + "comment_type": "Info", + "reference_doctype": "Issue", + "reference_name": replicated_issue.name, + "content": " - Split the Issue from {1}".format(self.name, frappe.bold(self.name)), + }).insert(ignore_permissions=True) + return replicated_issue.name def before_insert(self): diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html index f4a31a7e73d..2a70d8dbe9e 100644 --- a/erpnext/templates/generators/item/item_add_to_cart.html +++ b/erpnext/templates/generators/item/item_add_to_cart.html @@ -27,6 +27,7 @@ {% endif %} {% endif %} + {% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %} + {% endif %} @@ -64,4 +66,4 @@ }); -{% endif %} \ No newline at end of file +{% endif %} diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html index faf5e9278c7..d4deaaef688 100644 --- a/erpnext/templates/includes/footer/footer_powered.html +++ b/erpnext/templates/includes/footer/footer_powered.html @@ -1 +1,3 @@ -Powered by ERPNext +{% set domains = frappe.get_doc("Domain Settings").active_domains %} + +Powered by ERPNext - ERP Software {{ ('for ' + domains[0].domain + ' Companies') if domains else '' }} diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js index 65949bb18cc..90a1d862c3d 100644 --- a/erpnext/templates/includes/product_page.js +++ b/erpnext/templates/includes/product_page.js @@ -32,7 +32,7 @@ frappe.ready(function() { if(r.message.product_info.in_stock===0) { $(".item-stock").html("
    {{ _("Not in stock") }}
    "); } - else if(r.message.product_info.in_stock===1) { + else if(r.message.product_info.in_stock===1 && r.message.cart_settings.show_stock_availability) { var qty_display = "{{ _("In stock") }}"; if (r.message.product_info.show_stock_qty) { qty_display += " ("+r.message.product_info.stock_qty+")"; diff --git a/erpnext/templates/pages/rfq.py b/erpnext/templates/pages/rfq.py index 62ec60966bb..67679a1a7d3 100644 --- a/erpnext/templates/pages/rfq.py +++ b/erpnext/templates/pages/rfq.py @@ -5,8 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.utils import formatdate -from erpnext.controllers.website_list_for_contact import (get_customers_suppliers, - get_party_details) +from erpnext.controllers.website_list_for_contact import get_customers_suppliers def get_context(context): context.no_cache = 1 @@ -23,8 +22,8 @@ def get_supplier(): doctype = frappe.form_dict.doctype parties_doctype = 'Request for Quotation Supplier' if doctype == 'Request for Quotation' else doctype customers, suppliers = get_customers_suppliers(parties_doctype, frappe.session.user) - key, parties = get_party_details(customers, suppliers) - return parties[0] if key == 'supplier' else '' + + return suppliers[0] if suppliers else '' def check_supplier_has_docname_access(supplier): status = True