Merge pull request #3242 from neilLasrado/crm
view leads button added to campaign
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ef9d094..7a43823 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,9 +1,5 @@
# Contributing to Frappe / ERPNext
-### Update 16-Sep-14
-
-Please send pull requests to branch v5.0
-
## Reporting issues
We only accept issues that are bug reports or feature requests. Bugs must be isolated and reproducible problems. Please read the following guidelines before opening any issue.
@@ -17,6 +13,7 @@
1. **Share as much information as possible:** Include operating system and version, browser and version, when did you last update ERPNext, how is it customized, etc. where appropriate. Also include steps to reproduce the bug.
1. **Include Screenshots if possible:** Consider adding screenshots annotated with what goes wrong.
1. **Find and post the trace for bugs:** If you are reporting an issue from the browser, Open the Javascript Console and paste us any error messages you see.
+1. **Security Issues:** If you are reporting a security issue, please send a private email to <info@frappe.io>.
### Feature Requests
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 96b47c6..22e0a38 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -218,17 +218,13 @@
par_flds = ['project_name', 'due_date', 'is_opening', 'source', 'total_advance', 'get_advances_received',
'advances', 'sales_partner', 'commission_rate', 'total_commission', 'advances', 'from_date', 'to_date'];
- item_flds_normal = ['sales_order', 'delivery_note']
-
if(cint(doc.is_pos) == 1) {
hide_field(par_flds);
- cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_normal, false);
} else {
for (i in par_flds) {
var docfield = frappe.meta.docfield_map[doc.doctype][par_flds[i]];
if(!docfield.hidden) unhide_field(par_flds[i]);
}
- cur_frm.fields_dict['items'].grid.set_column_disp(item_flds_normal, true);
}
item_flds_stock = ['serial_no', 'batch_no', 'actual_qty', 'expense_account', 'warehouse', 'expense_account', 'warehouse']
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 9568c90..7c366c1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -196,7 +196,7 @@
# fetch charges
if self.taxes_and_charges and not len(self.get("taxes")):
- self.set_taxes("taxes", "taxes_and_charges")
+ self.set_taxes()
def get_advances(self):
super(SalesInvoice, self).get_advances(self.debit_to, "Customer", self.customer,
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index a7ee7c0..2731baa 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import cstr, flt, getdate
+from frappe.utils import flt, getdate
from frappe import _
def execute(filters=None):
@@ -153,10 +153,9 @@
for gle in gl_entries:
amount = flt(gle.debit, 3) - flt(gle.credit, 3)
- if filters.get("account") and \
- (gle.posting_date < getdate(filters.from_date) or cstr(gle.is_opening)=="Yes"):
- gle_map[gle.account].opening += amount
- opening += amount
+ if filters.get("account") and gle.posting_date < getdate(filters.from_date):
+ gle_map[gle.account].opening += amount
+ opening += amount
elif gle.posting_date <= getdate(filters.to_date):
gle_map[gle.account].entries.append(gle)
gle_map[gle.account].total_debit += flt(gle.debit, 3)
diff --git a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
index 16dd847..f54b860 100644
--- a/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
+++ b/erpnext/accounts/report/item_wise_purchase_register/item_wise_purchase_register.py
@@ -69,7 +69,7 @@
return frappe.db.sql("""select pi_item.parent, pi.posting_date, pi.credit_to, pi.company,
pi.supplier, pi.remarks, pi.base_net_total, pi_item.item_code, pi_item.item_name, pi_item.item_group,
- pi_item.project_name, pi_item.purchase_order, pi_item.purchase_receipt, pi_item.po_detail
+ pi_item.project_name, pi_item.purchase_order, pi_item.purchase_receipt, pi_item.po_detail,
pi_item.expense_account, pi_item.qty, pi_item.base_net_rate, pi_item.base_net_amount, pi.supplier_name
from `tabPurchase Invoice` pi, `tabPurchase Invoice Item` pi_item
where pi.name = pi_item.parent and pi.docstatus = 1 %s %s
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 96a3d97..6b7c9a2 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -26,7 +26,7 @@
'target_parent_dt': 'Material Request',
'target_parent_field': 'per_ordered',
'target_ref_field': 'qty',
- 'source_field': 'qty',
+ 'source_field': 'stock_qty',
'percent_join_field': 'prevdoc_docname',
'overflow_type': 'order'
}]
@@ -61,8 +61,7 @@
},
"Supplier Quotation Item": {
"ref_dn_field": "supplier_quotation_item",
- "compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="],
- ["uom", "="]],
+ "compare_fields": [["rate", "="], ["project_name", "="], ["item_code", "="]],
"is_child_table": True
}
})
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 6612cb5..2643883 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -140,30 +140,29 @@
"discount_percentage", "base_rate", "rate"]:
item.set(field, ret.get(field))
- def set_taxes(self, tax_parentfield, tax_master_field):
- if not self.meta.get_field(tax_parentfield):
+ def set_taxes(self):
+ if not self.meta.get_field("taxes"):
return
- tax_master_doctype = self.meta.get_field(tax_master_field).options
+ tax_master_doctype = self.meta.get_field("taxes_and_charges").options
- if not self.get(tax_parentfield):
- if not self.get(tax_master_field):
+ if not self.get("taxes"):
+ if not self.get("taxes_and_charges"):
# get the default tax master
- self.set(tax_master_field, frappe.db.get_value(tax_master_doctype, {"is_default": 1}))
+ self.set("taxes_and_charges", frappe.db.get_value(tax_master_doctype, {"is_default": 1}))
- self.append_taxes_from_master(tax_parentfield, tax_master_field, tax_master_doctype)
+ self.append_taxes_from_master(tax_master_doctype)
- def append_taxes_from_master(self, tax_parentfield, tax_master_field, tax_master_doctype=None):
- if self.get(tax_master_field):
+ def append_taxes_from_master(self, tax_master_doctype=None):
+ if self.get("taxes_and_charges"):
if not tax_master_doctype:
- tax_master_doctype = self.meta.get_field(tax_master_field).options
+ tax_master_doctype = self.meta.get_field("taxes_and_charges").options
- self.extend(tax_parentfield,
- get_taxes_and_charges(tax_master_doctype, self.get(tax_master_field), tax_parentfield))
+ self.extend("taxes", get_taxes_and_charges(tax_master_doctype, self.get("taxes_and_charges")))
def set_other_charges(self):
self.set("taxes", [])
- self.set_taxes("taxes", "taxes_and_charges")
+ self.set_taxes()
def validate_enabled_taxes_and_charges(self):
taxes_and_charges_doctype = self.meta.get_options("taxes_and_charges")
@@ -335,14 +334,19 @@
@frappe.whitelist()
def get_tax_rate(account_head):
return frappe.db.get_value("Account", account_head, "tax_rate")
+
+@frappe.whitelist()
+def get_default_taxes_and_charges(master_doctype):
+ default_tax = frappe.db.get_value(master_doctype, {"is_default": 1})
+ return get_taxes_and_charges(master_doctype, default_tax)
@frappe.whitelist()
-def get_taxes_and_charges(master_doctype, master_name, tax_parentfield):
+def get_taxes_and_charges(master_doctype, master_name):
from frappe.model import default_fields
tax_master = frappe.get_doc(master_doctype, master_name)
taxes_and_charges = []
- for i, tax in enumerate(tax_master.get(tax_parentfield)):
+ for i, tax in enumerate(tax_master.get("taxes")):
tax = tax.as_dict()
for fieldname in default_fields:
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index b9aee62..d4e69cf 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -44,8 +44,6 @@
self.update_if_missing(get_party_details(self.supplier, party_type="Supplier"))
self.set_missing_item_details()
- if self.get("__islocal"):
- self.set_taxes("taxes", "taxes_and_charges")
def set_supplier_from_item_default(self):
if self.meta.get_field("supplier") and not self.supplier:
@@ -283,5 +281,5 @@
for d in self.get("items"):
if d.meta.get_field("stock_qty") and not d.stock_qty:
if not d.conversion_factor:
- frappe.throw(_("Row {0}: Conversion Factor is mandatory"))
+ frappe.throw(_("Row {0}: Conversion Factor is mandatory").format(d.idx))
d.stock_qty = flt(d.qty) * flt(d.conversion_factor)
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index f0a2f08..2dde685 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -42,8 +42,6 @@
# set contact and address details for customer, if they are not mentioned
self.set_missing_lead_customer_details()
self.set_price_list_and_item_details()
- if self.get("__islocal"):
- self.set_taxes("taxes", "taxes_and_charges")
def set_missing_lead_customer_details(self):
if getattr(self, "customer", None):
diff --git a/erpnext/crm/doctype/newsletter_list/newsletter_list.py b/erpnext/crm/doctype/newsletter_list/newsletter_list.py
index 4f89c9a..ba0eec7 100644
--- a/erpnext/crm/doctype/newsletter_list/newsletter_list.py
+++ b/erpnext/crm/doctype/newsletter_list/newsletter_list.py
@@ -68,7 +68,8 @@
validate_email_add(email, True)
if email:
- try:
+ if not frappe.db.get_value("Newsletter List Subscriber",
+ {"newsletter_list": name, "email": email}):
frappe.get_doc({
"doctype": "Newsletter List Subscriber",
"newsletter_list": name,
@@ -76,10 +77,8 @@
}).insert()
count += 1
- except Exception, e:
- # ignore duplicate
- if e.args[0] != 1062:
- raise
+ else:
+ pass
frappe.msgprint(_("{0} subscribers added").format(count))
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index cbb8997..0bb9b4a 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -48,6 +48,22 @@
"search_index": 1
},
{
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "hidden": 0,
+ "label": "Description",
+ "oldfieldname": "reason",
+ "oldfieldtype": "Small Text",
+ "permlevel": 0,
+ "width": "300px"
+ },
+ {
+ "fieldname": "column_break1",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
+ "width": "50%"
+ },
+ {
"default": "Today",
"fieldname": "posting_date",
"fieldtype": "Date",
@@ -75,22 +91,6 @@
"search_index": 1
},
{
- "fieldname": "description",
- "fieldtype": "Small Text",
- "hidden": 0,
- "label": "Description",
- "oldfieldname": "reason",
- "oldfieldtype": "Small Text",
- "permlevel": 0,
- "width": "300px"
- },
- {
- "fieldname": "column_break1",
- "fieldtype": "Column Break",
- "permlevel": 0,
- "width": "50%"
- },
- {
"fieldname": "carry_forward",
"fieldtype": "Check",
"label": "Carry Forward",
@@ -138,7 +138,7 @@
"icon": "icon-ok",
"idx": 1,
"is_submittable": 1,
- "modified": "2015-04-06 06:03:27.883985",
+ "modified": "2015-05-08 03:44:08.092937",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Allocation",
diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json
index ac4525b..f644c69 100644
--- a/erpnext/hr/doctype/leave_type/leave_type.json
+++ b/erpnext/hr/doctype/leave_type/leave_type.json
@@ -51,7 +51,7 @@
{
"fieldname": "is_lwp",
"fieldtype": "Check",
- "label": "Is LWP",
+ "label": "Is Leave Without Pay",
"permlevel": 0
},
{
@@ -63,7 +63,7 @@
],
"icon": "icon-flag",
"idx": 1,
- "modified": "2015-02-05 05:11:40.849495",
+ "modified": "2015-05-08 05:15:24.194053",
"modified_by": "Administrator",
"module": "HR",
"name": "Leave Type",
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 33a3f31..5dff001 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -153,3 +153,4 @@
execute:frappe.delete_doc("Page","stock-level")
erpnext.patches.v5_0.reclculate_planned_operating_cost_in_production_order
erpnext.patches.v5_0.repost_requested_qty
+erpnext.patches.v5_0.fix_taxes_and_totals_in_party_currency
\ No newline at end of file
diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
index b7bd90e..73a56a8 100644
--- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
+++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
@@ -8,6 +8,8 @@
def execute():
from erpnext.utilities.repost_stock import repost
repost(allow_zero_rate=True, only_actual=True)
+
+ frappe.reload_doctype("Account")
warehouse_account = frappe.db.sql("""select name, master_name from tabAccount
where ifnull(account_type, '') = 'Warehouse'""")
diff --git a/erpnext/patches/v4_4/make_email_accounts.py b/erpnext/patches/v4_4/make_email_accounts.py
index 33f2c3f..e495bcd 100644
--- a/erpnext/patches/v4_4/make_email_accounts.py
+++ b/erpnext/patches/v4_4/make_email_accounts.py
@@ -6,7 +6,7 @@
# outgoing
outgoing = dict(frappe.db.sql("select field, value from tabSingles where doctype='Outgoing Email Settings'"))
- if outgoing and outgoing['mail_server']:
+ if outgoing and outgoing['mail_server'] and outgoing['mail_login']:
account = frappe.new_doc("Email Account")
mapping = {
"email_id": "mail_login",
@@ -27,7 +27,7 @@
# support
support = dict(frappe.db.sql("select field, value from tabSingles where doctype='Support Email Settings'"))
- if support and support['mail_server']:
+ if support and support['mail_server'] and support['mail_login']:
account = frappe.new_doc("Email Account")
mapping = {
"enable_incoming": "sync_support_mails",
@@ -51,7 +51,7 @@
# sales, jobs
for doctype in ("Sales Email Settings", "Jobs Email Settings"):
source = dict(frappe.db.sql("select field, value from tabSingles where doctype=%s", doctype))
- if source and source.get('host'):
+ if source and source.get('host') and source.get('username'):
account = frappe.new_doc("Email Account")
mapping = {
"enable_incoming": "extract_emails",
diff --git a/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py b/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py
new file mode 100644
index 0000000..062159b
--- /dev/null
+++ b/erpnext/patches/v5_0/fix_taxes_and_totals_in_party_currency.py
@@ -0,0 +1,66 @@
+
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe.model.meta import get_field_precision
+
+def execute():
+ if not frappe.db.sql("""select name from `tabPatch Log`
+ where patch = 'erpnext.patches.v5_0.taxes_and_totals_in_party_currency'"""):
+ return
+ selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]
+ buying_doctypes = ["Supplier Quotation", "Purchase Order", "Purchase Receipt", "Purchase Invoice"]
+
+ for dt in selling_doctypes:
+ update_values(dt, "Sales Taxes and Charges")
+
+ for dt in buying_doctypes:
+ update_values(dt, "Purchase Taxes and Charges")
+
+def update_values(dt, tax_table):
+ rate_field_precision = get_field_precision(frappe.get_meta(dt + " Item").get_field("rate"))
+ tax_amount_precision = get_field_precision(frappe.get_meta(tax_table).get_field("tax_amount"))
+
+ # update net_total, discount_on
+ frappe.db.sql("""
+ UPDATE
+ `tab{0}`
+ SET
+ total_taxes_and_charges = round(base_total_taxes_and_charges / conversion_rate, {1})
+ WHERE
+ docstatus < 2
+ and ifnull(base_total_taxes_and_charges, 0) != 0
+ and ifnull(total_taxes_and_charges, 0) = 0
+ """.format(dt, tax_amount_precision))
+
+ # update net_amount
+ frappe.db.sql("""
+ UPDATE
+ `tab{0}` par, `tab{1}` item
+ SET
+ item.net_amount = round(item.base_net_amount / par.conversion_rate, {2}),
+ item.net_rate = round(item.base_net_rate / par.conversion_rate, {2})
+ WHERE
+ par.name = item.parent
+ and par.docstatus < 2
+ and ((ifnull(item.base_net_amount, 0) != 0 and ifnull(item.net_amount, 0) = 0)
+ or (ifnull(item.base_net_rate, 0) != 0 and ifnull(item.net_rate, 0) = 0))
+ """.format(dt, dt + " Item", rate_field_precision))
+
+ # update tax in party currency
+ frappe.db.sql("""
+ UPDATE
+ `tab{0}` par, `tab{1}` tax
+ SET
+ tax.tax_amount = round(tax.base_tax_amount / par.conversion_rate, {2}),
+ tax.total = round(tax.base_total / conversion_rate, {2}),
+ tax.tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount / conversion_rate, {2})
+ WHERE
+ par.name = tax.parent
+ and par.docstatus < 2
+ and ((ifnull(tax.base_tax_amount, 0) != 0 and ifnull(tax.tax_amount, 0) = 0)
+ or (ifnull(tax.base_total, 0) != 0 and ifnull(tax.total, 0) = 0)
+ or (ifnull(tax.base_tax_amount_after_discount_amount, 0) != 0 and
+ ifnull(tax.tax_amount_after_discount_amount, 0) = 0))
+ """.format(dt, tax_table, tax_amount_precision))
\ No newline at end of file
diff --git a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py b/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py
index 8e1dbd3..31747b8 100644
--- a/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py
+++ b/erpnext/patches/v5_0/replace_renamed_fields_in_custom_scripts_and_print_formats.py
@@ -46,12 +46,20 @@
)
for fields in rename_map.values():
- renamed_fields += tuple(fields)
+ if fields[0] != "entries":
+ renamed_fields += tuple(fields)
return renamed_fields
def update_script(dt, name, script_field, script, renamed_fields):
for from_field, to_field in renamed_fields:
script = re.sub(r"\b{}\b".format(from_field), to_field, script)
+
+ if dt == "Journal Entry":
+ script = re.sub(r"\bentries\b", "accounts", script)
+ elif dt == "Bank Reconciliation":
+ script = re.sub(r"\bentries\b", "journal_entries", script)
+ elif dt in ("Sales Invoice", "Purchase Invoice"):
+ script = re.sub(r"\bentries\b", "items", script)
frappe.db.set_value(dt, name, script_field, script)
diff --git a/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py b/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py
index c595f4e..f4ed66b 100644
--- a/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py
+++ b/erpnext/patches/v5_0/taxes_and_totals_in_party_currency.py
@@ -46,17 +46,16 @@
WHERE
docstatus < 2
""".format(dt, net_total_precision))
-
-
+
# update net_amount
frappe.db.sql("""
UPDATE
`tab{0}` par, `tab{1}` item
SET
- item.base_net_amount = item.base_amount,
- item.base_net_rate = item.base_rate,
- item.net_amount = round(item.base_net_amount / par.conversion_rate, {2}),
- item.net_rate = round(item.base_net_rate / par.conversion_rate, {2}),
+ item.base_net_amount = round(item.base_amount, {2}),
+ item.base_net_rate = round(item.base_rate, {2}),
+ item.net_amount = round(item.base_amount / par.conversion_rate, {2}),
+ item.net_rate = round(item.base_rate / par.conversion_rate, {2}),
item.base_amount = round(item.amount * par.conversion_rate, {2}),
item.base_rate = round(item.rate * par.conversion_rate, {2})
WHERE
@@ -69,12 +68,12 @@
UPDATE
`tab{0}` par, `tab{1}` tax
SET
- tax.base_tax_amount = tax.tax_amount,
- tax.tax_amount = round(tax.base_tax_amount / par.conversion_rate, {2}),
+ tax.base_tax_amount = round(tax.tax_amount, {2}),
+ tax.tax_amount = round(tax.tax_amount / par.conversion_rate, {2}),
tax.base_total = round(tax.total, {2}),
- tax.total = round(tax.base_total / conversion_rate, {2}),
+ tax.total = round(tax.total / conversion_rate, {2}),
tax.base_tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount, {2}),
- tax.tax_amount_after_discount_amount = round(tax.base_tax_amount_after_discount_amount / conversion_rate, {2})
+ tax.tax_amount_after_discount_amount = round(tax.tax_amount_after_discount_amount / conversion_rate, {2})
WHERE
par.name = tax.parent
and par.docstatus < 2
diff --git a/erpnext/projects/doctype/dependent_task/__init__.py b/erpnext/projects/doctype/dependent_task/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/doctype/dependent_task/__init__.py
diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.json b/erpnext/projects/doctype/dependent_task/dependent_task.json
new file mode 100644
index 0000000..c649b53
--- /dev/null
+++ b/erpnext/projects/doctype/dependent_task/dependent_task.json
@@ -0,0 +1,50 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "creation": "2015-04-29 04:52:48.868079",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "fieldname": "task",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Task",
+ "no_copy": 0,
+ "options": "Task",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "modified": "2015-04-29 04:54:36.024844",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Dependent Task",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/dependent_task/dependent_task.py b/erpnext/projects/doctype/dependent_task/dependent_task.py
new file mode 100644
index 0000000..90a96ac
--- /dev/null
+++ b/erpnext/projects/doctype/dependent_task/dependent_task.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class DependentTask(Document):
+ pass
diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js
index 9756331..d9a611e 100644
--- a/erpnext/projects/doctype/task/task.js
+++ b/erpnext/projects/doctype/task/task.js
@@ -40,6 +40,7 @@
}
});
+cur_frm.add_fetch('task', 'subject', 'subject');
cur_frm.cscript = new erpnext.projects.Task({frm: cur_frm});
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index fc604c2..ddcc48b 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -78,13 +78,27 @@
"width": "300px"
},
{
- "fieldname": "time_and_budget",
+ "fieldname": "section_break",
"fieldtype": "Section Break",
- "label": "",
+ "label": "Depends On",
"oldfieldtype": "Section Break",
"permlevel": 0
},
{
+ "fieldname": "depends_on",
+ "fieldtype": "Table",
+ "label": "depends_on",
+ "options": "Task Depends On",
+ "permlevel": 0,
+ "precision": ""
+ },
+ {
+ "fieldname": "section_break_10",
+ "fieldtype": "Section Break",
+ "permlevel": 0,
+ "precision": ""
+ },
+ {
"fieldname": "exp_start_date",
"fieldtype": "Date",
"label": "Expected Start Date",
@@ -249,7 +263,7 @@
"idx": 1,
"istable": 0,
"max_attachments": 5,
- "modified": "2015-04-14 07:56:24.481667",
+ "modified": "2015-04-30 05:48:55.176993",
"modified_by": "Administrator",
"module": "Projects",
"name": "Task",
diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py
index a03340f..456c40a 100644
--- a/erpnext/projects/doctype/task/task.py
+++ b/erpnext/projects/doctype/task/task.py
@@ -4,12 +4,13 @@
from __future__ import unicode_literals
import frappe, json
-from frappe.utils import getdate
+from frappe.utils import getdate, date_diff, add_days, cstr
from frappe import _
-
from frappe.model.document import Document
+class CircularReferenceError(frappe.ValidationError): pass
+
class Task(Document):
def get_feed(self):
return '{0}: {1}'.format(_(self.status), self.subject)
@@ -36,6 +37,8 @@
frappe.throw(_("'Actual Start Date' can not be greater than 'Actual End Date'"))
def on_update(self):
+ self.check_recursion()
+ self.reschedule_dependent_tasks()
self.update_percentage()
self.update_project()
@@ -52,8 +55,8 @@
def update_time_and_costing(self):
tl = frappe.db.sql("""select min(from_time) as start_date, max(to_time) as end_date,
sum(billing_amount) as total_billing_amount, sum(costing_amount) as total_costing_amount,
- sum(hours) as time from `tabTime Log` where project = %s and task = %s and docstatus=1""",
- (self.project, self.name),as_dict=1)[0]
+ sum(hours) as time from `tabTime Log` where task = %s and docstatus=1"""
+ ,self.name, as_dict=1)[0]
if self.status == "Open":
self.status = "Working"
self.total_costing_amount= tl.total_costing_amount
@@ -68,6 +71,36 @@
project.flags.dont_sync_tasks = True
project.update_costing()
project.save()
+
+ def check_recursion(self):
+ if self.flags.ignore_recursion_check: return
+ check_list = [['task', 'parent'], ['parent', 'task']]
+ for d in check_list:
+ task_list, count = [self.name], 0
+ while (len(task_list) > count ):
+ tasks = frappe.db.sql(" select %s from `tabTask Depends On` where %s = %s " %
+ (d[0], d[1], '%s'), cstr(task_list[count]))
+ count = count + 1
+ for b in tasks:
+ if b[0] == self.name:
+ frappe.throw(_("Circular Reference Error"), CircularReferenceError)
+ if b[0]:
+ task_list.append(b[0])
+ if count == 15:
+ break
+
+ def reschedule_dependent_tasks(self):
+ end_date = self.exp_end_date or self.act_end_date
+ if end_date:
+ for task_name in frappe.db.sql("select name from `tabTask` as parent where %s in \
+ (select task from `tabTask Depends On` as child where parent.name = child.parent )", self.name, as_dict=1):
+ task = frappe.get_doc("Task", task_name.name)
+ if task.exp_start_date and task.exp_end_date and task.exp_start_date < getdate(end_date) and task.status == "Open" :
+ task_duration = date_diff(task.exp_end_date, task.exp_start_date)
+ task.exp_start_date = add_days(end_date, 1)
+ task.exp_end_date = add_days(task.exp_start_date, task_duration)
+ task.flags.ignore_recursion_check = True
+ task.save()
@frappe.whitelist()
def get_events(start, end, filters=None):
diff --git a/erpnext/projects/doctype/task/test_task.py b/erpnext/projects/doctype/task/test_task.py
index 62e560f..8880763 100644
--- a/erpnext/projects/doctype/task/test_task.py
+++ b/erpnext/projects/doctype/task/test_task.py
@@ -1,7 +1,148 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-
-
import frappe
-test_records = frappe.get_test_records('Task')
+import unittest
+from frappe.utils import getdate
+
+# test_records = frappe.get_test_records('Task')
+
+from erpnext.projects.doctype.task.task import CircularReferenceError
+
+class TestTask(unittest.TestCase):
+ def test_circular_reference(self):
+
+ task1 = frappe.new_doc('Task')
+ task1.update({
+ "status": "Open",
+ "subject": "_Test Task 1",
+ "exp_start_date": "2015-1-1",
+ "exp_end_date": "2015-1-10"
+ })
+ task1.save()
+
+ task2 = frappe.new_doc('Task')
+ task2.update({
+ "status": "Open",
+ "subject": "_Test Task 2",
+ "exp_start_date": "2015-1-11",
+ "exp_end_date": "2015-1-15",
+ "depends_on":[
+ {
+ "task": task1.name
+ }
+ ]
+ })
+ task2.save()
+
+ task3 = frappe.new_doc('Task')
+ task3.update({
+ "status": "Open",
+ "subject": "_Test Task 2",
+ "exp_start_date": "2015-1-11",
+ "exp_end_date": "2015-1-15",
+ "depends_on":[
+ {
+ "task": task2.name
+ }
+ ]
+ })
+ task3.save()
+
+ task1.append("depends_on", {
+ "task": task3.name
+ })
+ self.assertRaises(CircularReferenceError, task1.save)
+
+ task1.set("depends_on", [])
+ task1.save()
+
+ task4 = frappe.new_doc('Task')
+ task4.update({
+ "status": "Open",
+ "subject": "_Test Task 1",
+ "exp_start_date": "2015-1-1",
+ "exp_end_date": "2015-1-15",
+ "depends_on":[
+ {
+ "task": task1.name
+ }
+ ]
+ })
+ task4.save()
+
+ task3.append("depends_on", {
+ "task": task4.name
+ })
+
+ def test_reschedule_dependent_task(self):
+ task1 = frappe.new_doc('Task')
+ task1.update({
+ "status": "Open",
+ "subject": "_Test Task 1",
+ "exp_start_date": "2015-1-1",
+ "exp_end_date": "2015-1-10"
+ })
+ task1.save()
+
+ task2 = frappe.new_doc('Task')
+ task2.update({
+ "status": "Open",
+ "subject": "_Test Task 2",
+ "exp_start_date": "2015-1-11",
+ "exp_end_date": "2015-1-15",
+ "depends_on":[
+ {
+ "task": task1.name
+ }
+ ]
+ })
+ task2.save()
+
+ task3 = frappe.new_doc('Task')
+ task3.update({
+ "status": "Open",
+ "subject": "_Test Task 3",
+ "exp_start_date": "2015-1-16",
+ "exp_end_date": "2015-1-18",
+ "depends_on":[
+ {
+ "task": task2.name
+ }
+ ]
+ })
+ task3.save()
+
+ task1.update({
+ "exp_end_date": "2015-1-20"
+ })
+ task1.save()
+
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
+
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
+
+ time_log = frappe.new_doc('Time Log')
+ time_log.update({
+ "from_time": "2015-1-1",
+ "to_time": "2015-1-20",
+ "task": task1.name
+ })
+ time_log.submit()
+
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
+
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
+
+ time_log.cancel()
+
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_start_date"), getdate('2015-1-21'))
+ self.assertEqual(frappe.db.get_value("Task", task2.name, "exp_end_date"), getdate('2015-1-25'))
+
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_start_date"), getdate('2015-1-26'))
+ self.assertEqual(frappe.db.get_value("Task", task3.name, "exp_end_date"), getdate('2015-1-28'))
+
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task_depends_on/__init__.py b/erpnext/projects/doctype/task_depends_on/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/projects/doctype/task_depends_on/__init__.py
diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.json b/erpnext/projects/doctype/task_depends_on/task_depends_on.json
new file mode 100644
index 0000000..7a960c1
--- /dev/null
+++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.json
@@ -0,0 +1,66 @@
+{
+ "allow_copy": 0,
+ "allow_import": 0,
+ "allow_rename": 0,
+ "creation": "2015-04-29 04:52:48.868079",
+ "custom": 0,
+ "docstatus": 0,
+ "doctype": "DocType",
+ "document_type": "",
+ "fields": [
+ {
+ "allow_on_submit": 0,
+ "fieldname": "task",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "ignore_user_permissions": 0,
+ "in_filter": 0,
+ "in_list_view": 1,
+ "label": "Task",
+ "no_copy": 0,
+ "options": "Task",
+ "permlevel": 0,
+ "precision": "",
+ "print_hide": 0,
+ "read_only": 0,
+ "report_hide": 0,
+ "reqd": 0,
+ "search_index": 0,
+ "set_only_once": 0
+ },
+ {
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break",
+ "permlevel": 0,
+ "precision": ""
+ },
+ {
+ "fieldname": "subject",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Subject",
+ "options": "",
+ "permlevel": 0,
+ "precision": "",
+ "read_only": 1
+ }
+ ],
+ "hide_heading": 0,
+ "hide_toolbar": 0,
+ "in_create": 0,
+ "in_dialog": 0,
+ "is_submittable": 0,
+ "issingle": 0,
+ "istable": 1,
+ "modified": "2015-04-30 05:52:16.250948",
+ "modified_by": "Administrator",
+ "module": "Projects",
+ "name": "Task Depends On",
+ "name_case": "",
+ "owner": "Administrator",
+ "permissions": [],
+ "read_only": 0,
+ "read_only_onload": 0,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/projects/doctype/task_depends_on/task_depends_on.py b/erpnext/projects/doctype/task_depends_on/task_depends_on.py
new file mode 100644
index 0000000..723a0fc
--- /dev/null
+++ b/erpnext/projects/doctype/task_depends_on/task_depends_on.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model.document import Document
+
+class TaskDependsOn(Document):
+ pass
diff --git a/erpnext/projects/doctype/time_log/test_time_log.py b/erpnext/projects/doctype/time_log/test_time_log.py
index 3d9e0be..9b43b0d 100644
--- a/erpnext/projects/doctype/time_log/test_time_log.py
+++ b/erpnext/projects/doctype/time_log/test_time_log.py
@@ -1,6 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
from __future__ import unicode_literals
import frappe
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 27500a5..0e444cc 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -41,6 +41,11 @@
onload_post_render: function() {
var me = this;
+ if(this.frm.doc.__islocal && !(this.frm.doc.taxes || []).length
+ && !(this.frm.doc.__onload ? this.frm.doc.__onload.load_after_mapping : false)) {
+ this.apply_default_taxes();
+ }
+
if(this.frm.doc.__islocal && this.frm.doc.company && this.frm.doc["items"] && !this.frm.doc.is_pos) {
this.calculate_taxes_and_totals();
}
@@ -58,6 +63,23 @@
erpnext.pos.make_pos_btn(this.frm);
this.setup_sms();
},
+
+ apply_default_taxes: function() {
+ var me = this;
+ return frappe.call({
+ method: "erpnext.controllers.accounts_controller.get_default_taxes_and_charges",
+ args: {
+ "master_doctype": frappe.meta.get_docfield(me.frm.doc.doctype, "taxes_and_charges",
+ me.frm.doc.name).options
+ },
+ callback: function(r) {
+ if(!r.exc) {
+ me.frm.set_value("taxes", r.message);
+ me.calculate_taxes_and_totals();
+ }
+ }
+ });
+ },
setup_sms: function() {
var me = this;
@@ -260,22 +282,29 @@
price_list_currency: function() {
var me=this;
this.set_dynamic_labels();
- this.set_plc_conversion_rate();
+
+ var company_currency = this.get_company_currency();
+ // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
+ if(this.frm.doc.price_list_currency !== company_currency && !this.frm.doc.ignore_pricing_rule) {
+ this.get_exchange_rate(this.frm.doc.price_list_currency, company_currency,
+ function(exchange_rate) {
+ me.frm.set_value("plc_conversion_rate", exchange_rate);
+ });
+ } else {
+ this.plc_conversion_rate();
+ }
},
plc_conversion_rate: function() {
- this.set_plc_conversion_rate();
- if(!this.in_apply_price_list) {
- this.apply_price_list();
- }
- },
-
- set_plc_conversion_rate: function() {
if(this.frm.doc.price_list_currency === this.get_company_currency()) {
this.frm.set_value("plc_conversion_rate", 1.0);
+ } else if(this.frm.doc.price_list_currency === this.frm.doc.currency && this.frm.doc.plc_conversion_rate && cint(this.frm.doc.plc_conversion_rate) != 1 &&
+ cint(this.frm.doc.plc_conversion_rate) != cint(this.frm.doc.conversion_rate)) {
+ this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
}
- if(this.frm.doc.price_list_currency === this.frm.doc.currency) {
- this.frm.set_value("conversion_rate", this.frm.doc.plc_conversion_rate);
+
+ if(!this.in_apply_price_list) {
+ this.apply_price_list();
}
},
@@ -639,7 +668,6 @@
"master_doctype": frappe.meta.get_docfield(this.frm.doc.doctype, "taxes_and_charges",
this.frm.doc.name).options,
"master_name": this.frm.doc.taxes_and_charges,
- "tax_parentfield": "taxes"
},
callback: function(r) {
if(!r.exc) {
diff --git a/erpnext/selling/report/customers_not_buying_since_long_time/customers_not_buying_since_long_time.py b/erpnext/selling/report/customers_not_buying_since_long_time/customers_not_buying_since_long_time.py
index dc33051..2b2c550 100644
--- a/erpnext/selling/report/customers_not_buying_since_long_time/customers_not_buying_since_long_time.py
+++ b/erpnext/selling/report/customers_not_buying_since_long_time/customers_not_buying_since_long_time.py
@@ -44,7 +44,7 @@
def get_last_so_amt(customer):
res = frappe.db.sql("""select base_net_total from `tabSales Order`
where customer ='%(customer)s' and docstatus = 1 order by transaction_date desc
- limit 1""" % {'customer':customer})
+ limit 1""" % {'customer': frappe.db.escape(customer)})
return res and res[0][0] or 0
diff --git a/erpnext/setup/page/setup_wizard/setup_wizard.js b/erpnext/setup/page/setup_wizard/setup_wizard.js
index c1ec20d..6d4d826 100644
--- a/erpnext/setup/page/setup_wizard/setup_wizard.js
+++ b/erpnext/setup/page/setup_wizard/setup_wizard.js
@@ -419,23 +419,25 @@
load_chart_of_accounts: function(slide) {
var country = slide.wiz.get_values().country;
- frappe.call({
- method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_charts_for_country",
- args: {"country": country},
- callback: function(r) {
- if(r.message) {
- slide.get_input("chart_of_accounts").empty()
- .add_options(r.message);
+ if(country) {
+ frappe.call({
+ method: "erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts.get_charts_for_country",
+ args: {"country": country},
+ callback: function(r) {
+ if(r.message) {
+ slide.get_input("chart_of_accounts").empty()
+ .add_options(r.message);
- if (r.message.length===1) {
- var field = slide.get_field("chart_of_accounts");
- field.set_value(r.message[0]);
- field.df.hidden = 1;
- field.refresh();
+ if (r.message.length===1) {
+ var field = slide.get_field("chart_of_accounts");
+ field.set_value(r.message[0]);
+ field.df.hidden = 1;
+ field.refresh();
+ }
}
}
- }
- })
+ })
+ }
},
bind_events: function(slide) {
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/shopping_cart/cart.py
index 401f047..0b09eb7 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/shopping_cart/cart.py
@@ -256,7 +256,7 @@
quotation.set("taxes", [])
# append taxes
- quotation.append_taxes_from_master("taxes", "taxes_and_charges")
+ quotation.append_taxes_from_master()
def get_lead_or_customer():
customer = frappe.db.get_value("Contact", {"email_id": frappe.session.user}, "customer")
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 733821d..221e4eb 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -69,6 +69,9 @@
def set_applicable_charges_for_item(self):
based_on = self.distribute_charges_based_on.lower()
total = sum([flt(d.get(based_on)) for d in self.get("items")])
+
+ if not total:
+ frappe.throw(_("Total {0} for all items is zero, may you should change 'Distribute Charges Based On'").format(based_on))
for item in self.get("items"):
item.applicable_charges = flt(item.get(based_on)) * flt(self.total_taxes_and_charges) / flt(total)
diff --git a/erpnext/templates/includes/product_in_grid.html b/erpnext/templates/includes/product_in_grid.html
index 854f77f..ea49812 100644
--- a/erpnext/templates/includes/product_in_grid.html
+++ b/erpnext/templates/includes/product_in_grid.html
@@ -1,6 +1,6 @@
{% from "erpnext/templates/includes/macros.html" import product_image_square %}
-<a class="product-link" href="{{ route or page_name }}">
+<a class="product-link" href="{{ (route or page_name)|with_leading_slash }}">
<div class="col-sm-2 col-xs-4 product-image-wrapper">
{{ product_image_square(website_image) }}
<div class="text-ellipsis inline-block small product-text">{{ item_name }}</div>
diff --git a/erpnext/templates/includes/product_in_list.html b/erpnext/templates/includes/product_in_list.html
index 91d2d7a..98e46a7 100644
--- a/erpnext/templates/includes/product_in_list.html
+++ b/erpnext/templates/includes/product_in_list.html
@@ -1,15 +1,15 @@
<!-- TODO product listing -->
<div class="container content">
<div style="height: 120px; overflow: hidden;">
- <a href="{{ route or page_name }}">
+ <a href="{{ (route or page_name)|with_leading_slash }}">
{%- if website_image -%}
- <img class="product-image" style="width: 80%; margin: auto;" src="{{ website_image }}">
+ <img class="product-image" style="width: 80%; margin: auto;" src="{{ website_image|with_leading_slash }}">
{%- else -%}
<div style="width: 80%; height: 120px; background-color: #F7FAFC;"></div>
{%- endif -%}
</a>
</div>
<div style="height: 100px; overflow: hidden; font-size: 80%;">
- <div><a href="{{ route or page_name }}">{{ item_name }}</a></div>
+ <div><a href="{{ (route or page_name)|with_leading_slash }}">{{ item_name }}</a></div>
</div>
</div>