Merge pull request #3192 from neilLasrado/purchase-costing
Purchase costing in Projects
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index a824a12..683734b 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -13,7 +13,7 @@
def onload(self):
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
"frozen_accounts_modifier")
- if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.user.get_roles():
+ if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
self.get("__onload").can_freeze_account = True
def autoname(self):
@@ -59,7 +59,7 @@
if old_value and old_value != self.freeze_account:
frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
if not frozen_accounts_modifier or \
- frozen_accounts_modifier not in frappe.user.get_roles():
+ frozen_accounts_modifier not in frappe.get_roles():
throw(_("You are not authorized to set Frozen value"))
def validate_balance_must_be_debit_or_credit(self):
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index b17d75b..ae4dae0 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -110,7 +110,7 @@
if acc_frozen_upto:
frozen_accounts_modifier = frappe.db.get_value( 'Accounts Settings', None,'frozen_accounts_modifier')
if getdate(posting_date) <= getdate(acc_frozen_upto) \
- and not frozen_accounts_modifier in frappe.user.get_roles():
+ and not frozen_accounts_modifier in frappe.get_roles():
frappe.throw(_("You are not authorized to add or update entries before {0}").format(formatdate(acc_frozen_upto)))
def update_outstanding_amt(account, party_type, party, against_voucher_type, against_voucher, on_cancel=False):
@@ -155,5 +155,5 @@
if not frozen_accounts_modifier:
frappe.throw(_("Account {0} is frozen").format(account))
- elif frozen_accounts_modifier not in frappe.user.get_roles():
+ elif frozen_accounts_modifier not in frappe.get_roles():
frappe.throw(_("Not authorized to edit frozen Account {0}").format(account))
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 0bd2307..6317793 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -435,13 +435,12 @@
Pending Amount is {2}".format(d.idx, d.against_expense_claim, pending_amount)))
def validate_credit_debit_note(self):
- count = frappe.db.exists({
- "doctype": "Journal Entry",
- "stock_entry":self.stock_entry,
- "docstatus":1
- })
- if count:
- frappe.throw(_("{0} already made against stock entry {1}".format(self.voucher_type, self.stock_entry)))
+ if self.stock_entry:
+ if frappe.db.get_value("Stock Entry", self.stock_entry, "docstatus") != 1:
+ frappe.throw(_("Stock Entry {0} is not submitted").format(self.stock_entry))
+
+ if frappe.db.exists({"doctype": "Journal Entry", "stock_entry": self.stock_entry, "docstatus":1}):
+ frappe.msgprint(_("Warning: Another {0} # {1} exists against stock entry {2}".format(self.voucher_type, self.name, self.stock_entry)))
def validate_empty_accounts_table(self):
if not self.get('accounts'):
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 7ea0c66..00e595f 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -186,7 +186,7 @@
frappe.throw(_("Due Date cannot be before Posting Date"))
elif credit_days is not None and diff > flt(credit_days):
is_credit_controller = frappe.db.get_value("Accounts Settings", None,
- "credit_controller") in frappe.user.get_roles()
+ "credit_controller") in frappe.get_roles()
if is_credit_controller:
msgprint(_("Note: Due / Reference Date exceeds allowed customer credit days by {0} day(s)")
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index b744e29..9035626 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -8,6 +8,7 @@
"label": __("Company"),
"fieldtype": "Link",
"options": "Company",
+ "reqd": 1,
"default": frappe.defaults.get_user_default("company")
},
{
diff --git a/erpnext/buying/doctype/purchase_common/purchase_common.py b/erpnext/buying/doctype/purchase_common/purchase_common.py
index 8cedddf..21791f7 100644
--- a/erpnext/buying/doctype/purchase_common/purchase_common.py
+++ b/erpnext/buying/doctype/purchase_common/purchase_common.py
@@ -3,7 +3,7 @@
from __future__ import unicode_literals
import frappe
-from frappe.utils import flt
+from frappe.utils import flt, cstr
from frappe import _
from erpnext.stock.doctype.item.item import get_last_purchase_details
@@ -39,7 +39,7 @@
(flt(last_purchase_rate), d.item_code))
def validate_for_items(self, obj):
- check_list, chk_dupl_itm=[],[]
+ items = []
for d in obj.get("items"):
# validation for valid qty
if flt(d.qty) < 0 or (d.parenttype != 'Purchase Receipt' and not flt(d.qty)):
@@ -70,31 +70,11 @@
if not (obj.doctype=="Material Request" and getattr(obj, "material_request_type", None)=="Material Transfer"):
if item[0][1] != 'Yes' and item[0][2] != 'Yes':
frappe.throw(_("{0} must be a Purchased or Sub-Contracted Item in row {1}").format(d.item_code, d.idx))
-
- # list criteria that should not repeat if item is stock item
- e = [getattr(d, "schedule_date", None), d.item_code, d.description, d.warehouse, d.uom,
- d.meta.get_field('prevdoc_docname') and d.prevdoc_docname or d.meta.get_field('sales_order_no') and d.sales_order_no or '',
- d.meta.get_field('prevdoc_detail_docname') and d.prevdoc_detail_docname or '',
- d.meta.get_field('batch_no') and d.batch_no or '']
-
- # if is not stock item
- f = [getattr(d, "schedule_date", None), d.item_code, d.description]
-
- ch = frappe.db.sql("""select is_stock_item from `tabItem` where name = %s""", d.item_code)
-
- if ch and ch[0][0] == 'Yes':
- # check for same items
- if e in check_list:
- frappe.throw(_("Item {0} has been entered multiple times with same description or date or warehouse").format(d.item_code))
- else:
- check_list.append(e)
-
- elif ch and ch[0][0] == 'No':
- # check for same items
- if f in chk_dupl_itm:
- frappe.throw(_("Item {0} has been entered multiple times with same description or date").format(d.item_code))
- else:
- chk_dupl_itm.append(f)
+
+ items.append(cstr(d.item_code))
+ if items and len(items) != len(set(items)):
+ frappe.msgprint(_("Warning: Same item has been entered multiple times."))
+
def check_for_stopped_status(self, doctype, docname):
stopped = frappe.db.sql("""select name from `tab%s` where name = %s and
diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py
index 3d5e690..16387a5 100644
--- a/erpnext/controllers/trends.py
+++ b/erpnext/controllers/trends.py
@@ -31,7 +31,10 @@
for f in ["Fiscal Year", "Based On", "Period", "Company"]:
if not filters.get(f.lower().replace(" ", "_")):
frappe.throw(_("{0} is mandatory").format(f))
-
+
+ if not frappe.db.exists("Fiscal Year", filters.get("fiscal_year")):
+ frappe.throw(_("Fiscal Year: {0} does not exists").format(filters.get("fiscal_year")))
+
if filters.get("based_on") == filters.get("group_by"):
frappe.throw(_("'Based On' and 'Group By' can not be same"))
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index 3c9ae46..9282be2 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -22,7 +22,7 @@
from frappe.templates.pages.list import get_list
user = frappe.session.user
- if user != "Guest" and is_website_user(user):
+ if user != "Guest" and is_website_user():
# find party for this contact
customers, suppliers = get_customers_suppliers(doctype, user)
if customers:
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index 0fb8d48..b523416 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -11,14 +11,12 @@
from erpnext.controllers.selling_controller import SellingController
from erpnext.utilities.address_and_contact import load_address_and_contact
+sender_field = "email_id"
+
class Lead(SellingController):
def get_feed(self):
return '{0}: {1}'.format(_(self.status), self.lead_name)
- def set_sender(self, sender):
- """Will be called by **Communication** when a Lead is created from an incoming email."""
- self.email_id = sender
-
def onload(self):
customer = frappe.db.get_value("Customer", {"lead_name": self.name})
self.get("__onload").is_customer = customer
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index abf5fef..08d413f 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -2,32 +2,17 @@
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
-import frappe
+import frappe, json
from frappe.utils import cstr, cint
from frappe import msgprint, _
from frappe.model.mapper import get_mapped_doc
from erpnext.utilities.transaction_base import TransactionBase
+subject_field = "title"
+sender_field = "contact_email"
+
class Opportunity(TransactionBase):
- def set_sender(self, email_id):
- """Set lead against new opportunity"""
- lead_name = frappe.db.get_value("Lead", {"email_id": email_id})
- if not lead_name:
- lead = frappe.get_doc({
- "doctype": "Lead",
- "email_id": email_id,
- "lead_name": email_id
- })
- lead.insert(ignore_permissions=True)
- lead_name = lead.name
-
- self.enquiry_from = "Lead"
- self.lead = lead_name
-
- def set_subject(self, subject):
- self.title = subject
-
def after_insert(self):
if self.lead:
frappe.get_doc("Lead", self.lead).set_status(update=True)
@@ -40,6 +25,8 @@
(not cint(self.get("__islocal"))) else None,
})
+ self.make_new_lead_if_required()
+
if not self.enquiry_from:
frappe.throw(_("Opportunity From field is mandatory"))
@@ -55,6 +42,22 @@
from erpnext.accounts.utils import validate_fiscal_year
validate_fiscal_year(self.transaction_date, self.fiscal_year, _("Opportunity Date"), self)
+ def make_new_lead_if_required(self):
+ """Set lead against new opportunity"""
+ if not self.lead or self.customer:
+ lead_name = frappe.db.get_value("Lead", {"email_id": self.contact_email})
+ if not lead_name:
+ lead = frappe.get_doc({
+ "doctype": "Lead",
+ "email_id": self.contact_email,
+ "lead_name": self.contact_email
+ })
+ lead.insert(ignore_permissions=True)
+ lead_name = lead.name
+
+ self.enquiry_from = "Lead"
+ self.lead = lead_name
+
def declare_enquiry_lost(self,arg):
if not self.has_quotation():
frappe.db.set(self, 'status', 'Lost')
@@ -200,3 +203,11 @@
}, target_doc, set_missing_values)
return doclist
+
+@frappe.whitelist()
+def set_multiple_status(names, status):
+ names = json.loads(names)
+ for name in names:
+ opp = frappe.get_doc("Opportunity", name)
+ opp.status = status
+ opp.save()
diff --git a/erpnext/crm/doctype/opportunity/opportunity_list.js b/erpnext/crm/doctype/opportunity/opportunity_list.js
index 6c6c897..55a5463 100644
--- a/erpnext/crm/doctype/opportunity/opportunity_list.js
+++ b/erpnext/crm/doctype/opportunity/opportunity_list.js
@@ -6,5 +6,16 @@
indicator[1] = "green";
}
return indicator;
+ },
+ onload: function(listview) {
+ var method = "erpnext.crm.doctype.opportunity.opportunity.set_multiple_status";
+
+ listview.page.add_menu_item(__("Set as Open"), function() {
+ listview.call_for_selected_items(method, {"status": "Open"});
+ });
+
+ listview.page.add_menu_item(__("Set as Closed"), function() {
+ listview.call_for_selected_items(method, {"status": "Closed"});
+ });
}
};
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.json b/erpnext/hr/doctype/expense_claim/expense_claim.json
index e08856a..e9e29a8 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.json
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.json
@@ -237,7 +237,7 @@
"icon": "icon-money",
"idx": 1,
"is_submittable": 1,
- "modified": "2015-04-22 01:51:24.782515",
+ "modified": "2015-05-02 07:42:25.202983",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claim",
@@ -258,25 +258,6 @@
},
{
"amend": 1,
- "apply_user_permissions": 0,
- "cancel": 1,
- "create": 1,
- "delete": 1,
- "email": 0,
- "export": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "HR Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 1,
- "write": 1
- },
- {
- "amend": 1,
"apply_user_permissions": 1,
"cancel": 1,
"create": 1,
@@ -289,6 +270,7 @@
"role": "Expense Approver",
"share": 1,
"submit": 1,
+ "user_permission_doctypes": "[\"Company\",\"Expense Claim\",\"Fiscal Year\",\"Project\",\"Task\",\"User\"]",
"write": 1
},
{
@@ -305,6 +287,7 @@
"role": "HR User",
"share": 1,
"submit": 1,
+ "user_permission_doctypes": "[\"Company\",\"Expense Claim\",\"Fiscal Year\",\"Project\",\"Task\",\"User\"]",
"write": 1
}
],
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index 5fb1160..b0482cd 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -9,12 +9,14 @@
from frappe import _
from frappe.utils import comma_and
+sender_field = "email_id"
+
class JobApplicant(Document):
def onload(self):
offer_letter = frappe.get_all("Offer Letter", filters={"job_applicant": self.name})
if offer_letter:
self.get("__onload").offer_letter = offer_letter[0].name
-
+
def autoname(self):
keys = filter(None, (self.applicant_name, self.email_id))
if not keys:
@@ -31,6 +33,3 @@
if names:
frappe.throw(_("Email id must be unique, already exists for {0}").format(comma_and(names)), frappe.DuplicateEntryError)
-
- def set_sender(self, sender):
- self.email_id = sender
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 14932d2..4c87dc7 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -388,6 +388,7 @@
and bom_item.docstatus < 2
and bom_item.parent = %(bom)s
and item.name = bom_item.item_code
+ and ifnull(item.is_stock_item, 'No') = 'Yes'
{conditions}
group by item_code, stock_uom"""
diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
index 4a4724b..1298790 100644
--- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
+++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
@@ -248,8 +248,10 @@
ifnull(sum(ifnull(fb.qty, 0)/ifnull(bom.quantity, 1)), 0) as qty,
fb.description, fb.stock_uom, it.min_order_qty
from `tabBOM Explosion Item` fb, `tabBOM` bom, `tabItem` it
- where bom.name = fb.parent and it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No'
+ where bom.name = fb.parent and it.name = fb.item_code
+ and ifnull(it.is_pro_applicable, 'No') = 'No'
and ifnull(it.is_sub_contracted_item, 'No') = 'No'
+ and ifnull(it.is_stock_item, 'No') = 'Yes'
and fb.docstatus<2 and bom.name=%s
group by item_code, stock_uom""", bom, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
@@ -262,6 +264,7 @@
from `tabBOM Item` bom_item, `tabBOM` bom, tabItem item
where bom.name = bom_item.parent and bom.name = %s and bom_item.docstatus < 2
and bom_item.item_code = item.name
+ and ifnull(item.is_stock_item, 'No') = 'Yes'
group by item_code""", bom, as_dict=1):
bom_wise_item_details.setdefault(d.item_code, d)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index df2c694..e147ee3 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -146,4 +146,5 @@
erpnext.patches.v4_2.delete_gl_entries_for_cancelled_invoices
erpnext.patches.v5_0.project_costing
erpnext.patches.v5_0.update_temporary_account
-erpnext.patches.v5_0.update_advance_paid
\ No newline at end of file
+erpnext.patches.v5_0.update_advance_paid
+erpnext.patches.v5_0.link_warehouse_with_account
\ No newline at end of file
diff --git a/erpnext/patches/v4_2/party_model.py b/erpnext/patches/v4_2/party_model.py
index 897598f..d105349 100644
--- a/erpnext/patches/v4_2/party_model.py
+++ b/erpnext/patches/v4_2/party_model.py
@@ -15,24 +15,21 @@
delete_individual_party_account()
remove_customer_supplier_account_report()
-
-def link_warehouse_account():
- frappe.db.sql("""update tabAccount set warehouse=master_name
- where ifnull(account_type, '') = 'Warehouse' and ifnull(master_name, '') != ''""")
-
def create_receivable_payable_account():
receivable_payable_accounts = frappe._dict()
def _create_account(args):
- account = frappe.new_doc("Account")
- account.is_group = 0
- account.update(args)
- account.insert()
+ if not frappe.db.get_value("Account",
+ {"account_name": args["account_name"], "company": args["company"]}):
+ account = frappe.new_doc("Account")
+ account.is_group = 0
+ account.update(args)
+ account.insert()
- frappe.db.set_value("Company", args["company"], ("default_receivable_account"
- if args["account_type"]=="Receivable" else "default_payable_account"), account.name)
+ frappe.db.set_value("Company", args["company"], ("default_receivable_account"
+ if args["account_type"]=="Receivable" else "default_payable_account"), account.name)
- receivable_payable_accounts.setdefault(args["company"], {}).setdefault(args["account_type"], account.name)
+ receivable_payable_accounts.setdefault(args["company"], {}).setdefault(args["account_type"], account.name)
for company in frappe.db.sql_list("select name from tabCompany"):
_create_account({
@@ -52,8 +49,11 @@
return receivable_payable_accounts
def get_parent_account(company, master_type):
- parent_account = frappe.db.get_value("Company", company,
- "receivables_group" if master_type=="Customer" else "payables_group")
+ parent_account = None
+
+ if "receivables_group" in frappe.db.get_table_columns("Company"):
+ parent_account = frappe.db.get_value("Company", company,
+ "receivables_group" if master_type=="Customer" else "payables_group")
if not parent_account:
parent_account = frappe.db.get_value("Account", {"company": company,
"account_name": "Accounts Receivable" if master_type=="Customer" else "Accounts Payable"})
@@ -78,7 +78,8 @@
return
for dt in ["Journal Entry Account", "GL Entry"]:
- records = frappe.db.sql("""select name, account from `tab%s` where account in (%s)""" %
+ records = frappe.db.sql("""select name, account from `tab%s`
+ where account in (%s) and ifnull(party, '') = ''""" %
(dt, ", ".join(['%s']*len(account_map))), tuple(account_map.keys()), as_dict=1)
for i, d in enumerate(records):
account_details = account_map.get(d.account, {})
diff --git a/erpnext/patches/v5_0/link_warehouse_with_account.py b/erpnext/patches/v5_0/link_warehouse_with_account.py
new file mode 100644
index 0000000..338fd7a
--- /dev/null
+++ b/erpnext/patches/v5_0/link_warehouse_with_account.py
@@ -0,0 +1,10 @@
+# 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
+
+def execute():
+ if "master_name" in frappe.db.get_table_columns("Account"):
+ frappe.db.sql("""update tabAccount set warehouse=master_name
+ where ifnull(account_type, '') = 'Warehouse' and ifnull(master_name, '') != ''""")
\ No newline at end of file
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 97efe6f..c595f4e 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
@@ -3,6 +3,8 @@
# License: GNU General Public License v3. See license.txt
import frappe
+from frappe.model.meta import get_field_precision
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
def execute():
selling_doctypes = ["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"]
@@ -18,19 +20,32 @@
frappe.reload_doctype(dt)
frappe.reload_doctype(dt + " Item")
frappe.reload_doctype(tax_table)
-
+
+ net_total_precision = get_field_precision(frappe.get_meta(dt).get_field("net_total"))
+ for field in ("total", "base_total", "base_net_total"):
+ make_property_setter(dt, field, "precision", net_total_precision, "Select")
+
+ rate_field_precision = get_field_precision(frappe.get_meta(dt + " Item").get_field("rate"))
+ for field in ("net_rate", "base_net_rate", "net_amount", "base_net_amount", "base_rate", "base_amount"):
+ make_property_setter(dt + " Item", field, "precision", rate_field_precision, "Select")
+
+ tax_amount_precision = get_field_precision(frappe.get_meta(tax_table).get_field("tax_amount"))
+ for field in ("base_tax_amount", "total", "base_total", "tax_amount_after_discount_amount",
+ "base_tax_amount_after_discount_amount"):
+ make_property_setter(tax_table, field, "precision", tax_amount_precision, "Select")
+
# update net_total, discount_on
frappe.db.sql("""
UPDATE
`tab{0}`
SET
- total = net_total,
- base_total = net_total*conversion_rate,
- net_total = base_net_total / conversion_rate,
+ total = round(net_total, {1}),
+ base_total = round(net_total*conversion_rate, {1}),
+ net_total = round(base_net_total / conversion_rate, {1}),
apply_discount_on = "Grand Total"
WHERE
docstatus < 2
- """.format(dt))
+ """.format(dt, net_total_precision))
# update net_amount
@@ -40,14 +55,14 @@
SET
item.base_net_amount = item.base_amount,
item.base_net_rate = item.base_rate,
- item.net_amount = item.base_net_amount / par.conversion_rate,
- item.net_rate = item.base_net_rate / par.conversion_rate,
- item.base_amount = item.amount * par.conversion_rate,
- item.base_rate = item.rate * par.conversion_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_amount = round(item.amount * par.conversion_rate, {2}),
+ item.base_rate = round(item.rate * par.conversion_rate, {2})
WHERE
par.name = item.parent
and par.docstatus < 2
- """.format(dt, dt + " Item"))
+ """.format(dt, dt + " Item", rate_field_precision))
# update tax in party currency
frappe.db.sql("""
@@ -55,12 +70,12 @@
`tab{0}` par, `tab{1}` tax
SET
tax.base_tax_amount = tax.tax_amount,
- tax.tax_amount = tax.base_tax_amount / par.conversion_rate,
- tax.base_total = tax.total,
- tax.total = tax.base_total / conversion_rate,
- tax.base_tax_amount_after_discount_amount = tax.tax_amount_after_discount_amount,
- tax.tax_amount_after_discount_amount = tax.base_tax_amount_after_discount_amount / conversion_rate
+ tax.tax_amount = round(tax.base_tax_amount / par.conversion_rate, {2}),
+ tax.base_total = round(tax.total, {2}),
+ tax.total = round(tax.base_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})
WHERE
par.name = tax.parent
and par.docstatus < 2
- """.format(dt, tax_table))
+ """.format(dt, tax_table, tax_amount_precision))
diff --git a/erpnext/patches/v5_0/update_item_description_and_image.py b/erpnext/patches/v5_0/update_item_description_and_image.py
index 7e61314..6b47052 100644
--- a/erpnext/patches/v5_0/update_item_description_and_image.py
+++ b/erpnext/patches/v5_0/update_item_description_and_image.py
@@ -30,7 +30,8 @@
count = 1
for d in records:
- if cstr(d.description) == item_details.get(d.item_code).old_description:
+ if d.item_code and item_details.get(d.item_code) \
+ and cstr(d.description) == item_details.get(d.item_code).old_description:
image_url = item_details.get(d.item_code).image_url
desc = item_details.get(d.item_code).new_description
else:
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 590788d..c914f22 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -21,7 +21,7 @@
"status": task.status,
"start_date": task.exp_start_date,
"end_date": task.exp_end_date,
- "desciption": task.description,
+ "description": task.description,
"task_id": task.name
})
@@ -37,7 +37,7 @@
def sync_tasks(self):
"""sync tasks and remove table"""
if self.flags.dont_sync_tasks: return
-
+
task_names = []
for t in self.tasks:
if t.task_id:
@@ -51,7 +51,7 @@
"status": t.status,
"exp_start_date": t.start_date,
"exp_end_date": t.end_date,
- "desciption": t.description,
+ "description": t.description,
})
task.flags.ignore_links = True
@@ -62,7 +62,7 @@
# delete
for t in frappe.get_all("Task", ["name"], {"project": self.name, "name": ("not in", task_names)}):
frappe.delete_doc("Task", t.name)
-
+
self.tasks = []
def update_percent_complete(self):
@@ -73,13 +73,13 @@
project=%s and status in ('Closed', 'Cancelled')""", self.name)[0][0]
frappe.db.set_value("Project", self.name, "percent_complete",
int(float(completed) / total * 100))
-
+
def update_costing(self):
total_cost = frappe.db.sql("""select sum(total_costing_amount) as costing_amount,
sum(total_billing_amount) as billing_amount, sum(total_expense_claim) as expense_claim,
min(act_start_date) as start_date, max(act_end_date) as end_date, sum(actual_time) as time
from `tabTask` where project = %s""", self.name, as_dict=1)[0]
-
+
self.total_costing_amount = total_cost.costing_amount
self.total_billing_amount = total_cost.billing_amount
self.total_expense_claim = total_cost.expense_claim
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 2ea4bac..212d2ae 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -5,6 +5,66 @@
frappe.provide("erpnext.taxes");
frappe.provide("erpnext.taxes.flags");
+frappe.ui.form.on(cur_frm.doctype, {
+ onload: function(frm) {
+ if(frm.get_field("taxes")) {
+ frm.set_query("account_head", "taxes", function(doc) {
+ if(frm.cscript.tax_table == "Sales Taxes and Charges") {
+ var account_type = ["Tax", "Chargeable", "Expense Account"];
+ } else {
+ var account_type = ["Tax", "Chargeable", "Income Account"];
+ }
+
+ return {
+ query: "erpnext.controllers.queries.tax_account_query",
+ filters: {
+ "account_type": account_type,
+ "company": doc.company
+ }
+ }
+ });
+
+ frm.set_query("cost_center", "taxes", function(doc) {
+ return {
+ filters: {
+ 'company': doc.company,
+ "is_group": 0
+ }
+ }
+ });
+ }
+ },
+ validate: function(frm) {
+ // neither is absolutely mandatory
+ // check mandatory based on charge_type
+ frm.get_docfield("taxes", "rate").reqd = 0;
+ frm.get_docfield("taxes", "tax_amount").reqd = 0;
+
+ $.each(frm.doc.taxes || [], function(i, d) {
+ if(d.charge_type==="Actual") {
+ d.rate = 0;
+ if(!d.tax_amount) {
+ msgprint(__("Amount is mandatory for {0}", [d.account_head]));
+ validated = false;
+ }
+ } else {
+ d.tax_amount = 0;
+ if(!d.rate) {
+ msgprint(__("Rate is mandatory for {0}", [d.account_head]));
+ validated = false;
+ }
+ }
+ });
+ },
+ taxes_on_form_rendered: function(frm) {
+ erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm);
+ }
+});
+
+
+
+
+
cur_frm.cscript.account_head = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
@@ -134,42 +194,6 @@
}
}
-// setup conditional mandatory for tax and rates
-frappe.ui.form.on(cur_frm.doctype, "taxes_on_form_rendered", function(frm) {
- erpnext.taxes.set_conditional_mandatory_rate_or_amount(frm);
-});
-
-// setup queries for taxes
-frappe.ui.form.on(cur_frm.doctype, "onload", function(frm) {
- if(frm.get_field("taxes")) {
- frm.set_query("account_head", "taxes", function(doc) {
- if(frm.cscript.tax_table == "Sales Taxes and Charges") {
- var account_type = ["Tax", "Chargeable", "Expense Account"];
- } else {
- var account_type = ["Tax", "Chargeable", "Income Account"];
- }
-
- return {
- query: "erpnext.controllers.queries.tax_account_query",
- filters: {
- "account_type": account_type,
- "company": doc.company
- }
- }
- });
-
- frm.set_query("cost_center", "taxes", function(doc) {
- return {
- filters: {
- 'company': doc.company,
- "is_group": 0
- }
- }
- });
- }
-});
-
-
// For customizing print
cur_frm.pformat.total = function(doc) { return ''; }
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index f62eed2..b45e120 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -214,7 +214,8 @@
this.set_dynamic_labels();
var company_currency = this.get_company_currency();
- if(this.frm.doc.currency !== company_currency) {
+ // Added `ignore_pricing_rule` to determine if document is loading after mapping from another doc
+ if(this.frm.doc.currency !== company_currency && !this.frm.doc.ignore_pricing_rule) {
this.get_exchange_rate(this.frm.doc.currency, company_currency,
function(exchange_rate) {
me.frm.set_value("conversion_rate", exchange_rate);
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 7e722fc..2d0f850 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -161,7 +161,7 @@
# If not authorized person raise exception
credit_controller = frappe.db.get_value('Accounts Settings', None, 'credit_controller')
- if not credit_controller or credit_controller not in frappe.user.get_roles():
+ if not credit_controller or credit_controller not in frappe.get_roles():
throw(_("Please contact to the user who have Sales Master Manager {0} role")
.format(" / " + credit_controller if credit_controller else ""))
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 9525fa5..398e1e1 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -51,7 +51,7 @@
d.projected_qty = tot_avail_qty and flt(tot_avail_qty[0][0]) or 0
unique_chk_list = set(check_list)
if len(unique_chk_list) != len(check_list):
- frappe.msgprint(_("Warning:Same item has been entered multiple times."))
+ frappe.msgprint(_("Warning: Same item has been entered multiple times."))
def validate_sales_mntc_quotation(self):
for d in self.get('items'):
diff --git a/erpnext/setup/doctype/authorization_control/authorization_control.py b/erpnext/setup/doctype/authorization_control/authorization_control.py
index 5d937ac..f1c198c 100644
--- a/erpnext/setup/doctype/authorization_control/authorization_control.py
+++ b/erpnext/setup/doctype/authorization_control/authorization_control.py
@@ -32,7 +32,7 @@
if(d[0]): appr_users.append(d[0])
if(d[1]): appr_roles.append(d[1])
- if not has_common(appr_roles, frappe.user.get_roles()) and not has_common(appr_users, [session['user']]):
+ if not has_common(appr_roles, frappe.get_roles()) and not has_common(appr_users, [session['user']]):
frappe.msgprint(_("Not authroized since {0} exceeds limits").format(_(based_on)))
frappe.throw(_("Can be approved by {0}").format(comma_or(appr_roles + appr_users)))
@@ -77,7 +77,7 @@
auth_value = av_dis
if val == 1: add_cond += " and system_user = '"+session['user'].replace("'", "\\'")+"'"
- elif val == 2: add_cond += " and system_role IN %s" % ("('"+"','".join(frappe.user.get_roles())+"')")
+ elif val == 2: add_cond += " and system_role IN %s" % ("('"+"','".join(frappe.get_roles())+"')")
else: add_cond += " and ifnull(system_user,'') = '' and ifnull(system_role,'') = ''"
if based_on == 'Grand Total': auth_value = total
@@ -130,7 +130,7 @@
where transaction = %s and system_role IN (%s) and based_on IN (%s)
and (company = %s or ifnull(company,'')='')
and docstatus != 2
- """ % ('%s', "'"+"','".join(frappe.user.get_roles())+"'", "'"+"','".join(final_based_on)+"'", '%s'), (doctype_name, company))]
+ """ % ('%s', "'"+"','".join(frappe.get_roles())+"'", "'"+"','".join(final_based_on)+"'", '%s'), (doctype_name, company))]
for d in based_on:
self.bifurcate_based_on_type(doctype_name, total, av_dis, d, doc_obj, 2, company)
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 115fdfa..817fdb6 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -5,37 +5,35 @@
frappe.ui.form.on("Company", {
onload_post_render: function(frm) {
- frm.get_field("delete_company").$input.addClass("btn-danger");
+ frm.get_field("delete_company_transactions").$input.addClass("btn-danger");
},
country: function(frm) {
erpnext.company.set_chart_of_accounts_options(frm.doc);
},
- delete_company: function(frm) {
+ delete_company_transactions: function(frm) {
var d = frappe.prompt({
fieldtype:"Data",
fieldname: "company_name",
label: __("Please re-type company name to confirm"),
reqd: 1,
- description: __("Please make sure you really want to delete this company and all its transactions. Your master data will remain as it is. This action cannot be undone.")},
+ description: __("Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.")},
function(data) {
if(data.company_name !== frm.doc.name) {
frappe.msgprint("Company name not same");
return;
}
frappe.call({
- method: "erpnext.setup.doctype.company.delete_company.delete_company",
+ method: "erpnext.setup.doctype.company.delete_company_transactions.delete_company_transactions",
args: {
company_name: data.company_name
},
freeze: true,
- callback: function(r) {
- if(!r.exc) {
- frappe.model.clear_doc("Company", data.company_name);
- window.history.back();
- }
+ callback: function(r, rt) {
+ if(!r.exc)
+ frappe.msgprint(__("Successfully deleted all transactions related to this company!"));
}
});
- }, __("Delete Comany and all Related Transactions"), __("Delete"));
+ }, __("Delete all the Transactions for this Company"), __("Delete"));
d.get_primary_btn().addClass("btn-danger");
}
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index f7faf96..0e7b17a 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -397,16 +397,16 @@
"read_only": 0
},
{
- "fieldname": "delete_company",
+ "fieldname": "delete_company_transactions",
"fieldtype": "Button",
- "label": "Delete Company",
+ "label": "Delete Company Transactions",
"permlevel": 0,
"precision": ""
}
],
"icon": "icon-building",
"idx": 1,
- "modified": "2015-04-17 01:37:32.304374",
+ "modified": "2015-05-04 11:22:42.116328",
"modified_by": "Administrator",
"module": "Setup",
"name": "Company",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 26a2797..ad89114 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -167,6 +167,33 @@
where defkey='Company' and defvalue=%s""", (newdn, olddn))
frappe.defaults.clear_cache()
+
+ def on_trash(self):
+ """
+ Trash accounts and cost centers for this company if no gl entry exists
+ """
+ rec = frappe.db.sql("SELECT name from `tabGL Entry` where company = %s", self.name)
+ if not rec:
+ # delete Account
+ frappe.db.sql("delete from `tabAccount` where company = %s", self.name)
+
+ # delete cost center child table - budget detail
+ frappe.db.sql("""delete bd.* from `tabBudget Detail` bd, `tabCost Center` cc
+ where bd.parent = cc.name and cc.company = %s""", self.name)
+ #delete cost center
+ frappe.db.sql("delete from `tabCost Center` WHERE company = %s", self.name)
+
+ # delete account from customer and supplier
+ frappe.db.sql("delete from `tabParty Account` where company=%s", self.name)
+
+ if not frappe.db.get_value("Stock Ledger Entry", {"company": self.name}):
+ frappe.db.sql("""delete from `tabWarehouse` where company=%s""", self.name)
+
+ frappe.defaults.clear_default("company", value=self.name)
+
+ frappe.db.sql("""update `tabSingles` set value=""
+ where doctype='Global Defaults' and field='default_company'
+ and value=%s""", self.name)
@frappe.whitelist()
def replace_abbr(company, old, new):
diff --git a/erpnext/setup/doctype/company/delete_company.py b/erpnext/setup/doctype/company/delete_company_transactions.py
similarity index 82%
rename from erpnext/setup/doctype/company/delete_company.py
rename to erpnext/setup/doctype/company/delete_company_transactions.py
index 92b6c52..f27ba86 100644
--- a/erpnext/setup/doctype/company/delete_company.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -8,26 +8,19 @@
from frappe import _
@frappe.whitelist()
-def delete_company(company_name):
+def delete_company_transactions(company_name):
frappe.only_for("System Manager")
doc = frappe.get_doc("Company", company_name)
if frappe.session.user != doc.owner:
- frappe.throw(_("Company can only be deleted by the creator"), frappe.PermissionError)
+ frappe.throw(_("Transactions can only be deleted by the creator of the Company"), frappe.PermissionError)
delete_bins(company_name)
for doctype in frappe.db.sql_list("""select parent from
tabDocField where fieldtype='Link' and options='Company'"""):
- delete_for_doctype(doctype, company_name)
-
- frappe.delete_doc("Company", company_name)
-
- frappe.defaults.clear_default("company", value=doc.name)
-
- frappe.db.sql("""update `tabSingles` set value=""
- where doctype='Global Defaults' and field='default_company'
- and value=%s""", doc.name)
+ if doctype not in ("Account", "Cost Center", "Warehouse", "Budget Detail", "Party Account"):
+ delete_for_doctype(doctype, company_name)
def delete_for_doctype(doctype, company_name):
meta = frappe.get_meta(doctype)
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index b34983d..282d182 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -428,6 +428,7 @@
"reqd": 1
},
{
+ "depends_on": "eval:doc.is_purchase_item==\"Yes\"",
"fieldname": "default_supplier",
"fieldtype": "Link",
"ignore_user_permissions": 1,
@@ -448,7 +449,7 @@
"read_only": 0
},
{
- "depends_on": "eval:doc.is_purchase_item==\"Yes\"",
+ "depends_on": "",
"description": "Default Purchase Account in which cost of the item will be debited.",
"fieldname": "expense_account",
"fieldtype": "Link",
@@ -461,7 +462,7 @@
"read_only": 0
},
{
- "depends_on": "eval:doc.is_purchase_item==\"Yes\"",
+ "depends_on": "",
"description": "",
"fieldname": "buying_cost_center",
"fieldtype": "Link",
@@ -877,7 +878,7 @@
"icon": "icon-tag",
"idx": 1,
"max_attachments": 1,
- "modified": "2015-03-03 06:18:35.717586",
+ "modified": "2015-05-04 18:44:46.090445",
"modified_by": "Administrator",
"module": "Stock",
"name": "Item",
diff --git a/erpnext/stock/doctype/item/item_list.js b/erpnext/stock/doctype/item/item_list.js
index 168252e..c6beeff 100644
--- a/erpnext/stock/doctype/item/item_list.js
+++ b/erpnext/stock/doctype/item/item_list.js
@@ -3,7 +3,7 @@
"has_variants", "end_of_life", "is_sales_item"],
get_indicator: function(doc) {
- if(doc.end_of_life < frappe.datetime.get_today()) {
+ if(doc.end_of_life && doc.end_of_life < frappe.datetime.get_today()) {
return [__("Expired"), "grey", "end_of_life,<,Today"]
} else if(doc.has_variants) {
return [__("Template"), "blue", "has_variant,=,1"]
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 1b3dc06..97e7c20 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -82,14 +82,14 @@
stock_frozen_upto = frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto') or ''
if stock_frozen_upto:
stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
- if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.user.get_roles():
+ if getdate(self.posting_date) <= getdate(stock_frozen_upto) and not stock_auth_role in frappe.get_roles():
frappe.throw(_("Stock transactions before {0} are frozen").format(formatdate(stock_frozen_upto)), StockFreezeError)
stock_frozen_upto_days = int(frappe.db.get_value('Stock Settings', None, 'stock_frozen_upto_days') or 0)
if stock_frozen_upto_days:
stock_auth_role = frappe.db.get_value('Stock Settings', None,'stock_auth_role')
older_than_x_days_ago = (add_days(getdate(self.posting_date), stock_frozen_upto_days) <= date.today())
- if older_than_x_days_ago and not stock_auth_role in frappe.user.get_roles():
+ if older_than_x_days_ago and not stock_auth_role in frappe.get_roles():
frappe.throw(_("Not allowed to update stock transactions older than {0}").format(stock_frozen_upto_days), StockFreezeError)
def scrub_posting_time(self):
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index dc6809f..0ab03cb 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -10,14 +10,12 @@
from frappe.utils import now
from frappe.utils.user import is_website_user
+sender_field = "raised_by"
+
class Issue(Document):
def get_feed(self):
return "{0}: {1}".format(_(self.status), self.subject)
- def set_sender(self, sender):
- """Will be called by **Communication** when the Issue is created from an incoming email."""
- self.raised_by = sender
-
def validate(self):
self.update_status()
self.set_lead_contact(self.raised_by)
@@ -59,7 +57,7 @@
from frappe.templates.pages.list import get_list
user = frappe.session.user
ignore_permissions = False
- if is_website_user(user):
+ if is_website_user():
if not filters: filters = []
filters.append(("Issue", "raised_by", "=", user))
ignore_permissions = True