diff --git a/erpnext/__init__.py b/erpnext/__init__.py index fd0f5c1936..6ec290c5e5 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__ = '10.1.4' +__version__ = '10.1.5' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 496f41207c..9e3fa71e97 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -145,13 +145,13 @@ frappe.ui.form.on('Payment Entry', { frm.doc.paid_amount : frm.doc.received_amount; frm.toggle_display("write_off_difference_amount", (frm.doc.difference_amount && frm.doc.party && - (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) && (frm.doc.total_allocated_amount > party_amount))); frm.toggle_display("set_exchange_gain_loss", (frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount && - (frm.doc.paid_from_account_currency != company_currency || - frm.doc.paid_to_account_currency != company_currency))); + ((frm.doc.paid_from_account_currency != company_currency || + frm.doc.paid_to_account_currency != company_currency) && + frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency))); frm.refresh_fields(); }, @@ -300,7 +300,15 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.payment_type == "Pay") { frm.events.get_outstanding_documents(frm); } else if (frm.doc.payment_type == "Receive") { - frm.events.received_amount(frm); + if(frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) { + if(frm.doc.source_exchange_rate) { + frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate); + } + frm.set_value("received_amount", frm.doc.paid_amount); + + } else { + frm.events.received_amount(frm); + } } } ); @@ -317,26 +325,31 @@ frappe.ui.form.on('Payment Entry', { }, callback: function(r, rt) { if(r.message) { - frm.set_value(currency_field, r.message['account_currency']); - frm.set_value(balance_field, r.message['account_balance']); + frappe.run_serially([ + () => frm.set_value(currency_field, r.message['account_currency']), + () => { + frm.set_value(balance_field, r.message['account_balance']); - if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") { - frm.toggle_reqd(["reference_no", "reference_date"], - (r.message['account_type'] == "Bank" ? 1 : 0)); - if(!frm.doc.received_amount && frm.doc.paid_amount) - frm.events.paid_amount(frm); - } else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") { - frm.toggle_reqd(["reference_no", "reference_date"], - (r.message['account_type'] == "Bank" ? 1 : 0)); + if(frm.doc.payment_type=="Receive" && currency_field=="paid_to_account_currency") { + frm.toggle_reqd(["reference_no", "reference_date"], + (r.message['account_type'] == "Bank" ? 1 : 0)); + if(!frm.doc.received_amount && frm.doc.paid_amount) + frm.events.paid_amount(frm); + } else if(frm.doc.payment_type=="Pay" && currency_field=="paid_from_account_currency") { + frm.toggle_reqd(["reference_no", "reference_date"], + (r.message['account_type'] == "Bank" ? 1 : 0)); - if(!frm.doc.paid_amount && frm.doc.received_amount) - frm.events.received_amount(frm); - } + if(!frm.doc.paid_amount && frm.doc.received_amount) + frm.events.received_amount(frm); + } + }, + () => { + if(callback_function) callback_function(frm); - if(callback_function) callback_function(frm); - - frm.events.hide_unhide_fields(frm); - frm.events.set_dynamic_labels(frm); + frm.events.hide_unhide_fields(frm); + frm.events.set_dynamic_labels(frm); + } + ]); } } }); @@ -405,7 +418,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("base_received_amount", frm.doc.base_paid_amount); } - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); } // Make read only if Accounts Settings doesn't allow stale rates @@ -425,7 +438,7 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("base_paid_amount", frm.doc.base_received_amount); } - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); } frm.set_paid_amount_based_on_received_amount = false; @@ -456,7 +469,7 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.payment_type == "Pay") frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); else - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); frm.set_paid_amount_based_on_received_amount = false; }, @@ -476,7 +489,7 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.payment_type == "Receive") frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); else - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); }, get_outstanding_documents: function(frm) { @@ -565,8 +578,11 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.references.length == 0){ frm.events.get_outstanding_documents(frm); } - - frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + if(frm.doc.payment_type == 'Internal Transfer') { + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.paid_amount); + } else { + frm.events.allocate_party_amount_against_ref_docs(frm, frm.doc.received_amount); + } }, allocate_party_amount_against_ref_docs: function(frm, paid_amount) { @@ -651,10 +667,10 @@ frappe.ui.form.on('Payment Entry', { frm.set_value("total_allocated_amount", Math.abs(total_allocated_amount)); frm.set_value("base_total_allocated_amount", Math.abs(base_total_allocated_amount)); - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); }, - set_difference_amount: function(frm) { + set_unallocated_amount: function(frm) { var unallocated_amount = 0; var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], function(d) { return flt(d.amount) })); @@ -662,17 +678,34 @@ frappe.ui.form.on('Payment Entry', { if(frm.doc.party) { var party_amount = frm.doc.payment_type=="Receive" ? frm.doc.paid_amount : frm.doc.received_amount; + var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: ""; - if(frm.doc.total_allocated_amount < party_amount) { - if(frm.doc.payment_type == "Receive") { + if (frm.doc.party_account_currency == company_currency) { + if(frm.doc.payment_type == "Receive" && frm.doc.total_allocated_amount <= party_amount + total_deductions) { unallocated_amount = party_amount - (frm.doc.total_allocated_amount - total_deductions); - } else { + } else if (frm.doc.payment_type == "Pay" && frm.doc.total_allocated_amount <= party_amount - total_deductions) { unallocated_amount = party_amount - (frm.doc.total_allocated_amount + total_deductions); } + } else { + if(frm.doc.payment_type == "Receive" + && frm.doc.base_total_allocated_amount <= frm.doc.base_received_amount + total_deductions + && frm.doc.total_allocated_amount < frm.doc.paid_amount) { + unallocated_amount = (frm.doc.base_received_amount + total_deductions + - frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate; + } else if (frm.doc.payment_type == "Pay" + && frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions + && frm.doc.total_allocated_amount < frm.doc.received_amount) { + unallocated_amount = (frm.doc.base_paid_amount - (total_deductions + + frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate; + } } + } frm.set_value("unallocated_amount", unallocated_amount); - + frm.trigger("set_difference_amount"); + }, + + set_difference_amount: function(frm) { var difference_amount = 0; var base_unallocated_amount = flt(frm.doc.unallocated_amount) * (frm.doc.payment_type=="Receive" ? frm.doc.source_exchange_rate : frm.doc.target_exchange_rate); @@ -687,11 +720,18 @@ frappe.ui.form.on('Payment Entry', { difference_amount = flt(frm.doc.base_paid_amount) - flt(frm.doc.base_received_amount); } + var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [], + function(d) { return flt(d.amount) })); + frm.set_value("difference_amount", difference_amount - total_deductions); frm.events.hide_unhide_fields(frm); }, + unallocated_amount: function(frm) { + frm.trigger("set_difference_amount"); + }, + check_mandatory_to_fetch: function(frm) { $.each(["Company", "Party Type", "Party", "payment_type"], function(i, field) { if(!frm.doc[frappe.model.scrub(field)]) { @@ -771,7 +811,7 @@ frappe.ui.form.on('Payment Entry', { row.amount = flt(row.amount) + flt(frm.doc.difference_amount); refresh_field("deductions"); - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); } } }) @@ -818,10 +858,10 @@ frappe.ui.form.on('Payment Entry Reference', { frappe.ui.form.on('Payment Entry Deduction', { amount: function(frm) { - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); }, deductions_remove: function(frm) { - frm.events.set_difference_amount(frm); + frm.events.set_unallocated_amount(frm); } }) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index abf4ac9b15..9e9a4f1536 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -40,6 +40,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -71,6 +72,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 1, + "translatable": 0, "unique": 0 }, { @@ -102,6 +104,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -131,6 +134,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -162,6 +166,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -193,6 +198,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -224,6 +230,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -255,6 +262,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -288,6 +296,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -320,6 +329,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -349,6 +359,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -381,6 +392,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -411,6 +423,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -442,6 +455,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -474,6 +488,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -506,6 +521,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -538,6 +554,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -567,6 +584,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -599,6 +617,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -631,6 +650,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -663,6 +683,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -695,6 +716,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -727,6 +749,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -758,6 +781,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -790,6 +814,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -819,6 +844,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -851,6 +877,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -882,6 +909,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -914,6 +942,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -946,6 +975,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -978,6 +1008,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1010,6 +1041,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1041,6 +1073,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1072,6 +1105,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1104,6 +1138,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1134,6 +1169,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1163,6 +1199,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1188,12 +1225,13 @@ "precision": "", "print_hide": 1, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1226,6 +1264,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1257,6 +1296,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1289,6 +1329,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1320,6 +1361,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1350,6 +1392,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1381,6 +1424,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1410,6 +1454,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1441,6 +1486,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1472,6 +1518,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1504,6 +1551,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1536,6 +1584,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1566,6 +1615,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1595,6 +1645,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1626,6 +1677,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1657,6 +1709,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1687,6 +1740,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1718,6 +1772,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1748,6 +1803,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1778,6 +1834,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -1791,7 +1848,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-08-31 11:20:37.578469", + "modified": "2018-02-19 16:58:23.899015", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry", diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index e19295cf4c..7561b35245 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -286,17 +286,30 @@ class PaymentEntry(AccountsController): self.base_total_allocated_amount = abs(base_total_allocated_amount) def set_unallocated_amount(self): - self.unallocated_amount = 0; + self.unallocated_amount = 0 if self.party: - party_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount - total_deductions = sum([flt(d.amount) for d in self.get("deductions")]) - if self.total_allocated_amount < party_amount: - if self.payment_type == "Receive": - self.unallocated_amount = party_amount - (self.total_allocated_amount - total_deductions) - else: - self.unallocated_amount = party_amount - (self.total_allocated_amount + total_deductions) + if self.party_account_currency == self.company_currency: + if self.payment_type == "Receive" \ + and self.total_allocated_amount <= self.paid_amount + total_deductions: + self.unallocated_amount = self.paid_amount - \ + (self.total_allocated_amount - total_deductions) + elif self.payment_type == "Pay" \ + and self.total_allocated_amount <= self.received_amount - total_deductions: + self.unallocated_amount = self.received_amount - \ + (self.total_allocated_amount + total_deductions) + else: + if self.payment_type == "Receive" \ + and self.base_total_allocated_amount <= self.base_received_amount + total_deductions \ + and self.total_allocated_amount < self.paid_amount: + self.unallocated_amount = (self.base_received_amount + total_deductions - + self.base_total_allocated_amount) / self.source_exchange_rate + elif self.payment_type == "Pay" \ + and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \ + and self.total_allocated_amount < self.received_amount: + self.unallocated_amount = (self.base_paid_amount - (total_deductions + + self.base_total_allocated_amount)) / self.target_exchange_rate def set_difference_amount(self): base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate) diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py index 64cd3ad96d..9231aced3a 100644 --- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py @@ -141,7 +141,6 @@ class TestPaymentEntry(unittest.TestCase): def test_payment_entry_retrieves_last_exchange_rate(self): from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records, save_new_records - test_records = test_records save_new_records(test_records) pe = frappe.new_doc("Payment Entry") @@ -151,6 +150,7 @@ class TestPaymentEntry(unittest.TestCase): pe.paid_from = "_Test Bank USD - _TC" pe.paid_to = "_Test Bank - _TC" pe.paid_amount = 100 + pe.received_amount = 100 pe.reference_no = "3" pe.reference_date = "2016-01-10" pe.party_type = "Supplier" diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json index b4d63f87e6..10e147ed08 100644 --- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json +++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json @@ -98,7 +98,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2016-07-11 03:28:03.420683", + "modified": "2018-02-21 03:28:03.420683", "modified_by": "Administrator", "module": "Accounts", "name": "Payment Entry Deduction", diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py index 8d43950102..9cdead8dfc 100644 --- a/erpnext/accounts/doctype/payment_request/test_payment_request.py +++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py @@ -61,13 +61,11 @@ class TestPaymentRequest(unittest.TestCase): self.assertEquals(pr.currency, "USD") def test_payment_entry(self): - frappe.db.set_value("Company", "_Test Company", + frappe.db.set_value("Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC") - frappe.db.set_value("Company", "_Test Company", - "write_off_account", "_Test Write Off - _TC") - frappe.db.set_value("Company", "_Test Company", - "cost_center", "_Test Cost Center - _TC") - + frappe.db.set_value("Company", "_Test Company", "write_off_account", "_Test Write Off - _TC") + frappe.db.set_value("Company", "_Test Company", "cost_center", "_Test Cost Center - _TC") + so_inr = make_sales_order(currency="INR") pr = make_payment_request(dt="Sales Order", dn=so_inr.name, recipient_id="saurabh@erpnext.com", mute_email=1, submit_doc=1, return_doc=1) @@ -82,15 +80,15 @@ class TestPaymentRequest(unittest.TestCase): pr = make_payment_request(dt="Sales Invoice", dn=si_usd.name, recipient_id="saurabh@erpnext.com", mute_email=1, payment_gateway="_Test Gateway - USD", submit_doc=1, return_doc=1) - + pe = pr.set_as_paid() - + expected_gle = dict((d[0], d) for d in [ ["_Test Receivable USD - _TC", 0, 5000, si_usd.name], [pr.payment_account, 6290.0, 0, None], ["_Test Exchange Gain/Loss - _TC", 0, 1290, None] ]) - + gl_entries = frappe.db.sql("""select account, debit, credit, against_voucher from `tabGL Entry` where voucher_type='Payment Entry' and voucher_no=%s order by account asc""", pe.name, as_dict=1) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 93c22067d8..bb2d071dc2 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -106,7 +106,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte on_submit: function(doc, dt, dn) { var me = this; - if (frappe.get_route()[0] != 'Sales Invoice') { + if (frappe.get_route()[0] != 'Form') { return } diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py index e879f40c37..9e890f6bcc 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.py +++ b/erpnext/buying/doctype/purchase_order/purchase_order.py @@ -30,8 +30,7 @@ class PurchaseOrder(BuyingController): 'target_parent_field': 'per_ordered', 'target_ref_field': 'stock_qty', 'source_field': 'stock_qty', - 'percent_join_field': 'material_request', - 'overflow_type': 'order' + 'percent_join_field': 'material_request' }] def onload(self): @@ -232,12 +231,16 @@ class PurchaseOrder(BuyingController): pass def update_status_updater(self): - self.status_updater[0].update({ - "target_parent_dt": "Sales Order", - "target_dt": "Sales Order Item", + self.status_updater.append({ + 'source_dt': 'Purchase Order Item', + 'target_dt': 'Sales Order Item', 'target_field': 'ordered_qty', - "join_field": "sales_order_item", - "target_parent_field": '' + 'target_parent_dt': 'Sales Order', + 'target_parent_field': '', + 'join_field': 'sales_order_item', + 'source_dt': 'Purchase Order Item', + 'target_ref_field': 'stock_qty', + 'source_field': 'stock_qty' }) def update_delivered_qty_in_sales_order(self): diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 472a7a3fce..1af2fdd70d 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -666,6 +666,8 @@ class AccountsController(TransactionBase): self.remove(item) def set_payment_schedule(self): + if self.doctype == 'Sales Invoice' and self.is_pos: return + posting_date = self.get("bill_date") or self.get("posting_date") or self.get("transaction_date") date = self.get("due_date") due_date = date or posting_date @@ -695,6 +697,8 @@ class AccountsController(TransactionBase): dates = [] li = [] + if self.doctype == 'Sales Invoice' and self.is_pos: return + for d in self.get("payment_schedule"): if self.doctype == "Sales Order" and getdate(d.due_date) < getdate(self.transaction_date): frappe.throw(_("Row {0}: Due Date cannot be before posting date").format(d.idx)) @@ -708,6 +712,8 @@ class AccountsController(TransactionBase): .format(list=duplicates)) def validate_payment_schedule_amount(self): + if self.doctype == 'Sales Invoice' and self.is_pos: return + if self.get("payment_schedule"): total = 0 for d in self.get("payment_schedule"): diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py index d16f063c20..4b8bbee749 100644 --- a/erpnext/controllers/sales_and_purchase_return.py +++ b/erpnext/controllers/sales_and_purchase_return.py @@ -53,8 +53,9 @@ def validate_returned_items(doc): valid_items = frappe._dict() - select_fields = "item_code, qty, rate, parenttype" if doc.doctype=="Purchase Invoice" \ - else "item_code, qty, rate, serial_no, batch_no, parenttype" + select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor" + if doc.doctype != 'Purchase Invoice': + select_fields += ",serial_no, batch_no" if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']: select_fields += ",rejected_qty, received_qty" @@ -111,7 +112,7 @@ def validate_returned_items(doc): frappe.throw(_("Atleast one item should be entered with negative quantity in return document")) def validate_quantity(doc, args, ref, valid_items, already_returned_items): - fields = ['qty'] + fields = ['stock_qty'] if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']: fields.extend(['received_qty', 'rejected_qty']) @@ -119,16 +120,19 @@ def validate_quantity(doc, args, ref, valid_items, already_returned_items): for column in fields: returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0 - reference_qty = ref.get(column) + reference_qty = (ref.get(column) if column == 'stock_qty' + else ref.get(column) * ref.get("conversion_factor", 1.0)) + max_returnable_qty = flt(reference_qty) - returned_qty label = column.replace('_', ' ').title() + if reference_qty: if flt(args.get(column)) > 0: frappe.throw(_("{0} must be negative in return document").format(label)) elif returned_qty >= reference_qty and args.get(column): frappe.throw(_("Item {0} has already been returned") .format(args.item_code), StockOverReturnError) - elif abs(args.get(column)) > max_returnable_qty: + elif (abs(args.get(column)) * args.get("conversion_factor", 1.0)) > max_returnable_qty: frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}") .format(args.idx, reference_qty, args.item_code), StockOverReturnError) @@ -138,6 +142,7 @@ def get_ref_item_dict(valid_items, ref_item_row): valid_items.setdefault(ref_item_row.item_code, frappe._dict({ "qty": 0, "rate": 0, + "stock_qty": 0, "rejected_qty": 0, "received_qty": 0, "serial_no": [], @@ -145,6 +150,7 @@ def get_ref_item_dict(valid_items, ref_item_row): })) item_dict = valid_items[ref_item_row.item_code] item_dict["qty"] += ref_item_row.qty + item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0) if ref_item_row.get("rate", 0) > item_dict["rate"]: item_dict["rate"] = ref_item_row.get("rate", 0) @@ -161,9 +167,10 @@ def get_ref_item_dict(valid_items, ref_item_row): return valid_items def get_already_returned_items(doc): - column = 'child.item_code, sum(abs(child.qty)) as qty' + column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty' if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']: - column += ', sum(abs(child.rejected_qty)) as rejected_qty, sum(abs(child.received_qty)) as received_qty' + column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty, + sum(abs(child.received_qty) * child.conversion_factor) as received_qty""" data = frappe.db.sql(""" select {0} @@ -180,6 +187,7 @@ def get_already_returned_items(doc): for d in data: items.setdefault(d.item_code, frappe._dict({ "qty": d.get("qty"), + "stock_qty": d.get("stock_qty"), "received_qty": d.get("received_qty"), "rejected_qty": d.get("rejected_qty") })) diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py index 2f54fc0175..b46c752aa6 100644 --- a/erpnext/controllers/status_updater.py +++ b/erpnext/controllers/status_updater.py @@ -250,7 +250,7 @@ class StatusUpdater(Document): if args['detail_id']: if not args.get("extra_cond"): args["extra_cond"] = "" - + frappe.db.sql("""update `tab%(target_dt)s` set %(target_field)s = ( (select ifnull(sum(%(source_field)s), 0) @@ -275,7 +275,7 @@ class StatusUpdater(Document): """Update percent field in parent transaction""" self._update_modified(args, update_modified) - + if args.get('target_parent_field'): frappe.db.sql("""update `tab%(target_parent_dt)s` set %(target_parent_field)s = round( diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.js b/erpnext/education/doctype/fee_schedule/fee_schedule.js index a36b9cbe29..c4fff77d89 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.js +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.js @@ -38,9 +38,6 @@ frappe.ui.form.on('Fee Schedule', { if (data.reload && data.reload === 1) { frm.reload_doc(); } - if (data.progress && data.progress === "0") { - frappe.msgprint(__("Fee records will be created in the background. In case of any error the error message will be updated in the Schedule.")); - } if (data.progress) { let progress_bar = $(cur_frm.dashboard.progress_area).find(".progress-bar"); if (progress_bar) { @@ -74,6 +71,15 @@ frappe.ui.form.on('Fee Schedule', { }); }, "fa fa-play", "btn-success"); } + if (frm.doc.fee_creation_status == "Successful") { + frm.add_custom_button(__("View Fees Records"), function() { + frappe.route_options = { + fee_schedule: frm.doc.name + }; + frappe.set_route("List", "Fees"); + }); + } + }, fee_structure: function(frm) { diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.json b/erpnext/education/doctype/fee_schedule/fee_schedule.json index a77cc595ff..5aae690668 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.json +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.json @@ -4,7 +4,7 @@ "allow_import": 1, "allow_rename": 0, "autoname": "naming_series:", - "beta": 1, + "beta": 0, "creation": "2017-07-18 15:21:21.527136", "custom": 0, "docstatus": 0, @@ -1029,7 +1029,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-04 13:08:27.727709", + "modified": "2018-02-26 13:59:36.560780", "modified_by": "Administrator", "module": "Education", "name": "Fee Schedule", diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.py b/erpnext/education/doctype/fee_schedule/fee_schedule.py index 59acf462d9..b6df8c58d8 100644 --- a/erpnext/education/doctype/fee_schedule/fee_schedule.py +++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py @@ -56,8 +56,15 @@ class FeeSchedule(Document): self.db_set("fee_creation_status", "In Process") frappe.publish_realtime("fee_schedule_progress", {"progress": "0", "reload": 1}, user=frappe.session.user) - enqueue(generate_fee, queue='default', timeout=6000, event='generate_fee', - fee_schedule=self.name) + + total_records = sum([int(d.total_students) for d in self.student_groups]) + if total_records > 10: + frappe.msgprint(_('''Fee records will be created in the background. + In case of any error the error message will be updated in the Schedule.''')) + enqueue(generate_fee, queue='default', timeout=6000, event='generate_fee', + fee_schedule=self.name) + else: + generate_fee(self.name) def generate_fee(fee_schedule): doc = frappe.get_doc("Fee Schedule", fee_schedule) @@ -69,10 +76,7 @@ def generate_fee(fee_schedule): frappe.throw(_("Please setup Students under Student Groups")) for d in doc.student_groups: - students = frappe.db.sql(""" select sg.program, sg.batch, sgs.student, sgs.student_name - from `tabStudent Group` sg, `tabStudent Group Student` sgs - where sg.name=%s and sg.name=sgs.parent and sgs.active=1""", d.student_group, as_dict=1) - + students = get_students(d.student_group, doc.academic_year, doc.academic_term, doc.student_category) for student in students: try: fees_doc = get_mapped_doc("Fee Schedule", fee_schedule, { @@ -86,7 +90,7 @@ def generate_fee(fee_schedule): fees_doc.student = student.student fees_doc.student_name = student.student_name fees_doc.program = student.program - fees_doc.student_batch = student.batch + fees_doc.student_batch = student.student_batch_name fees_doc.send_payment_request = doc.send_email fees_doc.save() fees_doc.submit() @@ -110,6 +114,30 @@ def generate_fee(fee_schedule): {"progress": "100", "reload": 1}, user=frappe.session.user) +def get_students(student_group, academic_year, academic_term=None, student_category=None): + conditions = "" + if student_category: + conditions = " and pe.student_category='{}'".format(frappe.db.escape(student_category)) + if academic_term: + conditions = " and pe.academic_term='{}'".format(frappe.db.escape(academic_term)) + + students = frappe.db.sql(""" + select pe.student, pe.student_name, pe.program, pe.student_batch_name + from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe + where + pe.student = sgs.student and pe.academic_year = %s + and sgs.parent = %s and sgs.active = 1 + {conditions} + """.format(conditions=conditions), (academic_year, student_group), as_dict=1) + return students + + +@frappe.whitelist() +def get_total_students(student_group, academic_year, academic_term=None, student_category=None): + total_students = get_students(student_group, academic_year, academic_term, student_category) + return len(total_students) + + @frappe.whitelist() def get_fee_structure(source_name,target_doc=None): fee_request = get_mapped_doc("Fee Structure", source_name, @@ -117,23 +145,3 @@ def get_fee_structure(source_name,target_doc=None): "doctype": "Fee Schedule" }}, ignore_permissions=True) return fee_request - -@frappe.whitelist() -def get_total_students(student_group, academic_year, academic_term=None, student_category=None): - conditions = "" - if student_category: - conditions = " and pe.student_category='{}'".format(frappe.db.escape(student_category)) - if academic_term: - conditions = " and pe.academic_term='{}'".format(frappe.db.escape(academic_term)) - - - return frappe.db.sql(""" - select count(pe.name) - from `tabStudent Group Student` sgs, `tabProgram Enrollment` pe - where - pe.student = sgs.student - and pe.academic_year = %s - and sgs.parent = %s - and sgs.active = 1 - {conditions} - """.format(conditions=conditions), (academic_year, student_group))[0][0] diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js index c24a6bc30b..5e1013d6a2 100755 --- a/erpnext/hr/doctype/employee/employee.js +++ b/erpnext/hr/doctype/employee/employee.js @@ -81,7 +81,7 @@ frappe.ui.form.on('Employee',{ } frappe.call({ method: "erpnext.hr.doctype.employee.employee.create_user", - args: { employee: cur_frm.doc.name }, + args: { employee: frm.doc.name, email: frm.doc.prefered_email }, callback: function(r) { frm.set_value("user_id", r.message) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 9541b775a1..25d3ec4bc9 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -263,7 +263,7 @@ def deactivate_sales_person(status = None, employee = None): frappe.db.set_value("Sales Person", sales_person, "enabled", 0) @frappe.whitelist() -def create_user(employee, user = None): +def create_user(employee, user = None, email=None): emp = frappe.get_doc("Employee", employee) employee_name = emp.employee_name.split(" ") @@ -277,6 +277,9 @@ def create_user(employee, user = None): first_name = employee_name[0] + if email: + emp.prefered_email = email + user = frappe.new_doc("User") user.update({ "name": emp.employee_name, diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py index 9462211c09..d0c4a46e3e 100644 --- a/erpnext/hr/doctype/expense_claim/expense_claim.py +++ b/erpnext/hr/doctype/expense_claim/expense_claim.py @@ -45,10 +45,11 @@ class ExpenseClaim(AccountsController): }[cstr(self.docstatus or 0)] paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount) - if self.total_sanctioned_amount > 0 and self.total_sanctioned_amount == paid_amount\ + if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 + and flt(self.total_sanctioned_amount) == paid_amount)) \ and self.docstatus == 1 and self.approval_status == 'Approved': - self.status = "Paid" - elif self.total_sanctioned_amount > 0 and self.docstatus == 1 and self.approval_status == 'Approved': + self.status = "Paid" + elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved': self.status = "Unpaid" elif self.docstatus == 1 and self.approval_status == 'Rejected': self.status = 'Rejected' diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index cf15846f2f..4aa4141656 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -5,7 +5,10 @@ var in_progress = false; frappe.ui.form.on('Payroll Entry', { onload: function (frm) { - frm.doc.posting_date = frappe.datetime.nowdate(); + if (!frm.doc.posting_date) { + frm.doc.posting_date = frappe.datetime.nowdate(); + } + frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); }, diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 2a5b467845..e53a2a67fa 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -175,7 +175,7 @@ class PayrollEntry(Document): Get loan details from submitted salary slip based on selected criteria """ cond = self.get_filter_condition() - return frappe.db.sql(""" select eld.employee_loan_account, + return frappe.db.sql(""" select eld.employee_loan_account, eld.employee_loan, eld.interest_income_account, eld.principal_amount, eld.interest_amount, eld.total_payment from `tabSalary Slip` t1, `tabSalary Slip Loan` eld @@ -283,7 +283,12 @@ class PayrollEntry(Document): "account": data.employee_loan_account, "credit_in_account_currency": data.principal_amount }) - accounts.append({ + + if data.interest_amount and not data.interest_income_account: + frappe.throw(_("Select interest income account in employee loan {0}").format(data.employee_loan)) + + if data.interest_income_account and data.interest_amount: + accounts.append({ "account": data.interest_income_account, "credit_in_account_currency": data.interest_amount, "cost_center": self.cost_center, diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index a474569603..b9371e31fb 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -156,9 +156,10 @@ class SalarySlip(TransactionBase): }) def get_date_details(self): - date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date) - self.start_date = date_details.start_date - self.end_date = date_details.end_date + if not self.end_date: + date_details = get_start_end_dates(self.payroll_frequency, self.start_date or self.posting_date) + self.start_date = date_details.start_date + self.end_date = date_details.end_date def check_sal_struct(self, joining_date, relieving_date): cond = '' diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 8cafb9105d..5e1e52a504 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -47,7 +47,6 @@ class BOM(WebsiteGenerator): self.validate_currency() self.set_conversion_rate() self.validate_uom_is_interger() - self.update_stock_qty() self.set_bom_material_details() self.validate_materials() self.validate_operations() @@ -247,14 +246,12 @@ class BOM(WebsiteGenerator): set_default(self, "item") item = frappe.get_doc("Item", self.item) if item.default_bom != self.name: - item.default_bom = self.name - item.save(ignore_permissions = True) + frappe.db.set_value('Item', self.item, 'default_bom', self.name) else: frappe.db.set(self, "is_default", 0) item = frappe.get_doc("Item", self.item) if item.default_bom == self.name: - item.default_bom = None - item.save(ignore_permissions = True) + frappe.db.set_value('Item', self.item, 'default_bom', None) def clear_operations(self): if not self.with_operations: @@ -291,6 +288,8 @@ class BOM(WebsiteGenerator): m.uom = m.stock_uom m.qty = m.stock_qty + m.db_update() + def validate_uom_is_interger(self): from erpnext.utilities.transaction_base import validate_uom_is_integer validate_uom_is_integer(self, "uom", "qty", "BOM Item") @@ -333,19 +332,23 @@ class BOM(WebsiteGenerator): def check_recursion(self): """ Check whether recursion occurs in any bom""" + bom_list = self.traverse_tree() + bom_nos = frappe.get_all('BOM Item', fields=["bom_no"], + filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) - check_list = [['parent', 'bom_no', 'parent'], ['bom_no', 'parent', 'child']] - for d in check_list: - bom_list, count = [self.name], 0 - while (len(bom_list) > count ): - boms = frappe.db.sql(" select %s from `tabBOM Item` where %s = %s and parenttype='BOM'" % - (d[0], d[1], '%s'), cstr(bom_list[count])) - count = count + 1 - for b in boms: - if b[0] == self.name: - frappe.throw(_("BOM recursion: {0} cannot be parent or child of {2}").format(b[0], self.name)) - if b[0]: - bom_list.append(b[0]) + raise_exception = False + if bom_nos and self.name in [d.bom_no for d in bom_nos]: + raise_exception = True + + if not raise_exception: + bom_nos = frappe.get_all('BOM Item', fields=["parent"], + filters={'bom_no': self.name, 'parenttype': 'BOM'}) + + if self.name in [d.parent for d in bom_nos]: + raise_exception = True + + if raise_exception: + frappe.throw(_("BOM recursion: {0} cannot be parent or child of {2}").format(self.name, self.name)) def update_cost_and_exploded_items(self, bom_list=[]): bom_list = self.traverse_tree(bom_list) diff --git a/erpnext/manufacturing/doctype/bom/bom_tree.js b/erpnext/manufacturing/doctype/bom/bom_tree.js index 4ec9bef998..185b9ed4bc 100644 --- a/erpnext/manufacturing/doctype/bom/bom_tree.js +++ b/erpnext/manufacturing/doctype/bom/bom_tree.js @@ -60,5 +60,14 @@ frappe.treeview_settings["BOM"] = { condition: 'frappe.boot.user.can_create.indexOf("BOM") !== -1' } ], + onrender: function(node) { + if(node.is_root && node.data.value!="BOM") { + frappe.model.with_doc("BOM", node.data.value, function() { + var bom = frappe.model.get_doc("BOM", node.data.value); + node.data.image = bom.image || ""; + node.data.description = bom.description || ""; + }); + } + }, view_template: 'bom_item_preview' } \ No newline at end of file diff --git a/erpnext/patches.txt b/erpnext/patches.txt index e21da22691..dd707dafa0 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -490,3 +490,7 @@ erpnext.patches.v10_0.set_default_payment_terms_based_on_company erpnext.patches.v10_0.update_sales_order_link_to_purchase_order erpnext.patches.v10_0.added_extra_gst_custom_field_in_gstr2 #2018-02-13 erpnext.patches.v10_0.set_b2c_limit +erpnext.patches.v10_0.update_status_for_multiple_source_in_po +erpnext.patches.v10_0.set_auto_created_serial_no_in_stock_entry +erpnext.patches.v10_0.update_territory_and_customer_group +erpnext.patches.v10_0.update_warehouse_address_details \ No newline at end of file diff --git a/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py b/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py new file mode 100644 index 0000000000..c6470f21d7 --- /dev/null +++ b/erpnext/patches/v10_0/set_auto_created_serial_no_in_stock_entry.py @@ -0,0 +1,56 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + serialised_items = [d.name for d in frappe.get_all("Item", filters={"has_serial_no": 1})] + + if not serialised_items: + return + + for dt in ["Stock Entry Detail", "Purchase Receipt Item", "Purchase Invoice Item"]: + cond = "" + if dt=="Purchase Invoice Item": + cond = """ and parent in (select name from `tabPurchase Invoice` + where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.parent and update_stock=1)""" + + item_rows = frappe.db.sql(""" + select name + from `tab{0}` + where conversion_factor != 1 + and docstatus = 1 + and ifnull(serial_no, '') = '' + and item_code in ({1}) + {2} + """.format(dt, ', '.join(['%s']*len(serialised_items)), cond), tuple(serialised_items)) + + if item_rows: + sle_serial_nos = dict(frappe.db.sql(""" + select voucher_detail_no, serial_no + from `tabStock Ledger Entry` + where ifnull(serial_no, '') != '' + and voucher_detail_no in (%s) + """.format(', '.join(['%s']*len(item_rows))), + tuple([d[0] for d in item_rows]))) + + batch_size = 100 + for i in range(0, len(item_rows), batch_size): + batch_item_rows = item_rows[i:i + batch_size] + when_then = [] + for item_row in batch_item_rows: + + when_then.append('WHEN `name` = "{row_name}" THEN "{value}"'.format( + row_name=item_row[0], + value=sle_serial_nos.get(item_row[0]))) + + frappe.db.sql(""" + update + `tab{doctype}` + set + serial_no = CASE {when_then_cond} ELSE `serial_no` END + """.format( + doctype = dt, + when_then_cond=" ".join(when_then) + )) \ No newline at end of file diff --git a/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py b/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py new file mode 100644 index 0000000000..1de9d976f8 --- /dev/null +++ b/erpnext/patches/v10_0/update_status_for_multiple_source_in_po.py @@ -0,0 +1,38 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + + + # update the sales order item in the material request + frappe.reload_doc('stock', 'doctype', 'material_request_item') + frappe.db.sql('''update `tabMaterial Request Item` mri set sales_order_item = (select name from + `tabSales Order Item` soi where soi.parent=mri.sales_order and soi.item_code=mri.item_code) where docstatus = 1 and + ifnull(mri.sales_order, "")!="" + ''') + + # update the sales order item in the purchase order + frappe.db.sql('''update `tabPurchase Order Item` poi set sales_order_item = (select name from + `tabSales Order Item` soi where soi.parent=poi.sales_order and soi.item_code=poi.item_code) where docstatus = 1 and + ifnull(poi.sales_order, "")!="" + ''') + + # Update the status in material request and sales order + po_list = frappe.db.sql(''' + select parent from `tabPurchase Order Item` where ifnull(material_request, "")!="" and + ifnull(sales_order, "")!="" and docstatus=1 + ''',as_dict=1) + + for po in list(set([d.get("parent") for d in po_list if d.get("parent")])): + try: + po_doc = frappe.get_doc("Purchase Order", po) + + # update the so in the status updater + po_doc.update_status_updater() + po_doc.update_qty(update_modified=False) + + except Exception: + pass diff --git a/erpnext/patches/v10_0/update_territory_and_customer_group.py b/erpnext/patches/v10_0/update_territory_and_customer_group.py new file mode 100644 index 0000000000..afbbccf2ad --- /dev/null +++ b/erpnext/patches/v10_0/update_territory_and_customer_group.py @@ -0,0 +1,29 @@ +import frappe +from frappe.model.rename_doc import get_fetch_fields + +def execute(): + ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"] + customers = frappe.get_all('Customer', fields=["name", "customer_group"]) + + customer_group_fetch = get_fetch_fields('Customer', 'Customer Group', ignore_doctypes) + + batch_size = 1000 + for i in range(0, len(customers), batch_size): + batch_customers = customers[i:i + batch_size] + for d in customer_group_fetch: + when_then = [] + for customer in batch_customers: + value = frappe.db.escape(frappe.as_unicode(customer.get("customer_group"))) + + when_then.append(''' + WHEN `%s` = "%s" and %s != "%s" + THEN "%s" + '''%(d["master_fieldname"], frappe.db.escape(frappe.as_unicode(customer.name)), + d["linked_to_fieldname"], value, value)) + + frappe.db.sql(""" + update + `tab%s` + set + %s = CASE %s ELSE `%s` END + """%(d['doctype'], d.linked_to_fieldname, " ".join(when_then), d.linked_to_fieldname)) diff --git a/erpnext/patches/v10_0/update_warehouse_address_details.py b/erpnext/patches/v10_0/update_warehouse_address_details.py new file mode 100644 index 0000000000..b982b9a662 --- /dev/null +++ b/erpnext/patches/v10_0/update_warehouse_address_details.py @@ -0,0 +1,37 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + warehouse = frappe.db.sql("""select name, email_id, phone_no, mobile_no, address_line_1, + address_line_2, city, state, pin from `tabWarehouse` where ifnull(address_line_1, '') != '' + or ifnull(mobile_no, '') != '' + or ifnull(email_id, '') != '' """, as_dict=1) + + for d in warehouse: + try: + address = frappe.new_doc('Address') + address.name = d.name + address.address_title = d.name + address.address_line1 = d.address_line_1 + address.city = d.city + address.state = d.state + address.pincode = d.pin + address.db_insert() + address.append('links',{'link_doctype':'Warehouse','link_name':d.name}) + address.links[0].db_insert() + if d.name and (d.email_id or d.mobile_no or d.phone_no): + contact = frappe.new_doc('Contact') + contact.name = d.name + contact.first_name = d.name + contact.mobile_no = d.mobile_no + contact.email_id = d.email_id + contact.phone = d.phone_no + contact.db_insert() + contact.append('links',{'link_doctype':'Warehouse','link_name':d.name}) + contact.links[0].db_insert() + except frappe.DuplicateEntryError: + pass + \ No newline at end of file diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index 2284f85374..0ea1119c76 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -11,6 +11,7 @@ from frappe.desk.reportview import build_match_conditions from erpnext.utilities.transaction_base import TransactionBase from erpnext.accounts.party import validate_party_accounts, get_dashboard_info, get_timeline_data # keep this from frappe.contacts.address_and_contact import load_address_and_contact, delete_contact_and_address +from frappe.model.rename_doc import update_linked_doctypes class Customer(TransactionBase): def get_feed(self): @@ -53,6 +54,14 @@ class Customer(TransactionBase): self.flags.old_lead = self.lead_name validate_party_accounts(self) self.validate_credit_limit_on_change() + self.check_customer_group_change() + + def check_customer_group_change(self): + frappe.flags.customer_group_changed = False + + if not self.get('__islocal'): + if self.customer_group != frappe.db.get_value('Customer', self.name, 'customer_group'): + frappe.flags.customer_group_changed = True def on_update(self): self.validate_name_with_customer_group() @@ -65,6 +74,14 @@ class Customer(TransactionBase): if self.flags.is_new_doc: self.create_lead_address_contact() + self.update_customer_groups() + + def update_customer_groups(self): + ignore_doctypes = ["Lead", "Opportunity", "POS Profile", "Tax Rule", "Pricing Rule"] + if frappe.flags.customer_group_changed: + update_linked_doctypes('Customer', self.name, 'Customer Group', + self.customer_group, ignore_doctypes) + def create_primary_contact(self): if not self.customer_primary_contact and not self.lead_name: if self.mobile_no or self.email_id: diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js index 84176426be..081d4dbe9a 100644 --- a/erpnext/selling/doctype/quotation/quotation.js +++ b/erpnext/selling/doctype/quotation/quotation.js @@ -41,7 +41,7 @@ erpnext.selling.QuotationController = erpnext.selling.SellingController.extend({ var me = this; - if (doc.__islocal) { + if (doc.__islocal && !doc.valid_till) { this.frm.set_value('valid_till', frappe.datetime.add_months(doc.transaction_date, 1)) } diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index f1b56d9bd8..2f413072d6 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -435,6 +435,7 @@ def make_material_request(source_name, target_doc=None): "Sales Order Item": { "doctype": "Material Request Item", "field_map": { + "name": "sales_order_item", "parent": "sales_order", "stock_uom": "uom", "stock_qty": "qty" 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 d1f6b7a7c2..0fa082fddc 100644 --- a/erpnext/selling/page/point_of_sale/point_of_sale.js +++ b/erpnext/selling/page/point_of_sale/point_of_sale.js @@ -257,12 +257,16 @@ erpnext.pos.PointOfSale = class PointOfSale { frappe.msgprint(__("Quantity must be positive")); value = item.qty; } else { - item[field] = value; - if (field == "serial_no" && value) { - let serial_nos = value.split("\n"); - item["qty"] = serial_nos.filter(d => { - return d!==""; - }).length; + if (in_list(["qty", "serial_no", "batch"], field)) { + item[field] = value; + if (field == "serial_no" && value) { + let serial_nos = value.split("\n"); + item["qty"] = serial_nos.filter(d => { + return d!==""; + }).length; + } + } else { + return frappe.model.set_value(item.doctype, item.name, field, value); } } @@ -1475,7 +1479,7 @@ class Payment { fieldname: p.mode_of_payment, default: p.amount, onchange: () => { - const value = this.dialog.get_value(this.fieldname); + const value = this.dialog.get_value(this.fieldname) || 0; me.update_payment_value(this.fieldname, value); } }; diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py index 14a478d7e4..0ff7c2b3aa 100644 --- a/erpnext/setup/doctype/item_group/item_group.py +++ b/erpnext/setup/doctype/item_group/item_group.py @@ -102,7 +102,7 @@ def get_product_list_for_group(product_group=None, start=0, limit=10, search=Non or I.name like %(search)s)""" search = "%" + cstr(search) + "%" - query += """order by I.weightage desc, in_stock desc, I.item_name limit %s, %s""" % (start, limit) + query += """order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (start, limit) data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1) diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index 69931872f4..ff01a19be4 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -426,7 +426,9 @@ $.extend(erpnext.item, { filters: [ ["parent","=", attribute] ], - fields: ["attribute_value"] + fields: ["attribute_value"], + limit_start: 0, + limit_page_length: 500 } }).then((r) => { if(r.message) { diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index 8ebe757ffc..7241be3ef7 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -80,7 +80,6 @@ class TestItem(unittest.TestCase): } make_test_objects("Item Price") - print(frappe.get_all("Item Price")) details = get_item_details({ "item_code": "_Test Item", diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index defce62e2e..8f0a25dc43 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -241,7 +241,8 @@ def make_purchase_order(source_name, target_doc=None): ["parent", "material_request"], ["uom", "stock_uom"], ["uom", "uom"], - ["sales_order", "sales_order"] + ["sales_order", "sales_order"], + ["sales_order_item", "sales_order_item"] ], "postprocess": update_item, "condition": lambda doc: doc.ordered_qty < doc.stock_qty diff --git a/erpnext/stock/doctype/material_request_item/material_request_item.json b/erpnext/stock/doctype/material_request_item/material_request_item.json index ef2e7fc8b4..4ec5ed86de 100644 --- a/erpnext/stock/doctype/material_request_item/material_request_item.json +++ b/erpnext/stock/doctype/material_request_item/material_request_item.json @@ -670,6 +670,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "sales_order_item", + "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": "Sales Order Item", + "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": 1, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -898,8 +928,8 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-12-15 16:29:18.902085", - "modified_by": "nabinhait@gmail.com", + "modified": "2018-02-12 05:51:39.954530", + "modified_by": "Administrator", "module": "Stock", "name": "Material Request Item", "owner": "Administrator", diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index b656c3f0dc..29caea156a 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -11,7 +11,7 @@ from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchas from erpnext import set_perpetual_inventory from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError from erpnext.accounts.doctype.account.test_account import get_inventory_account - +from erpnext.stock.doctype.item.test_item import make_item class TestPurchaseReceipt(unittest.TestCase): def setUp(self): @@ -203,6 +203,22 @@ class TestPurchaseReceipt(unittest.TestCase): "delivery_document_no": return_pr.name }) + def test_purchase_return_for_multi_uom(self): + item_code = "_Test Purchase Return For Multi-UOM" + if not frappe.db.exists('Item', item_code): + item = make_item(item_code, {'stock_uom': 'Box'}) + row = item.append('uoms', { + 'uom': 'Unit', + 'conversion_factor': 0.1 + }) + row.db_update() + + pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0) + return_pr = make_purchase_receipt(item_code=item_code, qty=-10, uom="Unit", + stock_uom="Box", conversion_factor=0.1, is_return=1, return_against=pr.name) + + self.assertEquals(abs(return_pr.items[0].stock_qty), 1.0) + def test_closed_purchase_receipt(self): from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status @@ -255,7 +271,6 @@ class TestPurchaseReceipt(unittest.TestCase): def test_not_accept_duplicate_serial_no(self): from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry - from erpnext.stock.doctype.item.test_item import make_item from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note item_code = frappe.db.get_value('Item', {'has_serial_no': 1}) @@ -307,9 +322,10 @@ def make_purchase_receipt(**args): "rejected_qty": rejected_qty, "rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "", "rate": args.rate or 50, - "conversion_factor": 1.0, + "conversion_factor": args.conversion_factor or 1.0, "serial_no": args.serial_no, - "stock_uom": "_Test UOM" + "stock_uom": args.stock_uom or "_Test UOM", + "uom": args.uom or "_Test UOM" }) if not args.do_not_save: diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 80c93ef434..00a8a9305b 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -191,7 +191,7 @@ def validate_serial_no(sle, item_det): if sle.serial_no: frappe.throw(_("Item {0} is not setup for Serial Nos. Column must be blank").format(sle.item_code), SerialNoNotRequiredError) - else: + elif sle.is_cancelled == "No": if sle.serial_no: serial_nos = get_serial_nos(sle.serial_no) if cint(sle.actual_qty) != flt(sle.actual_qty): @@ -326,11 +326,16 @@ def update_serial_nos_after_submit(controller, parentfield): update_rejected_serial_nos = True if (controller.doctype in ("Purchase Receipt", "Purchase Invoice") and d.rejected_qty) else False accepted_serial_nos_updated = False - warehouse = d.t_warehouse if controller.doctype == "Stock Entry" else d.warehouse + if controller.doctype == "Stock Entry": + warehouse = d.t_warehouse + qty = d.transfer_qty + else: + warehouse = d.warehouse + qty = d.stock_qty for sle in stock_ledger_entries: if sle.voucher_detail_no==d.name: - if not accepted_serial_nos_updated and d.qty and abs(sle.actual_qty)==d.qty \ + if not accepted_serial_nos_updated and qty and abs(sle.actual_qty)==qty \ and sle.warehouse == warehouse and sle.serial_no != d.serial_no: d.serial_no = sle.serial_no frappe.db.set_value(d.doctype, d.name, "serial_no", sle.serial_no) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js index 9093946d31..d4be6b4755 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.js +++ b/erpnext/stock/doctype/stock_entry/stock_entry.js @@ -278,6 +278,14 @@ frappe.ui.form.on('Stock Entry', { frm.set_value("total_additional_costs", flt(total_additional_costs, precision("total_additional_costs"))); }, + + source_warehouse_address: function(frm) { + erpnext.utils.get_address_display(frm, 'source_warehouse_address', 'source_address_display', false); + }, + + target_warehouse_address: function(frm) { + erpnext.utils.get_address_display(frm, 'target_warehouse_address', 'target_address_display', false); + }, }) frappe.ui.form.on('Stock Entry Detail', { diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json index f68690cf8f..f6709baf19 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.json +++ b/erpnext/stock/doctype/stock_entry/stock_entry.json @@ -737,6 +737,68 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "from_warehouse", + "fieldname": "source_warehouse_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": "Source Warehouse 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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_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": "Source Warehouse Address", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -797,6 +859,68 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "to_warehouse", + "fieldname": "target_warehouse_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": "Target Warehouse 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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "target_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": "Target Warehouse Address", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1739,7 +1863,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-06-13 14:28:47.818067", + "modified": "2018-03-01 12:27:12.884611", "modified_by": "Administrator", "module": "Stock", "name": "Stock Entry", diff --git a/erpnext/stock/doctype/warehouse/warehouse.js b/erpnext/stock/doctype/warehouse/warehouse.js index 5c23a9729a..1bfa416bdf 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.js +++ b/erpnext/stock/doctype/warehouse/warehouse.js @@ -5,6 +5,15 @@ frappe.ui.form.on("Warehouse", { refresh: function(frm) { frm.toggle_display('warehouse_name', frm.doc.__islocal); + frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal); + + + if(!frm.doc.__islocal) { + frappe.contacts.render_address_and_contact(frm); + + } else { + frappe.contacts.clear_address_and_contact(frm); + } frm.add_custom_button(__("Stock Balance"), function() { frappe.set_route("query-report", "Stock Balance", {"warehouse": frm.doc.name}); diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json index b6307a4633..82d0d0c4fb 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.json +++ b/erpnext/stock/doctype/warehouse/warehouse.json @@ -226,6 +226,125 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "address_and_contact", + "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, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "address_html", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Address HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "contact_html", + "fieldtype": "HTML", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Contact HTML", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -699,7 +818,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-01-23 16:45:45.546649", + "modified": "2018-02-28 09:15:33.463838", "modified_by": "Administrator", "module": "Stock", "name": "Warehouse", diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py index 5186408721..0eee6baddd 100644 --- a/erpnext/stock/doctype/warehouse/warehouse.py +++ b/erpnext/stock/doctype/warehouse/warehouse.py @@ -7,6 +7,7 @@ from frappe.utils import cint, validate_email_add from frappe import throw, _ from frappe.utils.nestedset import NestedSet from erpnext.stock import get_warehouse_account +from frappe.contacts.address_and_contact import load_address_and_contact class Warehouse(NestedSet): nsm_parent_field = 'parent_warehouse' @@ -25,10 +26,8 @@ class Warehouse(NestedSet): if account: self.set_onload('account', account) + load_address_and_contact(self) - def validate(self): - if self.email_id: - validate_email_add(self.email_id, True) def on_update(self): self.update_nsm_model() diff --git a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py index fdf4442797..e1249ea3cd 100644 --- a/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py +++ b/erpnext/stock/report/itemwise_recommended_reorder_level/itemwise_recommended_reorder_level.py @@ -24,9 +24,8 @@ def execute(filters=None): data = [] for item in items: - - total_outgoing = consumed_item_map.get(item.name, 0)+delivered_item_map.get(item.name,0) - avg_daily_outgoing = flt(total_outgoing/diff, float_preceision) + total_outgoing = consumed_item_map.get(item.name, 0) + delivered_item_map.get(item.name,0) + avg_daily_outgoing = flt(total_outgoing / diff, float_preceision) reorder_level = (avg_daily_outgoing * flt(item.lead_time_days)) + flt(item.safety_stock) data.append([item.name, item.item_name, item.description, item.safety_stock, item.lead_time_days, @@ -45,12 +44,11 @@ def get_columns(): def get_item_info(): return frappe.db.sql("""select name, item_name, description, safety_stock, - lead_time_days from tabItem""", as_dict=1) + lead_time_days from tabItem""", as_dict=1) def get_consumed_items(condition): - cn_items = frappe.db.sql("""select se_item.item_code, - sum(se_item.actual_qty) as 'consume_qty' + sum(se_item.transfer_qty) as 'consume_qty' from `tabStock Entry` se, `tabStock Entry Detail` se_item where se.name = se_item.parent and se.docstatus = 1 and ifnull(se_item.t_warehouse, '') = '' %s @@ -63,17 +61,16 @@ def get_consumed_items(condition): return cn_items_map def get_delivered_items(condition): - - dn_items = frappe.db.sql("""select dn_item.item_code, sum(dn_item.qty) as dn_qty + dn_items = frappe.db.sql("""select dn_item.item_code, sum(dn_item.stock_qty) as dn_qty from `tabDelivery Note` dn, `tabDelivery Note Item` dn_item where dn.name = dn_item.parent and dn.docstatus = 1 %s group by dn_item.item_code""" % (condition), as_dict=1) - si_items = frappe.db.sql("""select si_item.item_name, sum(si_item.qty) as si_qty + si_items = frappe.db.sql("""select si_item.item_code, sum(si_item.stock_qty) as si_qty from `tabSales Invoice` si, `tabSales Invoice Item` si_item where si.name = si_item.parent and si.docstatus = 1 and - si.update_stock = 1 and si.is_pos = 1 %s - group by si_item.item_name""" % (condition), as_dict=1) + si.update_stock = 1 %s + group by si_item.item_code""" % (condition), as_dict=1) dn_item_map = {} for item in dn_items: diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py index 86e102982b..e93957d97a 100644 --- a/erpnext/stock/report/stock_ageing/stock_ageing.py +++ b/erpnext/stock/report/stock_ageing/stock_ageing.py @@ -96,6 +96,8 @@ def get_item_conditions(filters): def get_sle_conditions(filters): conditions = [] if filters.get("warehouse"): - conditions.append("warehouse=%(warehouse)s") + lft, rgt = frappe.db.get_value('Warehouse', filters.get("warehouse"), ['lft', 'rgt']) + conditions.append("""warehouse in (select wh.name from `tabWarehouse` wh + where wh.lft >= {0} and rgt <= {1})""".format(lft, rgt)) return "and {}".format(" and ".join(conditions)) if conditions else "" diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 874a382192..db9c2a682f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -452,22 +452,22 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no, where item_code = %s and valuation_rate > 0 order by posting_date desc, posting_time desc, name desc limit 1""", item_code) - valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0 + if last_valuation_rate: + return flt(last_valuation_rate[0][0]) # as there is previous records, it might come with zero rate + + # If negative stock allowed, and item delivered without any incoming entry, + # system does not found any SLE, then take valuation rate from Item + valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate") if not valuation_rate: - # If negative stock allowed, and item delivered without any incoming entry, - # syste does not found any SLE, then take valuation rate from Item - valuation_rate = frappe.db.get_value("Item", item_code, "valuation_rate") + # try Item Standard rate + valuation_rate = frappe.db.get_value("Item", item_code, "standard_rate") if not valuation_rate: - # try Item Standard rate - valuation_rate = frappe.db.get_value("Item", item_code, "standard_rate") - - if not valuation_rate: - # try in price list - valuation_rate = frappe.db.get_value('Item Price', - dict(item_code=item_code, buying=1, currency=currency), - 'price_list_rate') + # try in price list + valuation_rate = frappe.db.get_value('Item Price', + dict(item_code=item_code, buying=1, currency=currency), + 'price_list_rate') if not allow_zero_rate and not valuation_rate \ and cint(erpnext.is_perpetual_inventory_enabled(company)): diff --git a/erpnext/templates/emails/reorder_item.html b/erpnext/templates/emails/reorder_item.html index c1aa897038..05af3167ba 100644 --- a/erpnext/templates/emails/reorder_item.html +++ b/erpnext/templates/emails/reorder_item.html @@ -9,7 +9,7 @@