diff --git a/erpnext/__init__.py b/erpnext/__init__.py index be77f546fec..46cf3132c78 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.67' +__version__ = '11.1.68' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py index 3afb4bc8c6a..bc773772554 100644 --- a/erpnext/accounts/deferred_revenue.py +++ b/erpnext/accounts/deferred_revenue.py @@ -174,6 +174,8 @@ def make_gl_entries(doc, credit_account, debit_account, against, # GL Entry for crediting the amount in the deferred expense from erpnext.accounts.general_ledger import make_gl_entries + if amount == 0: return + gl_entries = [] gl_entries.append( doc.get_gl_dict({ diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 68efe377190..cccced8e0be 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -100,7 +100,10 @@ class Account(NestedSet): if ancestors: if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"): return - frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) + + if not frappe.db.get_value("Account", + {'account_name': self.account_name, 'company': ancestors[0]}, 'name'): + frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) else: descendants = get_descendants_of('Company', self.company) if not descendants: return @@ -114,21 +117,7 @@ class Account(NestedSet): if not parent_acc_name_map: return - for company in descendants: - if not parent_acc_name_map.get(company): - frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA") - .format(company, parent_acc_name)) - - doc = frappe.copy_doc(self) - doc.flags.ignore_root_company_validation = True - doc.update({ - "company": company, - "account_currency": None, - "parent_account": parent_acc_name_map[company] - }) - doc.save() - frappe.msgprint(_("Account {0} is added in the child company {1}") - .format(doc.name, company)) + self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name) def validate_group_or_ledger(self): if self.get("__islocal"): @@ -170,6 +159,49 @@ 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 create_account_for_child_company(self, parent_acc_name_map, descendants, parent_acc_name): + for company in descendants: + if not parent_acc_name_map.get(company): + frappe.throw(_("While creating account for child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA") + .format(company, parent_acc_name)) + + filters = { + "account_name": self.account_name, + "company": company + } + + if self.account_number: + filters["account_number"] = self.account_number + + child_account = frappe.db.get_value("Account", filters, 'name') + + if not child_account: + doc = frappe.copy_doc(self) + doc.flags.ignore_root_company_validation = True + doc.update({ + "company": company, + # parent account's currency should be passed down to child account's curreny + # if it is None, it picks it up from default company currency, which might be unintended + "account_currency": self.account_currency, + "parent_account": parent_acc_name_map[company] + }) + + doc.save() + frappe.msgprint(_("Account {0} is added in the child company {1}") + .format(doc.name, company)) + elif child_account: + # update the parent company's value in child companies + doc = frappe.get_doc("Account", child_account) + parent_value_changed = False + for field in ['account_type', 'account_currency', + 'freeze_account', 'balance_must_be']: + if doc.get(field) != self.get(field): + parent_value_changed = True + doc.set(field, self.get(field)) + + if parent_value_changed: + doc.save() + 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/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js index 5707d154c19..491ae506986 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.js +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js @@ -294,7 +294,7 @@ frappe.ui.form.on('Payment Entry', { () => { frm.set_party_account_based_on_party = false; if (r.message.bank_account) { - frm.set_value("bank_account", r.message.bank_account); + frm.set_value("party_bank_account", r.message.bank_account); } } ]); diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json index 96e1e1eb162..5a53a0e4214 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.json +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json @@ -1791,6 +1791,41 @@ "translatable": 0, "unique": 0 }, + { + "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": 0, + "in_standard_filter": 0, + "label": "Status", + "length": 0, + "no_copy": 0, + "options": "\nDraft\nSubmitted\nCancelled", + "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, @@ -2205,7 +2240,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-03-27 17:39:54.163016", + "modified": "2019-11-06 15:15:45.223497", "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 d57f2c59052..21634c1df3c 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -60,6 +60,7 @@ class PaymentEntry(AccountsController): self.validate_duplicate_entry() self.validate_allocated_amount() self.ensure_supplier_is_not_blocked() + self.set_status() def on_submit(self): self.setup_party_account_field() @@ -69,6 +70,7 @@ class PaymentEntry(AccountsController): self.update_outstanding_amounts() self.update_advance_paid() self.update_expense_claim() + self.set_status() def on_cancel(self): @@ -78,6 +80,7 @@ class PaymentEntry(AccountsController): self.update_advance_paid() self.update_expense_claim() self.delink_advance_entry_references() + self.set_status() def update_outstanding_amounts(self): self.set_missing_ref_details(force=True) @@ -274,6 +277,14 @@ class PaymentEntry(AccountsController): frappe.throw(_("Against Journal Entry {0} does not have any unmatched {1} entry") .format(d.reference_name, dr_or_cr)) + def set_status(self): + if self.docstatus == 2: + self.status = 'Cancelled' + elif self.docstatus == 1: + self.status = 'Submitted' + else: + self.status = 'Draft' + def set_amounts(self): self.set_amounts_in_company_currency() self.set_total_allocated_amount() diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 60446d0e86c..0b3a0b451e9 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -350,7 +350,7 @@ class SalesInvoice(SellingController): timesheet.calculate_percentage_billed() timesheet.flags.ignore_validate_update_after_submit = True timesheet.set_status() - timesheet.save() + timesheet.save(ignore_permissions=True) def update_time_sheet_detail(self, timesheet, args, sales_invoice): for data in timesheet.time_logs: @@ -992,10 +992,8 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - if serial_no and frappe.db.exists('Serial No', serial_no): - sno = frappe.get_doc('Serial No', serial_no) - sno.sales_invoice = invoice - sno.db_update() + if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code: + frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice) def validate_serial_numbers(self): """ @@ -1041,8 +1039,9 @@ class SalesInvoice(SellingController): continue for serial_no in item.serial_no.split("\n"): - sales_invoice = frappe.db.get_value("Serial No", serial_no, "sales_invoice") - if sales_invoice and self.name != sales_invoice: + sales_invoice, item_code = frappe.db.get_value("Serial No", serial_no, + ["sales_invoice", "item_code"]) + if sales_invoice and item_code == item.item_code and self.name != sales_invoice: sales_invoice_company = frappe.db.get_value("Sales Invoice", sales_invoice, "company") if sales_invoice_company == self.company: frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}" diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 95444428b8a..0e606e1fbeb 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -187,7 +187,11 @@ class ReceivablePayableReport(object): self.data.append(row) def set_invoice_details(self, row): - row.update(self.invoice_details.get(row.voucher_no, {})) + invoice_details = self.invoice_details.get(row.voucher_no, {}) + if row.due_date: + invoice_details.pop("due_date", None) + row.update(invoice_details) + if row.voucher_type == 'Sales Invoice': if self.filters.show_delivery_notes: self.set_delivery_notes(row) 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 86f001cba4e..47da0ae9d43 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -36,6 +36,9 @@ class AccountsReceivableSummary(ReceivablePayableReport): self.filters.report_date) or {} for party, party_dict in iteritems(self.party_total): + if party_dict.outstanding <= 0: + continue + row = frappe._dict() row.party = party diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js index 4bc29da2c7d..8c11514aa64 100644 --- a/erpnext/accounts/report/balance_sheet/balance_sheet.js +++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt frappe.require("assets/erpnext/js/financial_statements.js", function() { - frappe.query_reports["Balance Sheet"] = erpnext.financial_statements; + frappe.query_reports["Balance Sheet"] = $.extend({}, erpnext.financial_statements); frappe.query_reports["Balance Sheet"]["filters"].push({ "fieldname": "accumulated_values", diff --git a/erpnext/accounts/report/sales_register/sales_register.py b/erpnext/accounts/report/sales_register/sales_register.py index 3cc2f915268..caf1c5f9eec 100644 --- a/erpnext/accounts/report/sales_register/sales_register.py +++ b/erpnext/accounts/report/sales_register/sales_register.py @@ -70,6 +70,7 @@ def _execute(filters, additional_table_columns=None, additional_query_columns=No if tax_acc not in income_accounts: tax_amount_precision = get_field_precision(frappe.get_meta("Sales Taxes and Charges").get_field("tax_amount"), currency=company_currency) or 2 tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc), tax_amount_precision) + total_tax += tax_amount row.append(tax_amount) # total tax, grand total, outstanding amount & rounded total diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py index 5758b0bc6b1..e0c539ef90d 100644 --- a/erpnext/accounts/report/trial_balance/trial_balance.py +++ b/erpnext/accounts/report/trial_balance/trial_balance.py @@ -75,8 +75,7 @@ def get_data(filters): accumulate_values_into_parents(accounts, accounts_by_name) data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency) - data = filter_out_zero_value_rows(data, parent_children_map, - show_zero_values=filters.get("show_zero_values")) + data = filter_out_zero_value_rows(data, parent_children_map, show_zero_values=filters.get("show_zero_values")) return data @@ -175,33 +174,11 @@ def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, d["closing_debit"] = d["opening_debit"] + d["debit"] d["closing_credit"] = d["opening_credit"] + d["credit"] - total_row["debit"] += d["debit"] - total_row["credit"] += d["credit"] - if d["root_type"] == "Asset" or d["root_type"] == "Equity" or d["root_type"] == "Expense": - d["opening_debit"] -= d["opening_credit"] - d["closing_debit"] -= d["closing_credit"] + prepare_opening_closing(d) - # For opening - check_opening_closing_has_negative_value(d, "opening_debit", "opening_credit") - - # For closing - check_opening_closing_has_negative_value(d, "closing_debit", "closing_credit") - - if d["root_type"] == "Liability" or d["root_type"] == "Income": - d["opening_credit"] -= d["opening_debit"] - d["closing_credit"] -= d["closing_debit"] - - # For opening - check_opening_closing_has_negative_value(d, "opening_credit", "opening_debit") - - # For closing - check_opening_closing_has_negative_value(d, "closing_credit", "closing_debit") - - total_row["opening_debit"] += d["opening_debit"] - total_row["closing_debit"] += d["closing_debit"] - total_row["opening_credit"] += d["opening_credit"] - total_row["closing_credit"] += d["closing_credit"] + for field in value_fields: + total_row[field] += d[field] return total_row @@ -215,6 +192,10 @@ def prepare_data(accounts, filters, total_row, parent_children_map, company_curr data = [] for d in accounts: + # Prepare opening closing for group account + if parent_children_map.get(d.account): + prepare_opening_closing(d) + has_value = False row = { "account": d.name, @@ -301,11 +282,16 @@ def get_columns(): } ] -def check_opening_closing_has_negative_value(d, dr_or_cr, switch_to_column): - # If opening debit has negetive value then move it to opening credit and vice versa. +def prepare_opening_closing(row): + dr_or_cr = "debit" if row["root_type"] in ["Asset", "Equity", "Expense"] else "credit" + reverse_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit" - if d[dr_or_cr] < 0: - d[switch_to_column] = abs(d[dr_or_cr]) - d[dr_or_cr] = 0.0 - else: - d[switch_to_column] = 0.0 + for col_type in ["opening", "closing"]: + valid_col = col_type + "_" + dr_or_cr + reverse_col = col_type + "_" + reverse_dr_or_cr + row[valid_col] -= row[reverse_col] + if row[valid_col] < 0: + row[reverse_col] = abs(row[valid_col]) + row[valid_col] = 0.0 + else: + row[reverse_col] = 0.0 \ No newline at end of file diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 4d6e22e1915..013e56db0e3 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -1097,6 +1097,8 @@ def get_supplier_block_status(party_name): @frappe.whitelist() def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name): data = json.loads(trans_items) + sales_doctypes = ['Sales Order', 'Sales Invoice', 'Delivery Note', 'Quotation'] + for d in data: child_item = frappe.get_doc(parent_doctype + ' Item', d.get("docname")) @@ -1118,18 +1120,22 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name): # if rate is greater than price_list_rate, set margin # or set discount child_item.discount_percentage = 0 - child_item.margin_type = "Amount" - child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, - child_item.precision("margin_rate_or_amount")) - child_item.rate_with_margin = child_item.rate + + if parent_doctype in sales_doctypes: + child_item.margin_type = "Amount" + child_item.margin_rate_or_amount = flt(child_item.rate - child_item.price_list_rate, + child_item.precision("margin_rate_or_amount")) + child_item.rate_with_margin = child_item.rate else: child_item.discount_percentage = flt((1 - flt(child_item.rate) / flt(child_item.price_list_rate)) * 100.0, child_item.precision("discount_percentage")) child_item.discount_amount = flt( child_item.price_list_rate) - flt(child_item.rate) - child_item.margin_type = "" - child_item.margin_rate_or_amount = 0 - child_item.rate_with_margin = 0 + + if parent_doctype in sales_doctypes: + child_item.margin_type = "" + child_item.margin_rate_or_amount = 0 + child_item.rate_with_margin = 0 child_item.flags.ignore_validate_update_after_submit = True child_item.save() diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index 241d3ab7c98..f795f35ed33 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -152,6 +152,20 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters): def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False): conditions = [] + #Get searchfields from meta and use in Item Link field query + meta = frappe.get_meta("Item", cached=True) + searchfields = meta.get_search_fields() + + if "description" in searchfields: + searchfields.remove("description") + + columns = [field for field in searchfields if not field in ["name", "item_group", "description"]] + columns = ", ".join(columns) + + searchfields = searchfields + [field for field in[searchfield or "name", "item_code", "item_group", "item_name"] + if not field in searchfields] + searchfields = " or ".join([field + " like %(txt)s" for field in searchfields]) + description_cond = '' if frappe.db.count('Item', cache=True) < 50000: # scan description only if items are less than 50000 @@ -162,17 +176,14 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals concat(substr(tabItem.item_name, 1, 40), "..."), item_name) as item_name, tabItem.item_group, if(length(tabItem.description) > 40, \ - concat(substr(tabItem.description, 1, 40), "..."), description) as decription + concat(substr(tabItem.description, 1, 40), "..."), description) as description, + {columns} from tabItem where tabItem.docstatus < 2 and tabItem.has_variants=0 and tabItem.disabled=0 and (tabItem.end_of_life > %(today)s or ifnull(tabItem.end_of_life, '0000-00-00')='0000-00-00') - and (tabItem.`{key}` LIKE %(txt)s - or tabItem.item_code LIKE %(txt)s - or tabItem.item_group LIKE %(txt)s - or tabItem.item_name LIKE %(txt)s - or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) + and ({scond} or tabItem.item_code IN (select parent from `tabItem Barcode` where barcode LIKE %(txt)s) {description_cond}) {fcond} {mcond} order by @@ -182,6 +193,8 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals name, item_name limit %(start)s, %(page_len)s """.format( key=searchfield, + columns=columns, + scond=searchfields, fcond=get_filters_cond(doctype, filters, conditions).replace('%', '%%'), mcond=get_match_cond(doctype).replace('%', '%%'), description_cond = description_cond), @@ -280,22 +293,31 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): "page_len": page_len } + having_clause = "having sum(sle.actual_qty) > 0" + if filters.get("is_return"): + having_clause = "" + if args.get('warehouse'): - batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date) - from `tabStock Ledger Entry` sle - INNER JOIN `tabBatch` batch on sle.batch_no = batch.name - where - batch.disabled = 0 - and sle.item_code = %(item_code)s - and sle.warehouse = %(warehouse)s - and (sle.batch_no like %(txt)s - or batch.manufacturing_date like %(txt)s) - and batch.docstatus < 2 - {0} - {match_conditions} - group by batch_no having sum(sle.actual_qty) > 0 - order by batch.expiry_date, sle.batch_no desc - limit %(start)s, %(page_len)s""".format(cond, match_conditions=get_match_cond(doctype)), args) + batch_nos = frappe.db.sql("""select sle.batch_no, round(sum(sle.actual_qty),2), sle.stock_uom, + concat('MFG-',batch.manufacturing_date), concat('EXP-',batch.expiry_date) + from `tabStock Ledger Entry` sle + INNER JOIN `tabBatch` batch on sle.batch_no = batch.name + where + batch.disabled = 0 + and sle.item_code = %(item_code)s + and sle.warehouse = %(warehouse)s + and (sle.batch_no like %(txt)s + or batch.manufacturing_date like %(txt)s) + and batch.docstatus < 2 + {cond} + {match_conditions} + group by batch_no {having_clause} + order by batch.expiry_date, sle.batch_no desc + limit %(start)s, %(page_len)s""".format( + cond=cond, + match_conditions=get_match_cond(doctype), + having_clause = having_clause + ), args) return batch_nos else: diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json index d679d16079c..c1457db025c 100644 --- a/erpnext/crm/doctype/opportunity/opportunity.json +++ b/erpnext/crm/doctype/opportunity/opportunity.json @@ -1468,7 +1468,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-05-17 19:03:32.740910", + "modified": "2019-10-22 11:11:32.740910", "modified_by": "Administrator", "module": "CRM", "name": "Opportunity", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a067e28747e..0753c58566e 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -233,7 +233,6 @@ scheduler_events = { ], "daily": [ "erpnext.stock.reorder_item.reorder_item", - "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.support.doctype.issue.issue.auto_close_tickets", "erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity", "erpnext.controllers.accounts_controller.update_invoice_status", @@ -252,6 +251,7 @@ scheduler_events = { "erpnext.projects.doctype.project.project.send_project_status_email_to_users" ], "daily_long": [ + "erpnext.setup.doctype.email_digest.email_digest.send", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms" ], "monthly_long": [ diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d925b144889..d1f3991f0e1 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -157,10 +157,11 @@ class Employee(NestedSet): def validate_status(self): if self.status == 'Left': reports_to = frappe.db.get_all('Employee', - filters={'reports_to': self.name} + filters={'reports_to': self.name, 'status': "Active"}, + fields = ['name','employee_name'] ) if reports_to: - link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name) for employee in reports_to] + link_to_employees = [frappe.utils.get_link_to_form('Employee', employee.name, label=employee.employee_name) for employee in reports_to] throw(_("Employee status cannot be set to 'Left' as following employees are currently reporting to this employee: ") + ', '.join(link_to_employees), EmployeeLeftValidationError) if not self.relieving_date: diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py index 437f1694b91..0ed2c86b4c4 100644 --- a/erpnext/manufacturing/doctype/bom/bom.py +++ b/erpnext/manufacturing/doctype/bom/bom.py @@ -38,9 +38,11 @@ class BOM(WebsiteGenerator): names = [d[-1][1:] for d in filter(lambda x: len(x) > 1 and x[-1], names)] # split by (-) if cancelled - names = [cint(name.split('-')[-1]) for name in names] - - idx = max(names) + 1 + if names: + names = [cint(name.split('-')[-1]) for name in names] + idx = max(names) + 1 + else: + idx = 1 else: idx = 1 diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json index 11b85238af2..98cdbab5f58 100644 --- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json +++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -14,10 +15,12 @@ "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": "item_code", "fieldtype": "Link", "hidden": 0, @@ -41,14 +44,17 @@ "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": "item_name", "fieldtype": "Data", "hidden": 0, @@ -72,14 +78,17 @@ "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": "warehouse", "fieldtype": "Link", "hidden": 0, @@ -103,14 +112,17 @@ "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_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -132,14 +144,17 @@ "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": "quantity", "fieldtype": "Float", "hidden": 0, @@ -162,14 +177,51 @@ "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": "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, + "options": "UOM", + "permlevel": 0, + "precision": "", + "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, + "fetch_if_empty": 0, "fieldname": "actual_qty", "fieldtype": "Float", "hidden": 0, @@ -192,14 +244,86 @@ "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": "item_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": "Item 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_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 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": 0, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "oldfieldname": "description", + "oldfieldtype": "Small Text", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": "300px", + "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": "300px" + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "min_order_qty", "fieldtype": "Float", "hidden": 0, @@ -222,14 +346,17 @@ "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_8", "fieldtype": "Section Break", "hidden": 0, @@ -252,14 +379,17 @@ "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_order", "fieldtype": "Link", "hidden": 0, @@ -283,14 +413,17 @@ "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": "requested_qty", "fieldtype": "Float", "hidden": 0, @@ -313,6 +446,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -326,7 +460,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-15 13:08:30.535963", + "modified": "2019-11-08 14:59:58.805613", "modified_by": "Administrator", "module": "Manufacturing", "name": "Material Request Plan Item", @@ -340,5 +474,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 -} \ No newline at end of file + "track_seen": 0, + "track_views": 0 +} diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js index dbbf3d33f7c..cc59776d45f 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.js +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js @@ -122,6 +122,8 @@ frappe.ui.form.on('Production Plan', { item.quantity = d.quantity; item.sales_order = d.sales_order; item.warehouse = d.warehouse; + item.description = d.description; + item.uom = d.uom; }); } refresh_field('mr_items'); diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py index 40b78f215ec..1d7963da544 100644 --- a/erpnext/manufacturing/doctype/production_plan/production_plan.py +++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py @@ -472,7 +472,9 @@ def get_material_request_items(row, sales_order, company, ignore_existing_ordere or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"), 'actual_qty': actual_qty, 'min_order_qty': row['min_order_qty'], - 'sales_order': sales_order + 'sales_order': sales_order, + 'description': row.get("description"), + 'uom': row.get("purchase_uom") or row.get("stock_uom") } def get_sales_orders(self): diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js index 22d74e8ee6e..f4be3119e98 100644 --- a/erpnext/manufacturing/doctype/work_order/work_order.js +++ b/erpnext/manufacturing/doctype/work_order/work_order.js @@ -318,7 +318,7 @@ frappe.ui.form.on("Work Order", { }, project: function(frm) { - if(!erpnext.in_production_item_onchange) { + if(!erpnext.in_production_item_onchange && !frm.doc.bom_no) { frm.trigger("production_item"); } }, diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 37a91d07783..3928c639ca4 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -607,3 +607,4 @@ erpnext.patches.v11_1.set_quotation_status erpnext.patches.v11_1.update_default_supplier_in_item_defaults erpnext.patches.v11_1.set_status_for_material_request_type_manufacture erpnext.patches.v11_1.set_produced_qty_field_in_sales_order_for_work_order +erpnext.patches.v11_1.set_payment_entry_status \ No newline at end of file diff --git a/erpnext/patches/v11_1/set_payment_entry_status.py b/erpnext/patches/v11_1/set_payment_entry_status.py new file mode 100644 index 00000000000..fafbec6a9a7 --- /dev/null +++ b/erpnext/patches/v11_1/set_payment_entry_status.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + frappe.reload_doctype("Payment Entry") + frappe.db.sql("""update `tabPayment Entry` set status = CASE + WHEN docstatus = 1 THEN 'Submitted' + WHEN docstatus = 2 THEN 'Cancelled' + ELSE 'Draft' + END;""") \ No newline at end of file diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index b7f547dbcaf..e1ef1366c35 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -713,7 +713,7 @@ "depends_on": "", "fetch_if_empty": 0, "fieldname": "depends_on_tasks", - "fieldtype": "Data", + "fieldtype": "Long Text", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -1433,4 +1433,4 @@ "track_changes": 0, "track_seen": 1, "track_views": 0 -} \ No newline at end of file +} diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 8a469cd269b..97d15130eb0 100755 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -7,7 +7,7 @@ import json import frappe from frappe import _, throw -from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate +from frappe.utils import add_days, cstr, date_diff, get_link_to_form, getdate, today from frappe.utils.nestedset import NestedSet @@ -201,6 +201,9 @@ def set_multiple_status(names, status): def set_tasks_as_overdue(): tasks = frappe.get_all("Task", filters={'status':['not in',['Cancelled', 'Closed']]}) for task in tasks: + if frappe.db.get_value("Task", task.name, "status") in 'Pending Review': + if getdate(frappe.db.get_value("Task", task.name, "review_date")) < getdate(today()): + continue frappe.get_doc("Task", task.name).update_status() @frappe.whitelist() diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 17fad54453b..d29750950d7 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -1422,6 +1422,11 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ 'item_code': item.item_code, 'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(), } + + if (doc.is_return) { + filters["is_return"] = 1; + } + if (item.warehouse) filters["warehouse"] = item.warehouse; return { diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 015aefb8b35..c03f68558ba 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -60,8 +60,8 @@ def validate_gstin_check_digit(gstin, label='GSTIN'): total += digit factor = 2 if factor == 1 else 1 if gstin[-1] != code_point_chars[((mod - (total % mod)) % mod)]: - frappe.throw(_("Invalid {0}! The check digit validation has failed. " + - "Please ensure you've typed the {0} correctly.".format(label))) + frappe.throw(_("""Invalid {0}! The check digit validation has failed. + Please ensure you've typed the {0} correctly.""".format(label))) def get_itemised_tax_breakup_header(item_doctype, tax_accounts): if frappe.get_meta(item_doctype).has_field('gst_hsn_code'): diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js index 98d62369ff2..bbfa9746de2 100644 --- a/erpnext/selling/doctype/sales_order/sales_order.js +++ b/erpnext/selling/doctype/sales_order/sales_order.js @@ -142,7 +142,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend( // delivery note if(flt(doc.per_delivered, 6) < 100 && allow_delivery) { - this.frm.add_custom_button(__('Delivery'), + this.frm.add_custom_button(__('Delivery Note'), function() { me.make_delivery_note_based_on_delivery_date(); }, __("Make")); if(["Sales", "Shopping Cart"].indexOf(doc.order_type)!==-1){ diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index fb683a36324..d3ab2ac5a7d 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -645,12 +645,15 @@ def make_sales_invoice(source_name, target_doc=None, ignore_permissions=False): if source_parent.project: target.cost_center = frappe.db.get_value("Project", source_parent.project, "cost_center") - if not target.cost_center and target.item_code: + if target.item_code: item = get_item_defaults(target.item_code, source_parent.company) item_group = get_item_group_defaults(target.item_code, source_parent.company) - target.cost_center = item.get("selling_cost_center") \ + cost_center = item.get("selling_cost_center") \ or item_group.get("selling_cost_center") + if cost_center: + target.cost_center = cost_center + doclist = get_mapped_doc("Sales Order", source_name, { "Sales Order": { "doctype": "Sales Invoice", diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py index a9861e83c16..c1ae739365f 100644 --- a/erpnext/shopping_cart/cart.py +++ b/erpnext/shopping_cart/cart.py @@ -454,9 +454,8 @@ def get_applicable_shipping_rules(party=None, quotation=None): shipping_rules = get_shipping_rules(quotation) if shipping_rules: - rule_label_map = frappe.db.get_values("Shipping Rule", shipping_rules, "label") # we need this in sorted order as per the position of the rule in the settings page - return [[rule, rule_label_map.get(rule)] for rule in shipping_rules] + return [[rule, rule] for rule in shipping_rules] def get_shipping_rules(quotation=None, cart_settings=None): if not quotation: diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js index 6fc51ecdd9d..0ae7c37b3f8 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js @@ -6,9 +6,9 @@ frappe.listview_settings['Delivery Note'] = { return [__("Return"), "darkgrey", "is_return,=,Yes"]; } else if (doc.status === "Closed") { return [__("Closed"), "green", "status,=,Closed"]; - } else if (doc.grand_total !== 0 && flt(doc.per_billed, 2) < 100) { + } else if (flt(doc.per_billed, 2) < 100) { return [__("To Bill"), "orange", "per_billed,<,100"]; - } else if (doc.grand_total === 0 || flt(doc.per_billed, 2) == 100) { + } else if (flt(doc.per_billed, 2) == 100) { return [__("Completed"), "green", "per_billed,=,100"]; } }, diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py index cf725272051..b45d5389d46 100644 --- a/erpnext/stock/doctype/packed_item/packed_item.py +++ b/erpnext/stock/doctype/packed_item/packed_item.py @@ -7,7 +7,6 @@ from __future__ import unicode_literals import frappe, json from frappe.utils import cstr, flt from erpnext.stock.get_item_details import get_item_details - from frappe.model.document import Document class PackedItem(Document): @@ -31,8 +30,11 @@ def get_bin_qty(item, warehouse): return det and det[0] or frappe._dict() def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description): + if doc.amended_from: + old_packed_items_map = get_old_packed_item_details(doc.packed_items) + else: + old_packed_items_map = False item = get_packing_item_details(packing_item_code, doc.company) - # check if exists exists = 0 for d in doc.get("packed_items"): @@ -52,11 +54,10 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip pi.uom = item.stock_uom pi.qty = flt(qty) pi.description = description - if not pi.warehouse: + if not pi.warehouse and not doc.amended_from: 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) - - if not pi.batch_no: + if not pi.batch_no and not doc.amended_from: pi.batch_no = cstr(main_item_row.get("batch_no")) if not pi.target_warehouse: pi.target_warehouse = main_item_row.get("target_warehouse") @@ -64,9 +65,13 @@ def update_packing_list_item(doc, packing_item_code, qty, main_item_row, descrip pi.actual_qty = flt(bin.get("actual_qty")) pi.projected_qty = flt(bin.get("projected_qty")) + if old_packed_items_map: + pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no + pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no + pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse + def make_packing_list(doc): """make packing list for Product Bundle item""" - if doc.get("_action") and doc._action == "update_after_submit": return parent_items = [] @@ -108,8 +113,14 @@ def get_items_from_product_bundle(args): "qty": flt(args["quantity"]) * flt(item.qty) }) items.append(get_item_details(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"]) + +def get_old_packed_item_details(old_packed_items): + old_packed_items_map = {} + for items in old_packed_items: + old_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict()) + return old_packed_items_map \ No newline at end of file diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index e6806784cda..b27c92d609e 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -227,7 +227,9 @@ class StockEntry(StockController): for d in self.get("items"): if not d.expense_account: - frappe.throw(_("Please enter Difference Account")) + frappe.throw(_("Please enter Difference Account or set default Stock Adjustment Account for company {0}") + .format(frappe.bold(self.company))) + elif self.is_opening == "Yes" and frappe.db.get_value("Account", d.expense_account, "report_type") == "Profit and Loss": frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Entry is an Opening Entry"), OpeningEntryAccountError) diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py index af5e5e9f0a0..44779ae90ad 100644 --- a/erpnext/stock/report/stock_ledger/stock_ledger.py +++ b/erpnext/stock/report/stock_ledger/stock_ledger.py @@ -19,10 +19,26 @@ def execute(filters=None): if opening_row: data.append(opening_row) + actual_qty = stock_value = 0 + for sle in sl_entries: item_detail = item_details[sle.item_code] sle.update(item_detail) + + if filters.get("batch_no"): + actual_qty += sle.actual_qty + stock_value += sle.stock_value_difference + + if sle.voucher_type == 'Stock Reconciliation': + actual_qty = sle.qty_after_transaction + stock_value = sle.stock_value + + sle.update({ + "qty_after_transaction": actual_qty, + "stock_value": stock_value + }) + data.append(sle) if include_uom: @@ -67,7 +83,7 @@ def get_stock_ledger_entries(filters, items): return frappe.db.sql("""select concat_ws(" ", posting_date, posting_time) as date, item_code, warehouse, actual_qty, qty_after_transaction, incoming_rate, valuation_rate, - stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project + stock_value, voucher_type, voucher_no, batch_no, serial_no, company, project, stock_value_difference from `tabStock Ledger Entry` sle where company = %(company)s and posting_date between %(from_date)s and %(to_date)s