Merge remote-tracking branch 'frappe/develop' into wip-4.1
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
index 5592bc5..42c80b4 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 @@
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 52298bb..4574390 100644
--- a/erpnext/accounts/page/financial_analytics/financial_analytics.js
+++ b/erpnext/accounts/page/financial_analytics/financial_analytics.js
@@ -206,11 +206,11 @@
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 accaeb4..afccdfa 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -52,9 +52,8 @@
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 a30cde7..789e7a3 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -141,7 +141,7 @@
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 @@
'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 df320dc..33f03b6 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -19,7 +19,7 @@
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 dcc1665..7d962e3 100644
--- a/erpnext/hr/doctype/salary_manager/salary_manager.py
+++ b/erpnext/hr/doctype/salary_manager/salary_manager.py
@@ -128,7 +128,7 @@
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 8755837..761b63c 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -58,3 +58,6 @@
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 36c7781..7dae02f 100644
--- a/erpnext/patches/v4_0/apply_user_permissions.py
+++ b/erpnext/patches/v4_0/apply_user_permissions.py
@@ -27,7 +27,9 @@
# 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 0000000..f65b7cb
--- /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 05d9bc5..1b8a0c6 100644
--- a/erpnext/patches/v4_0/split_email_settings.py
+++ b/erpnext/patches/v4_0/split_email_settings.py
@@ -28,6 +28,7 @@
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 @@
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 0000000..c0f9ee0
--- /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 fb94b88..10fe650 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.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 da7dd7a..bbc9f81 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -68,13 +68,14 @@
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 @@
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 @@
}, 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 f8f0d09..9951fc8 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -48,7 +48,7 @@
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 acdd27e..9788290 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.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 d992488..a4ff250 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -51,6 +51,7 @@
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 ac81f88..d8164f7 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -70,6 +70,9 @@
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 @@
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 @@
"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 9c251b8..cdf5aa1 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -255,7 +255,7 @@
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",