diff --git a/erpnext/__init__.py b/erpnext/__init__.py index 3f6504d7956..c06ccca3aa5 100644 --- a/erpnext/__init__.py +++ b/erpnext/__init__.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -__version__ = '8.0.13' +__version__ = '8.0.14' def get_default_company(user=None): '''Get default company for user''' diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json index 7a4d40d6ee4..4f268a24ccf 100644 --- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json +++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "beta": 0, @@ -25,7 +26,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Make Accounting Entry For Every Stock Movement", "length": 0, "no_copy": 0, @@ -33,6 +36,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -51,7 +55,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Accounts Frozen Upto", "length": 0, "no_copy": 0, @@ -59,6 +65,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -77,7 +84,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries", "length": 0, "no_copy": 0, @@ -86,6 +95,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -103,7 +113,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "length": 0, "no_copy": 0, "permlevel": 0, @@ -111,6 +123,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -129,7 +142,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 1, + "in_standard_filter": 0, "label": "Credit Controller", "length": 0, "no_copy": 0, @@ -138,6 +153,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -155,7 +171,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Check Supplier Invoice Number Uniqueness", "length": 0, "no_copy": 0, @@ -164,6 +182,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -181,7 +200,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Make Payment via Journal Entry", "length": 0, "no_copy": 0, @@ -190,6 +211,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -208,7 +230,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, + "in_standard_filter": 0, "label": "Unlink Payment on Cancellation of Invoice", "length": 0, "no_copy": 0, @@ -217,6 +241,37 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "book_asset_depreciation_entry_automatically", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Book Asset Depreciation Entry Automatically", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -224,18 +279,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, - "icon": "fa fa-cog", + "icon": "icon-cog", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-10-20 16:12:38.595075", + "modified": "2017-04-18 13:35:59.166250", "modified_by": "Administrator", "module": "Accounts", "name": "Accounts Settings", @@ -251,7 +306,6 @@ "export": 0, "if_owner": 0, "import": 0, - "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, @@ -266,6 +320,8 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_order": "ASC", + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/accounts/doctype/asset/depreciation.py b/erpnext/accounts/doctype/asset/depreciation.py index 15c155c7ac5..397342dc46f 100644 --- a/erpnext/accounts/doctype/asset/depreciation.py +++ b/erpnext/accounts/doctype/asset/depreciation.py @@ -8,6 +8,10 @@ from frappe import _ from frappe.utils import flt, today, getdate def post_depreciation_entries(date=None): + # Return if automatic booking of asset depreciation is disabled + if not frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically"): + return + if not date: date = today() for asset in get_depreciable_assets(date): diff --git a/erpnext/accounts/doctype/asset/test_asset.py b/erpnext/accounts/doctype/asset/test_asset.py index 51496b918c9..000bc5ccd25 100644 --- a/erpnext/accounts/doctype/asset/test_asset.py +++ b/erpnext/accounts/doctype/asset/test_asset.py @@ -166,6 +166,23 @@ class TestAsset(unittest.TestCase): self.assertEqual(gle, expected_gle) self.assertEqual(asset.get("value_after_depreciation"), 70000) + + def test_depreciation_entry_cancellation(self): + asset = frappe.get_doc("Asset", "Macbook Pro 1") + asset.submit() + post_depreciation_entries(date="2021-01-01") + + asset.load_from_db() + + # cancel depreciation entry + depr_entry = asset.get("schedules")[0].journal_entry + self.assertTrue(depr_entry) + frappe.get_doc("Journal Entry", depr_entry).cancel() + + asset.load_from_db() + depr_entry = asset.get("schedules")[0].journal_entry + self.assertFalse(depr_entry) + def test_scrap_asset(self): asset = frappe.get_doc("Asset", "Macbook Pro 1") @@ -297,4 +314,7 @@ def set_depreciation_settings_in_company(): company.depreciation_expense_account = "_Test Depreciations - _TC" company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC" company.depreciation_cost_center = "_Test Cost Center - _TC" - company.save() \ No newline at end of file + company.save() + + # Enable booking asset depreciation entry automatically + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index a471c48c575..b9b8fc29685 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -72,6 +72,7 @@ class JournalEntry(AccountsController): self.update_expense_claim() self.update_employee_loan() self.unlink_advance_entry_reference() + self.unlink_asset_reference() def unlink_advance_entry_reference(self): for d in self.get("accounts"): @@ -81,6 +82,18 @@ class JournalEntry(AccountsController): d.reference_type = '' d.reference_name = '' d.db_update() + + def unlink_asset_reference(self): + for d in self.get("accounts"): + if d.reference_type=="Asset" and d.reference_name: + asset = frappe.get_doc("Asset", d.reference_name) + for s in asset.get("schedules"): + if s.journal_entry == self.name: + s.db_set("journal_entry", None) + asset.value_after_depreciation += s.depreciation_amount + + asset.db_set("value_after_depreciation", asset.value_after_depreciation) + asset.set_status() def validate_party(self): for d in self.get("accounts"): diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index 93685dbbaf9..5af0d9f1be6 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -23,6 +23,8 @@ class PricingRule(Document): self.validate_price_or_discount() self.validate_max_discount() + if not self.margin_type: self.margin_rate_or_amount = 0.0 + def validate_mandatory(self): for field in ["apply_on", "applicable_for"]: tocheck = frappe.scrub(self.get(field) or "") @@ -143,7 +145,7 @@ def get_pricing_rule_for_item(args): }) if args.ignore_pricing_rule or not args.item_code: - if frappe.db.exists(args.doctype, args.name) and args.get("pricing_rule"): + if args.get("pricing_rule"): item_details = remove_pricing_rule(args, item_details) return item_details @@ -178,7 +180,7 @@ def get_pricing_rule_for_item(args): item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount if pricing_rule.price_or_discount == "Price": item_details.update({ - "price_list_rate": pricing_rule.price/flt(args.conversion_rate) \ + "price_list_rate": (pricing_rule.price/flt(args.conversion_rate)) * args.conversion_factor or 1.0 \ if args.conversion_rate else 0.0, "discount_percentage": 0.0 }) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index bf370c86845..36e5a1d8b27 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -1422,7 +1422,7 @@ "unique": 0 }, { - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, @@ -1508,7 +1508,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -1939,7 +1939,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-04-17 13:44:17.460674", + "modified": "2017-04-19 11:54:16.112134", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", @@ -1953,4 +1953,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} diff --git a/erpnext/accounts/doctype/sales_invoice/pos.py b/erpnext/accounts/doctype/sales_invoice/pos.py index 419c5579e0b..f1f997a7f69 100644 --- a/erpnext/accounts/doctype/sales_invoice/pos.py +++ b/erpnext/accounts/doctype/sales_invoice/pos.py @@ -32,7 +32,7 @@ def get_pos_data(): 'doc': doc, 'default_customer': pos_profile.get('customer'), 'items': get_items_list(pos_profile), - 'item_groups': get_item_group(pos_profile), + 'item_groups': get_item_groups(pos_profile), 'customers': customers, 'address': get_customers_address(customers), 'serial_no_data': get_serial_no_data(pos_profile, doc.company), @@ -132,7 +132,7 @@ def get_items_list(pos_profile): if pos_profile.get('item_groups'): # Get items based on the item groups defined in the POS profile for d in pos_profile.get('item_groups'): - item_groups.extend(get_child_nodes('Item Group', d.item_group)) + item_groups.extend([d.name for d in get_child_nodes('Item Group', d.item_group)]) cond = "item_group in (%s)"%(', '.join(['%s']*len(item_groups))) return frappe.db.sql(""" @@ -146,14 +146,19 @@ def get_items_list(pos_profile): disabled = 0 and has_variants = 0 and is_sales_item = 1 and {cond} """.format(cond=cond), tuple(item_groups), as_dict=1) -def get_item_group(pos_profile): +def get_item_groups(pos_profile): + item_group_dict = {} if pos_profile.get('item_groups'): item_groups = [] for d in pos_profile.get('item_groups'): item_groups.extend(get_child_nodes('Item Group', d.item_group)) - return item_groups else: - return frappe.db.sql_list("""Select name from `tabItem Group` order by name""") + item_groups = frappe.db.sql("""Select name, + lft, rgt from `tabItem Group` order by lft""", as_dict=1) + + for data in item_groups: + item_group_dict[data.name] = [data.lft, data.rgt] + return item_group_dict def get_customers_list(pos_profile): cond = "1=1" @@ -161,7 +166,7 @@ def get_customers_list(pos_profile): if pos_profile.get('customer_groups'): # Get customers based on the customer groups defined in the POS profile for d in pos_profile.get('customer_groups'): - customer_groups.extend(get_child_nodes('Customer Group', d.customer_group)) + customer_groups.extend([d.name for d in get_child_nodes('Customer Group', d.customer_group)]) cond = "customer_group in (%s)"%(', '.join(['%s']*len(customer_groups))) return frappe.db.sql(""" select name, customer_name, customer_group, @@ -187,8 +192,8 @@ def get_customers_address(customers): def get_child_nodes(group_type, root): lft, rgt = frappe.db.get_value(group_type, root, ["lft", "rgt"]) - return frappe.db.sql_list(""" Select name from `tab{tab}` where - lft >= {lft} and rgt <= {rgt}""".format(tab=group_type, lft=lft, rgt=rgt)) + return frappe.db.sql(""" Select name, lft, rgt from `tab{tab}` where + lft >= {lft} and rgt <= {rgt} order by lft""".format(tab=group_type, lft=lft, rgt=rgt), as_dict=1) def get_serial_no_data(pos_profile, company): # get itemwise serial no data diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json index 732c4465a80..0b7b76d1b3b 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_records.json +++ b/erpnext/accounts/doctype/sales_invoice/test_records.json @@ -147,9 +147,9 @@ "price_list_rate": 50, "qty": 10, "rate": 50, - "uom": "_Test UOM", + "uom": "_Test UOM 1", "conversion_factor": 1, - "stock_uom": "_Test UOM" + "stock_uom": "_Test UOM 1" }, { "cost_center": "_Test Cost Center - _TC", @@ -273,9 +273,9 @@ "parentfield": "items", "price_list_rate": 62.5, "qty": 10, - "uom": "_Test UOM", - "conversion_factor": 1, - "stock_uom": "_Test UOM" + "uom": "_Test UOM 1", + "conversion_factor": 1, + "stock_uom": "_Test UOM 1" }, { diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py index 305b689e15b..0ada8478a10 100644 --- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py @@ -1043,6 +1043,25 @@ class TestSalesInvoice(unittest.TestCase): #check outstanding after advance cancellation self.assertEqual(flt(si.outstanding_amount), flt(si.grand_total + si.total_advance, si.precision("outstanding_amount"))) + def test_multiple_uom_in_selling(self): + si = frappe.copy_doc(test_records[1]) + + si.items[0].uom = "_Test UOM 1" + si.items[0].conversion_factor = None + si.items[0].price_list_rate = None + si.save() + + expected_values = { + "keys": ["price_list_rate", "stock_uom", "uom", "conversion_factor", "rate", "amount", + "base_price_list_rate", "base_rate", "base_amount"], + "_Test Item": [1000, "_Test UOM", "_Test UOM 1", 10.0, 1000, 1000, 1000, 1000, 1000] + } + + # check if the conversion_factor and price_list_rate is calculated according to uom + for d in si.get("items"): + for i, k in enumerate(expected_values["keys"]): + self.assertEquals(d.get(k), expected_values[d.item_code][i]) + def create_sales_invoice(**args): si = frappe.new_doc("Sales Invoice") args = frappe._dict(args) diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json index 205b61e8821..80f8b57fb99 100644 --- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json +++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json @@ -1548,7 +1548,7 @@ "unique": 0 }, { - "allow_on_submit": 1, + "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, @@ -2094,7 +2094,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-04-05 23:28:13.520429", + "modified": "2017-04-19 11:53:26.682964", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice Item", diff --git a/erpnext/accounts/page/pos/pos.js b/erpnext/accounts/page/pos/pos.js index 670a577c4e7..70067614b6b 100644 --- a/erpnext/accounts/page/pos/pos.js +++ b/erpnext/accounts/page/pos/pos.js @@ -407,8 +407,8 @@ erpnext.pos.PointOfSale = erpnext.taxes_and_totals.extend({ }); this.search_item_group = this.wrapper.find('.search-item-group'); - - var dropdown_html = me.item_groups.map(function(item_group) { + sorted_item_groups = this.get_sorted_item_groups() + var dropdown_html = sorted_item_groups.map(function(item_group) { return "
\n\t{{ company }}
\n\t{{ __(\"POS No : \") }}{{offline_pos_name}}
\n
\n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
\n
| {{ __(\"Item\") }} | \n\t\t\t{{ __(\"Qty\") }} | \n\t\t\t{{ __(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_name }}\n\t\t\t | \n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }} @ {{ format_currency(item.rate, currency) }} | \n\t\t\t{{ format_currency(item.amount, currency) }} | \n\t\t
| \n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ row.description }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t | \n\t\t
{{ terms }}
\n{{ __(\"Thank you, please visit again.\") }}
", + "html": "\n\n\n\t{{ company }}
\n\t{{ __(\"POS No : \") }} {{ offline_pos_name }}
\n
\n\t{{ __(\"Date\") }}: {{ dateutil.global_date_format(posting_date) }}
\n
| {{ __(\"Item\") }} | \n\t\t\t{{ __(\"Qty\") }} | \n\t\t\t{{ __(\"Amount\") }} | \n\t\t
|---|---|---|
| \n\t\t\t\t{{ item.item_name }}\n\t\t\t | \n\t\t\t{{ format_number(item.qty, null,precision(\"difference\")) }} @ {{ format_currency(item.rate, currency) }} | \n\t\t\t{{ format_currency(item.amount, currency) }} | \n\t\t
| \n\t\t\t\t{{ __(\"Net Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(total, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ row.description }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(row.tax_amount, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Discount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(discount_amount, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Grand Total\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(grand_total, currency) }}\n\t\t\t | \n\t\t
| \n\t\t\t\t{{ __(\"Paid Amount\") }}\n\t\t\t | \n\t\t\t\n\t\t\t\t{{ format_currency(paid_amount, currency) }}\n\t\t\t | \n\t\t
{{ terms }}
\n{{ __(\"Thank you, please visit again.\") }}
", "idx": 0, "line_breaks": 0, - "modified": "2017-04-17 12:12:00.153763", + "modified": "2017-04-19 13:28:05.129504", "modified_by": "Administrator", "module": "Accounts", "name": "Point of Sale", diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json index e776b3ea981..ecce1184d6d 100644 --- a/erpnext/buying/doctype/purchase_order/purchase_order.json +++ b/erpnext/buying/doctype/purchase_order/purchase_order.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "autoname": "naming_series:", @@ -341,7 +342,7 @@ "remember_last_selected_value": 1, "report_hide": 0, "reqd": 1, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -1207,6 +1208,8 @@ "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, "label": "Link to material requests", @@ -3185,18 +3188,18 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-file-text", "idx": 105, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-28 18:20:15.650815", + "modified": "2017-04-18 18:49:49.535066", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order", @@ -3294,4 +3297,4 @@ "title_field": "title", "track_changes": 0, "track_seen": 0 -} +} \ No newline at end of file diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json index 12d0da85d77..0037745817b 100755 --- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json +++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "autoname": "hash", @@ -98,7 +99,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -156,7 +157,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -799,6 +800,7 @@ "oldfieldtype": "Currency", "options": "Company:company:default_currency", "permlevel": 0, + "precision": "9", "print_hide": 1, "print_hide_if_no_value": 0, "print_width": "100px", @@ -1647,17 +1649,17 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-02-17 16:44:55.434162", + "modified": "2017-04-18 18:49:08.604055", "modified_by": "Administrator", "module": "Buying", "name": "Purchase Order Item", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 910c19c9ca0..742e59b4d69 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -186,15 +186,19 @@ class AccountsController(TransactionBase): ret = get_item_details(args) - for fieldname, value in ret.items(): if item.meta.get_field(fieldname) and value is not None: if (item.get(fieldname) is None or fieldname in force_item_fields): item.set(fieldname, value) - elif fieldname == "cost_center" and not item.get("cost_center"): + elif fieldname in ['cost_center', 'conversion_factor'] and not item.get(fieldname): item.set(fieldname, value) + elif fieldname == "serial_no": + stock_qty = item.get("stock_qty") * -1 if item.get("stock_qty") < 0 else item.get("stock_qty") + if stock_qty != len(item.get('serial_no').split('\n')): + item.set(fieldname, value) + elif fieldname == "conversion_factor" and not item.get("conversion_factor"): item.set(fieldname, value) diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py index af51f70147f..1cd705b8c47 100644 --- a/erpnext/controllers/selling_controller.py +++ b/erpnext/controllers/selling_controller.py @@ -41,7 +41,7 @@ class SellingController(StockController): # 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() + self.set_price_list_and_item_details(for_validate=for_validate) def set_missing_lead_customer_details(self): if getattr(self, "customer", None): @@ -60,9 +60,9 @@ class SellingController(StockController): posting_date=self.get('transaction_date') or self.get('posting_date'), company=self.company)) - def set_price_list_and_item_details(self): + def set_price_list_and_item_details(self, for_validate=False): self.set_price_list_currency("Selling") - self.set_missing_item_details() + self.set_missing_item_details(for_validate=for_validate) def apply_shipping_rule(self): if self.shipping_rule: diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 0355c269d0b..362d07515bc 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -60,6 +60,7 @@ class calculate_taxes_and_totals(object): if item.doctype in ['Quotation Item', 'Sales Order Item', 'Delivery Note Item', 'Sales Invoice Item']: item.total_margin = self.calculate_margin(item) + item.rate = flt(item.total_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))\ if item.total_margin > 0 else item.rate diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py index bf3199ea5f9..b2db39c9f07 100644 --- a/erpnext/demo/user/fixed_asset.py +++ b/erpnext/demo/user/fixed_asset.py @@ -18,6 +18,9 @@ def work(): # fixed_asset.work() already run return + # Enable booking asset depreciation entry automatically + frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1) + # post depreciation entries as on today post_depreciation_entries() diff --git a/erpnext/docs/user/manual/en/stock/item/purchase-details.md b/erpnext/docs/user/manual/en/stock/item/purchase-details.md index 5749178448b..47983dfc625 100644 --- a/erpnext/docs/user/manual/en/stock/item/purchase-details.md +++ b/erpnext/docs/user/manual/en/stock/item/purchase-details.md @@ -1,4 +1,4 @@ -# purchase details +# Purchase Details # How Do I Track Warranty Status? diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 72fa47dd815..458b2dd5c19 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -35,9 +35,11 @@ class Attendance(Document): frappe.throw(_("No leave record found for employee {0} for {1}").format(self.employee, self.attendance_date)) def validate_attendance_date(self): + date_of_joining = frappe.db.get_value("Employee", self.employee, "date_of_joining") + if getdate(self.attendance_date) > getdate(nowdate()): frappe.throw(_("Attendance can not be marked for future dates")) - elif getdate(self.attendance_date) < frappe.db.get_value("Employee", self.employee, "date_of_joining"): + elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining): frappe.throw(_("Attendance date can not be less than employee's joining date")) def validate_employee(self): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 9c2b1c49ad1..111cd65dc22 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -385,4 +385,7 @@ erpnext.patches.v8_0.addresses_linked_to_lead execute:frappe.delete_doc('DocType', 'Purchase Common') erpnext.patches.v8_0.update_stock_qty_value_in_purchase_invoice erpnext.patches.v8_0.update_supplier_address_in_stock_entry -erpnext.patches.v8_0.rename_is_sample_item_to_allow_zero_valuation_rate \ No newline at end of file +erpnext.patches.v8_0.rename_is_sample_item_to_allow_zero_valuation_rate +erpnext.patches.v8_0.set_null_to_serial_nos_for_disabled_sales_invoices +erpnext.patches.v8_0.enable_booking_asset_depreciation_automatically +erpnext.patches.v8_0.set_project_copied_from diff --git a/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py b/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py new file mode 100644 index 00000000000..1088d702dd0 --- /dev/null +++ b/erpnext/patches/v8_0/enable_booking_asset_depreciation_automatically.py @@ -0,0 +1,9 @@ +# 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 + +def execute(): + frappe.db.set_value("Accounts Settings", None, + "book_asset_depreciation_entry_automatically", 1) \ No newline at end of file diff --git a/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py b/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py index 744cfed9983..e517df5fdb9 100644 --- a/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py +++ b/erpnext/patches/v8_0/rename_is_sample_item_to_allow_zero_valuation_rate.py @@ -9,4 +9,5 @@ def execute(): for doctype in doc_list: frappe.reload_doctype(doctype) - rename_field(doctype, "is_sample_item", "allow_zero_valuation_rate") \ No newline at end of file + if "is_sample_item" in frappe.db.get_table_columns(doctype): + rename_field(doctype, "is_sample_item", "allow_zero_valuation_rate") \ No newline at end of file diff --git a/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py b/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py new file mode 100644 index 00000000000..197d6ded61a --- /dev/null +++ b/erpnext/patches/v8_0/set_null_to_serial_nos_for_disabled_sales_invoices.py @@ -0,0 +1,14 @@ +# 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 erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty + +def execute(): + frappe.db.sql(""" + update + `tabSales Invoice Item` + set serial_no = NULL + where + parent in (select name from `tabSales Invoice` where update_stock = 0 and docstatus = 1)""") \ No newline at end of file diff --git a/erpnext/patches/v8_0/set_project_copied_from.py b/erpnext/patches/v8_0/set_project_copied_from.py new file mode 100644 index 00000000000..d4287978cf8 --- /dev/null +++ b/erpnext/patches/v8_0/set_project_copied_from.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doctype("Project") + + frappe.db.sql(''' + UPDATE `tabProject` + SET copied_from=name + WHERE copied_from is NULL + ''') \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index e809328df97..32a3ffd3351 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:project_name", @@ -553,6 +554,35 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "copied_from", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Copied From", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_on_submit": 0, "bold": 0, @@ -1052,7 +1082,7 @@ "search_index": 0, "set_only_once": 0, "unique": 0 - }, + }, { "allow_on_submit": 0, "bold": 0, @@ -1174,19 +1204,19 @@ "unique": 0 } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "icon": "fa fa-puzzle-piece", "idx": 29, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 4, - "modified": "2017-02-17 17:24:04.146872", - "modified_by": "Administrator", + "modified": "2017-04-19 13:16:32.462005", + "modified_by": "faris@erpnext.com", "module": "Projects", "name": "Project", "owner": "Administrator", @@ -1261,4 +1291,4 @@ "timeline_field": "customer", "track_changes": 0, "track_seen": 1 -} +} \ No newline at end of file diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py index 37734f18760..40493e1ed61 100644 --- a/erpnext/projects/doctype/project/project.py +++ b/erpnext/projects/doctype/project/project.py @@ -205,6 +205,32 @@ class Project(Document): def on_update(self): self.load_tasks() self.sync_tasks() + self.update_dependencies_on_duplicated_project() + + def update_dependencies_on_duplicated_project(self): + if self.flags.dont_sync_tasks: return + if not self.copied_from: + self.copied_from = self.name + + if self.name != self.copied_from and self.get('__unsaved'): + # duplicated project + dependency_map = {} + for task in self.tasks: + name, depends_on_tasks = frappe.db.get_value( + 'Task', { "subject": task.title, "project": self.copied_from }, ['name', 'depends_on_tasks'] + ) + depends_on_tasks = [x for x in depends_on_tasks.split(',') if x] + dependency_map[task.title] = [ x['subject'] for x in frappe.get_list( + 'Task Depends On', {"parent": name}, ['subject'])] + + for key, value in dependency_map.iteritems(): + task_name = frappe.db.get_value('Task', {"subject": key, "project": self.name }) + task_doc = frappe.get_doc('Task', task_name) + + for dt in value: + dt_name = frappe.db.get_value('Task', {"subject": dt, "project": self.name }) + task_doc.append('depends_on', {"task": dt_name}) + task_doc.save() def get_timeline_data(doctype, name): '''Return timeline for attendance''' diff --git a/erpnext/projects/doctype/task/task.py b/erpnext/projects/doctype/task/task.py index 44c81a67c51..43240b20e72 100644 --- a/erpnext/projects/doctype/task/task.py +++ b/erpnext/projects/doctype/task/task.py @@ -53,9 +53,9 @@ class Task(Document): frappe.throw(_("Progress % for a task cannot be more than 100.")) def update_depends_on(self): - depends_on_tasks = "" + depends_on_tasks = self.depends_on_tasks or "" for d in self.depends_on: - if d.task: + if d.task and not d.task in depends_on_tasks: depends_on_tasks += d.task + "," self.depends_on_tasks = depends_on_tasks diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js index 6f55a4411a3..08dd224fb14 100644 --- a/erpnext/public/js/controllers/taxes_and_totals.js +++ b/erpnext/public/js/controllers/taxes_and_totals.js @@ -7,7 +7,7 @@ erpnext.taxes_and_totals = erpnext.payments.extend({ if(item.margin_type == "Percentage"){ item.total_margin = flt(item.price_list_rate) + flt(item.price_list_rate) * ( flt(item.margin_rate_or_amount) / 100); - }else{ + } else { item.total_margin = flt(item.price_list_rate) + flt(item.margin_rate_or_amount); } diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 7e0dccc6a1c..eec789a4ea8 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -260,6 +260,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ customer: me.frm.doc.customer, supplier: me.frm.doc.supplier, currency: me.frm.doc.currency, + update_stock: in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, conversion_rate: me.frm.doc.conversion_rate, price_list: me.frm.doc.selling_price_list || me.frm.doc.buying_price_list, @@ -274,14 +275,16 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ doctype: me.frm.doc.doctype, name: me.frm.doc.name, project: item.project || me.frm.doc.project, - qty: item.qty, - stock_qty: item.stock_qty + qty: item.qty || 1, + stock_qty: item.stock_qty, + conversion_factor: item.conversion_factor } }, callback: function(r) { if(!r.exc) { me.frm.script_manager.trigger("price_list_rate", cdt, cdn); + me.toggle_conversion_factor(item); } } }); @@ -566,11 +569,18 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ frappe.model.round_floats_in(item, ["qty", "conversion_factor"]); item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item)); refresh_field("stock_qty", item.name, item.parentfield); + this.toggle_conversion_factor(item); + this.apply_price_list(item, true); } }, + toggle_conversion_factor: function(item) { + // toggle read only property for conversion factor field if the uom and stock uom are same + this.frm.fields_dict.items.grid.toggle_enable("conversion_factor", + (item.uom != item.stock_uom)? true: false) + }, + qty: function(doc, cdt, cdn) { - this.apply_pricing_rule(frappe.get_doc(cdt, cdn), true); this.conversion_factor(doc, cdt, cdn); }, @@ -762,7 +772,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ "ignore_pricing_rule": me.frm.doc.ignore_pricing_rule, "doctype": me.frm.doc.doctype, "name": me.frm.doc.name, - "is_return": cint(me.frm.doc.is_return) + "is_return": cint(me.frm.doc.is_return), + "update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0, + "conversion_factor": me.frm.doc.conversion_factor }; }, @@ -781,7 +793,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ "parent": d.parent, "pricing_rule": d.pricing_rule, "warehouse": d.warehouse, - "serial_no": d.serial_no + "serial_no": d.serial_no, + "conversion_factor": d.conversion_factor || 1.0 }); // if doctype is Quotation Item / Sales Order Iten then add Margin Type and rate in item_list @@ -808,16 +821,13 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ for(var i=0, l=children.length; i