diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js index 5592bc5220..42c80b4e26 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js @@ -7,8 +7,10 @@ frappe.listview_settings['Sales Invoice'] = { add_columns: [{"content":"Percent Paid", width:"10%", type:"bar-graph", label: "Payment Received"}], prepare_data: function(data) { - data["Percent Paid"] = (data.docstatus===1 && flt(data.grand_total)) - ? (((flt(data.grand_total) - flt(data.outstanding_amount)) / flt(data.grand_total)) * 100) - : 0; + if (data.docstatus === 1) { + data["Percent Paid"] = flt(data.grand_total) + ? (((flt(data.grand_total) - flt(data.outstanding_amount)) / flt(data.grand_total)) * 100) + : 100.0; + } } }; diff --git a/erpnext/accounts/page/financial_analytics/financial_analytics.js b/erpnext/accounts/page/financial_analytics/financial_analytics.js index 52298bbf18..4574390ce1 100644 --- a/erpnext/accounts/page/financial_analytics/financial_analytics.js +++ b/erpnext/accounts/page/financial_analytics/financial_analytics.js @@ -206,11 +206,11 @@ erpnext.FinancialAnalytics = erpnext.AccountTreeGrid.extend({ if(me.pl_or_bs=='Balance Sheet') { $.each(me.data, function(i, ac) { if((ac.rgt - ac.lft)==1 && ac.report_type=='Balance Sheet') { - var opening = 0; + var opening = flt(ac["opening_dr"]) - flt(ac["opening_cr"]); //if(opening) throw opening; $.each(me.columns, function(i, col) { if(col.formatter==me.currency_formatter) { - if(col.balance_type=="Dr") { + if(col.balance_type=="Dr" && !in_list(["opening_dr", "opening_cr"], col.field)) { opening = opening + flt(ac[col.date + "_dr"]) - flt(ac[col.date + "_cr"]); me.set_debit_or_credit(ac, col.date, opening); diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index accaeb4498..afccdfa0e3 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -52,9 +52,8 @@ class BuyingController(StockController): validate_warehouse_company(w, self.company) def validate_stock_or_nonstock_items(self): - if not self.get_stock_items(): - tax_for_valuation = [d.account_head for d in - self.get("other_charges") + if self.meta.get_field("other_charges") and not self.get_stock_items(): + tax_for_valuation = [d.account_head for d in self.get("other_charges") if d.category in ["Valuation", "Valuation and Total"]] if tax_for_valuation: frappe.throw(_("Tax Category can not be 'Valuation' or 'Valuation and Total' as all items are non-stock items")) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index a30cde72a5..789e7a331a 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -141,7 +141,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters): concat(substr(tabItem.description, 1, 40), "..."), description) as decription from tabItem where tabItem.docstatus < 2 - and (ifnull(tabItem.end_of_life, '') = '' or tabItem.end_of_life > %(today)s) + and (tabItem.end_of_life is null or tabItem.end_of_life > %(today)s) and (tabItem.`{key}` LIKE %(txt)s or tabItem.item_name LIKE %(txt)s) {fcond} {mcond} @@ -236,13 +236,21 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters): 'page_len': page_len}) def get_account_list(doctype, txt, searchfield, start, page_len, filters): - if isinstance(filters, dict): - if not filters.get("group_or_ledger"): - filters["group_or_ledger"] = "Ledger" - elif isinstance(filters, list): - if "group_or_ledger" not in [d[0] for d in filters]: - filters.append(["Account", "group_or_ledger", "=", "Ledger"]) + filter_list = [] - return frappe.widgets.reportview.execute("Account", filters = filters, + if isinstance(filters, dict): + for key, val in filters.items(): + if isinstance(val, (list, tuple)): + filter_list.append([doctype, key, val[0], val[1]]) + else: + filter_list.append([doctype, key, "=", val]) + + if "group_or_ledger" not in [d[1] for d in filter_list]: + filter_list.append(["Account", "group_or_ledger", "=", "Ledger"]) + + if searchfield and txt: + filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt]) + + return frappe.widgets.reportview.execute("Account", filters = filter_list, fields = ["name", "parent_account"], limit_start=start, limit_page_length=page_len, as_list=True) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index df320dcda1..33f03b6ce4 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -19,7 +19,7 @@ class SellingController(StockController): if frappe.db.get_value('Sales Email Settings', None, 'extract_emails'): return frappe.db.get_value('Sales Email Settings', None, 'email_id') else: - return frappe.session.user + return comm.sender or frappe.session.user def set_missing_values(self, for_validate=False): super(SellingController, self).set_missing_values(for_validate) diff --git a/erpnext/hr/doctype/salary_manager/salary_manager.py b/erpnext/hr/doctype/salary_manager/salary_manager.py index dcc1665691..7d962e3c69 100644 --- a/erpnext/hr/doctype/salary_manager/salary_manager.py +++ b/erpnext/hr/doctype/salary_manager/salary_manager.py @@ -128,7 +128,7 @@ class SalaryManager(Document): for ss in ss_list: ss_obj = frappe.get_doc("Salary Slip",ss[0]) try: - frappe.db.set(ss_obj, 'email_check', cint(self.send_mail)) + frappe.db.set(ss_obj, 'email_check', cint(self.send_email)) if cint(self.send_email) == 1: ss_obj.send_mail_funct() diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 8755837b70..761b63c9fd 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -58,3 +58,6 @@ execute:frappe.reset_perms("Stock Ledger Entry") #2014-06-09 erpnext.patches.v4_0.create_custom_fields_for_india_specific_fields erpnext.patches.v4_0.save_default_letterhead erpnext.patches.v4_0.update_custom_print_formats_for_renamed_fields +erpnext.patches.v4_0.update_other_charges_in_custom_purchase_print_formats +erpnext.patches.v4_0.create_price_list_if_missing +execute:frappe.db.sql("update `tabItem` set end_of_life=null where end_of_life='0000-00-00'") #2014-06-16 diff --git a/erpnext/patches/v4_0/apply_user_permissions.py b/erpnext/patches/v4_0/apply_user_permissions.py index 36c778195b..7dae02f785 100644 --- a/erpnext/patches/v4_0/apply_user_permissions.py +++ b/erpnext/patches/v4_0/apply_user_permissions.py @@ -27,7 +27,9 @@ def update_hr_permissions(): # save employees to run on_update events for employee in frappe.db.sql_list("""select name from `tabEmployee` where docstatus < 2"""): try: - frappe.get_doc("Employee", employee).save() + emp = frappe.get_doc("Employee", employee) + emp.ignore_mandatory = True + emp.save() except EmployeeUserDisabledError: pass diff --git a/erpnext/patches/v4_0/create_price_list_if_missing.py b/erpnext/patches/v4_0/create_price_list_if_missing.py new file mode 100644 index 0000000000..f65b7cb571 --- /dev/null +++ b/erpnext/patches/v4_0/create_price_list_if_missing.py @@ -0,0 +1,35 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils.nestedset import get_root_of + +def execute(): + # setup not complete + if not frappe.db.sql("""select name from tabCompany limit 1"""): + return + + if "shopping_cart" in frappe.get_installed_apps(): + frappe.reload_doc("shopping_cart", "doctype", "shopping_cart_settings") + + if not frappe.db.sql("select name from `tabPrice List` where buying=1"): + create_price_list(_("Standard Buying"), buying=1) + + if not frappe.db.sql("select name from `tabPrice List` where selling=1"): + create_price_list(_("Standard Selling"), selling=1) + +def create_price_list(pl_name, buying=0, selling=0): + price_list = frappe.get_doc({ + "doctype": "Price List", + "price_list_name": pl_name, + "enabled": 1, + "buying": buying, + "selling": selling, + "currency": frappe.db.get_default("currency"), + "valid_for_territories": [{ + "territory": get_root_of("Territory") + }] + }) + price_list.insert() diff --git a/erpnext/patches/v4_0/split_email_settings.py b/erpnext/patches/v4_0/split_email_settings.py index 05d9bc5800..1b8a0c6968 100644 --- a/erpnext/patches/v4_0/split_email_settings.py +++ b/erpnext/patches/v4_0/split_email_settings.py @@ -28,6 +28,7 @@ def map_outgoing_email_settings(email_settings): outgoing_email_settings.set(to_fieldname, email_settings.get(from_fieldname)) + outgoing_email_settings._fix_numeric_types() outgoing_email_settings.save() def map_support_email_settings(email_settings): @@ -47,6 +48,7 @@ def map_support_email_settings(email_settings): support_email_settings.set(to_fieldname, email_settings.get(from_fieldname)) + support_email_settings._fix_numeric_types() support_email_settings.save() def get_email_settings(): diff --git a/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py b/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py new file mode 100644 index 0000000000..c0f9ee008f --- /dev/null +++ b/erpnext/patches/v4_0/update_other_charges_in_custom_purchase_print_formats.py @@ -0,0 +1,12 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +import re + +def execute(): + for name, html in frappe.db.sql("""select name, html from `tabPrint Format` + where standard = 'No' and html like '%%purchase\\_tax\\_details%%'"""): + html = re.sub(r"\bpurchase_tax_details\b", "other_charges", html) + frappe.db.set_value("Print Format", name, "html", html) diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index fb94b88809..10fe6503e6 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -118,10 +118,10 @@ cur_frm.fields_dict['transporter_name'].get_query = function(doc) { } cur_frm.cscript['Make Packing Slip'] = function() { - n = frappe.model.make_new_doc_and_get_name('Packing Slip'); - ps = locals["Packing Slip"][n]; - ps.delivery_note = cur_frm.doc.name; - loaddoc('Packing Slip', n); + frappe.model.open_mapped_doc({ + method: "erpnext.stock.doctype.delivery_note.delivery_note.make_packing_slip", + frm: cur_frm + }) } var set_print_hide= function(doc, cdt, cdn){ diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py index da7dd7af56..bbc9f81ff4 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.py +++ b/erpnext/stock/doctype/delivery_note/delivery_note.py @@ -68,13 +68,14 @@ class DeliveryNote(SellingController): self.validate_for_items() self.validate_warehouse() self.validate_uom_is_integer("stock_uom", "qty") - self.update_current_stock() self.validate_with_previous_doc() from erpnext.stock.doctype.packed_item.packed_item import make_packing_list make_packing_list(self, 'delivery_note_details') - self.status = 'Draft' + self.update_current_stock() + + if not self.status: self.status = 'Draft' if not self.installation_status: self.installation_status = 'Not Installed' def validate_with_previous_doc(self): @@ -133,14 +134,17 @@ class DeliveryNote(SellingController): def update_current_stock(self): - for d in self.get('delivery_note_details'): - bin = frappe.db.sql("select actual_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1) - d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0 + if self._action != "update_after_submit": + for d in self.get('delivery_note_details'): + d.actual_qty = frappe.db.get_value("Bin", {"item_code": d.item_code, + "warehouse": d.warehouse}, "actual_qty") - for d in self.get('packing_details'): - bin = frappe.db.sql("select actual_qty, projected_qty from `tabBin` where item_code = %s and warehouse = %s", (d.item_code, d.warehouse), as_dict = 1) - d.actual_qty = bin and flt(bin[0]['actual_qty']) or 0 - d.projected_qty = bin and flt(bin[0]['projected_qty']) or 0 + for d in self.get('packing_details'): + bin_qty = frappe.db.get_value("Bin", {"item_code": d.item_code, + "warehouse": d.warehouse}, ["actual_qty", "projected_qty"], as_dict=True) + if bin_qty: + d.actual_qty = flt(bin_qty.actual_qty) + d.projected_qty = flt(bin_qty.projected_qty) def on_submit(self): self.validate_packed_qty() @@ -346,3 +350,19 @@ def make_installation_note(source_name, target_doc=None): }, target_doc) return doclist + +@frappe.whitelist() +def make_packing_slip(source_name, target_doc=None): + doclist = get_mapped_doc("Delivery Note", source_name, { + "Delivery Note": { + "doctype": "Packing Slip", + "field_map": { + "name": "delivery_note" + }, + "validation": { + "docstatus": ["=", 0] + } + } + }, target_doc) + + return doclist diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py index f8f0d097ff..9951fc88c2 100644 --- a/erpnext/stock/doctype/material_request/material_request.py +++ b/erpnext/stock/doctype/material_request/material_request.py @@ -48,7 +48,7 @@ class MaterialRequest(BuyingController): def validate_schedule_date(self): for d in self.get('indent_details'): - if d.schedule_date < self.transaction_date: + if d.schedule_date and d.schedule_date < self.transaction_date: frappe.throw(_("Expected Date cannot be before Material Request Date")) # Validate diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js index acdd27e1ab..9788290a1b 100644 --- a/erpnext/stock/doctype/packing_slip/packing_slip.js +++ b/erpnext/stock/doctype/packing_slip/packing_slip.js @@ -18,7 +18,7 @@ cur_frm.fields_dict['item_details'].grid.get_field('item_code').get_query = cur_frm.cscript.onload_post_render = function(doc, cdt, cdn) { if(doc.delivery_note && doc.__islocal) { - cur_frm.cscript.get_items(doc, cdt, cdn); + cur_frm.cscript.get_items(doc, cdt, cdn); } } diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py index d992488fc6..a4ff25044b 100644 --- a/erpnext/stock/doctype/price_list/price_list.py +++ b/erpnext/stock/doctype/price_list/price_list.py @@ -51,6 +51,7 @@ class PriceList(Document): if self.name == b.get(price_list_fieldname): b.set(price_list_fieldname, None) + b.ignore_permissions = True b.save() for module in ["Selling", "Buying"]: diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index ac81f88e88..d8164f7e21 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -70,6 +70,9 @@ class StockEntry(StockController): def set_transfer_qty(self): for item in self.get("mtn_details"): + if not flt(item.qty): + frappe.throw(_("Row {0}: Qty is mandatory").format(item.idx)) + item.transfer_qty = flt(item.qty * item.conversion_factor, self.precision("transfer_qty", item)) def validate_item(self): @@ -191,6 +194,11 @@ class StockEntry(StockController): raw_material_cost = 0.0 + if not self.posting_date or not self.posting_time: + frappe.throw(_("Posting date and posting time is mandatory")) + + allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock") + for d in self.get('mtn_details'): args = frappe._dict({ "item_code": d.item_code, @@ -200,13 +208,21 @@ class StockEntry(StockController): "qty": d.s_warehouse and -1*d.transfer_qty or d.transfer_qty, "serial_no": d.serial_no }) + # get actual stock at source warehouse d.actual_qty = get_previous_sle(args).get("qty_after_transaction") or 0 + if d.s_warehouse and not allow_negative_stock and d.actual_qty <= d.transfer_qty: + frappe.throw(_("""Row {0}: Qty not avalable in warehouse {1} on {2} {3}. + Available Qty: {4}, Transfer Qty: {5}""").format(d.idx, d.s_warehouse, + self.posting_date, self.posting_time, d.actual_qty, d.transfer_qty)) + # get incoming rate if not d.bom_no: - if not flt(d.incoming_rate): - d.incoming_rate = self.get_incoming_rate(args) + if not flt(d.incoming_rate) or d.s_warehouse or self.purpose == "Sales Return": + incoming_rate = self.get_incoming_rate(args) + if incoming_rate: + d.incoming_rate = incoming_rate d.amount = flt(d.transfer_qty) * flt(d.incoming_rate) raw_material_cost += flt(d.amount) diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 9c251b8b29..cdf5aa1f3d 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -255,7 +255,7 @@ def apply_pricing_rule(args): args = frappe._dict(args) out = frappe._dict() - if not args.get("item_code"): return + if args.get("doctype") == "Material Request" or not args.get("item_code"): return out if not args.get("item_group") or not args.get("brand"): args.item_group, args.brand = frappe.db.get_value("Item",