From c819ea38a0712f8b99a7c274794c51ed7b5f1966 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 25 Sep 2018 14:53:12 +0500 Subject: [PATCH 01/76] -Added Item Default child table for Brand -Item Default Precedence: Get defaults from Item Group, if no default, fetch from Brand -Remove auto set Item's Item Defaults from Item Group: Item Default should be manually entered to override both Item Group and Brand defaults --- erpnext/setup/doctype/brand/brand.json | 431 ++++++++++++++----------- erpnext/setup/doctype/brand/brand.py | 16 +- erpnext/stock/get_item_details.py | 32 +- 3 files changed, 282 insertions(+), 197 deletions(-) diff --git a/erpnext/setup/doctype/brand/brand.json b/erpnext/setup/doctype/brand/brand.json index 2914ef1bc81..5558358078c 100644 --- a/erpnext/setup/doctype/brand/brand.json +++ b/erpnext/setup/doctype/brand/brand.json @@ -1,204 +1,269 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 1, - "allow_rename": 1, - "autoname": "field:brand", - "beta": 0, - "creation": "2013-02-22 01:27:54", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "field:brand", + "beta": 0, + "creation": "2013-02-22 01:27:54", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Setup", + "editable_grid": 0, "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "brand", - "fieldtype": "Data", - "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": "Brand Name", - "length": 0, - "no_copy": 0, - "oldfieldname": "brand", - "oldfieldtype": "Data", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "brand", + "fieldtype": "Data", + "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": "Brand Name", + "length": 0, + "no_copy": 0, + "oldfieldname": "brand", + "oldfieldtype": "Data", + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, "unique": 1 - }, + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "description", - "fieldtype": "Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Description", - "length": 0, - "no_copy": 0, - "oldfieldname": "description", - "oldfieldtype": "Text", - "permlevel": 0, - "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, - "translatable": 0, - "unique": 0, + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Description", + "length": 0, + "no_copy": 0, + "oldfieldname": "description", + "oldfieldtype": "Text", + "permlevel": 0, + "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, + "translatable": 0, + "unique": 0, "width": "300px" + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "defaults", + "fieldtype": "Section Break", + "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": "Defaults", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "brand_defaults", + "fieldtype": "Table", + "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": "Brand Defaults", + "length": 0, + "no_copy": 0, + "options": "Item Default", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-certificate", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2018-08-29 06:35:44.740318", - "modified_by": "Administrator", - "module": "Setup", - "name": "Brand", - "owner": "Administrator", + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "icon": "fa fa-certificate", + "idx": 1, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-09-25 13:27:37.090157", + "modified_by": "Administrator", + "module": "Setup", + "name": "Brand", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 1, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Item Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, "write": 1 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Sales User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Sales User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 - }, + }, { - "amend": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Accounts User", - "set_user_permissions": 0, - "share": 0, - "submit": 0, + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 0, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 0, + "submit": 0, "write": 0 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_order": "ASC", - "track_changes": 0, - "track_seen": 0, + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 1, + "sort_order": "ASC", + "track_changes": 0, + "track_seen": 0, "track_views": 0 } \ No newline at end of file diff --git a/erpnext/setup/doctype/brand/brand.py b/erpnext/setup/doctype/brand/brand.py index d90aa5a9b64..12839d18aea 100644 --- a/erpnext/setup/doctype/brand/brand.py +++ b/erpnext/setup/doctype/brand/brand.py @@ -3,8 +3,22 @@ from __future__ import unicode_literals import frappe +import copy from frappe.model.document import Document class Brand(Document): - pass \ No newline at end of file + pass + +def get_brand_defaults(item, company): + item = frappe.get_cached_doc("Item", item) + if item.brand: + brand = frappe.get_cached_doc("Brand", item.brand) + + for d in brand.brand_defaults or []: + if d.company == company: + row = copy.deepcopy(d.as_dict()) + row.pop("name") + return row + + return frappe._dict() \ No newline at end of file diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 01ee9db1ca1..ae643f7818c 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -13,6 +13,7 @@ from erpnext.stock.doctype.batch.batch import get_batch_no from erpnext import get_company_currency from erpnext.stock.doctype.item.item import get_item_defaults, get_uom_conv_factor from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.setup.doctype.brand.brand import get_brand_defaults from six import string_types, iteritems @@ -218,9 +219,10 @@ def get_basic_details(args, item): item_defaults = get_item_defaults(item.name, args.company) item_group_defaults = get_item_group_defaults(item.name, args.company) + brand_defaults = get_brand_defaults(item.name, args.company) warehouse = user_default_warehouse or item_defaults.get("default_warehouse") or\ - item_group_defaults.get("default_warehouse") or args.warehouse + item_group_defaults.get("default_warehouse") or brand_defaults.get("default_warehouse") or args.warehouse if args.get('doctype') == "Material Request" and not args.get('material_request_type'): args['material_request_type'] = frappe.db.get_value('Material Request', @@ -242,9 +244,9 @@ def get_basic_details(args, item): "description": cstr(item.description).strip(), "image": cstr(item.image).strip(), "warehouse": warehouse, - "income_account": get_default_income_account(args, item_defaults, item_group_defaults), - "expense_account": get_default_expense_account(args, item_defaults, item_group_defaults), - "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults), + "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), + "expense_account": get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults), + "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), 'has_serial_no': item.has_serial_no, 'has_batch_no': item.has_batch_no, "batch_no": None, @@ -263,7 +265,7 @@ def get_basic_details(args, item): "net_rate": 0.0, "net_amount": 0.0, "discount_percentage": 0.0, - "supplier": get_default_supplier(args, item_defaults, item_group_defaults), + "supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults), "update_stock": args.get("update_stock") if args.get('doctype') in ['Sales Invoice', 'Purchase Invoice'] else 0, "delivered_by_supplier": item.delivered_by_supplier if args.get("doctype") in ["Sales Order", "Sales Invoice"] else 0, "is_fixed_asset": item.is_fixed_asset, @@ -325,14 +327,16 @@ def calculate_service_end_date(args, item=None): return deferred_detail -def get_default_income_account(args, item, item_group): +def get_default_income_account(args, item, item_group, brand): return (item.get("income_account") or item_group.get("income_account") + or brand.get("income_account") or args.income_account) -def get_default_expense_account(args, item, item_group): +def get_default_expense_account(args, item, item_group, brand): return (item.get("expense_account") or item_group.get("expense_account") + or brand.get("expense_account") or args.expense_account) def get_default_deferred_account(args, item, fieldname=None): @@ -343,22 +347,23 @@ def get_default_deferred_account(args, item, fieldname=None): else: return None -def get_default_cost_center(args, item, item_group): +def get_default_cost_center(args, item, item_group, brand): cost_center = None if args.get('project'): cost_center = frappe.db.get_value("Project", args.get("project"), "cost_center", cache=True) if not cost_center: if args.get('customer'): - cost_center = item.get('selling_cost_center') or item_group.get('selling_cost_center') + cost_center = item.get('selling_cost_center') or item_group.get('selling_cost_center') or brand.get('selling_cost_center') else: - cost_center = item.get('buying_cost_center') or item_group.get('buying_cost_center') + cost_center = item.get('buying_cost_center') or item_group.get('buying_cost_center') or brand.get('buying_cost_center') return cost_center or args.get("cost_center") -def get_default_supplier(args, item, item_group): +def get_default_supplier(args, item, item_group, brand): return (item.get("default_supplier") - or item_group.get("default_supplier")) + or item_group.get("default_supplier") + or brand.get("default_supplier")) def get_price_list_rate(args, item_doc, out): meta = frappe.get_meta(args.parenttype or args.doctype) @@ -825,10 +830,11 @@ def get_default_bom(item_code=None): def get_valuation_rate(item_code, company, warehouse=None): item = get_item_defaults(item_code, company) item_group = get_item_group_defaults(item_code, company) + brand = get_brand_defaults(item_code, company) # item = frappe.get_doc("Item", item_code) if item.get("is_stock_item"): if not warehouse: - warehouse = item.get("default_warehouse") or item_group.get("default_warehouse") + warehouse = item.get("default_warehouse") or item_group.get("default_warehouse") or brand.get("default_warehouse") return frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, ["valuation_rate"], as_dict=True) or {"valuation_rate": 0} From 127b3f552b62f40db1d88cc2a29758ddf972b51c Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 26 Sep 2018 23:23:56 +0500 Subject: [PATCH 02/76] At least add a row for old logic to work properly --- erpnext/stock/doctype/item/item.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 566b6386fec..e2922b33a04 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -128,6 +128,7 @@ class Item(WebsiteGenerator): self.validate_uom_conversion_factor() self.validate_item_defaults() self.update_defaults_from_item_group() + self.update_defaults_add_company() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -135,6 +136,10 @@ class Item(WebsiteGenerator): from `tabWebsite Item Group` where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name) + def update_defaults_add_company(self): + if not self.item_defaults: + self.append("item_defaults", {"company": frappe.defaults.get_defaults().company}) + def on_update(self): invalidate_cache_for_item(self) self.validate_name_with_item_group() From 7d9ee83d5ae3af28feae380098b267751a293a07 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Wed, 26 Sep 2018 23:41:12 +0500 Subject: [PATCH 03/76] -Fix for updated get_default_cost_center -Back to not updating item's defaults child table --- erpnext/stock/doctype/item/item.py | 11 ++--------- erpnext/stock/doctype/stock_entry/stock_entry.py | 4 +++- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index e2922b33a04..a1aa6b14bd0 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -86,7 +86,7 @@ class Item(WebsiteGenerator): def after_insert(self): '''set opening stock and item price''' if self.standard_rate: - for default in self.item_defaults: + for default in self.item_defaults or [frappe._dict()]: self.add_price(default.default_price_list) if self.opening_stock: @@ -126,9 +126,6 @@ class Item(WebsiteGenerator): self.validate_fixed_asset() self.validate_retain_sample() self.validate_uom_conversion_factor() - self.validate_item_defaults() - self.update_defaults_from_item_group() - self.update_defaults_add_company() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") @@ -136,10 +133,6 @@ class Item(WebsiteGenerator): from `tabWebsite Item Group` where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name) - def update_defaults_add_company(self): - if not self.item_defaults: - self.append("item_defaults", {"company": frappe.defaults.get_defaults().company}) - def on_update(self): invalidate_cache_for_item(self) self.validate_name_with_item_group() @@ -181,7 +174,7 @@ class Item(WebsiteGenerator): from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry # default warehouse, or Stores - for default in self.item_defaults: + for default in self.item_defaults or [frappe._dict({'company': frappe.defaults.get_defaults().company})]: default_warehouse = (default.default_warehouse or frappe.db.get_single_value('Stock Settings', 'default_warehouse') or frappe.db.get_value('Warehouse', {'warehouse_name': _('Stores')})) diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py index 5d3c6c4adcc..5c6555a7a16 100644 --- a/erpnext/stock/doctype/stock_entry/stock_entry.py +++ b/erpnext/stock/doctype/stock_entry/stock_entry.py @@ -10,6 +10,7 @@ from erpnext.stock.utils import get_incoming_rate from erpnext.stock.stock_ledger import get_previous_sle, NegativeStockError, get_valuation_rate from erpnext.stock.get_item_details import get_bin_details, get_default_cost_center, get_conversion_factor, get_reserved_qty_for_so from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults +from erpnext.setup.doctype.brand.brand import get_brand_defaults from erpnext.stock.doctype.batch.batch import get_batch_no, set_batch_nos, get_batch_qty from erpnext.stock.doctype.item.item import get_item_defaults from erpnext.manufacturing.doctype.bom.bom import validate_bom_no, add_additional_cost @@ -631,6 +632,7 @@ class StockEntry(StockController): item = item[0] item_group_defaults = get_item_group_defaults(item.name, self.company) + brand_defaults = get_brand_defaults(item.name, self.company) ret = frappe._dict({ 'uom' : item.stock_uom, @@ -639,7 +641,7 @@ class StockEntry(StockController): 'image' : item.image, 'item_name' : item.item_name, 'expense_account' : args.get("expense_account"), - 'cost_center' : get_default_cost_center(args, item, item_group_defaults), + 'cost_center' : get_default_cost_center(args, item, item_group_defaults, brand_defaults), 'qty' : args.get("qty"), 'transfer_qty' : args.get('qty'), 'conversion_factor' : 1, From 07acc2bf5a68456a7ccd17402d62bd7fb761038b Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Tue, 23 Oct 2018 23:18:16 +0500 Subject: [PATCH 04/76] Allow brand in quick entry --- erpnext/setup/doctype/brand/brand.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/doctype/brand/brand.json b/erpnext/setup/doctype/brand/brand.json index 5558358078c..03e025c7621 100644 --- a/erpnext/setup/doctype/brand/brand.json +++ b/erpnext/setup/doctype/brand/brand.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, @@ -14,7 +15,7 @@ "fields": [ { "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, + "allow_in_quick_entry": 1, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -156,7 +157,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-09-25 13:27:37.090157", + "modified": "2018-10-23 23:18:06.067612", "modified_by": "Administrator", "module": "Setup", "name": "Brand", From 1ea98cbbe1f1d93adac518c5c4c354615fd2f8b0 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Fri, 28 Dec 2018 00:29:43 +0500 Subject: [PATCH 05/76] fix: missing line due to conflict resolution --- erpnext/stock/doctype/item/item.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index a1aa6b14bd0..a9360011ff0 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -126,6 +126,7 @@ class Item(WebsiteGenerator): self.validate_fixed_asset() self.validate_retain_sample() self.validate_uom_conversion_factor() + self.validate_item_defaults() if not self.get("__islocal"): self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group") From 00aaa4877945a9258638405e308c1943bdffe692 Mon Sep 17 00:00:00 2001 From: Saif Ur Rehman Date: Sat, 2 Feb 2019 02:43:44 +0500 Subject: [PATCH 06/76] test(Item): test_item_defaults --- .../accounts/doctype/account/test_account.py | 2 + erpnext/setup/doctype/brand/test_records.json | 11 ++++ erpnext/stock/doctype/item/test_item.py | 55 +++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index acaa0966a20..1c3375050e4 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -129,6 +129,8 @@ def _make_test_records(verbose): ["_Test Write Off", "Indirect Expenses", 0, None, None], ["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None], + ["_Test Account Sales", "Direct Income", 0, None, None], + # related to Account Inventory Integration ["_Test Account Stock In Hand", "Current Assets", 0, None, None], diff --git a/erpnext/setup/doctype/brand/test_records.json b/erpnext/setup/doctype/brand/test_records.json index d2a4ad4e238..17b5a6b0a3b 100644 --- a/erpnext/setup/doctype/brand/test_records.json +++ b/erpnext/setup/doctype/brand/test_records.json @@ -2,5 +2,16 @@ { "brand": "_Test Brand", "doctype": "Brand" + }, + { + "brand": "_Test Brand With Item Defaults", + "doctype": "Brand", + "brand_defaults": [{ + "company": "_Test Company", + "expense_account": "_Test Account Cost for Goods Sold - _TC", + "income_account": "_Test Account Sales - _TC", + "buying_cost_center": "_Test Cost Center - _TC", + "selling_cost_center": "_Test Cost Center - _TC" + }] } ] \ No newline at end of file diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py index d02559ebcb1..6ec7ba1d1fc 100644 --- a/erpnext/stock/doctype/item/test_item.py +++ b/erpnext/stock/doctype/item/test_item.py @@ -102,6 +102,61 @@ class TestItem(unittest.TestCase): for key, value in iteritems(to_check): self.assertEqual(value, details.get(key)) + def test_item_defaults(self): + frappe.delete_doc_if_exists("Item", "Test Item With Defaults", force=1) + make_item("Test Item With Defaults", { + "item_group": "_Test Item Group", + "brand": "_Test Brand With Item Defaults", + "item_defaults": [{ + "company": "_Test Company", + "default_warehouse": "_Test Warehouse 2 - _TC", # no override + "expense_account": "_Test Account Stock Expenses - _TC", # override brand default + "buying_cost_center": "_Test Write Off Cost Center - _TC", # override item group default + }] + }) + + sales_item_check = { + "item_code": "Test Item With Defaults", + "warehouse": "_Test Warehouse 2 - _TC", # from item + "income_account": "_Test Account Sales - _TC", # from brand + "expense_account": "_Test Account Stock Expenses - _TC", # from item + "cost_center": "_Test Cost Center 2 - _TC", # from item group + } + sales_item_details = get_item_details({ + "item_code": "Test Item With Defaults", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Sales Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "customer": "_Test Customer", + }) + for key, value in iteritems(sales_item_check): + self.assertEqual(value, sales_item_details.get(key)) + + purchase_item_check = { + "item_code": "Test Item With Defaults", + "warehouse": "_Test Warehouse 2 - _TC", # from item + "expense_account": "_Test Account Stock Expenses - _TC", # from item + "income_account": "_Test Account Sales - _TC", # from brand + "cost_center": "_Test Write Off Cost Center - _TC" # from item + } + purchase_item_details = get_item_details({ + "item_code": "Test Item With Defaults", + "company": "_Test Company", + "price_list": "_Test Price List", + "currency": "_Test Currency", + "doctype": "Purchase Invoice", + "conversion_rate": 1, + "price_list_currency": "_Test Currency", + "plc_conversion_rate": 1, + "supplier": "_Test Supplier", + }) + for key, value in iteritems(purchase_item_check): + self.assertEqual(value, purchase_item_details.get(key)) + def test_item_attribute_change_after_variant(self): frappe.delete_doc_if_exists("Item", "_Test Variant Item-L", force=1) From d635f722fc7f7c38c4988fdd77eddf2b77e3ae1c Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Mon, 11 Feb 2019 15:16:25 +0530 Subject: [PATCH 07/76] fix(stock_balance): Make balance qty and value columns more visible --- erpnext/stock/report/stock_balance/stock_balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index 14b1852a7f7..d6b171f93c9 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -79,14 +79,14 @@ def get_columns(): {"label": _("Description"), "fieldname": "description", "width": 140}, {"label": _("Warehouse"), "fieldname": "warehouse", "fieldtype": "Link", "options": "Warehouse", "width": 100}, {"label": _("Stock UOM"), "fieldname": "stock_uom", "fieldtype": "Link", "options": "UOM", "width": 90}, + {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, + {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, {"label": _("Opening Qty"), "fieldname": "opening_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, {"label": _("Opening Value"), "fieldname": "opening_val", "fieldtype": "Float", "width": 110}, {"label": _("In Qty"), "fieldname": "in_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("In Value"), "fieldname": "in_val", "fieldtype": "Float", "width": 80}, {"label": _("Out Qty"), "fieldname": "out_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Out Value"), "fieldname": "out_val", "fieldtype": "Float", "width": 80}, - {"label": _("Balance Qty"), "fieldname": "bal_qty", "fieldtype": "Float", "width": 100, "convertible": "qty"}, - {"label": _("Balance Value"), "fieldname": "bal_val", "fieldtype": "Currency", "width": 100}, {"label": _("Valuation Rate"), "fieldname": "val_rate", "fieldtype": "Currency", "width": 90, "convertible": "rate"}, {"label": _("Reorder Level"), "fieldname": "reorder_level", "fieldtype": "Float", "width": 80, "convertible": "qty"}, {"label": _("Reorder Qty"), "fieldname": "reorder_qty", "fieldtype": "Float", "width": 80, "convertible": "qty"}, From 1ac17ec1c4e402d384200954a0dc077d8083c662 Mon Sep 17 00:00:00 2001 From: Jigar Tarpara Date: Tue, 19 Feb 2019 10:21:01 +0530 Subject: [PATCH 08/76] Add credit month in validation --- .../doctype/payment_terms_template/payment_terms_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py index 7042df05942..2b2b6afe79f 100644 --- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py +++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py @@ -32,7 +32,7 @@ class PaymentTermsTemplate(Document): def check_duplicate_terms(self): terms = [] for term in self.terms: - term_info = (term.credit_days, term.due_date_based_on) + term_info = (term.credit_days, term.credit_months, term.due_date_based_on) if term_info in terms: frappe.msgprint( _('The Payment Term at row {0} is possibly a duplicate.').format(term.idx), From b263876918bd05a75acf7b73d154161abecc9453 Mon Sep 17 00:00:00 2001 From: bcornwellmott Date: Tue, 5 Mar 2019 16:44:02 -0800 Subject: [PATCH 09/76] Add date to currency exchange caching The current currency exchange caching does not keep track of the transaction date, so if you are backdating transactions, the system probably isn't pulling the correct exchange rate (if it's looking up the currency exchange rate on the fly) --- erpnext/setup/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py index 01e0b7d441a..d1c206d8b1d 100644 --- a/erpnext/setup/utils.py +++ b/erpnext/setup/utils.py @@ -93,7 +93,7 @@ def get_exchange_rate(from_currency, to_currency, transaction_date=None, args=No try: cache = frappe.cache() - key = "currency_exchange_rate:{0}:{1}".format(from_currency, to_currency) + key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency) value = cache.get(key) if not value: @@ -143,4 +143,4 @@ def insert_record(records): def welcome_email(): site_name = get_default_company() title = _("Welcome to {0}".format(site_name)) - return title \ No newline at end of file + return title From 3d0121369f5321cd4f91e236a4ffd48ec02e7744 Mon Sep 17 00:00:00 2001 From: Joyce Babu Date: Wed, 6 Mar 2019 13:04:45 +0530 Subject: [PATCH 10/76] Add 'Half-Yearly' option to Earned Leave Frequency in addition to the current values of Monthly, Quarterly and Yearly --- erpnext/hr/doctype/leave_type/leave_type.json | 2 +- erpnext/hr/utils.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index 8f7b5a876c0..6a7a80a7845 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -561,7 +561,7 @@ "label": "Earned Leave Frequency", "length": 0, "no_copy": 0, - "options": "Monthly\nQuarterly\nYearly", + "options": "Monthly\nQuarterly\nHalf-Yearly\nYearly", "permlevel": 0, "precision": "", "print_hide": 0, diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 02262012f1c..e0b6a51d1ab 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -260,7 +260,7 @@ def allocate_earned_leaves(): fields=["name", "max_leaves_allowed", "earned_leave_frequency", "rounding"], filters={'is_earned_leave' : 1}) today = getdate() - divide_by_frequency = {"Yearly": 1, "Quarterly": 4, "Monthly": 12} + divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} if e_leave_types: for e_leave_type in e_leave_types: leave_allocations = frappe.db.sql("""select name, employee, from_date, to_date from `tabLeave Allocation` where '{0}' @@ -297,6 +297,9 @@ def check_frequency_hit(from_date, to_date, frequency): if frequency == "Quarterly": if not months % 3: return True + elif frequency == "Half-Yearly": + if not months % 6: + return True elif frequency == "Yearly": if not months % 12: return True From e66f59654e67649611f309d172087b05ac7ea023 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Wed, 6 Mar 2019 15:42:32 +0530 Subject: [PATCH 11/76] fix(report): Incorrectdata for balance quantity and value in Stock Balance report --- erpnext/stock/report/stock_balance/stock_balance.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py index d6b171f93c9..ae919b6019f 100644 --- a/erpnext/stock/report/stock_balance/stock_balance.py +++ b/erpnext/stock/report/stock_balance/stock_balance.py @@ -43,11 +43,11 @@ def execute(filters=None): item_map[item]["item_group"], item_map[item]["brand"], item_map[item]["description"], warehouse, - item_map[item]["stock_uom"], qty_dict.opening_qty, + item_map[item]["stock_uom"], qty_dict.bal_qty, + qty_dict.bal_val, qty_dict.opening_qty, qty_dict.opening_val, qty_dict.in_qty, qty_dict.in_val, qty_dict.out_qty, - qty_dict.out_val, qty_dict.bal_qty, - qty_dict.bal_val, qty_dict.val_rate, + qty_dict.out_val, qty_dict.val_rate, item_reorder_level, item_reorder_qty, company From 9757a603fa99c3d05151c7b94d2eddffb04a6ddb Mon Sep 17 00:00:00 2001 From: Jay Parikh Date: Wed, 6 Mar 2019 12:44:41 +0000 Subject: [PATCH 12/76] Fix Company Default to Total Stock Summary Report --- .../stock/report/total_stock_summary/total_stock_summary.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/erpnext/stock/report/total_stock_summary/total_stock_summary.js b/erpnext/stock/report/total_stock_summary/total_stock_summary.js index 223a603004c..b7461c485f6 100644 --- a/erpnext/stock/report/total_stock_summary/total_stock_summary.js +++ b/erpnext/stock/report/total_stock_summary/total_stock_summary.js @@ -18,7 +18,9 @@ frappe.query_reports["Total Stock Summary"] = { "label": __("Company"), "fieldtype": "Link", "width": "80", - "options": "Company" + "options": "Company", + "default": frappe.defaults.get_user_default("Company"), + "reqd": 1 }, ] } From 783331c64573cf56635bbe1cdb8e59c8f32db018 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 Mar 2019 18:44:04 +0530 Subject: [PATCH 13/76] sales_order to work_order item desc fix --- erpnext/selling/doctype/sales_order/sales_order.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index b589cdeaa43..2345762233f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -388,6 +388,7 @@ class SalesOrder(SellingController): items.append(dict( name= i.name, item_code= i.item_code, + description= i.description, bom = bom, warehouse = i.warehouse, pending_qty = pending_qty, @@ -398,6 +399,7 @@ class SalesOrder(SellingController): items.append(dict( name= i.name, item_code= i.item_code, + description= i.description, bom = '', warehouse = i.warehouse, pending_qty = pending_qty, @@ -901,7 +903,8 @@ def make_work_orders(items, sales_order, company, project=None): sales_order=sales_order, sales_order_item=i['sales_order_item'], project=project, - fg_warehouse=i['warehouse'] + fg_warehouse=i['warehouse'], + description=i['description'] )).insert() work_order.set_work_order_operations() work_order.save() From 4f1737c2dd248850f8d086736e170b44b66d1a2f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 Mar 2019 22:19:15 +0530 Subject: [PATCH 14/76] fix sales order test for creating work order from sales order --- erpnext/selling/doctype/sales_order/test_sales_order.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 0eb19e3417c..f270938ad3a 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -573,7 +573,8 @@ class TestSalesOrder(unittest.TestCase): "item_code": item.get("item_code"), "pending_qty": item.get("pending_qty"), "sales_order_item": item.get("sales_order_item"), - "bom": item.get("bom") + "bom": item.get("bom"), + "description": item.get("description") }) so_item_name[item.get("sales_order_item")]= item.get("pending_qty") make_work_orders(json.dumps({"items":po_items}), so.name, so.company) From 59699bf46e2288ef89208ed7a602016455754aef Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 Mar 2019 23:33:03 +0530 Subject: [PATCH 15/76] [debug] locally tests passed but travis failed --- erpnext/selling/doctype/sales_order/sales_order.py | 1 + erpnext/selling/doctype/sales_order/test_sales_order.py | 1 + 2 files changed, 2 insertions(+) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 2345762233f..a84fa1430bb 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -886,6 +886,7 @@ def get_supplier(doctype, txt, searchfield, start, page_len, filters): def make_work_orders(items, sales_order, company, project=None): '''Make Work Orders against the given Sales Order for the given `items`''' items = json.loads(items).get('items') + print(items) out = [] for i in items: diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index f270938ad3a..584b5b99c66 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -577,6 +577,7 @@ class TestSalesOrder(unittest.TestCase): "description": item.get("description") }) so_item_name[item.get("sales_order_item")]= item.get("pending_qty") + print(po_items) make_work_orders(json.dumps({"items":po_items}), so.name, so.company) # Check if Work Orders were raised From 7ab961f79881c4861dd2c7664aaceb165ebc3bc7 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 Mar 2019 23:53:38 +0530 Subject: [PATCH 16/76] remove debug code --- erpnext/selling/doctype/sales_order/sales_order.py | 1 - erpnext/selling/doctype/sales_order/test_sales_order.py | 1 - 2 files changed, 2 deletions(-) diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index a84fa1430bb..2345762233f 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -886,7 +886,6 @@ def get_supplier(doctype, txt, searchfield, start, page_len, filters): def make_work_orders(items, sales_order, company, project=None): '''Make Work Orders against the given Sales Order for the given `items`''' items = json.loads(items).get('items') - print(items) out = [] for i in items: diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py index 584b5b99c66..f270938ad3a 100644 --- a/erpnext/selling/doctype/sales_order/test_sales_order.py +++ b/erpnext/selling/doctype/sales_order/test_sales_order.py @@ -577,7 +577,6 @@ class TestSalesOrder(unittest.TestCase): "description": item.get("description") }) so_item_name[item.get("sales_order_item")]= item.get("pending_qty") - print(po_items) make_work_orders(json.dumps({"items":po_items}), so.name, so.company) # Check if Work Orders were raised From 78a32ae172063a9456c51185ccb348d97db1c58a Mon Sep 17 00:00:00 2001 From: deepeshgarg007 Date: Thu, 7 Mar 2019 20:00:07 +0530 Subject: [PATCH 17/76] fix: Remove validation for relieving date from additional salary --- erpnext/hr/doctype/additional_salary/additional_salary.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary/additional_salary.py b/erpnext/hr/doctype/additional_salary/additional_salary.py index e25e69e75f2..968a1c4571c 100644 --- a/erpnext/hr/doctype/additional_salary/additional_salary.py +++ b/erpnext/hr/doctype/additional_salary/additional_salary.py @@ -19,8 +19,6 @@ class AdditionalSalary(Document): ["date_of_joining", "relieving_date"]) if date_of_joining and getdate(self.payroll_date) < getdate(date_of_joining): frappe.throw(_("Payroll date can not be less than employee's joining date")) - elif relieving_date and getdate(self.payroll_date) > getdate(relieving_date): - frappe.throw(_("To date can not greater than employee's relieving date")) def get_amount(self, sal_start_date, sal_end_date): start_date = getdate(sal_start_date) From 821a002125cfed5b71559b7281c3f8cd7c634bae Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 7 Mar 2019 19:27:54 +0530 Subject: [PATCH 18/76] feat: added provision to disable CWIP accounting in asset settings --- .../purchase_invoice/purchase_invoice.py | 18 +++++-- erpnext/assets/doctype/asset/asset.py | 23 ++++++--- .../asset_settings/asset_settings.json | 50 +++++++++++++++++-- .../purchase_receipt/purchase_receipt.py | 5 +- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index c0d0d837fe7..bc44bc80f4e 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -8,6 +8,7 @@ from frappe.utils import cint, cstr, formatdate, flt, getdate, nowdate from frappe import _, throw import frappe.defaults +from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.controllers.buying_controller import BuyingController from erpnext.accounts.party import get_party_account, get_due_date from erpnext.accounts.utils import get_account_currency, get_fiscal_year @@ -17,7 +18,7 @@ from erpnext.accounts.general_ledger import make_gl_entries, merge_similar_entri from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt from erpnext.buying.utils import check_for_closed_status from erpnext.accounts.general_ledger import get_round_off_account_and_cost_center -from erpnext.assets.doctype.asset.asset import get_asset_account +from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled from frappe.model.mapper import get_mapped_doc from six import iteritems from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_invoice,\ @@ -238,6 +239,13 @@ class PurchaseInvoice(BuyingController): item.expense_account = warehouse_account[item.warehouse]["account"] else: item.expense_account = stock_not_billed_account + elif item.is_fixed_asset and is_cwip_accounting_disabled(): + if not item.asset: + frappe.throw(_("Row {0}: asset is required for item {1}") + .format(item.idx, item.item_code)) + + item.expense_account = get_asset_category_account(item.asset, 'fixed_asset_account', + company = self.company) elif item.is_fixed_asset and item.pr_detail: item.expense_account = asset_received_but_not_billed elif not item.expense_account and for_validate: @@ -383,7 +391,9 @@ class PurchaseInvoice(BuyingController): self.make_supplier_gl_entry(gl_entries) self.make_item_gl_entries(gl_entries) - self.get_asset_gl_entry(gl_entries) + if not is_cwip_accounting_disabled(): + self.get_asset_gl_entry(gl_entries) + self.make_tax_gl_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries) @@ -475,7 +485,7 @@ class PurchaseInvoice(BuyingController): "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"])) - elif not item.is_fixed_asset: + elif not item.is_fixed_asset or (item.is_fixed_asset and is_cwip_accounting_disabled()): gl_entries.append( self.get_gl_dict({ "account": item.expense_account if not item.enable_deferred_expense else item.deferred_expense_account, @@ -520,7 +530,7 @@ class PurchaseInvoice(BuyingController): base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) if (not item.expense_account or frappe.db.get_value('Account', - item.expense_account, 'account_type') != 'Asset Received But Not Billed'): + item.expense_account, 'account_type') not in ['Asset Received But Not Billed', 'Fixed Asset']): arbnb_account = self.get_company_default("asset_received_but_not_billed") item.expense_account = arbnb_account diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a38b40bc60b..6b145956f37 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -33,7 +33,7 @@ class Asset(AccountsController): self.validate_in_use_date() self.set_status() self.update_stock_movement() - if not self.booked_fixed_asset: + if not self.booked_fixed_asset and not is_cwip_accounting_disabled(): self.make_gl_entries() def on_cancel(self): @@ -71,14 +71,15 @@ class Asset(AccountsController): if not flt(self.gross_purchase_amount): frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError) - if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): - frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). - format(self.item_code)) + if not is_cwip_accounting_disabled(): + if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice): + frappe.throw(_("Please create purchase receipt or purchase invoice for the item {0}"). + format(self.item_code)) - if (not self.purchase_receipt and self.purchase_invoice - and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')): - frappe.throw(_("Update stock must be enable for the purchase invoice {0}"). - format(self.purchase_invoice)) + if (not self.purchase_receipt and self.purchase_invoice + and not frappe.db.get_value('Purchase Invoice', self.purchase_invoice, 'update_stock')): + frappe.throw(_("Update stock must be enable for the purchase invoice {0}"). + format(self.purchase_invoice)) if not self.calculate_depreciation: return @@ -404,6 +405,9 @@ def update_maintenance_status(): asset.set_status('Out of Order') def make_post_gl_entry(): + if is_cwip_accounting_disabled(): + return + assets = frappe.db.sql_list(""" select name from `tabAsset` where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) @@ -551,3 +555,6 @@ def make_journal_entry(asset_name): }) return je + +def is_cwip_accounting_disabled(): + return cint(frappe.db.get_single_value("Asset Settings", "disable_cwip_accounting")) \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_settings/asset_settings.json b/erpnext/assets/doctype/asset_settings/asset_settings.json index d6ddd33c302..a3fee96f4ee 100644 --- a/erpnext/assets/doctype/asset_settings/asset_settings.json +++ b/erpnext/assets/doctype/asset_settings/asset_settings.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, @@ -14,10 +15,12 @@ "fields": [ { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "depreciation_options", "fieldtype": "Section Break", "hidden": 0, @@ -40,14 +43,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "schedule_based_on_fiscal_year", "fieldtype": "Check", "hidden": 0, @@ -70,10 +76,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -81,6 +89,7 @@ "default": "360", "depends_on": "eval:doc.schedule_based_on_fiscal_year", "description": "This value is used for pro-rata temporis calculation", + "fetch_if_empty": 0, "fieldname": "number_of_days_in_fiscal_year", "fieldtype": "Data", "hidden": 0, @@ -103,6 +112,40 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "disable_cwip_accounting", + "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": "Disable CWIP Accounting", + "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, + "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -116,7 +159,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2018-01-05 10:10:39.803255", + "modified": "2019-03-08 10:44:41.924547", "modified_by": "Administrator", "module": "Assets", "name": "Asset Settings", @@ -125,7 +168,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -145,7 +187,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -171,5 +212,6 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1, - "track_seen": 0 + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index cb51e220b83..ed7f2ca4323 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -13,7 +13,7 @@ from erpnext.controllers.buying_controller import BuyingController from erpnext.accounts.utils import get_account_currency from frappe.desk.notifications import clear_doctype_notifications from erpnext.buying.utils import check_for_closed_status -from erpnext.assets.doctype.asset.asset import get_asset_account +from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_disabled from six import iteritems form_grid_templates = { @@ -258,7 +258,8 @@ class PurchaseReceipt(BuyingController): d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) - self.get_asset_gl_entry(gl_entries) + if not is_cwip_accounting_disabled(): + self.get_asset_gl_entry(gl_entries) # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): From 701c0b792ca70d58bb40f7af778f65559b6a46b9 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Mar 2019 12:30:42 +0530 Subject: [PATCH 19/76] feat: Pre mark attendance for Leave Application --- erpnext/hr/doctype/attendance/attendance.json | 35 +++++++++++++++- erpnext/hr/doctype/attendance/attendance.py | 3 +- .../leave_application/leave_application.py | 31 +++++++------- .../test_leave_application.py | 41 ++++++++++++++++++- 4 files changed, 91 insertions(+), 19 deletions(-) diff --git a/erpnext/hr/doctype/attendance/attendance.json b/erpnext/hr/doctype/attendance/attendance.json index 97d28e7e88f..2459b7a6df0 100644 --- a/erpnext/hr/doctype/attendance/attendance.json +++ b/erpnext/hr/doctype/attendance/attendance.json @@ -218,6 +218,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "leave_application", + "fieldtype": "Link", + "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": "Leave Application", + "length": 0, + "no_copy": 0, + "options": "Leave Application", + "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, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -428,7 +461,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2019-01-30 11:28:13.075959", + "modified": "2019-03-08 12:00:14.043535", "modified_by": "Administrator", "module": "HR", "name": "Attendance", diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py index 74a53035ad9..7dd9f0e10c6 100644 --- a/erpnext/hr/doctype/attendance/attendance.py +++ b/erpnext/hr/doctype/attendance/attendance.py @@ -40,7 +40,8 @@ class Attendance(Document): 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()): + # leaves can be marked for future dates + if self.status not in ('On Leave', 'Half Day') and getdate(self.attendance_date) > getdate(nowdate()): frappe.throw(_("Attendance can not be marked for future dates")) 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")) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index b85f38b295e..819bbf9d20f 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -117,28 +117,29 @@ class LeaveApplication(Document): def update_attendance(self): if self.status == "Approved": - attendance = frappe.db.sql("""select name from `tabAttendance` where employee = %s\ - and (attendance_date between %s and %s) and docstatus < 2""",(self.employee, self.from_date, self.to_date), as_dict=1) + for dt in daterange(getdate(self.from_date), getdate(self.to_date)): + date = dt.strftime("%Y-%m-%d") + status = "Half Day" if date == self.half_day_date else "On Leave" - if attendance: - for d in attendance: - doc = frappe.get_doc("Attendance", d.name) - if getdate(self.half_day_date) == doc.attendance_date: - status = "Half Day" - else: - status = "On Leave" - frappe.db.sql("""update `tabAttendance` set status = %s, leave_type = %s\ - where name = %s""",(status, self.leave_type, d.name)) + attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee, + attenance_date = date, docstatus = ('!=', 2))) - elif getdate(self.to_date) <= getdate(nowdate()): - for dt in daterange(getdate(self.from_date), getdate(self.to_date)): - date = dt.strftime("%Y-%m-%d") + if attendance_name: + # update existing attendance, change absent to on leave + doc = frappe.get_doc('Attendance', attendance_date) + if doc.status != status: + doc.db_set('status', status) + doc.db_set('leave_type', self.leave_type) + doc.db_set('leave_application', self.name) + else: + # make new attendance and submit it doc = frappe.new_doc("Attendance") doc.employee = self.employee doc.attendance_date = date doc.company = self.company doc.leave_type = self.leave_type - doc.status = "Half Day" if date == self.half_day_date else "On Leave" + doc.leave_application = self.name + doc.status = status doc.flags.ignore_validate = True doc.insert(ignore_permissions=True) doc.submit() diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py index a682e8b76f5..d3dcca1da05 100644 --- a/erpnext/hr/doctype/leave_application/test_leave_application.py +++ b/erpnext/hr/doctype/leave_application/test_leave_application.py @@ -7,7 +7,7 @@ import unittest from erpnext.hr.doctype.leave_application.leave_application import LeaveDayBlockedError, OverlapError, NotAnOptionalHoliday, get_leave_balance_on from frappe.permissions import clear_user_permissions_for_doctype -from frappe.utils import add_days, nowdate, now_datetime +from frappe.utils import add_days, nowdate, now_datetime, getdate test_dependencies = ["Leave Allocation", "Leave Block List"] @@ -67,6 +67,43 @@ class TestLeaveApplication(unittest.TestCase): application.to_date = "2013-01-05" return application + def test_attendance_creation(self): + '''check attendance is automatically created on leave approval''' + make_allocation_record() + application = self.get_application(_test_records[0]) + application.status = 'Approved' + application.from_date = '2018-01-01' + application.to_date = '2018-01-03' + application.insert() + application.submit() + + attendance = frappe.get_all('Attendance', ['name', 'status', 'attendance_date'], dict(leave_application = application.name)) + + # attendance created for all 3 days + self.assertEqual(len(attendance), 3) + + # all on leave + self.assertTrue(all([d.status == 'On Leave' for d in attendance])) + + # dates + dates = [d.attendance_date for d in attendance] + for d in ('2018-01-01', '2018-01-02', '2018-01-03'): + self.assertTrue(getdate(d) in dates) + + def test_overwrite_attendance(self): + # employee marked as absent + doc = frappe.new_doc("Attendance") + doc.employee = '_T-Employee-00001' + doc.attendance_date = '2018-01-01' + doc.company = '_Test Company' + doc.status = 'Absent' + doc.flags.ignore_validate = True + doc.insert(ignore_permissions=True) + doc.submit() + + # now check if the status has been updated + self.test_attendance_creation() + def test_block_list(self): self._clear_roles() @@ -428,7 +465,7 @@ def make_allocation_record(employee=None, leave_type=None): "employee": employee or "_T-Employee-00001", "leave_type": leave_type or "_Test Leave Type", "from_date": "2013-01-01", - "to_date": "2015-12-31", + "to_date": "2019-12-31", "new_leaves_allocated": 30 }) From 96842c9de6f336ce23926e145cfe777bcc1e5559 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 8 Mar 2019 12:36:43 +0530 Subject: [PATCH 20/76] fix(typo): leave_application.py --- erpnext/hr/doctype/leave_application/leave_application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 819bbf9d20f..cb0484f58f6 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -126,7 +126,7 @@ class LeaveApplication(Document): if attendance_name: # update existing attendance, change absent to on leave - doc = frappe.get_doc('Attendance', attendance_date) + doc = frappe.get_doc('Attendance', attendance_name) if doc.status != status: doc.db_set('status', status) doc.db_set('leave_type', self.leave_type) From 1692fbaf1c6a1a2f839ad0b1806c1153c40ba92c Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 8 Mar 2019 18:37:34 +0530 Subject: [PATCH 21/76] fetch from fix --- erpnext/regional/united_arab_emirates/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/erpnext/regional/united_arab_emirates/setup.py b/erpnext/regional/united_arab_emirates/setup.py index 3c8328b1074..250659e54da 100644 --- a/erpnext/regional/united_arab_emirates/setup.py +++ b/erpnext/regional/united_arab_emirates/setup.py @@ -28,24 +28,24 @@ def make_custom_fields(): purchase_invoice_fields = [ dict(fieldname='company_trn', label='Company TRN', fieldtype='Read Only', insert_after='shipping_address', - options='company.tax_id', print_hide=1), + fetch_from='company.tax_id', print_hide=1), dict(fieldname='supplier_name_in_arabic', label='Supplier Name in Arabic', fieldtype='Read Only', insert_after='supplier_name', - options='supplier.supplier_name_in_arabic', print_hide=1) + fetch_from='supplier.supplier_name_in_arabic', print_hide=1) ] sales_invoice_fields = [ dict(fieldname='company_trn', label='Company TRN', fieldtype='Read Only', insert_after='company_address', - options='company.tax_id', print_hide=1), + fetch_from='company.tax_id', print_hide=1), dict(fieldname='customer_name_in_arabic', label='Customer Name in Arabic', fieldtype='Read Only', insert_after='customer_name', - options='customer.customer_name_in_arabic', print_hide=1), + fetch_from='customer.customer_name_in_arabic', print_hide=1), ] invoice_item_fields = [ dict(fieldname='tax_code', label='Tax Code', - fieldtype='Read Only', options='item_code.tax_code', insert_after='description', + fieldtype='Read Only', fetch_from='item_code.tax_code', insert_after='description', allow_on_submit=1, print_hide=1), dict(fieldname='tax_rate', label='Tax Rate', fieldtype='Float', insert_after='tax_code', From 52729bf36934711d2d748f99b73be35fb4651da1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sun, 10 Mar 2019 14:26:30 +0530 Subject: [PATCH 22/76] feat: child table to add multiple time logs in job card --- .../doctype/job_card/job_card.js | 29 ++- .../doctype/job_card/job_card.json | 205 +++++++++++++---- .../doctype/job_card/job_card.py | 109 +++++---- .../doctype/job_card_time_log/__init__.py | 0 .../job_card_time_log/job_card_time_log.json | 208 ++++++++++++++++++ .../job_card_time_log/job_card_time_log.py | 9 + .../doctype/work_order/test_work_order.py | 15 +- erpnext/patches.txt | 1 + .../patches/v11_1/make_job_card_time_logs.py | 29 +++ 9 files changed, 513 insertions(+), 92 deletions(-) create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/__init__.py create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json create mode 100644 erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py create mode 100644 erpnext/patches/v11_1/make_job_card_time_logs.py diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js index 3fe9b8af30d..95549d5a248 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.js +++ b/erpnext/manufacturing/doctype/job_card/job_card.js @@ -18,20 +18,27 @@ frappe.ui.form.on('Job Card', { } if (frm.doc.docstatus == 0) { - if (!frm.doc.actual_start_date || !frm.doc.actual_end_date) { - frm.trigger("make_dashboard"); - } + frm.trigger("make_dashboard"); - if (!frm.doc.actual_start_date) { + if (!frm.doc.job_started) { frm.add_custom_button(__("Start Job"), () => { - frm.set_value('actual_start_date', frappe.datetime.now_datetime()); + let row = frappe.model.add_child(frm.doc, 'Job Card Time Log', 'time_logs'); + row.from_time = frappe.datetime.now_datetime(); + frm.set_value('job_started', 1); + frm.set_value('started_time' , row.from_time); frm.save(); }); - } else if (!frm.doc.actual_end_date) { + } else { frm.add_custom_button(__("Complete Job"), () => { - frm.set_value('actual_end_date', frappe.datetime.now_datetime()); - frm.save(); - frm.savesubmit(); + let completed_time = frappe.datetime.now_datetime(); + frm.doc.time_logs.forEach(d => { + if (d.from_time && !d.to_time) { + d.to_time = completed_time; + frm.set_value('started_time' , ''); + frm.set_value('job_started', 0); + frm.save(); + } + }) }); } } @@ -53,8 +60,8 @@ frappe.ui.form.on('Job Card', { var section = frm.dashboard.add_section(timer); - if (frm.doc.actual_start_date) { - let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.actual_start_date),"seconds"); + if (frm.doc.started_time) { + let currentIncrement = moment(frappe.datetime.now_datetime()).diff(moment(frm.doc.started_time),"seconds"); initialiseTimer(); function initialiseTimer() { diff --git a/erpnext/manufacturing/doctype/job_card/job_card.json b/erpnext/manufacturing/doctype/job_card/job_card.json index b020c89053c..39c5cce313b 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.json +++ b/erpnext/manufacturing/doctype/job_card/job_card.json @@ -21,6 +21,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "work_order", "fieldtype": "Link", "hidden": 0, @@ -54,6 +55,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "bom_no", "fieldtype": "Link", "hidden": 0, @@ -87,6 +89,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "workstation", "fieldtype": "Link", "hidden": 0, @@ -120,6 +123,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "operation", "fieldtype": "Link", "hidden": 0, @@ -153,6 +157,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_4", "fieldtype": "Column Break", "hidden": 0, @@ -185,6 +190,7 @@ "collapsible": 0, "columns": 0, "default": "Today", + "fetch_if_empty": 0, "fieldname": "posting_date", "fieldtype": "Date", "hidden": 0, @@ -217,6 +223,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, @@ -250,6 +257,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "for_quantity", "fieldtype": "Float", "hidden": 0, @@ -282,6 +290,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "wip_warehouse", "fieldtype": "Link", "hidden": 0, @@ -315,6 +324,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "timing_detail", "fieldtype": "Section Break", "hidden": 0, @@ -347,6 +357,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "employee", "fieldtype": "Link", "hidden": 0, @@ -380,7 +391,74 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "time_in_mins", + "fetch_if_empty": 0, + "fieldname": "time_logs", + "fieldtype": "Table", + "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": "Time Logs", + "length": 0, + "no_copy": 0, + "options": "Job Card Time Log", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "section_break_13", + "fieldtype": "Section Break", + "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, + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "total_completed_qty", "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, @@ -389,7 +467,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Time In Mins", + "label": "Total Completed Qty", "length": 0, "no_copy": 0, "permlevel": 0, @@ -412,7 +490,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_13", + "fetch_if_empty": 0, + "fieldname": "column_break_15", "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, @@ -443,8 +522,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "actual_start_date", - "fieldtype": "Datetime", + "fetch_if_empty": 0, + "fieldname": "total_time_in_mins", + "fieldtype": "Float", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -452,46 +532,14 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Actual Start Date", + "label": "Total Time in Mins", "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, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "actual_end_date", - "fieldtype": "Datetime", - "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": "Actual End Date", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -507,6 +555,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "section_break_8", "fieldtype": "Section Break", "hidden": 0, @@ -539,6 +588,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "items", "fieldtype": "Table", "hidden": 0, @@ -572,6 +622,7 @@ "bold": 0, "collapsible": 1, "columns": 0, + "fetch_if_empty": 0, "fieldname": "more_information", "fieldtype": "Section Break", "hidden": 0, @@ -604,6 +655,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "operation_id", "fieldtype": "Data", "hidden": 1, @@ -637,6 +689,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "transferred_qty", "fieldtype": "Float", "hidden": 0, @@ -670,6 +723,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "fetch_if_empty": 0, "fieldname": "requested_qty", "fieldtype": "Float", "hidden": 0, @@ -702,6 +756,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "project", "fieldtype": "Link", "hidden": 0, @@ -735,6 +790,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "remarks", "fieldtype": "Small Text", "hidden": 0, @@ -767,6 +823,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, "fieldname": "column_break_20", "fieldtype": "Column Break", "hidden": 0, @@ -799,6 +856,7 @@ "collapsible": 0, "columns": 0, "default": "Open", + "fetch_if_empty": 0, "fieldname": "status", "fieldtype": "Select", "hidden": 0, @@ -832,6 +890,73 @@ "bold": 0, "collapsible": 0, "columns": 0, + "fetch_if_empty": 0, + "fieldname": "job_started", + "fieldtype": "Check", + "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": "Job Started", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "started_time", + "fieldtype": "Datetime", + "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": "Started Time", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, "fieldname": "amended_from", "fieldtype": "Link", "hidden": 0, @@ -868,7 +993,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-12-13 17:23:57.986381", + "modified": "2019-03-10 17:38:37.499871", "modified_by": "Administrator", "module": "Manufacturing", "name": "Job Card", diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py index ea9f714fc84..23a4e5105c0 100644 --- a/erpnext/manufacturing/doctype/job_card/job_card.py +++ b/erpnext/manufacturing/doctype/job_card/job_card.py @@ -11,44 +11,56 @@ from frappe.model.document import Document class JobCard(Document): def validate(self): - self.validate_actual_dates() - self.set_time_in_mins() + self.validate_time_logs() self.set_status() - def validate_actual_dates(self): - if get_datetime(self.actual_start_date) > get_datetime(self.actual_end_date): - frappe.throw(_("Actual start date must be less than actual end date")) + def validate_time_logs(self): + self.total_completed_qty = 0.0 + self.total_time_in_mins = 0.0 - if not (self.employee and self.actual_start_date and self.actual_end_date): - return + for d in self.get('time_logs'): + if get_datetime(d.from_time) > get_datetime(d.to_time): + frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx)) - data = frappe.db.sql(""" select name from `tabJob Card` - where - ((%(actual_start_date)s > actual_start_date and %(actual_start_date)s < actual_end_date) or - (%(actual_end_date)s > actual_start_date and %(actual_end_date)s < actual_end_date) or - (%(actual_start_date)s <= actual_start_date and %(actual_end_date)s >= actual_end_date)) and - name != %(name)s and employee = %(employee)s and docstatus =1 - """, { - 'actual_start_date': self.actual_start_date, - 'actual_end_date': self.actual_end_date, - 'employee': self.employee, - 'name': self.name - }, as_dict=1) + data = self.get_overlap_for(d) + if data: + frappe.throw(_("Row {0}: From Time and To Time of {1} is overlapping with {2}") + .format(d.idx, self.name, data.name)) - if data: - frappe.throw(_("Start date and end date is overlapping with the job card {1}") - .format(data[0].name, data[0].name)) + if d.from_time and d.to_time: + d.time_in_mins = time_diff_in_hours(d.to_time, d.from_time) * 60 + self.total_time_in_mins += d.time_in_mins - def set_time_in_mins(self): - if self.actual_start_date and self.actual_end_date: - self.time_in_mins = time_diff_in_hours(self.actual_end_date, self.actual_start_date) * 60 + if d.completed_qty: + self.total_completed_qty += d.completed_qty + + def get_overlap_for(self, args): + existing = frappe.db.sql("""select jc.name as name from + `tabJob Card Time Log` jctl, `tabJob Card` jc where jctl.parent = jc.name and + ( + (%(from_time)s > jctl.from_time and %(from_time)s < jctl.to_time) or + (%(to_time)s > jctl.from_time and %(to_time)s < jctl.to_time) or + (%(from_time)s <= jctl.from_time and %(to_time)s >= jctl.to_time)) + and jctl.name!=%(name)s + and jc.name!=%(parent)s + and jc.docstatus < 2 + and jc.employee = %(employee)s """, + { + "from_time": args.from_time, + "to_time": args.to_time, + "name": args.name or "No Name", + "parent": args.parent or "No Name", + "employee": self.employee + }, as_dict=True) + + return existing[0] if existing else None def get_required_items(self): if not self.get('work_order'): return doc = frappe.get_doc('Work Order', self.get('work_order')) - if doc.transfer_material_against == 'Work Order' and doc.skip_transfer: + if doc.transfer_material_against == 'Work Order' or doc.skip_transfer: return for d in doc.required_items: @@ -67,36 +79,51 @@ class JobCard(Document): }) def on_submit(self): - self.validate_dates() + self.validate_job_card() self.update_work_order() self.set_transferred_qty() - def validate_dates(self): - if not self.actual_start_date and not self.actual_end_date: - frappe.throw(_("Actual start date and actual end date is mandatory")) - def on_cancel(self): self.update_work_order() self.set_transferred_qty() + def validate_job_card(self): + if not self.time_logs: + frappe.throw(_("Time logs are required for job card {0}").format(self.name)) + + if self.total_completed_qty <= 0.0: + frappe.throw(_("Total completed qty must be greater than zero")) + + if self.total_completed_qty > self.for_quantity: + frappe.throw(_("Total completed qty can not be greater than for quantity")) + def update_work_order(self): if not self.work_order: return - data = frappe.db.get_value("Job Card", {'docstatus': 1, 'operation_id': self.operation_id}, - ['sum(time_in_mins)', 'min(actual_start_date)', 'max(actual_end_date)', 'sum(for_quantity)']) + for_quantity, time_in_mins = 0, 0 + from_time_list, to_time_list = [], [] - if data: - time_in_mins, actual_start_date, actual_end_date, for_quantity = data + for d in frappe.get_all('Job Card', + filters = {'docstatus': 1, 'operation_id': self.operation_id}): + doc = frappe.get_doc('Job Card', d.name) + + for_quantity += doc.total_completed_qty + time_in_mins += doc.total_time_in_mins + for time_log in doc.time_logs: + from_time_list.append(time_log.from_time) + to_time_list.append(time_log.to_time) + + if for_quantity: wo = frappe.get_doc('Work Order', self.work_order) for data in wo.operations: if data.name == self.operation_id: data.completed_qty = for_quantity data.actual_operation_time = time_in_mins - data.actual_start_time = actual_start_date - data.actual_end_time = actual_end_date + data.actual_start_time = min(from_time_list) + data.actual_end_time = max(to_time_list) wo.flags.ignore_validate_update_after_submit = True wo.update_operation_status() @@ -132,9 +159,11 @@ class JobCard(Document): break if completed: - job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order, + job_cards = frappe.get_all('Job Card', filters = {'work_order': self.work_order, 'docstatus': ('!=', 2)}, fields = 'sum(transferred_qty) as qty', group_by='operation_id') - qty = min([d.qty for d in job_cards]) + + if job_cards: + qty = min([d.qty for d in job_cards]) doc.db_set('material_transferred_for_manufacturing', qty) @@ -147,7 +176,7 @@ class JobCard(Document): 2: "Cancelled" }[self.docstatus or 0] - if self.actual_start_date: + if self.time_logs: self.status = 'Work In Progress' if (self.docstatus == 1 and diff --git a/erpnext/manufacturing/doctype/job_card_time_log/__init__.py b/erpnext/manufacturing/doctype/job_card_time_log/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json new file mode 100644 index 00000000000..2aab71dee4f --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.json @@ -0,0 +1,208 @@ +{ + "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2019-03-08 23:56:43.187569", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "from_time", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "From Time", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "to_time", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "To Time", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "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, + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fetch_if_empty": 0, + "fieldname": "time_in_mins", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Time In Mins", + "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, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0", + "fetch_if_empty": 0, + "fieldname": "completed_qty", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Completed Qty", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 1, + "max_attachments": 0, + "modified": "2019-03-10 17:08:46.504910", + "modified_by": "Administrator", + "module": "Manufacturing", + "name": "Job Card Time Log", + "name_case": "", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "ASC", + "track_changes": 1, + "track_seen": 0, + "track_views": 0 +} \ No newline at end of file diff --git a/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py new file mode 100644 index 00000000000..3dc66891216 --- /dev/null +++ b/erpnext/manufacturing/doctype/job_card_time_log/job_card_time_log.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from frappe.model.document import Document + +class JobCardTimeLog(Document): + pass diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 69381c53b3e..b292047aa63 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -302,6 +302,19 @@ class TestWorkOrder(unittest.TestCase): self.assertEqual(len(ste.additional_costs), 1) self.assertEqual(ste.total_additional_costs, 1000) + def test_job_card(self): + data = frappe.get_cached_value('BOM', + {'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item']) + + if data: + bom, bom_item = data + + bom_doc = frappe.get_doc('BOM', bom) + work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom) + + job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order}) + self.assertEqual(len(job_cards), len(bom_doc.operations)) + def test_work_order_with_non_transfer_item(self): items = {'Finished Good Transfer Item': 1, '_Test FG Item': 1, '_Test FG Item 1': 0} for item, allow_transfer in items.items(): @@ -346,7 +359,7 @@ def make_wo_order_test_record(**args): wo_order = frappe.new_doc("Work Order") wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item" - wo_order.bom_no = frappe.db.get_value("BOM", {"item": wo_order.production_item, + wo_order.bom_no = args.bom_no or frappe.db.get_value("BOM", {"item": wo_order.production_item, "is_active": 1, "is_default": 1}) wo_order.qty = args.qty or 10 wo_order.wip_warehouse = args.wip_warehouse or "_Test Warehouse - _TC" diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 433141cdf67..7d49ad5fe36 100755 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -588,3 +588,4 @@ execute:frappe.delete_doc('DocType', 'Notification Control') erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019 +erpnext.patches.v11_1.make_job_card_time_logs \ No newline at end of file diff --git a/erpnext/patches/v11_1/make_job_card_time_logs.py b/erpnext/patches/v11_1/make_job_card_time_logs.py new file mode 100644 index 00000000000..6e708df48d8 --- /dev/null +++ b/erpnext/patches/v11_1/make_job_card_time_logs.py @@ -0,0 +1,29 @@ +# 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(): + frappe.reload_doc('manufacturing', 'doctype', 'job_card_time_log') + + if (frappe.db.table_exists("Job Card") + and frappe.get_meta("Job Card").has_field("actual_start_date")): + time_logs = [] + for d in frappe.get_all('Job Card', + fields = ["actual_start_date", "actual_end_date", "time_in_mins", "name", "for_quantity"], + filters = {'docstatus': ("<", 2)}): + if d.actual_start_date: + time_logs.append([d.actual_start_date, d.actual_end_date, d.time_in_mins, + d.for_quantity, d.name, 'Job Card', 'time_logs', frappe.generate_hash("", 10)]) + + if time_logs: + frappe.db.sql(""" INSERT INTO + `tabJob Card Time Log` + (from_time, to_time, time_in_mins, completed_qty, parent, parenttype, parentfield, name) + values {values} + """.format(values = ','.join(['%s'] * len(time_logs))), tuple(time_logs)) + + frappe.reload_doc('manufacturing', 'doctype', 'job_card') + frappe.db.sql(""" update `tabJob Card` set total_completed_qty = for_quantity, + total_time_in_mins = time_in_mins where docstatus < 2 """) \ No newline at end of file From e86ac3c8d4b4d26f5c1ee47232b697138fddc060 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 11 Mar 2019 10:37:28 +0530 Subject: [PATCH 23/76] feat: timesheet Employee Summary Report --- .../employee_billing_summary/__init__.py | 0 .../employee_billing_summary.js | 27 ++++ .../employee_billing_summary.json | 36 ++++++ .../employee_billing_summary.py | 119 ++++++++++++++++++ erpnext/public/node_modules | 1 + 5 files changed, 183 insertions(+) create mode 100644 erpnext/projects/report/employee_billing_summary/__init__.py create mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.js create mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.json create mode 100644 erpnext/projects/report/employee_billing_summary/employee_billing_summary.py create mode 120000 erpnext/public/node_modules diff --git a/erpnext/projects/report/employee_billing_summary/__init__.py b/erpnext/projects/report/employee_billing_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js new file mode 100644 index 00000000000..e6e674666d2 --- /dev/null +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js @@ -0,0 +1,27 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Employee Billing Summary"] = { + "filters": [ + { + fieldname: "employee", + label: __("Employee"), + fieldtype: "Link", + options: "Employee", + }, + { + fieldname:"from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.get_today() + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.add_days(frappe.datetime.get_today(), 30) + }, + + ] +} diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json new file mode 100644 index 00000000000..433ebac5ddf --- /dev/null +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "creation": "2019-03-08 15:08:19.929728", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2019-03-08 15:08:19.929728", + "modified_by": "Administrator", + "module": "Projects", + "name": "Employee Billing Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Employee Billing Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "HR User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py new file mode 100644 index 00000000000..47323efabe5 --- /dev/null +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py @@ -0,0 +1,119 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import time_diff_in_hours + +def execute(filters=None): + filters = frappe._dict(filters or {}) + print(filters) + + columns = get_columns() + + data = get_data(filters) + return columns, data + +def get_columns(): + return [ + { + "label": _("Employee ID"), + "fieldtype": "Link", + "fieldname": "employee", + "options": "Employee", + "width": 300 + }, + { + "label": _("Employee Name"), + "fieldtype": "data", + "fieldname": "employee_name", + "hidden": 1, + "width": 200 + }, + { + "label": _("Timesheet"), + "fieldtype": "Link", + "fieldname": "timesheet", + "options": "Timesheet", + "width": 150 + }, + { + "label": _("Date"), + "fieldtype": "Date", + "fieldname": "date", + "width": 150 + }, + { + "label": _("Total Billable Hours"), + "fieldtype": "Int", + "fieldname": "total_billable_hours", + "width": 50 + }, + { + "label": _("Total Hours"), + "fieldtype": "Int", + "fieldname": "total_hours", + "width": 50 + }, + { + "label": _("Amount"), + "fieldtype": "Int", + "fieldname": "amount", + "width": 50 + } + ] + +def get_data(filters): + data = [] + if "employee" in filters: + record= frappe.db.sql('''SELECT + employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount + FROM + `tabTimesheet` + WHERE + employee = %s and (start_date <= %s and end_date >= %s)''',(filters.employee, filters.to_date, filters.from_date), + as_dict=1 + ) + for entries in record: + + timesheet_details = frappe.get_all( + "Timesheet Detail", + filters={"parent": entries.name}, + fields=["*"] + ) + + total_hours = 0 + total_billable_hours = 0 + print("-------------------------------------------->>>>>>>") + for time in timesheet_details: + time_start = time.from_time + time_end = frappe.utils.add_to_date(time.from_time, hours=time.hours) + + from_date = frappe.utils.get_datetime(filters.from_date) + to_date = frappe.utils.get_datetime(filters.to_date) + + if time_start <= from_date and time_end <= to_date: + total_hours += abs(time_diff_in_hours(time_end, from_date)) + print(from_date, time_end) + print("case 1", entries.name,time_diff_in_hours(time_end, from_date)) + elif time_start >= from_date and time_end >= to_date: + total_hours += abs(time_diff_in_hours(to_date, time_start)) + print(time_start, to_date) + print("case 2", entries.name,time_diff_in_hours(to_date, time_start)) + elif time_start >= from_date and time_end <= to_date: + total_hours = entries.total_hours + print("case 3 all set", entries.name) + + print(total_hours) + print("-------------------------------------------->>>>>>>") + row = { + "employee": entries.employee, + "employee_name": entries.employee_name, + "timesheet": entries.name, + "total_billable_hours": entries.total_billable_hours, + "total_hours": entries.total_hours, + "amount": 1 + } + data.append(row) + return data \ No newline at end of file diff --git a/erpnext/public/node_modules b/erpnext/public/node_modules new file mode 120000 index 00000000000..229573e0576 --- /dev/null +++ b/erpnext/public/node_modules @@ -0,0 +1 @@ +/Users/anuragmishra/test/apps/erpnext/node_modules \ No newline at end of file From 59f4556d95213c6d6ea9c99869ba0724b8855fdb Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 11 Mar 2019 11:46:01 +0530 Subject: [PATCH 24/76] Commonify code --- .../employee_billing_summary.js | 1 - .../employee_billing_summary.py | 35 ++++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js index e6e674666d2..b792e818d81 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js @@ -22,6 +22,5 @@ frappe.query_reports["Employee Billing Summary"] = { fieldtype: "Date", default: frappe.datetime.add_days(frappe.datetime.get_today(), 30) }, - ] } diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py index 47323efabe5..491fa764d96 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py @@ -8,8 +8,6 @@ from frappe.utils import time_diff_in_hours def execute(filters=None): filters = frappe._dict(filters or {}) - print(filters) - columns = get_columns() data = get_data(filters) @@ -85,7 +83,8 @@ def get_data(filters): total_hours = 0 total_billable_hours = 0 - print("-------------------------------------------->>>>>>>") + total_amount = 0 + for time in timesheet_details: time_start = time.from_time time_end = frappe.utils.add_to_date(time.from_time, hours=time.hours) @@ -94,26 +93,28 @@ def get_data(filters): to_date = frappe.utils.get_datetime(filters.to_date) if time_start <= from_date and time_end <= to_date: - total_hours += abs(time_diff_in_hours(time_end, from_date)) - print(from_date, time_end) - print("case 1", entries.name,time_diff_in_hours(time_end, from_date)) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, from_date, total_hours, total_billable_hours, total_amount) elif time_start >= from_date and time_end >= to_date: - total_hours += abs(time_diff_in_hours(to_date, time_start)) - print(time_start, to_date) - print("case 2", entries.name,time_diff_in_hours(to_date, time_start)) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, to_date, time_start, total_hours, total_billable_hours, total_amount) elif time_start >= from_date and time_end <= to_date: - total_hours = entries.total_hours - print("case 3 all set", entries.name) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, time_start, total_hours, total_billable_hours, total_amount) - print(total_hours) - print("-------------------------------------------->>>>>>>") row = { "employee": entries.employee, "employee_name": entries.employee_name, "timesheet": entries.name, - "total_billable_hours": entries.total_billable_hours, - "total_hours": entries.total_hours, - "amount": 1 + "total_billable_hours": total_billable_hours, + "total_hours": total_hours, + "amount": total_amount } + data.append(row) - return data \ No newline at end of file + return data + +def get_billable_and_total_hours(time, end, start, total_hours, total_billable_hours, total_amount): + total_hours += abs(time_diff_in_hours(end, start)) + if time.billable: + total_billable_hours += abs(time_diff_in_hours(end, start)) + total_amount += total_billable_hours * time.billing_rate + + return total_hours, total_billable_hours, total_amount From 9b64baa7342b3ba01c427837a9516c3512301d0f Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Mon, 11 Mar 2019 12:02:25 +0530 Subject: [PATCH 25/76] Minor Fixes --- erpnext/public/node_modules | 1 - 1 file changed, 1 deletion(-) delete mode 120000 erpnext/public/node_modules diff --git a/erpnext/public/node_modules b/erpnext/public/node_modules deleted file mode 120000 index 229573e0576..00000000000 --- a/erpnext/public/node_modules +++ /dev/null @@ -1 +0,0 @@ -/Users/anuragmishra/test/apps/erpnext/node_modules \ No newline at end of file From 7c3e017ac35b46cb0ebd56ab193f38300f85824b Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 11 Mar 2019 14:47:57 +0530 Subject: [PATCH 26/76] fix: Amazon integration issues --- .../amazon_mws_settings/amazon_methods.py | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index c21f11ead7f..9afa32b9df3 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -162,6 +162,8 @@ def create_item_code(amazon_item_json, sku): igroup.parent_item_group = mws_settings.item_group igroup.insert() + item.append("item_defaults", {'company':mws_settings.company}) + item.insert(ignore_permissions=True) create_item_price(amazon_item_json, item.item_code) @@ -213,7 +215,7 @@ def get_orders(after_date): fulfillment_channels=["MFN", "AFN"], lastupdatedafter=after_date, orderstatus=statuses, - max_results='20') + max_results='50') while True: orders_list = [] @@ -432,8 +434,7 @@ def get_order_items(market_place_order_id): return final_order_items def get_item_code(order_item): - asin = order_item.ASIN - item_code = frappe.db.get_value("Item", {"amazon_item_code": asin}, "item_code") + item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code") if item_code: return item_code @@ -451,11 +452,16 @@ def get_charges_and_fees(market_place_order_id): shipment_item_list = return_as_list(shipment_event.ShipmentEvent.ShipmentItemList.ShipmentItem) for shipment_item in shipment_item_list: - charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent) - fees = return_as_list(shipment_item.ItemFeeList.FeeComponent) + charges, fees = [] + + if 'ItemChargeList' in shipment_item.keys(): + charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent) + + if 'ItemFeeList' in shipment_item.keys(): + fees = return_as_list(shipment_item.ItemFeeList.FeeComponent) for charge in charges: - if(charge.ChargeType != "Principal"): + if(charge.ChargeType != "Principal") and float(charge.ChargeAmount.CurrencyAmount) != 0: charge_account = get_account(charge.ChargeType) charges_fees.get("charges").append({ "charge_type":"Actual", @@ -465,13 +471,14 @@ def get_charges_and_fees(market_place_order_id): }) for fee in fees: - fee_account = get_account(fee.FeeType) - charges_fees.get("fees").append({ - "charge_type":"Actual", - "account_head": fee_account, - "tax_amount": fee.FeeAmount.CurrencyAmount, - "description": fee.FeeType + " for " + shipment_item.SellerSKU - }) + if float(fee.FeeAmount.CurrencyAmount) != 0: + fee_account = get_account(fee.FeeType) + charges_fees.get("fees").append({ + "charge_type":"Actual", + "account_head": fee_account, + "tax_amount": fee.FeeAmount.CurrencyAmount, + "description": fee.FeeType + " for " + shipment_item.SellerSKU + }) return charges_fees From c0e1000919257b8ddc9f8f3425c260f9e15320a8 Mon Sep 17 00:00:00 2001 From: pawan Date: Mon, 11 Mar 2019 14:58:17 +0530 Subject: [PATCH 27/76] fix: SKU issue --- .../doctype/amazon_mws_settings/amazon_methods.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py index 9afa32b9df3..1c39d8818c4 100644 --- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py +++ b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py @@ -434,6 +434,7 @@ def get_order_items(market_place_order_id): return final_order_items def get_item_code(order_item): + sku = order_item.SellerSKU item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code") if item_code: return item_code From 121825517815ffa9f77475bfd10e9dffa5cd2f58 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 11 Mar 2019 17:43:44 +0530 Subject: [PATCH 28/76] feat: Project Billing Summary for timesheet --- erpnext/projects/report/billing_summary.py | 137 ++++++++++++++++++ .../employee_billing_summary.py | 110 +------------- .../project_billing_summary/__init__.py | 0 .../project_billing_summary.js | 26 ++++ .../project_billing_summary.json | 36 +++++ .../project_billing_summary.py | 14 ++ 6 files changed, 215 insertions(+), 108 deletions(-) create mode 100644 erpnext/projects/report/billing_summary.py create mode 100644 erpnext/projects/report/project_billing_summary/__init__.py create mode 100644 erpnext/projects/report/project_billing_summary/project_billing_summary.js create mode 100644 erpnext/projects/report/project_billing_summary/project_billing_summary.json create mode 100644 erpnext/projects/report/project_billing_summary/project_billing_summary.py diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py new file mode 100644 index 00000000000..e34e90b1a44 --- /dev/null +++ b/erpnext/projects/report/billing_summary.py @@ -0,0 +1,137 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.utils import time_diff_in_hours + + +def get_columns(): + return [ + { + "label": _("Employee ID"), + "fieldtype": "Link", + "fieldname": "employee", + "options": "Employee", + "width": 300 + }, + { + "label": _("Employee Name"), + "fieldtype": "data", + "fieldname": "employee_name", + "hidden": 1, + "width": 200 + }, + { + "label": _("Timesheet"), + "fieldtype": "Link", + "fieldname": "timesheet", + "options": "Timesheet", + "width": 150 + }, + { + "label": _("Total Billable Hours"), + "fieldtype": "Int", + "fieldname": "total_billable_hours", + "width": 50 + }, + { + "label": _("Total Hours"), + "fieldtype": "Int", + "fieldname": "total_hours", + "width": 50 + }, + { + "label": _("Amount"), + "fieldtype": "Int", + "fieldname": "amount", + "width": 100 + } + ] + +def get_data(filters): + data = [] + + if "employee" in filters: + record= frappe.db.sql('''SELECT + employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount + FROM + `tabTimesheet` + WHERE + employee = %s and (start_date <= %s and end_date >= %s)''',(filters.employee, filters.to_date, filters.from_date), + as_dict=1 + ) + + elif "project" in filters: + record= frappe.db.sql('''SELECT + employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount + FROM + `tabTimesheet` + WHERE + start_date <= %s and end_date >= %s''',(filters.to_date, filters.from_date), + as_dict=1 + ) + else: + record = {} + + + for entries in record: + + timesheet_details_filter = {"parent": entries.name} + + if "project" in filters: + timesheet_details_filter["project"] = filters.project + + timesheet_details = frappe.get_all( + "Timesheet Detail", + filters = timesheet_details_filter, + fields=["*"] + ) + + total_hours = 0 + total_billable_hours = 0 + total_amount = 0 + check_entries = False + + for time in timesheet_details: + + check_entries = True + + time_start = time.from_time + time_end = frappe.utils.add_to_date(time.from_time, hours=time.hours) + + from_date = frappe.utils.get_datetime(filters.from_date) + to_date = frappe.utils.get_datetime(filters.to_date) + + if time_start <= from_date and time_end <= to_date: + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, from_date, total_hours, total_billable_hours, total_amount) + elif time_start >= from_date and time_end >= to_date: + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, to_date, time_start, total_hours, total_billable_hours, total_amount) + elif time_start >= from_date and time_end <= to_date: + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, time_start, total_hours, total_billable_hours, total_amount) + + row = { + "employee": entries.employee, + "employee_name": entries.employee_name, + "timesheet": entries.name, + "total_billable_hours": total_billable_hours, + "total_hours": total_hours, + "amount": total_amount + } + + if check_entries: + data.append(row) + check_entries = False + + return data + + +def get_billable_and_total_hours(time, end, start, total_hours, total_billable_hours, total_amount): + total_hours += abs(time_diff_in_hours(end, start)) + if time.billable: + total_billable_hours += abs(time_diff_in_hours(end, start)) + total_amount += total_billable_hours * time.billing_rate + + return total_hours, total_billable_hours, total_amount \ No newline at end of file diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py index 491fa764d96..cd5ad7803a5 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.py @@ -4,117 +4,11 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import time_diff_in_hours +from erpnext.projects.report.billing_summary import get_columns, get_data def execute(filters=None): filters = frappe._dict(filters or {}) columns = get_columns() data = get_data(filters) - return columns, data - -def get_columns(): - return [ - { - "label": _("Employee ID"), - "fieldtype": "Link", - "fieldname": "employee", - "options": "Employee", - "width": 300 - }, - { - "label": _("Employee Name"), - "fieldtype": "data", - "fieldname": "employee_name", - "hidden": 1, - "width": 200 - }, - { - "label": _("Timesheet"), - "fieldtype": "Link", - "fieldname": "timesheet", - "options": "Timesheet", - "width": 150 - }, - { - "label": _("Date"), - "fieldtype": "Date", - "fieldname": "date", - "width": 150 - }, - { - "label": _("Total Billable Hours"), - "fieldtype": "Int", - "fieldname": "total_billable_hours", - "width": 50 - }, - { - "label": _("Total Hours"), - "fieldtype": "Int", - "fieldname": "total_hours", - "width": 50 - }, - { - "label": _("Amount"), - "fieldtype": "Int", - "fieldname": "amount", - "width": 50 - } - ] - -def get_data(filters): - data = [] - if "employee" in filters: - record= frappe.db.sql('''SELECT - employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount - FROM - `tabTimesheet` - WHERE - employee = %s and (start_date <= %s and end_date >= %s)''',(filters.employee, filters.to_date, filters.from_date), - as_dict=1 - ) - for entries in record: - - timesheet_details = frappe.get_all( - "Timesheet Detail", - filters={"parent": entries.name}, - fields=["*"] - ) - - total_hours = 0 - total_billable_hours = 0 - total_amount = 0 - - for time in timesheet_details: - time_start = time.from_time - time_end = frappe.utils.add_to_date(time.from_time, hours=time.hours) - - from_date = frappe.utils.get_datetime(filters.from_date) - to_date = frappe.utils.get_datetime(filters.to_date) - - if time_start <= from_date and time_end <= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, from_date, total_hours, total_billable_hours, total_amount) - elif time_start >= from_date and time_end >= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, to_date, time_start, total_hours, total_billable_hours, total_amount) - elif time_start >= from_date and time_end <= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, time_start, total_hours, total_billable_hours, total_amount) - - row = { - "employee": entries.employee, - "employee_name": entries.employee_name, - "timesheet": entries.name, - "total_billable_hours": total_billable_hours, - "total_hours": total_hours, - "amount": total_amount - } - - data.append(row) - return data - -def get_billable_and_total_hours(time, end, start, total_hours, total_billable_hours, total_amount): - total_hours += abs(time_diff_in_hours(end, start)) - if time.billable: - total_billable_hours += abs(time_diff_in_hours(end, start)) - total_amount += total_billable_hours * time.billing_rate - - return total_hours, total_billable_hours, total_amount + return columns, data \ No newline at end of file diff --git a/erpnext/projects/report/project_billing_summary/__init__.py b/erpnext/projects/report/project_billing_summary/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js new file mode 100644 index 00000000000..18dbbd19bbd --- /dev/null +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js @@ -0,0 +1,26 @@ +// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Project Billing Summary"] = { + "filters": [ + { + fieldname: "project", + label: __("Project"), + fieldtype: "Link", + options: "Project", + }, + { + fieldname:"from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.get_today() + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.add_days(frappe.datetime.get_today(), 30) + }, + ] +} diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.json b/erpnext/projects/report/project_billing_summary/project_billing_summary.json new file mode 100644 index 00000000000..a3f91c802d5 --- /dev/null +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.json @@ -0,0 +1,36 @@ +{ + "add_total_row": 0, + "creation": "2019-03-11 16:22:39.460524", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2019-03-11 16:22:39.460524", + "modified_by": "Administrator", + "module": "Projects", + "name": "Project Billing Summary", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Timesheet", + "report_name": "Project Billing Summary", + "report_type": "Script Report", + "roles": [ + { + "role": "Projects User" + }, + { + "role": "HR User" + }, + { + "role": "Manufacturing User" + }, + { + "role": "Employee" + }, + { + "role": "Accounts User" + } + ] +} \ No newline at end of file diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.py b/erpnext/projects/report/project_billing_summary/project_billing_summary.py new file mode 100644 index 00000000000..cd5ad7803a5 --- /dev/null +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.py @@ -0,0 +1,14 @@ +# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from erpnext.projects.report.billing_summary import get_columns, get_data + +def execute(filters=None): + filters = frappe._dict(filters or {}) + columns = get_columns() + + data = get_data(filters) + return columns, data \ No newline at end of file From 34e1f92a7e80f94673b54a5a48dcd37a320a421a Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Mon, 11 Mar 2019 18:17:22 +0530 Subject: [PATCH 29/76] fix: readability --- erpnext/projects/report/billing_summary.py | 104 +++++++++++---------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index e34e90b1a44..cbea5f5c40c 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -54,63 +54,37 @@ def get_columns(): def get_data(filters): data = [] - if "employee" in filters: - record= frappe.db.sql('''SELECT - employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount - FROM - `tabTimesheet` - WHERE - employee = %s and (start_date <= %s and end_date >= %s)''',(filters.employee, filters.to_date, filters.from_date), - as_dict=1 - ) - - elif "project" in filters: - record= frappe.db.sql('''SELECT - employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount - FROM - `tabTimesheet` - WHERE - start_date <= %s and end_date >= %s''',(filters.to_date, filters.from_date), - as_dict=1 - ) - else: - record = {} - + record = get_records(filters) for entries in record: - - timesheet_details_filter = {"parent": entries.name} - - if "project" in filters: - timesheet_details_filter["project"] = filters.project - - timesheet_details = frappe.get_all( - "Timesheet Detail", - filters = timesheet_details_filter, - fields=["*"] - ) - total_hours = 0 total_billable_hours = 0 total_amount = 0 - check_entries = False + entries_exists = False - for time in timesheet_details: + timesheet_details = get_timesheet_details(filters, entries.name) - check_entries = True + for activity in timesheet_details: - time_start = time.from_time - time_end = frappe.utils.add_to_date(time.from_time, hours=time.hours) + entries_exists = True + + time_start = activity.from_time + time_end = frappe.utils.add_to_date(activity.from_time, hours=activity.hours) from_date = frappe.utils.get_datetime(filters.from_date) to_date = frappe.utils.get_datetime(filters.to_date) if time_start <= from_date and time_end <= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, from_date, total_hours, total_billable_hours, total_amount) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, + time_end, from_date, total_hours, total_billable_hours, total_amount) + elif time_start >= from_date and time_end >= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, to_date, time_start, total_hours, total_billable_hours, total_amount) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, + to_date, time_start, total_hours, total_billable_hours, total_amount) + elif time_start >= from_date and time_end <= to_date: - total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(time, time_end, time_start, total_hours, total_billable_hours, total_amount) + total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, + time_end, time_start, total_hours, total_billable_hours, total_amount) row = { "employee": entries.employee, @@ -121,17 +95,51 @@ def get_data(filters): "amount": total_amount } - if check_entries: + if entries_exists: data.append(row) - check_entries = False + entries_exists = False return data +def get_records(filters): + if "employee" in filters: + return frappe.db.sql('''SELECT + employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount + FROM + `tabTimesheet` + WHERE + employee = %s and (start_date <= %s and end_date >= %s)''', + (filters.employee, filters.to_date, filters.from_date), + as_dict=1 + ) -def get_billable_and_total_hours(time, end, start, total_hours, total_billable_hours, total_amount): + elif "project" in filters: + return frappe.db.sql('''SELECT + employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount + FROM + `tabTimesheet` + WHERE + start_date <= %s and end_date >= %s''',(filters.to_date, filters.from_date), + as_dict=1 + ) + else: + return {} + +def get_billable_and_total_hours(activity, end, start, total_hours, total_billable_hours, total_amount): total_hours += abs(time_diff_in_hours(end, start)) - if time.billable: + if activity.billable: total_billable_hours += abs(time_diff_in_hours(end, start)) - total_amount += total_billable_hours * time.billing_rate + total_amount += total_billable_hours * activity.billing_rate + return total_hours, total_billable_hours, total_amount - return total_hours, total_billable_hours, total_amount \ No newline at end of file +def get_timesheet_details(filters, parent): + timesheet_details_filter = {"parent": parent} + + if "project" in filters: + timesheet_details_filter["project"] = filters.project + + return frappe.get_all( + "Timesheet Detail", + filters = timesheet_details_filter, + fields=["*"] + ) \ No newline at end of file From 175709ed5070ebd1b61394e42b39e84595cf507e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 12 Mar 2019 10:55:39 +0530 Subject: [PATCH 30/76] fix: Update Training Level - Rename Expert to Intermediate, since it was ambiguous with Advance --- erpnext/hr/doctype/training_event/training_event.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/training_event/training_event.json b/erpnext/hr/doctype/training_event/training_event.json index 4b812a992e0..527ac1bc8fb 100644 --- a/erpnext/hr/doctype/training_event/training_event.json +++ b/erpnext/hr/doctype/training_event/training_event.json @@ -214,7 +214,7 @@ "label": "Level", "length": 0, "no_copy": 0, - "options": "\nBeginner\nExpert\nAdvance", + "options": "\nBeginner\nIntermediate\nAdvance", "permlevel": 0, "precision": "", "print_hide": 0, @@ -847,4 +847,4 @@ "title_field": "event_name", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} From 47f1080f1e4e819c88c94fd87489c7a08e2eaa21 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 12 Mar 2019 10:57:35 +0530 Subject: [PATCH 31/76] fix: Update modified --- erpnext/hr/doctype/training_event/training_event.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/training_event/training_event.json b/erpnext/hr/doctype/training_event/training_event.json index 527ac1bc8fb..fcf845a5879 100644 --- a/erpnext/hr/doctype/training_event/training_event.json +++ b/erpnext/hr/doctype/training_event/training_event.json @@ -809,7 +809,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-23 06:13:29.065781", + "modified": "2019-03-12 10:56:29.065781", "modified_by": "Administrator", "module": "HR", "name": "Training Event", From 9a1adc7c4937d664e7227144672d53a72b9d1776 Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 12 Mar 2019 11:00:02 +0530 Subject: [PATCH 32/76] Added mandatory feilds to the report --- .../employee_billing_summary/employee_billing_summary.js | 7 +++++-- .../project_billing_summary/project_billing_summary.js | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js index b792e818d81..65c2a690cf2 100644 --- a/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js +++ b/erpnext/projects/report/employee_billing_summary/employee_billing_summary.js @@ -9,18 +9,21 @@ frappe.query_reports["Employee Billing Summary"] = { label: __("Employee"), fieldtype: "Link", options: "Employee", + reqd: 1 }, { fieldname:"from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.datetime.get_today() + default: frappe.datetime.get_today(), + reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.get_today(), 30) + default: frappe.datetime.add_days(frappe.datetime.get_today(), 30), + reqd: 1 }, ] } diff --git a/erpnext/projects/report/project_billing_summary/project_billing_summary.js b/erpnext/projects/report/project_billing_summary/project_billing_summary.js index 18dbbd19bbd..62362c35cff 100644 --- a/erpnext/projects/report/project_billing_summary/project_billing_summary.js +++ b/erpnext/projects/report/project_billing_summary/project_billing_summary.js @@ -9,18 +9,21 @@ frappe.query_reports["Project Billing Summary"] = { label: __("Project"), fieldtype: "Link", options: "Project", + reqd: 1 }, { fieldname:"from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.datetime.get_today() + default: frappe.datetime.get_today(), + reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.get_today(), 30) + default: frappe.datetime.add_days(frappe.datetime.get_today(), 30), + reqd: 1 }, ] } From 5327d0253d7904419b8c21ec573c8c3875d3fb79 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 13:20:50 +0530 Subject: [PATCH 33/76] feat(issue): Create tasks from issues --- erpnext/support/doctype/issue/issue.js | 9 ++++++++- erpnext/support/doctype/issue/issue.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) mode change 100644 => 100755 erpnext/support/doctype/issue/issue.js mode change 100644 => 100755 erpnext/support/doctype/issue/issue.py diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js old mode 100644 new mode 100755 index d0a9bf38084..05c91303781 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -9,6 +9,13 @@ frappe.ui.form.on("Issue", { frm.set_value("status", "Closed"); frm.save(); }); + + frm.add_custom_button(__("Task"), function () { + frappe.model.open_mapped_doc({ + method: "erpnext.support.doctype.issue.issue.make_task", + frm: frm + }); + }, __("Make")); } else { frm.add_custom_button(__("Reopen"), function() { frm.set_value("status", "Open"); @@ -37,7 +44,7 @@ frappe.ui.form.on("Issue", { if (!frm.timeline.wrapper.find('.btn-split-issue').length) { let split_issue = __("Split Issue") $(``) .appendTo(frm.timeline.wrapper.find('.comment-header .asset-details:not([data-communication-type="Comment"])')) if (!frm.timeline.wrapper.data("split-issue-event-attached")){ diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py old mode 100644 new mode 100755 index 0b5eb539c8d..3e498c82697 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -7,11 +7,13 @@ import json from frappe import _ from frappe.model.document import Document +from frappe.model.mapper import get_mapped_doc 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) @@ -97,6 +99,7 @@ class Issue(Document): doc.save(ignore_permissions=True) return replicated_issue.name + def get_list_context(context=None): return { "title": _("Issues"), @@ -107,6 +110,7 @@ def get_list_context(context=None): 'no_breadcrumbs': True } + def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None): from frappe.www.list import get_list user = frappe.session.user @@ -124,12 +128,14 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) + @frappe.whitelist() def set_status(name, status): st = frappe.get_doc("Issue", name) st.status = status st.save() + def auto_close_tickets(): """ auto close the replied support tickets after 7 days """ auto_close_after_days = frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7 @@ -150,6 +156,7 @@ def set_multiple_status(names, status): for name in names: set_status(name, status) + def has_website_permission(doc, ptype, user, verbose=False): from erpnext.controllers.website_list_for_contact import has_website_permission permission_based_on_customer = has_website_permission(doc, ptype, user, verbose) @@ -160,3 +167,18 @@ def has_website_permission(doc, ptype, user, verbose=False): def update_issue(contact, method): """Called when Contact is deleted""" frappe.db.sql("""UPDATE `tabIssue` set contact='' where contact=%s""", contact.name) + + +@frappe.whitelist() +def make_task(source_name, target_doc=None): + def set_missing_values(source, target): + if not target.project: + target.project = frappe.db.get_value("Project", {"customer": source.customer}) + + doclist = get_mapped_doc("Issue", source_name, { + "Issue": { + "doctype": "Task" + } + }, target_doc, set_missing_values) + + return doclist From 2ba21fb66c8109a93c4170c2c828960da61903cb Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 16:27:57 +0530 Subject: [PATCH 34/76] fix(issue): Add issue name in Task and fix description in Issue --- erpnext/projects/doctype/task/task.json | 126 +++++++++++++++-------- erpnext/support/doctype/issue/issue.json | 12 ++- erpnext/support/doctype/issue/issue.py | 38 ++++--- 3 files changed, 113 insertions(+), 63 deletions(-) diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index d904d7092b5..2602aef626a 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -79,6 +79,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "issue", + "fieldtype": "Link", + "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": "Issue", + "length": 0, + "no_copy": 0, + "options": "Issue", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -213,6 +246,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "color", + "fieldtype": "Color", + "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": "Color", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -251,11 +316,11 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, - "collapsible_depends_on": "", + "collapsible": 1, + "collapsible_depends_on": "eval:doc.__islocal", "columns": 0, "depends_on": "", - "fieldname": "section_break_10", + "fieldname": "sb_timeline", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -264,6 +329,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Timeline", "length": 0, "no_copy": 0, "permlevel": 0, @@ -513,38 +579,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "color", - "fieldtype": "Color", - "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": "Color", - "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, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_in_quick_entry": 0, @@ -554,7 +588,7 @@ "collapsible_depends_on": "", "columns": 0, "depends_on": "", - "fieldname": "section_break0", + "fieldname": "sb_details", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -563,10 +597,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Details", "length": 0, "no_copy": 0, "oldfieldtype": "Section Break", - "options": "Simple", + "options": "", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -596,7 +631,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Details", + "label": "Task Description", "length": 0, "no_copy": 0, "oldfieldname": "description", @@ -624,7 +659,7 @@ "collapsible_depends_on": "", "columns": 0, "depends_on": "", - "fieldname": "section_break", + "fieldname": "sb_depends_on", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -726,7 +761,7 @@ "columns": 0, "depends_on": "", "description": "", - "fieldname": "actual", + "fieldname": "sb_actual", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -893,10 +928,10 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, "depends_on": "", - "fieldname": "section_break_17", + "fieldname": "sb_costing", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -905,6 +940,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Costing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -1058,9 +1094,9 @@ "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, - "fieldname": "more_details", + "fieldname": "sb_more_info", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -1069,7 +1105,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "", + "label": "More Info", "length": 0, "no_copy": 0, "permlevel": 0, diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 21cf2f78486..7cb0df28a59 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, @@ -349,8 +350,9 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 1, + "collapsible_depends_on": "eval:doc.status!=\"Closed\"", "columns": 0, - "fieldname": "section_break_7", + "fieldname": "sb_details", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -416,7 +418,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "response", + "fieldname": "sb_response", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -511,7 +513,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "additional_info", + "fieldname": "sb_additional_info", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -736,7 +738,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "section_break_19", + "fieldname": "sb_resoution", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -1035,7 +1037,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-08-21 14:44:27.615004", + "modified": "2019-02-14 02:55:47.562611", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 3e498c82697..7e13947ee94 100755 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -19,10 +19,12 @@ class Issue(Document): return "{0}: {1}".format(_(self.status), self.subject) def validate(self): - if (self.get("__islocal") and self.via_customer_portal): + if self.is_new() and self.via_customer_portal: self.flags.create_communication = True + if not self.raised_by: self.raised_by = frappe.session.user + self.update_status() self.set_lead_contact(self.raised_by) @@ -32,16 +34,18 @@ class Issue(Document): def on_update(self): # create the communication email and remove the description - if (self.flags.create_communication and self.via_customer_portal): + if self.flags.create_communication and self.via_customer_portal: self.create_communication() self.flags.communication_created = None def set_lead_contact(self, email_id): import email.utils + email_id = email.utils.parseaddr(email_id)[1] if email_id: if not self.lead: self.lead = frappe.db.get_value("Lead", {"email_id": email_id}) + if not self.contact and not self.customer: self.contact = frappe.db.get_value("Contact", {"email_id": email_id}) @@ -81,22 +85,27 @@ class Issue(Document): communication.ignore_mandatory = True communication.save() - self.db_set("description", "") - def split_issue(self, subject, communication_id): # Bug: Pressing enter doesn't send subject from copy import deepcopy + replicated_issue = deepcopy(self) replicated_issue.subject = subject frappe.get_doc(replicated_issue).insert() + # Replicate linked Communications - # todo get all communications in timeline before this, and modify them to append them to new doc + # TODO: get all communications in timeline before this, and modify them to append them to new doc comm_to_split_from = frappe.get_doc("Communication", communication_id) - communications = frappe.get_all("Communication", filters={"reference_name": comm_to_split_from.reference_name, "reference_doctype": "Issue", "creation": ('>=', comm_to_split_from.creation)}) + communications = frappe.get_all("Communication", + filters={"reference_doctype": "Issue", + "reference_name": comm_to_split_from.reference_name, + "creation": ('>=', comm_to_split_from.creation)}) + for communication in communications: doc = frappe.get_doc("Communication", communication.name) doc.reference_name = replicated_issue.name doc.save(ignore_permissions=True) + return replicated_issue.name @@ -113,9 +122,11 @@ def get_list_context(context=None): def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, order_by=None): from frappe.www.list import get_list + user = frappe.session.user contact = frappe.db.get_value('Contact', {'user': user}, 'name') customer = None + if contact: contact_doc = frappe.get_doc('Contact', contact) customer = contact_doc.get_link_for('Customer') @@ -129,6 +140,13 @@ def get_issue_list(doctype, txt, filters, limit_start, limit_page_length=20, ord return get_list(doctype, txt, filters, limit_start, limit_page_length, ignore_permissions=ignore_permissions) +@frappe.whitelist() +def set_multiple_status(names, status): + names = json.loads(names) + for name in names: + set_status(name, status) + + @frappe.whitelist() def set_status(name, status): st = frappe.get_doc("Issue", name) @@ -137,7 +155,7 @@ def set_status(name, status): def auto_close_tickets(): - """ auto close the replied support tickets after 7 days """ + """Auto-close replied support tickets after 7 days""" auto_close_after_days = frappe.db.get_value("Support Settings", "Support Settings", "close_issue_after_days") or 7 issues = frappe.db.sql(""" select name from tabIssue where status='Replied' and @@ -150,12 +168,6 @@ def auto_close_tickets(): doc.flags.ignore_mandatory = True doc.save() -@frappe.whitelist() -def set_multiple_status(names, status): - names = json.loads(names) - for name in names: - set_status(name, status) - def has_website_permission(doc, ptype, user, verbose=False): from erpnext.controllers.website_list_for_contact import has_website_permission From fa77b591ace3929aeb373d725173fcc55a38cda2 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Feb 2019 16:32:11 +0530 Subject: [PATCH 35/76] fix(issue): View Tasks against an Issue --- erpnext/support/doctype/issue/issue.js | 4 ++++ erpnext/support/doctype/issue/issue.py | 0 2 files changed, 4 insertions(+) mode change 100755 => 100644 erpnext/support/doctype/issue/issue.js mode change 100755 => 100644 erpnext/support/doctype/issue/issue.py diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js old mode 100755 new mode 100644 index 05c91303781..27bb46986bd --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -4,6 +4,10 @@ frappe.ui.form.on("Issue", { }, refresh: function(frm) { + frm.add_custom_button(__("Task"), function () { + frappe.set_route("List", "Task", { "issue": frm.doc.name }); + }, __("View")); + if(frm.doc.status!=="Closed") { frm.add_custom_button(__("Close"), function() { frm.set_value("status", "Closed"); diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py old mode 100755 new mode 100644 From bf7c69f3c4f5b5c268e4ed6a762e0bba3703a40f Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Fri, 15 Feb 2019 14:21:10 +0530 Subject: [PATCH 36/76] fix(issue): Don't auto-set project --- erpnext/support/doctype/issue/issue.js | 8 ++++---- erpnext/support/doctype/issue/issue.py | 12 +++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 27bb46986bd..03e1aa4f878 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -3,13 +3,13 @@ frappe.ui.form.on("Issue", { frm.email_field = "raised_by"; }, - refresh: function(frm) { + refresh: function (frm) { frm.add_custom_button(__("Task"), function () { frappe.set_route("List", "Task", { "issue": frm.doc.name }); }, __("View")); - if(frm.doc.status!=="Closed") { - frm.add_custom_button(__("Close"), function() { + if (frm.doc.status !== "Closed") { + frm.add_custom_button(__("Close"), function () { frm.set_value("status", "Closed"); frm.save(); }); @@ -21,7 +21,7 @@ frappe.ui.form.on("Issue", { }); }, __("Make")); } else { - frm.add_custom_button(__("Reopen"), function() { + frm.add_custom_button(__("Reopen"), function () { frm.set_value("status", "Open"); frm.save(); }); diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 7e13947ee94..de3d144a7ef 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -33,7 +33,7 @@ class Issue(Document): clear(self.doctype, self.name) def on_update(self): - # create the communication email and remove the description + # Add a communication in the issue timeline if self.flags.create_communication and self.via_customer_portal: self.create_communication() self.flags.communication_created = None @@ -183,14 +183,8 @@ def update_issue(contact, method): @frappe.whitelist() def make_task(source_name, target_doc=None): - def set_missing_values(source, target): - if not target.project: - target.project = frappe.db.get_value("Project", {"customer": source.customer}) - - doclist = get_mapped_doc("Issue", source_name, { + return get_mapped_doc("Issue", source_name, { "Issue": { "doctype": "Task" } - }, target_doc, set_missing_values) - - return doclist + }, target_doc) From f4bce6a66e6701b05fed036ef5b5e4ee7295109d Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Mon, 18 Feb 2019 12:53:11 +0530 Subject: [PATCH 37/76] fix(issue): Replace Make buttons on Issue and Task with a dashboard --- erpnext/projects/doctype/task/task.js | 13 ------------- .../projects/doctype/task/task_dashboard.py | 19 +++++++++++++++++++ erpnext/support/doctype/issue/issue.js | 4 ---- .../support/doctype/issue/issue_dashboard.py | 15 +++++++++++++++ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 erpnext/projects/doctype/task/task_dashboard.py create mode 100644 erpnext/support/doctype/issue/issue_dashboard.py diff --git a/erpnext/projects/doctype/task/task.js b/erpnext/projects/doctype/task/task.js index 93423db7624..9a8af694264 100644 --- a/erpnext/projects/doctype/task/task.js +++ b/erpnext/projects/doctype/task/task.js @@ -35,19 +35,6 @@ frappe.ui.form.on("Task", { } if(!doc.__islocal) { - if(frappe.model.can_read("Timesheet")) { - frm.add_custom_button(__("Timesheet"), function() { - frappe.route_options = {"project": doc.project, "task": doc.name} - frappe.set_route("List", "Timesheet"); - }, __("View"), true); - } - if(frappe.model.can_read("Expense Claim")) { - frm.add_custom_button(__("Expense Claims"), function() { - frappe.route_options = {"project": doc.project, "task": doc.name} - frappe.set_route("List", "Expense Claim"); - }, __("View"), true); - } - if(frm.perm[0].write) { if(frm.doc.status!=="Completed" && frm.doc.status!=="Cancelled") { frm.add_custom_button(__("Completed"), function() { diff --git a/erpnext/projects/doctype/task/task_dashboard.py b/erpnext/projects/doctype/task/task_dashboard.py new file mode 100644 index 00000000000..b776b98f676 --- /dev/null +++ b/erpnext/projects/doctype/task/task_dashboard.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'fieldname': 'task', + 'transactions': [ + { + 'label': _('Activity'), + 'items': ['Timesheet'] + }, + { + 'label': _('Accounting'), + 'items': ['Expense Claim'] + } + ] + } diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 03e1aa4f878..ce75304e77f 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -4,10 +4,6 @@ frappe.ui.form.on("Issue", { }, refresh: function (frm) { - frm.add_custom_button(__("Task"), function () { - frappe.set_route("List", "Task", { "issue": frm.doc.name }); - }, __("View")); - if (frm.doc.status !== "Closed") { frm.add_custom_button(__("Close"), function () { frm.set_value("status", "Closed"); diff --git a/erpnext/support/doctype/issue/issue_dashboard.py b/erpnext/support/doctype/issue/issue_dashboard.py new file mode 100644 index 00000000000..2ac7c816156 --- /dev/null +++ b/erpnext/support/doctype/issue/issue_dashboard.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +from frappe import _ + + +def get_data(): + return { + 'fieldname': 'issue', + 'transactions': [ + { + 'label': _('Activity'), + 'items': ['Task'] + } + ] + } From 641e412853e8259128b67409414342d054e7a08f Mon Sep 17 00:00:00 2001 From: Anurag Mishra Date: Tue, 12 Mar 2019 13:12:31 +0530 Subject: [PATCH 38/76] fix: Removed raw query and used frappe.get_all --- erpnext/projects/report/billing_summary.py | 40 +++++----------------- 1 file changed, 9 insertions(+), 31 deletions(-) diff --git a/erpnext/projects/report/billing_summary.py b/erpnext/projects/report/billing_summary.py index cbea5f5c40c..214dcef8fdb 100644 --- a/erpnext/projects/report/billing_summary.py +++ b/erpnext/projects/report/billing_summary.py @@ -7,7 +7,6 @@ import frappe from frappe import _ from frappe.utils import time_diff_in_hours - def get_columns(): return [ { @@ -53,7 +52,6 @@ def get_columns(): def get_data(filters): data = [] - record = get_records(filters) for entries in record: @@ -61,27 +59,20 @@ def get_data(filters): total_billable_hours = 0 total_amount = 0 entries_exists = False - timesheet_details = get_timesheet_details(filters, entries.name) - for activity in timesheet_details: - entries_exists = True - time_start = activity.from_time time_end = frappe.utils.add_to_date(activity.from_time, hours=activity.hours) - from_date = frappe.utils.get_datetime(filters.from_date) to_date = frappe.utils.get_datetime(filters.to_date) if time_start <= from_date and time_end <= to_date: total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, time_end, from_date, total_hours, total_billable_hours, total_amount) - elif time_start >= from_date and time_end >= to_date: total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, to_date, time_start, total_hours, total_billable_hours, total_amount) - elif time_start >= from_date and time_end <= to_date: total_hours, total_billable_hours, total_amount = get_billable_and_total_hours(activity, time_end, time_start, total_hours, total_billable_hours, total_amount) @@ -102,28 +93,15 @@ def get_data(filters): return data def get_records(filters): - if "employee" in filters: - return frappe.db.sql('''SELECT - employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount - FROM - `tabTimesheet` - WHERE - employee = %s and (start_date <= %s and end_date >= %s)''', - (filters.employee, filters.to_date, filters.from_date), - as_dict=1 - ) + record_filters = [ + ["start_date", "<=", filters.to_date], + ["end_date", ">=", filters.from_date] + ] - elif "project" in filters: - return frappe.db.sql('''SELECT - employee, employee_name, name, total_billable_hours, total_hours, total_billable_amount - FROM - `tabTimesheet` - WHERE - start_date <= %s and end_date >= %s''',(filters.to_date, filters.from_date), - as_dict=1 - ) - else: - return {} + if "employee" in filters: + record_filters.append(["employee", "=", filters.employee]) + + return frappe.get_all("Timesheet", filters=record_filters, fields=[" * "] ) def get_billable_and_total_hours(activity, end, start, total_hours, total_billable_hours, total_amount): total_hours += abs(time_diff_in_hours(end, start)) @@ -142,4 +120,4 @@ def get_timesheet_details(filters, parent): "Timesheet Detail", filters = timesheet_details_filter, fields=["*"] - ) \ No newline at end of file + ) From 82433845e5cf34d16996a163afe622888c9bd4d4 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Thu, 14 Mar 2019 13:16:20 +0530 Subject: [PATCH 39/76] fix(transaction): Add link to payments made by Customer / Supplier in their dashboards --- erpnext/buying/doctype/supplier/supplier_dashboard.py | 11 ++++++++++- .../selling/doctype/customer/customer_dashboard.py | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/erpnext/buying/doctype/supplier/supplier_dashboard.py b/erpnext/buying/doctype/supplier/supplier_dashboard.py index aea1e2d38c0..887a0937369 100644 --- a/erpnext/buying/doctype/supplier/supplier_dashboard.py +++ b/erpnext/buying/doctype/supplier/supplier_dashboard.py @@ -1,11 +1,16 @@ from __future__ import unicode_literals + from frappe import _ + def get_data(): return { 'heatmap': True, 'heatmap_message': _('This is based on transactions against this Supplier. See timeline below for details'), 'fieldname': 'supplier', + 'non_standard_fieldnames': { + 'Payment Entry': 'party_name' + }, 'transactions': [ { 'label': _('Procurement'), @@ -15,9 +20,13 @@ def get_data(): 'label': _('Orders'), 'items': ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'] }, + { + 'label': _('Payments'), + 'items': ['Payment Entry'] + }, { 'label': _('Pricing'), 'items': ['Pricing Rule'] } ] - } \ No newline at end of file + } diff --git a/erpnext/selling/doctype/customer/customer_dashboard.py b/erpnext/selling/doctype/customer/customer_dashboard.py index f2f430a61e2..3142ea22a4e 100644 --- a/erpnext/selling/doctype/customer/customer_dashboard.py +++ b/erpnext/selling/doctype/customer/customer_dashboard.py @@ -1,11 +1,16 @@ from __future__ import unicode_literals + from frappe import _ + def get_data(): return { 'heatmap': True, 'heatmap_message': _('This is based on transactions against this Customer. See timeline below for details'), 'fieldname': 'customer', + 'non_standard_fieldnames': { + 'Payment Entry': 'party_name' + }, 'transactions': [ { 'label': _('Pre Sales'), @@ -15,6 +20,10 @@ def get_data(): 'label': _('Orders'), 'items': ['Sales Order', 'Delivery Note', 'Sales Invoice'] }, + { + 'label': _('Payments'), + 'items': ['Payment Entry'] + }, { 'label': _('Support'), 'items': ['Issue'] @@ -32,4 +41,4 @@ def get_data(): 'items': ['Subscription'] } ] - } \ No newline at end of file + } From e14758c89713c07ce35f15d6ff1a011aab95e999 Mon Sep 17 00:00:00 2001 From: Bassam Ramadan Date: Thu, 14 Mar 2019 11:31:50 +0200 Subject: [PATCH 40/76] fix: add returns field to cashier closing (#16911) --- .../cashier_closing/cashier_closing.json | 858 +++++++++--------- .../cashier_closing/cashier_closing.py | 4 +- 2 files changed, 435 insertions(+), 427 deletions(-) diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.json b/erpnext/accounts/doctype/cashier_closing/cashier_closing.json index 14e9070f302..115728dc7b6 100644 --- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.json +++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.json @@ -1,426 +1,434 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "naming_series:", - "beta": 0, - "creation": "2018-06-18 16:51:49.994750", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Cashier-closing-", - "fieldname": "naming_series", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 1, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Series", - "length": 0, - "no_copy": 0, - "options": "Cashier-closing-", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "user", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "User", - "length": 0, - "no_copy": 0, - "options": "User", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Today", - "fieldname": "date", - "fieldtype": "Date", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "Date", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "from_time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "From Time", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "", - "fieldname": "time", - "fieldtype": "Time", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 1, - "label": "To Time", - "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "expense", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Expense", - "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, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "custody", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Custody", - "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, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.00", - "fieldname": "outstanding_amount", - "fieldtype": "Float", - "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": "Outstanding Amount", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0.0", - "fieldname": "payments", - "fieldtype": "Table", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Payments", - "length": 0, - "no_copy": 0, - "options": "Cashier Closing Payments", - "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, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "net_amount", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 1, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Net Amount", - "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, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "amended_from", - "fieldtype": "Link", - "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": "Amended From", - "length": 0, - "no_copy": 1, - "options": "Cashier Closing", - "permlevel": 0, - "print_hide": 1, - "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, - "translatable": 0, - "unique": 0 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 1, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-02-19 08:35:23.157327", - "modified_by": "Administrator", - "module": "Accounts", - "name": "Cashier Closing", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 1, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "naming_series:", + "beta": 0, + "creation": "2018-06-18 16:51:49.994750", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "POS-CLO-", + "fieldname": "naming_series", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 1, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Series", + "length": 0, + "no_copy": 0, + "options": "POS-CLO-", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Today", + "fieldname": "date", + "fieldtype": "Date", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "Date", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "From Time", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 1, + "label": "To Time", + "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": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0.00", + "fieldname": "expense", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Expense", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0.00", + "fieldname": "custody", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Custody", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0.00", + "fieldname": "returns", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Returns", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0.00", + "fieldname": "outstanding_amount", + "fieldtype": "Float", + "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": "Outstanding Amount", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "2", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "0.0", + "fieldname": "payments", + "fieldtype": "Table", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Payments", + "length": 0, + "no_copy": 0, + "options": "Cashier Closing Payments", + "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, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "net_amount", + "fieldtype": "Float", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 1, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Net Amount", + "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_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "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": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Cashier Closing", + "permlevel": 0, + "print_hide": 1, + "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 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 1, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2019-03-14 09:14:26.727129", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Cashier Closing", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 0 + } \ No newline at end of file diff --git a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py index 906bc7f18a5..6de62ee5777 100644 --- a/erpnext/accounts/doctype/cashier_closing/cashier_closing.py +++ b/erpnext/accounts/doctype/cashier_closing/cashier_closing.py @@ -29,8 +29,8 @@ class CashierClosing(Document): for i in self.payments: total += flt(i.amount) - self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.net_amount = total + self.outstanding_amount + self.expense - self.custody + self.returns def validate_time(self): if self.from_time >= self.time: - frappe.throw(_("From Time Should Be Less Than To Time")) + frappe.throw(_("From Time Should Be Less Than To Time")) \ No newline at end of file From 08a209bc529358c0d641e189c6c1c59e74b67937 Mon Sep 17 00:00:00 2001 From: rushin29 Date: Fri, 15 Mar 2019 15:28:50 +0530 Subject: [PATCH 41/76] fix: gst_state_number for address with Unregistered GST (#16798) --- erpnext/regional/india/utils.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index e7d0d5052ec..9747b245694 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -9,6 +9,8 @@ from erpnext.hr.utils import get_salary_assignment from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip def validate_gstin_for_india(doc, method): + if hasattr(doc, 'gst_state') and doc.gst_state: + doc.gst_state_number = state_numbers[doc.gst_state] if not hasattr(doc, 'gstin') or not doc.gstin: return From b3434072367a214361e0932a9cdc4350a71fcd30 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Sat, 16 Mar 2019 13:38:53 +0530 Subject: [PATCH 42/76] refactor: change validate email add to validate email address --- erpnext/crm/doctype/lead/lead.py | 4 ++-- erpnext/hr/doctype/employee/employee.py | 6 +++--- erpnext/hr/doctype/job_applicant/job_applicant.py | 4 ++-- erpnext/non_profit/doctype/member/member.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py index 29ca71bd88e..442b6c2db2b 100644 --- a/erpnext/crm/doctype/lead/lead.py +++ b/erpnext/crm/doctype/lead/lead.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import (cstr, validate_email_add, cint, comma_and, has_gravatar, now, getdate, nowdate) +from frappe.utils import (cstr, validate_email_address, cint, comma_and, has_gravatar, now, getdate, nowdate) from frappe.model.mapper import get_mapped_doc from erpnext.controllers.selling_controller import SellingController @@ -38,7 +38,7 @@ class Lead(SellingController): if self.email_id: if not self.flags.ignore_email_validation: - validate_email_add(self.email_id, True) + validate_email_address(self.email_id, True) if self.email_id == self.lead_owner: frappe.throw(_("Lead Owner cannot be same as the Lead")) diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index d518cd89957..a403c393b95 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe.utils import getdate, validate_email_add, today, add_years, format_datetime +from frappe.utils import getdate, validate_email_address, today, add_years, format_datetime from frappe.model.naming import set_name_by_naming_series from frappe import throw, _, scrub from frappe.permissions import add_user_permission, remove_user_permission, \ @@ -142,9 +142,9 @@ class Employee(NestedSet): def validate_email(self): if self.company_email: - validate_email_add(self.company_email, True) + validate_email_address(self.company_email, True) if self.personal_email: - validate_email_add(self.personal_email, True) + validate_email_address(self.personal_email, True) def validate_status(self): if self.status == 'Left': diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py index ea81fe793d8..4fc7719f383 100644 --- a/erpnext/hr/doctype/job_applicant/job_applicant.py +++ b/erpnext/hr/doctype/job_applicant/job_applicant.py @@ -7,7 +7,7 @@ from __future__ import unicode_literals from frappe.model.document import Document import frappe from frappe import _ -from frappe.utils import comma_and, validate_email_add +from frappe.utils import comma_and, validate_email_address sender_field = "email_id" @@ -28,7 +28,7 @@ class JobApplicant(Document): def validate(self): self.check_email_id_is_unique() if self.email_id: - validate_email_add(self.email_id, True) + validate_email_address(self.email_id, True) if not self.applicant_name and self.email_id: guess = self.email_id.split('@')[0] diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py index b9b2dd8fc97..9afaf90e7ab 100644 --- a/erpnext/non_profit/doctype/member/member.py +++ b/erpnext/non_profit/doctype/member/member.py @@ -17,5 +17,5 @@ class Member(Document): self.validate_email_type(self.email) def validate_email_type(self, email): - from frappe.utils import validate_email_add - validate_email_add(email.strip(), True) \ No newline at end of file + from frappe.utils import validate_email_address + validate_email_address(email.strip(), True) \ No newline at end of file From 2ae7ed4cf0de848f1fbca04e278780f14a9daaf6 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Sun, 17 Mar 2019 09:49:24 +0530 Subject: [PATCH 43/76] fix: Gross profit report fix (#16935) --- erpnext/accounts/report/gross_profit/gross_profit.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py index 073516fb88e..e5aaafaff49 100644 --- a/erpnext/accounts/report/gross_profit/gross_profit.py +++ b/erpnext/accounts/report/gross_profit/gross_profit.py @@ -125,10 +125,11 @@ class GrossProfitGenerator(object): # get buying amount if row.item_code in product_bundles: - row.buying_amount = self.get_buying_amount_from_product_bundle(row, - product_bundles[row.item_code]) + row.buying_amount = flt(self.get_buying_amount_from_product_bundle(row, + product_bundles[row.item_code]), self.currency_precision) else: - row.buying_amount = self.get_buying_amount(row, row.item_code) + row.buying_amount = flt(self.get_buying_amount(row, row.item_code), + self.currency_precision) # get buying rate if row.qty: @@ -215,7 +216,7 @@ class GrossProfitGenerator(object): if packed_item.get("parent_detail_docname")==row.item_row: buying_amount += self.get_buying_amount(row, packed_item.item_code) - return buying_amount + return flt(buying_amount, self.currency_precision) def get_buying_amount(self, row, item_code): # IMP NOTE From 3ead70ba3c4304f8f1248a91c128e18cf8996170 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 18 Mar 2019 08:22:57 +0530 Subject: [PATCH 44/76] fix: Change IBAN Account length from 25 to 30 (#16847) --- erpnext/accounts/doctype/bank_account/bank_account.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/bank_account/bank_account.json b/erpnext/accounts/doctype/bank_account/bank_account.json index 4897097b4ac..84a8e6857c3 100644 --- a/erpnext/accounts/doctype/bank_account/bank_account.json +++ b/erpnext/accounts/doctype/bank_account/bank_account.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 1, @@ -290,7 +291,7 @@ "in_list_view": 1, "in_standard_filter": 0, "label": "IBAN", - "length": 25, + "length": 30, "no_copy": 0, "permlevel": 0, "precision": "", @@ -669,7 +670,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-07-20 13:55:36.996465", + "modified": "2019-03-05 17:56:05.103238", "modified_by": "Administrator", "module": "Accounts", "name": "Bank Account", From ace95d5a67915ea0b621f59d834750a9d450609f Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 18 Mar 2019 11:53:04 +0530 Subject: [PATCH 45/76] feat: added cost center filter in AR/AP reports --- .../doctype/purchase_invoice/purchase_invoice.js | 9 +++++++++ .../doctype/sales_invoice/sales_invoice.js | 9 +++++++++ .../report/accounts_payable/accounts_payable.js | 14 ++++++++++++++ .../accounts_payable_summary.js | 14 ++++++++++++++ .../accounts_receivable/accounts_receivable.js | 14 ++++++++++++++ .../accounts_receivable/accounts_receivable.py | 7 +++++++ .../accounts_receivable_summary.js | 14 ++++++++++++++ erpnext/selling/doctype/customer/customer.py | 15 ++++++++++++--- .../customer_credit_balance.js | 16 +++++++++++++++- .../customer_credit_balance.py | 5 +++-- 10 files changed, 111 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index 5c0e8fa74ad..ac2ce8e6d83 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -510,6 +510,15 @@ frappe.ui.form.on("Purchase Invoice", { } } } + + frm.set_query("cost_center", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); }, onload: function(frm) { diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index a4a5940fc79..9a40d2b17e5 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -564,6 +564,15 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("cost_center", function() { + return { + filters: { + company: frm.doc.company, + is_group: 0 + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Sales Return', diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 0a025f68d53..9dd552f3b85 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -50,6 +50,20 @@ frappe.query_reports["Accounts Payable"] = { "fieldtype": "Link", "options": "Finance Book" }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + } + } + }, { "fieldname":"supplier", "label": __("Supplier"), diff --git a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js index 7823cac89cb..31c0193f339 100644 --- a/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js +++ b/erpnext/accounts/report/accounts_payable_summary/accounts_payable_summary.js @@ -50,6 +50,20 @@ frappe.query_reports["Accounts Payable Summary"] = { "fieldtype": "Link", "options": "Finance Book" }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + } + } + }, { "fieldname":"supplier", "label": __("Supplier"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index bbfee1112f6..dce7e755788 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -50,6 +50,20 @@ frappe.query_reports["Accounts Receivable"] = { "fieldtype": "Link", "options": "Finance Book" }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + } + } + }, { "fieldname":"customer", "label": __("Customer"), diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 0e3317dcaa5..4932ae1327d 100755 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -537,6 +537,13 @@ class ReceivablePayableReport(object): where supplier_group=%s)""") values.append(self.filters.get("supplier_group")) + if self.filters.get("cost_center"): + lft, rgt = frappe.get_cached_value("Cost Center", + self.filters.get("cost_center"), ['lft', 'rgt']) + + conditions.append("""cost_center in (select name from `tabCost Center` where + lft >= {0} and rgt <= {1})""".format(lft, rgt)) + accounts = [d.name for d in frappe.get_all("Account", filters={"account_type": account_type, "company": self.filters.company})] conditions.append("account in (%s)" % ','.join(['%s'] *len(accounts))) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js index a6f1457954c..47b087db8b0 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.js @@ -50,6 +50,20 @@ frappe.query_reports["Accounts Receivable Summary"] = { "fieldtype": "Link", "options": "Finance Book" }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + } + } + }, { "fieldname":"customer", "label": __("Customer"), diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index a8fae7b5a96..ec27498cc7c 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -260,12 +260,21 @@ def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, throw(_("Please contact to the user who have Sales Master Manager {0} role") .format(" / " + credit_controller if credit_controller else "")) -def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False): +def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=False, cost_center=None): # Outstanding based on GL Entries + + cond = "" + if cost_center: + lft, rgt = frappe.get_cached_value("Cost Center", + cost_center, ['lft', 'rgt']) + + cond = """ and cost_center in (select name from `tabCost Center` where + lft >= {0} and rgt <= {1})""".format(lft, rgt) + outstanding_based_on_gle = frappe.db.sql(""" select sum(debit) - sum(credit) - from `tabGL Entry` - where party_type = 'Customer' and party = %s and company=%s""", (customer, company)) + from `tabGL Entry` where party_type = 'Customer' + and party = %s and company=%s {0}""".format(cond), (customer, company), debug=1) outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.js b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.js index 3a99eb0891d..de8abdc498a 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.js +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.js @@ -16,6 +16,20 @@ frappe.query_reports["Customer Credit Balance"] = { "label": __("Customer"), "fieldtype": "Link", "options": "Customer" - } + }, + { + "fieldname":"cost_center", + "label": __("Cost Center"), + "fieldtype": "Link", + "options": "Cost Center", + get_query: () => { + var company = frappe.query_report.get_filter_value('company'); + return { + filters: { + 'company': company + } + } + } + }, ] } diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py index fe58af61c82..a57d9757408 100644 --- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py +++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py @@ -21,9 +21,10 @@ def execute(filters=None): row = [] outstanding_amt = get_customer_outstanding(d.name, filters.get("company"), - ignore_outstanding_sales_order=d.bypass_credit_limit_check_at_sales_order) + ignore_outstanding_sales_order=d.bypass_credit_limit_check_at_sales_order, + cost_center=filters.get("cost_center")) - credit_limit = get_credit_limit(d.name, filters.get("company")) + credit_limit = get_credit_limit(d.name, filters.get("company")) bal = flt(credit_limit) - flt(outstanding_amt) From aa03ea2a56a9a5d513ba35b43629a4cf04861f07 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 18 Mar 2019 12:36:42 +0530 Subject: [PATCH 46/76] disbale pl check for Sales and Purchase Invoice --- erpnext/accounts/doctype/gl_entry/gl_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py index 810b6f79b51..5c76799e4ea 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.py +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py @@ -74,7 +74,8 @@ class GLEntry(Document): def check_pl_account(self): if self.is_opening=='Yes' and \ - frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss": + frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and \ + self.voucher_type not in ['Purchase Invoice', 'Sales Invoice']: frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry") .format(self.voucher_type, self.voucher_no, self.account)) From 1a4bfdd090c0c5f73bde90f81d3783d2b7d0a4c7 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Mon, 28 Jan 2019 16:35:41 +0530 Subject: [PATCH 47/76] feat: selecting parent_company should disable chart_of_accounts based fields --- erpnext/setup/doctype/company/company.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 70e047a4e80..dbfbc5932ea 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -16,6 +16,14 @@ frappe.ui.form.on("Company", { filters: {"is_additional_component": 1} } }); + + frm.set_df_property("create_chart_of_accounts_based_on", "read_only", frm.doc.parent_company ? 1 : 0); + frm.set_df_property("existing_company", "read_only", frm.doc.parent_company ? 1 : 0); + frm.set_query("parent_company", function() { + return { + filters: {"is_group": 1} + } + }); }, company_name: function(frm) { @@ -28,6 +36,12 @@ frappe.ui.form.on("Company", { } }, + parent_company: function(frm) { + if(!frm.doc.parent_company) return; + frm.set_value("create_chart_of_accounts_based_on", "Existing Company"); + frm.set_value("existing_company", frm.doc.parent_company); + }, + date_of_commencement: function(frm) { if(frm.doc.date_of_commencement Date: Mon, 28 Jan 2019 16:39:14 +0530 Subject: [PATCH 48/76] feat: once company is saved, parent_company cannot be selected --- erpnext/setup/doctype/company/company.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index dbfbc5932ea..5592e9bd64d 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -53,8 +53,9 @@ frappe.ui.form.on("Company", { }, refresh: function(frm) { - if(frm.doc.abbr && !frm.doc.__islocal) { + if(!frm.doc.__islocal) { frm.set_df_property("abbr", "read_only", 1); + frm.set_df_property("parent_company", "read_only", 1); } frm.toggle_display('address_html', !frm.doc.__islocal); From 2def228da88043f944053826d4e7a92f15895c6d Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Tue, 29 Jan 2019 12:28:56 +0530 Subject: [PATCH 49/76] feat: allow adding account only if topmost parent company - hidden filter for Parent Company added - Add button overriden with new condition --- .../accounts/doctype/account/account_tree.js | 58 ++++++++++++++++--- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index a9cbdd5dee7..5a80258e71b 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -4,13 +4,40 @@ frappe.treeview_settings["Account"] = { breadcrumbs: "Accounts", title: __("Chart Of Accounts"), get_tree_root: false, - filters: [{ - fieldname: "company", - fieldtype:"Select", - options: erpnext.utils.get_tree_options("company"), - label: __("Company"), - default: erpnext.utils.get_tree_default("company") - }], + filters: [ + { + fieldname: "company", + fieldtype:"Select", + options: erpnext.utils.get_tree_options("company"), + label: __("Company"), + default: erpnext.utils.get_tree_default("company"), + on_change: function() { + var me = frappe.treeview_settings['Account'].treeview; + var company = me.page.fields_dict.company.get_value(); + frappe.call({ + method: "frappe.client.get_value", + args: { + doctype: "Company", + fieldname: "parent_company", + filters: { name: company}, + }, + callback: function(r, rt) { + if(r.message) { + me.page.fields_dict.parent_company.set_value(r.message["parent_company"] || ""); + } + } + }); + } + }, + { + fieldname: "parent_company", + fieldtype:"Data", + fetch_from: "company.parent_company", + label: __("Parent Company"), + hidden: true, + disable_onchange: true + } + ], root_label: "Accounts", get_tree_nodes: 'erpnext.accounts.utils.get_children', add_tree_node: 'erpnext.accounts.utils.add_ac', @@ -42,8 +69,8 @@ frappe.treeview_settings["Account"] = { ], ignore_fields:["parent_account"], onload: function(treeview) { - frappe.treeview_settings['Account'].page = {}; - $.extend(frappe.treeview_settings['Account'].page, treeview.page); + frappe.treeview_settings['Account'].treeview = {}; + $.extend(frappe.treeview_settings['Account'].treeview, treeview); function get_company() { return treeview.page.fields_dict.company.get_value(); } @@ -93,6 +120,19 @@ frappe.treeview_settings["Account"] = { } }, toolbar: [ + { + label:__("Add Child"), + condition: function(node) { + return frappe.boot.user.can_create.indexOf("Account") !== -1 && + !frappe.treeview_settings['Account'].treeview.page.fields_dict.parent_company.get_value() && + node.expandable && !node.hide_add; + }, + click: function(node) { + var me = frappe.treeview_settings['Account'].treeview; + me.new_node(node); + }, + btnClass: "hidden-xs" + }, { condition: function(node) { return !node.root && frappe.boot.user.can_read.indexOf("GL Entry") !== -1 From 0407bf1e005a316322118bcf7043b834183fb788 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 30 Jan 2019 11:12:15 +0530 Subject: [PATCH 50/76] fix: override primary action button, change filter to root company --- .../accounts/doctype/account/account_tree.js | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 5a80258e71b..4bedb3c26c7 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -15,25 +15,23 @@ frappe.treeview_settings["Account"] = { var me = frappe.treeview_settings['Account'].treeview; var company = me.page.fields_dict.company.get_value(); frappe.call({ - method: "frappe.client.get_value", + method: "erpnext.accounts.doctype.account.account.get_root_company", args: { - doctype: "Company", - fieldname: "parent_company", - filters: { name: company}, + company: company, }, callback: function(r, rt) { if(r.message) { - me.page.fields_dict.parent_company.set_value(r.message["parent_company"] || ""); + let root_company = r.message.length ? r.message[0] : ""; + me.page.fields_dict.root_company.set_value(root_company); } } }); } }, { - fieldname: "parent_company", + fieldname: "root_company", fieldtype:"Data", - fetch_from: "company.parent_company", - label: __("Parent Company"), + label: __("Root Company"), hidden: true, disable_onchange: true } @@ -105,6 +103,18 @@ frappe.treeview_settings["Account"] = { } }, + post_render: function(treeview) { + frappe.treeview_settings['Account'].treeview["tree"] = treeview.tree; + treeview.page.set_primary_action(__("New"), function() { + let root_company = treeview.page.fields_dict.root_company.get_value(); + + if(root_company) { + frappe.throw(__("Please add the account to root level Company - ") + root_company); + } else { + treeview.new_node(); + } + }, "octicon octicon-plus"); + }, onrender: function(node) { if(frappe.boot.user.can_read.indexOf("GL Entry") !== -1){ var dr_or_cr = node.data.balance < 0 ? "Cr" : "Dr"; @@ -124,12 +134,12 @@ frappe.treeview_settings["Account"] = { label:__("Add Child"), condition: function(node) { return frappe.boot.user.can_create.indexOf("Account") !== -1 && - !frappe.treeview_settings['Account'].treeview.page.fields_dict.parent_company.get_value() && + !frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() && node.expandable && !node.hide_add; }, click: function(node) { var me = frappe.treeview_settings['Account'].treeview; - me.new_node(node); + me.new_node(); }, btnClass: "hidden-xs" }, @@ -143,7 +153,7 @@ frappe.treeview_settings["Account"] = { "account": node.label, "from_date": frappe.sys_defaults.year_start_date, "to_date": frappe.sys_defaults.year_end_date, - "company": frappe.treeview_settings['Account'].page.fields_dict.company.get_value() + "company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value() }; frappe.set_route("query-report", "General Ledger"); }, From 88c990901d5a060a7d2e9c4db27c30d95b064d8f Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 30 Jan 2019 11:12:28 +0530 Subject: [PATCH 51/76] fix: added validation to if account is being added to child company --- erpnext/accounts/doctype/account/account.py | 14 ++++++++++++++ erpnext/setup/doctype/company/company.py | 1 + 2 files changed, 15 insertions(+) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index 5d504b94be6..d699146049f 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -34,6 +34,7 @@ class Account(NestedSet): return self.validate_parent() self.validate_root_details() + self.validate_root_company() validate_field_number("Account", self.name, self.account_number, self.company, "account_number") self.validate_group_or_ledger() self.set_root_and_report_type() @@ -90,6 +91,13 @@ class Account(NestedSet): if not self.parent_account and not self.is_group: frappe.throw(_("Root Account must be a group")) + def validate_root_company(self): + # fetch all ancestors in top-down hierarchy + if frappe.local.flags.ignore_root_company_validation: return + ancestors = get_root_company(self.company) + if ancestors: + frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) + def validate_group_or_ledger(self): if self.get("__islocal"): return @@ -250,3 +258,9 @@ def merge_account(old, new, is_group, root_type, company): frappe.rename_doc("Account", old, new, merge=1, ignore_permissions=1) return new + +@frappe.whitelist() +def get_root_company(company): + # return the topmost company in the hierarchy + ancestors = frappe.utils.nestedset.get_ancestors_of('Company', company, "lft asc") + return [ancestors[0]] if ancestors else [] diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index c49c2642511..33361e81d46 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -141,6 +141,7 @@ class Company(NestedSet): def create_default_accounts(self): from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import create_charts + frappe.local.flags.ignore_root_company_validation = True create_charts(self.name, self.chart_of_accounts, self.existing_company) frappe.db.set(self, "default_receivable_account", frappe.db.get_value("Account", From d787ef8b8e504a499c5fda283861a3f17910f781 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 30 Jan 2019 14:00:56 +0530 Subject: [PATCH 52/76] feat: sync account created for a group company to all its descendants --- erpnext/accounts/doctype/account/account.py | 30 ++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index d699146049f..dea82d8ed6c 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe.utils import cint, cstr from frappe import throw, _ -from frappe.utils.nestedset import NestedSet +from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_of class RootNotEditable(frappe.ValidationError): pass class BalanceMismatchError(frappe.ValidationError): pass @@ -34,7 +34,6 @@ class Account(NestedSet): return self.validate_parent() self.validate_root_details() - self.validate_root_company() validate_field_number("Account", self.name, self.account_number, self.company, "account_number") self.validate_group_or_ledger() self.set_root_and_report_type() @@ -42,6 +41,7 @@ class Account(NestedSet): self.validate_frozen_accounts_modifier() self.validate_balance_must_be_debit_or_credit() self.validate_account_currency() + self.validate_root_company_and_sync_account_to_children() def validate_parent(self): """Fetch Parent Details and validate parent account""" @@ -91,12 +91,30 @@ class Account(NestedSet): if not self.parent_account and not self.is_group: frappe.throw(_("Root Account must be a group")) - def validate_root_company(self): - # fetch all ancestors in top-down hierarchy - if frappe.local.flags.ignore_root_company_validation: return + def validate_root_company_and_sync_account_to_children(self): + # ignore validation while creating new compnay or while syncing to child companies + if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation: + return + ancestors = get_root_company(self.company) if ancestors: frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) + else: + descendants = get_descendants_of('Company', self.company) + acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") + + acc_name_map = {} + for d in frappe.db.get_values('Account', + {"company": ["in", descendants], "account_name": acc_name}, + ["company", "name"], as_dict=True): + acc_name_map[d["company"]] = d["name"] + + for company in descendants: + doc = frappe.copy_doc(self) + doc.flags.ignore_root_company_validation = True + doc.update({"company": company, "account_currency": None, + "parent": acc_name_map[company], "parent_account": acc_name_map[company]}) + doc.save() def validate_group_or_ledger(self): if self.get("__islocal"): @@ -262,5 +280,5 @@ def merge_account(old, new, is_group, root_type, company): @frappe.whitelist() def get_root_company(company): # return the topmost company in the hierarchy - ancestors = frappe.utils.nestedset.get_ancestors_of('Company', company, "lft asc") + ancestors = get_ancestors_of('Company', company, "lft asc") return [ancestors[0]] if ancestors else [] From 73acb8c83702e636a4b126add7a2181ddad2a012 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 13 Feb 2019 16:31:37 +0530 Subject: [PATCH 53/76] fix: set chart of accounts based on parent company on server side --- erpnext/setup/doctype/company/company.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 33361e81d46..ad9d64baf53 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -39,6 +39,7 @@ class Company(NestedSet): self.validate_coa_input() self.validate_perpetual_inventory() self.check_country_change() + self.set_chart_of_accounts() def validate_abbr(self): if not self.abbr: @@ -174,6 +175,12 @@ class Company(NestedSet): self.country != frappe.get_cached_value('Company', self.name, 'country'): frappe.flags.country_change = True + def set_chart_of_accounts(self): + ''' If parent company is set, chart of accounts will be based on that company ''' + if self.parent_company: + self.create_chart_of_accounts_based_on = "Existing Company" + self.existing_company = self.parent_company + def set_default_accounts(self): self._set_default_account("default_cash_account", "Cash") self._set_default_account("default_bank_account", "Bank") From f71cb8dc6db03ee5ddc50aac58ef52b4fc783c71 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 13 Feb 2019 16:32:04 +0530 Subject: [PATCH 54/76] fix: return if no descendants found --- erpnext/accounts/doctype/account/account.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index dea82d8ed6c..e241fc4a3af 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -101,9 +101,10 @@ class Account(NestedSet): frappe.throw(_("Please add the account to root level Company - %s" % ancestors[0])) else: descendants = get_descendants_of('Company', self.company) - acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") + if not descendants: return acc_name_map = {} + acc_name = frappe.db.get_value('Account', self.parent_account, "account_name") for d in frappe.db.get_values('Account', {"company": ["in", descendants], "account_name": acc_name}, ["company", "name"], as_dict=True): From cc65447e62d768354984bb042e6f3a01043fc031 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 13 Feb 2019 16:32:50 +0530 Subject: [PATCH 55/76] fix: chart of accounts field toggling improv --- erpnext/setup/doctype/company/company.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 5592e9bd64d..aff4baf9314 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -17,8 +17,6 @@ frappe.ui.form.on("Company", { } }); - frm.set_df_property("create_chart_of_accounts_based_on", "read_only", frm.doc.parent_company ? 1 : 0); - frm.set_df_property("existing_company", "read_only", frm.doc.parent_company ? 1 : 0); frm.set_query("parent_company", function() { return { filters: {"is_group": 1} @@ -37,9 +35,10 @@ frappe.ui.form.on("Company", { }, parent_company: function(frm) { - if(!frm.doc.parent_company) return; - frm.set_value("create_chart_of_accounts_based_on", "Existing Company"); - frm.set_value("existing_company", frm.doc.parent_company); + var bool = frm.doc.parent_company ? true : false; + frm.set_value('create_chart_of_accounts_based_on', bool ? "Existing Company" : ""); + frm.set_value('existing_company', bool ? frm.doc.parent_company : ""); + disbale_coa_fields(frm, bool); }, date_of_commencement: function(frm) { @@ -54,8 +53,9 @@ frappe.ui.form.on("Company", { refresh: function(frm) { if(!frm.doc.__islocal) { - frm.set_df_property("abbr", "read_only", 1); + frm.doc.abbr && frm.set_df_property("abbr", "read_only", 1); frm.set_df_property("parent_company", "read_only", 1); + disbale_coa_fields(frm); } frm.toggle_display('address_html', !frm.doc.__islocal); @@ -271,3 +271,9 @@ erpnext.company.set_custom_query = function(frm, v) { } }); } + +var disbale_coa_fields = function(frm, bool=true) { + frm.set_df_property("create_chart_of_accounts_based_on", "read_only", bool); + frm.set_df_property("chart_of_accounts", "read_only", bool); + frm.set_df_property("existing_company", "read_only", bool); +}; \ No newline at end of file From 11bba571afb7a6bd49e5ac6bed623bdc8149e7be Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 13 Feb 2019 16:33:28 +0530 Subject: [PATCH 56/76] test case added to check account syncing --- .../accounts/doctype/account/test_account.py | 13 +++++++ .../setup/doctype/company/test_records.json | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py index acaa0966a20..4c057d95492 100644 --- a/erpnext/accounts/doctype/account/test_account.py +++ b/erpnext/accounts/doctype/account/test_account.py @@ -97,6 +97,19 @@ class TestAccount(unittest.TestCase): self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\ "Softwares - _TC", doc.is_group, doc.root_type, doc.company) + def test_account_sync(self): + del frappe.local.flags["ignore_root_company_validation"] + acc = frappe.new_doc("Account") + acc.account_name = "Test Sync Account" + acc.parent_account = "Temporary Accounts - _TC3" + acc.company = "_Test Company 3" + acc.insert() + + acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"}) + acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"}) + self.assertEqual(acc_tc_4, "Test Sync Account - _TC4") + self.assertEqual(acc_tc_5, "Test Sync Account - _TC5") + def _make_test_records(verbose): from frappe.test_runner import make_test_objects diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index 7e26ca32074..bf9d4bdd190 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -28,5 +28,39 @@ "domain": "Retail", "chart_of_accounts": "Standard", "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC3", + "company_name": "_Test Company 3", + "is_group": 1, + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC4", + "company_name": "_Test Company 4", + "parent_company": "_Test Company 3", + "is_group": 1, + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC5", + "company_name": "_Test Company 5", + "parent_company": "_Test Company 4", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" } ] From 01086ff60c54a5463f23f747c2681ac45a531ff9 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 13 Feb 2019 16:33:42 +0530 Subject: [PATCH 57/76] codacy fixes --- erpnext/accounts/doctype/account/account_tree.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/account/account_tree.js b/erpnext/accounts/doctype/account/account_tree.js index 4bedb3c26c7..df0486cc271 100644 --- a/erpnext/accounts/doctype/account/account_tree.js +++ b/erpnext/accounts/doctype/account/account_tree.js @@ -19,7 +19,7 @@ frappe.treeview_settings["Account"] = { args: { company: company, }, - callback: function(r, rt) { + callback: function(r) { if(r.message) { let root_company = r.message.length ? r.message[0] : ""; me.page.fields_dict.root_company.set_value(root_company); @@ -137,7 +137,7 @@ frappe.treeview_settings["Account"] = { !frappe.treeview_settings['Account'].treeview.page.fields_dict.root_company.get_value() && node.expandable && !node.hide_add; }, - click: function(node) { + click: function() { var me = frappe.treeview_settings['Account'].treeview; me.new_node(); }, From d4e4316d0b9432a0b4ed36a54cdd97bc3a49d2fc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 18 Mar 2019 10:53:43 +0530 Subject: [PATCH 58/76] fix: test cases and codacy --- erpnext/accounts/doctype/account/account.py | 2 + .../hr/doctype/staffing_plan/staffing_plan.py | 10 ++ .../staffing_plan/test_staffing_plan.py | 10 +- .../setup/doctype/company/test_records.json | 128 +++++++++--------- 4 files changed, 81 insertions(+), 69 deletions(-) diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py index e241fc4a3af..427f3dba20d 100644 --- a/erpnext/accounts/doctype/account/account.py +++ b/erpnext/accounts/doctype/account/account.py @@ -116,6 +116,8 @@ class Account(NestedSet): doc.update({"company": company, "account_currency": None, "parent": acc_name_map[company], "parent_account": acc_name_map[company]}) doc.save() + frappe.msgprint(_("Account {0} is added in the child company {1}") + .format(doc.name, company)) def validate_group_or_ledger(self): if self.get("__islocal"): diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 70e185cde56..83e53135ef4 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -20,6 +20,7 @@ class StaffingPlan(Document): self.total_estimated_budget = 0 for detail in self.get("staffing_details"): + self.set_vacancies(detail) self.validate_overlap(detail) self.validate_with_subsidiary_plans(detail) self.validate_with_parent_plan(detail) @@ -39,6 +40,15 @@ class StaffingPlan(Document): else: detail.vacancies = detail.number_of_positions = detail.total_estimated_cost = 0 self.total_estimated_budget += detail.total_estimated_cost + def set_vacancies(self, row): + if not row.vacancies: + current_openings = 0 + for field in ['current_count', 'current_openings']: + if row.get(field): + current_openings += row.get(field) + + row.vacancies = row.number_of_positions - current_openings + def validate_overlap(self, staffing_plan_detail): # Validate if any submitted Staffing Plan exist for any Designations in this plan # and spd.vacancies>0 ? diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py index 66d9cdd07a5..22dba99af00 100644 --- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.py @@ -18,7 +18,7 @@ class TestStaffingPlan(unittest.TestCase): if frappe.db.exists("Staffing Plan", "Test"): return staffing_plan = frappe.new_doc("Staffing Plan") - staffing_plan.company = "_Test Company 3" + staffing_plan.company = "_Test Company 10" staffing_plan.name = "Test" staffing_plan.from_date = nowdate() staffing_plan.to_date = add_days(nowdate(), 10) @@ -67,7 +67,7 @@ class TestStaffingPlan(unittest.TestCase): if frappe.db.exists("Staffing Plan", "Test 1"): return staffing_plan = frappe.new_doc("Staffing Plan") - staffing_plan.company = "_Test Company 3" + staffing_plan.company = "_Test Company 10" staffing_plan.name = "Test 1" staffing_plan.from_date = nowdate() staffing_plan.to_date = add_days(nowdate(), 10) @@ -85,11 +85,11 @@ def _set_up(): make_company() def make_company(): - if frappe.db.exists("Company", "_Test Company 3"): + if frappe.db.exists("Company", "_Test Company 10"): return company = frappe.new_doc("Company") - company.company_name = "_Test Company 3" - company.abbr = "_TC3" + company.company_name = "_Test Company 10" + company.abbr = "_TC10" company.parent_company = "_Test Company" company.default_currency = "INR" company.country = "India" diff --git a/erpnext/setup/doctype/company/test_records.json b/erpnext/setup/doctype/company/test_records.json index bf9d4bdd190..58d8b5c3346 100644 --- a/erpnext/setup/doctype/company/test_records.json +++ b/erpnext/setup/doctype/company/test_records.json @@ -1,66 +1,66 @@ [ - { - "abbr": "_TC", - "company_name": "_Test Company", - "country": "India", - "default_currency": "INR", - "doctype": "Company", - "domain": "Manufacturing", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - }, - { - "abbr": "_TC1", - "company_name": "_Test Company 1", - "country": "United States", - "default_currency": "USD", - "doctype": "Company", - "domain": "Retail", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - }, - { - "abbr": "_TC2", - "company_name": "_Test Company 2", - "default_currency": "EUR", - "country": "Germany", - "doctype": "Company", - "domain": "Retail", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - }, - { - "abbr": "_TC3", - "company_name": "_Test Company 3", - "is_group": 1, - "country": "India", - "default_currency": "INR", - "doctype": "Company", - "domain": "Manufacturing", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - }, - { - "abbr": "_TC4", - "company_name": "_Test Company 4", - "parent_company": "_Test Company 3", - "is_group": 1, - "country": "India", - "default_currency": "INR", - "doctype": "Company", - "domain": "Manufacturing", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - }, - { - "abbr": "_TC5", - "company_name": "_Test Company 5", - "parent_company": "_Test Company 4", - "country": "India", - "default_currency": "INR", - "doctype": "Company", - "domain": "Manufacturing", - "chart_of_accounts": "Standard", - "default_holiday_list": "_Test Holiday List" - } + { + "abbr": "_TC", + "company_name": "_Test Company", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC1", + "company_name": "_Test Company 1", + "country": "United States", + "default_currency": "USD", + "doctype": "Company", + "domain": "Retail", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC2", + "company_name": "_Test Company 2", + "default_currency": "EUR", + "country": "Germany", + "doctype": "Company", + "domain": "Retail", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC3", + "company_name": "_Test Company 3", + "is_group": 1, + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC4", + "company_name": "_Test Company 4", + "parent_company": "_Test Company 3", + "is_group": 1, + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + }, + { + "abbr": "_TC5", + "company_name": "_Test Company 5", + "parent_company": "_Test Company 4", + "country": "India", + "default_currency": "INR", + "doctype": "Company", + "domain": "Manufacturing", + "chart_of_accounts": "Standard", + "default_holiday_list": "_Test Holiday List" + } ] From ac199580af30b82bbb5dd77a5c25b4483b4536bb Mon Sep 17 00:00:00 2001 From: Himanshu Date: Mon, 18 Mar 2019 15:44:54 +0530 Subject: [PATCH 59/76] fix(Customer Ledger): ambiguous error in where clause (#16914) fix for error "InternalError: (1052, u"Column 'company' in where clause is ambiguous")" in Customer Ledger Summary --- .../report/customer_ledger_summary/customer_ledger_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py index e33bd61411e..eceabf56af7 100644 --- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py +++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py @@ -195,7 +195,7 @@ class PartyLedgerSummaryReport(object): conditions = [""] if self.filters.company: - conditions.append("company=%(company)s") + conditions.append("gle.company=%(company)s") self.filters.company_finance_book = erpnext.get_default_finance_book(self.filters.company) From 5b43e2f31a187594a16cd52083fe2c8438856285 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 18 Mar 2019 17:00:44 +0530 Subject: [PATCH 60/76] code cleanup --- erpnext/selling/doctype/customer/customer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py index ec27498cc7c..ba20cbc73ee 100644 --- a/erpnext/selling/doctype/customer/customer.py +++ b/erpnext/selling/doctype/customer/customer.py @@ -274,7 +274,7 @@ def get_customer_outstanding(customer, company, ignore_outstanding_sales_order=F outstanding_based_on_gle = frappe.db.sql(""" select sum(debit) - sum(credit) from `tabGL Entry` where party_type = 'Customer' - and party = %s and company=%s {0}""".format(cond), (customer, company), debug=1) + and party = %s and company=%s {0}""".format(cond), (customer, company)) outstanding_based_on_gle = flt(outstanding_based_on_gle[0][0]) if outstanding_based_on_gle else 0 From f060831cced725e26fe18a85dd7c24c6fa51b058 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Mon, 18 Mar 2019 18:04:34 +0530 Subject: [PATCH 61/76] fix(escaping): make_italian_localization_fields.py --- erpnext/patches/v11_0/make_italian_localization_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/patches/v11_0/make_italian_localization_fields.py b/erpnext/patches/v11_0/make_italian_localization_fields.py index 44a281f86f4..d9a7b35e77e 100644 --- a/erpnext/patches/v11_0/make_italian_localization_fields.py +++ b/erpnext/patches/v11_0/make_italian_localization_fields.py @@ -19,7 +19,7 @@ def execute(): # Set state codes condition = "" for state, code in state_codes.items(): - condition += " when '{0}' then '{1}'".format(frappe.db.escape(state), frappe.db.escape(code)) + condition += " when {0} then {1}".format(frappe.db.escape(state), frappe.db.escape(code)) if condition: condition = "state_code = (case state {0} end),".format(condition) From 642a5b69f58337db0911704668e532e8f0457ac0 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 18 Mar 2019 21:53:33 +0530 Subject: [PATCH 62/76] fix: change customer to supplier --- .../report/supplier_ledger_summary/supplier_ledger_summary.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js index 6fd16f20908..f81297760ed 100644 --- a/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js +++ b/erpnext/accounts/report/supplier_ledger_summary/supplier_ledger_summary.js @@ -35,9 +35,9 @@ frappe.query_reports["Supplier Ledger Summary"] = { }, { "fieldname":"party", - "label": __("Customer"), + "label": __("Supplier"), "fieldtype": "Link", - "options": "Customer", + "options": "Supplier", on_change: () => { var party = frappe.query_report.get_filter_value('party'); if (party) { From 5f8b358fd4746394cbe5c349b0e5bd1280c5af3a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 Mar 2019 11:48:32 +0530 Subject: [PATCH 63/76] Website: Product Configurator and Bootstrap 4 (#15965) - Refactored Homepage with customisable Hero Section - New Homepage Section to add content on Homepage as cards or using Custom HTML - Products page at "/all-products" with customisable filters - Item Configure dialog to find an Item Variant filtered by attribute values - Contact Us dialog on Item page - Customisable Item page content using the Website Content field --- erpnext/config/website.py | 10 + erpnext/hooks.py | 3 +- erpnext/hr/doctype/job_opening/job_opening.py | 23 + .../templates/job_opening_row.html | 9 + erpnext/patches.txt | 2 +- .../add_variant_of_in_item_attribute_table.py | 8 + .../v12_0/set_default_homepage_type.py | 4 + erpnext/portal/doctype/homepage/homepage.js | 7 +- erpnext/portal/doctype/homepage/homepage.json | 277 +++- erpnext/portal/doctype/homepage/homepage.py | 2 - .../portal/doctype/homepage/test_homepage.py | 19 + .../doctype/homepage_section/__init__.py | 0 .../homepage_section/homepage_section.js | 6 + .../homepage_section/homepage_section.json | 336 +++++ .../homepage_section/homepage_section.py | 12 + .../homepage_section/test_homepage_section.py | 76 + .../doctype/homepage_section_card/__init__.py | 0 .../homepage_section_card.json | 203 +++ .../homepage_section_card.py | 9 + .../products_settings/products_settings.js | 11 + .../products_settings/products_settings.json | 604 ++++---- .../products_settings/products_settings.py | 21 + .../doctype/website_attribute/__init__.py | 0 .../website_attribute/website_attribute.json | 76 + .../website_attribute/website_attribute.py | 9 + .../doctype/website_filter_field/__init__.py | 0 .../website_filter_field.json | 76 + .../website_filter_field.py | 9 + .../portal/product_configurator/__init__.py | 0 .../item_variants_cache.py | 94 ++ .../test_product_configurator.py | 84 ++ erpnext/portal/product_configurator/utils.py | 402 ++++++ erpnext/public/build.json | 2 +- erpnext/public/js/shopping_cart.js | 10 +- erpnext/public/js/templates/address_list.html | 4 +- erpnext/public/js/templates/contact_list.html | 4 +- erpnext/public/js/website_theme.js | 17 + erpnext/public/less/products.less | 69 + erpnext/public/less/website.less | 29 +- erpnext/public/node_modules | 1 + erpnext/public/scss/website.scss | 53 + .../quotation_item/quotation_item.json | 146 +- .../setup/doctype/item_group/item_group.py | 20 +- .../operations/default_website.py | 2 +- erpnext/shopping_cart/cart.py | 55 +- .../shopping_cart_settings.json | 1238 +++++++++-------- erpnext/shopping_cart/product_info.py | 4 +- erpnext/stock/doctype/item/item.js | 4 + erpnext/stock/doctype/item/item.json | 74 +- erpnext/stock/doctype/item/item.py | 101 +- erpnext/stock/doctype/item/test_records.json | 3 +- .../doctype/item_attribute/item_attribute.js | 6 + .../item_attribute/item_attribute.json | 101 +- .../item_variant_attribute.json | 57 +- erpnext/templates/generators/item.html | 143 -- erpnext/templates/generators/item/item.html | 32 + .../generators/item/item_add_to_cart.html | 67 + .../generators/item/item_configure.html | 23 + .../generators/item/item_configure.js | 318 +++++ .../generators/item/item_details.html | 22 + .../templates/generators/item/item_image.html | 107 ++ .../templates/generators/item/item_inquiry.js | 70 + .../generators/item/item_specifications.html | 16 + erpnext/templates/generators/item_group.html | 41 +- erpnext/templates/includes/address_row.html | 12 +- erpnext/templates/includes/cart.js | 97 +- .../templates/includes/cart/address_card.html | 12 + .../templates/includes/cart/cart_address.html | 161 ++- .../templates/includes/cart/cart_items.html | 69 +- .../includes/footer/footer_extension.html | 17 +- .../includes/footer/footer_powered.html | 3 +- erpnext/templates/includes/macros.html | 43 +- .../includes/navbar/navbar_items.html | 12 +- .../includes/order/order_macros.html | 8 +- .../templates/includes/order/order_taxes.html | 44 +- erpnext/templates/includes/product_page.js | 215 --- erpnext/templates/pages/cart.html | 138 +- erpnext/templates/pages/help.html | 2 +- erpnext/templates/pages/home.css | 9 + erpnext/templates/pages/home.html | 130 +- erpnext/templates/pages/home.py | 45 +- .../pages/material_request_info.html | 4 +- .../pages/non_profit/leave-chapter.html | 2 +- erpnext/templates/pages/order.html | 40 +- erpnext/templates/pages/product_search.html | 2 +- erpnext/templates/pages/projects.html | 4 +- erpnext/templates/pages/task_info.html | 4 +- erpnext/www/all-products/__init__.py | 0 erpnext/www/all-products/index.html | 163 +++ erpnext/www/all-products/index.js | 161 +++ erpnext/www/all-products/index.py | 26 + erpnext/www/all-products/item_row.html | 24 + erpnext/www/all-products/not_found.html | 1 + 93 files changed, 5057 insertions(+), 1622 deletions(-) create mode 100644 erpnext/hr/doctype/job_opening/templates/job_opening_row.html create mode 100644 erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py create mode 100644 erpnext/patches/v12_0/set_default_homepage_type.py create mode 100644 erpnext/portal/doctype/homepage/test_homepage.py create mode 100644 erpnext/portal/doctype/homepage_section/__init__.py create mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.js create mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.json create mode 100644 erpnext/portal/doctype/homepage_section/homepage_section.py create mode 100644 erpnext/portal/doctype/homepage_section/test_homepage_section.py create mode 100644 erpnext/portal/doctype/homepage_section_card/__init__.py create mode 100644 erpnext/portal/doctype/homepage_section_card/homepage_section_card.json create mode 100644 erpnext/portal/doctype/homepage_section_card/homepage_section_card.py create mode 100644 erpnext/portal/doctype/website_attribute/__init__.py create mode 100644 erpnext/portal/doctype/website_attribute/website_attribute.json create mode 100644 erpnext/portal/doctype/website_attribute/website_attribute.py create mode 100644 erpnext/portal/doctype/website_filter_field/__init__.py create mode 100644 erpnext/portal/doctype/website_filter_field/website_filter_field.json create mode 100644 erpnext/portal/doctype/website_filter_field/website_filter_field.py create mode 100644 erpnext/portal/product_configurator/__init__.py create mode 100644 erpnext/portal/product_configurator/item_variants_cache.py create mode 100644 erpnext/portal/product_configurator/test_product_configurator.py create mode 100644 erpnext/portal/product_configurator/utils.py create mode 100644 erpnext/public/js/website_theme.js create mode 100644 erpnext/public/less/products.less create mode 120000 erpnext/public/node_modules create mode 100644 erpnext/public/scss/website.scss create mode 100644 erpnext/stock/doctype/item_attribute/item_attribute.js delete mode 100644 erpnext/templates/generators/item.html create mode 100644 erpnext/templates/generators/item/item.html create mode 100644 erpnext/templates/generators/item/item_add_to_cart.html create mode 100644 erpnext/templates/generators/item/item_configure.html create mode 100644 erpnext/templates/generators/item/item_configure.js create mode 100644 erpnext/templates/generators/item/item_details.html create mode 100644 erpnext/templates/generators/item/item_image.html create mode 100644 erpnext/templates/generators/item/item_inquiry.js create mode 100644 erpnext/templates/generators/item/item_specifications.html create mode 100644 erpnext/templates/includes/cart/address_card.html delete mode 100644 erpnext/templates/includes/product_page.js create mode 100644 erpnext/templates/pages/home.css create mode 100644 erpnext/www/all-products/__init__.py create mode 100644 erpnext/www/all-products/index.html create mode 100644 erpnext/www/all-products/index.js create mode 100644 erpnext/www/all-products/index.py create mode 100644 erpnext/www/all-products/item_row.html create mode 100644 erpnext/www/all-products/not_found.html diff --git a/erpnext/config/website.py b/erpnext/config/website.py index 59e7d404d44..d31b0578812 100644 --- a/erpnext/config/website.py +++ b/erpnext/config/website.py @@ -11,6 +11,16 @@ def get_data(): "name": "Homepage", "description": _("Settings for website homepage"), }, + { + "type": "doctype", + "name": "Homepage Section", + "description": _("Add cards or custom sections on homepage"), + }, + { + "type": "doctype", + "name": "Products Settings", + "description": _("Settings for website product listing"), + }, { "type": "doctype", "name": "Shopping Cart Settings", diff --git a/erpnext/hooks.py b/erpnext/hooks.py index a6876ac1f3f..d28666c48ba 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -22,7 +22,8 @@ web_include_css = "assets/css/erpnext-web.css" doctype_js = { "Communication": "public/js/communication.js", - "Event": "public/js/event.js" + "Event": "public/js/event.js", + "Website Theme": "public/js/website_theme.js" } welcome_email = "erpnext.setup.utils.welcome_email" diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py index 4fc2ac1deda..00883d75f19 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.py +++ b/erpnext/hr/doctype/job_opening/job_opening.py @@ -53,3 +53,26 @@ class JobOpening(WebsiteGenerator): def get_list_context(context): context.title = _("Jobs") context.introduction = _('Current Job Openings') + context.get_list = get_job_openings + +def get_job_openings(doctype, txt=None, filters=None, limit_start=0, limit_page_length=20, order_by=None): + fields = ['name', 'status', 'job_title', 'description'] + + filters = filters or {} + filters.update({ + 'status': 'Open' + }) + + if txt: + filters.update({ + 'job_title': ['like', '%{0}%'.format(txt)], + 'description': ['like', '%{0}%'.format(txt)] + }) + + return frappe.get_all(doctype, + filters, + fields, + start=limit_start, + page_length=limit_page_length, + order_by=order_by + ) diff --git a/erpnext/hr/doctype/job_opening/templates/job_opening_row.html b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html new file mode 100644 index 00000000000..5da8cc82a2e --- /dev/null +++ b/erpnext/hr/doctype/job_opening/templates/job_opening_row.html @@ -0,0 +1,9 @@ +
+

{{ doc.job_title }}

+

{{ doc.description }}

+ +
diff --git a/erpnext/patches.txt b/erpnext/patches.txt index b7e673da340..cb1e77c73f3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -577,6 +577,7 @@ erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 erpnext.patches.v11_0.update_delivery_trip_status erpnext.patches.v11_0.set_missing_gst_hsn_code erpnext.patches.v11_0.rename_bom_wo_fields +erpnext.patches.v12_0.set_default_homepage_type erpnext.patches.v11_0.rename_additional_salary_component_additional_salary erpnext.patches.v11_0.renamed_from_to_fields_in_project erpnext.patches.v11_0.add_permissions_in_gst_settings @@ -584,5 +585,4 @@ erpnext.patches.v11_1.setup_guardian_role execute:frappe.delete_doc('DocType', 'Notification Control') erpnext.patches.v11_0.remove_barcodes_field_from_copy_fields_to_variants erpnext.patches.v12_0.set_task_status -erpnext.patches.v10_0.item_barcode_childtable_migrate # 16-02-2019 erpnext.patches.v11_0.make_italian_localization_fields # 01-03-2019 diff --git a/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py new file mode 100644 index 00000000000..bc6119067cf --- /dev/null +++ b/erpnext/patches/v12_0/add_variant_of_in_item_attribute_table.py @@ -0,0 +1,8 @@ +import frappe + +def execute(): + frappe.db.sql(''' + UPDATE `tabItem Variant Attribute` t1 + INNER JOIN `tabItem` t2 ON t2.name = t1.parent + SET t1.variant_of = t2.variant_of + ''') diff --git a/erpnext/patches/v12_0/set_default_homepage_type.py b/erpnext/patches/v12_0/set_default_homepage_type.py new file mode 100644 index 00000000000..241e4b9b5e1 --- /dev/null +++ b/erpnext/patches/v12_0/set_default_homepage_type.py @@ -0,0 +1,4 @@ +import frappe + +def execute(): + frappe.db.set_value('Homepage', 'Homepage', 'hero_section_based_on', 'Default') \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js index 0b07814f759..ca34d695764 100644 --- a/erpnext/portal/doctype/homepage/homepage.js +++ b/erpnext/portal/doctype/homepage/homepage.js @@ -11,7 +11,12 @@ frappe.ui.form.on('Homepage', { }, refresh: function(frm) { - + frm.add_custom_button(__('Set Meta Tags'), () => { + frappe.utils.set_meta_tag('home'); + }); + frm.add_custom_button(__('Customize Homepage Sections'), () => { + frappe.set_route('List', 'Homepage Section', 'List'); + }); }, }); diff --git a/erpnext/portal/doctype/homepage/homepage.json b/erpnext/portal/doctype/homepage/homepage.json index 81433b1c5d8..ad27278dc69 100644 --- a/erpnext/portal/doctype/homepage/homepage.json +++ b/erpnext/portal/doctype/homepage/homepage.json @@ -1,5 +1,7 @@ { "allow_copy": 0, + "allow_events_in_timeline": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, "autoname": "", @@ -10,18 +12,24 @@ "doctype": "DocType", "document_type": "Setup", "editable_grid": 0, + "engine": "InnoDB", "fields": [ { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "company", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, "label": "Company", "length": 0, "no_copy": 0, @@ -31,24 +39,63 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "title", - "fieldtype": "Data", + "columns": 0, + "fieldname": "hero_section_based_on", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, - "label": "TItle", + "in_standard_filter": 0, + "label": "Hero Section Based On", + "length": 0, + "no_copy": 0, + "options": "Default\nSlideshow\nHomepage Section", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_2", + "fieldtype": "Column Break", + "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, "length": 0, "no_copy": 0, "permlevel": 0, @@ -56,16 +103,88 @@ "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, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "title", + "fieldtype": "Data", + "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": "Title", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", + "fieldname": "section_break_4", + "fieldtype": "Section Break", + "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": "Hero Section", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.hero_section_based_on === 'Default'", "description": "Company Tagline for website homepage", "fieldname": "tag_line", "fieldtype": "Data", @@ -73,7 +192,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, "label": "Tag Line", "length": 0, "no_copy": 0, @@ -82,16 +203,22 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.hero_section_based_on === 'Default'", "description": "Company Description for website homepage", "fieldname": "description", "fieldtype": "Text", @@ -99,7 +226,9 @@ "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, - "in_list_view": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, "label": "Description", "length": 0, "no_copy": 0, @@ -108,23 +237,133 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.hero_section_based_on === 'Default'", + "fieldname": "hero_image", + "fieldtype": "Attach Image", + "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": "Hero Image", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.hero_section_based_on === 'Slideshow'", + "description": "", + "fieldname": "slideshow", + "fieldtype": "Link", + "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": "Homepage Slideshow", + "length": 0, + "no_copy": 0, + "options": "Website Slideshow", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.hero_section_based_on === 'Homepage Section'", + "fieldname": "hero_section", + "fieldtype": "Link", + "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": "Homepage Section", + "length": 0, + "no_copy": 0, + "options": "Homepage Section", + "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, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "", "fieldname": "products_section", "fieldtype": "Section Break", "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": "Products", "length": 0, "no_copy": 0, @@ -133,16 +372,21 @@ "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, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "/products", "fieldname": "products_url", "fieldtype": "Data", @@ -150,7 +394,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": "URL for \"All Products\"", "length": 0, "no_copy": 0, @@ -159,16 +405,21 @@ "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, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_in_quick_entry": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "description": "Products to be shown on website homepage", "fieldname": "products", "fieldtype": "Table", @@ -176,7 +427,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": "Products", "length": 0, "no_copy": 0, @@ -186,14 +439,17 @@ "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, + "translatable": 0, "unique": 0, "width": "40px" } ], + "has_web_view": 0, "hide_heading": 0, "hide_toolbar": 0, "idx": 0, @@ -203,7 +459,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-08-29 01:28:00.961623", + "modified": "2019-03-02 23:12:59.676202", "modified_by": "Administrator", "module": "Portal", "name": "Homepage", @@ -212,7 +468,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -232,7 +487,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -254,8 +508,11 @@ "quick_entry": 0, "read_only": 0, "read_only_onload": 0, + "show_name_in_global_search": 0, "sort_field": "modified", "sort_order": "DESC", "title_field": "company", - "track_seen": 0 + "track_changes": 1, + "track_seen": 0, + "track_views": 0 } \ No newline at end of file diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py index f8f73fdcd0a..4e4d4774abf 100644 --- a/erpnext/portal/doctype/homepage/homepage.py +++ b/erpnext/portal/doctype/homepage/homepage.py @@ -9,8 +9,6 @@ from frappe.website.utils import delete_page_cache class Homepage(Document): def validate(self): - if not self.products: - self.setup_items() if not self.description: self.description = frappe._("This is an example website auto-generated from ERPNext") delete_page_cache('home') diff --git a/erpnext/portal/doctype/homepage/test_homepage.py b/erpnext/portal/doctype/homepage/test_homepage.py new file mode 100644 index 00000000000..b262c4640cc --- /dev/null +++ b/erpnext/portal/doctype/homepage/test_homepage.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest +from frappe.tests.test_website import set_request +from frappe.website.render import render + +class TestHomepage(unittest.TestCase): + def test_homepage_load(self): + set_request(method='GET', path='home') + response = render() + + self.assertEquals(response.status_code, 200) + + html = frappe.safe_decode(response.get_data()) + self.assertTrue(' + {% endif %} -{% block style %} - -{% endblock %} + {% for section in homepage_sections %} + {{ render_homepage_section(section) }} + {% endfor %} + +{% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py index 82d525ac779..4b688b13dff 100644 --- a/erpnext/templates/pages/home.py +++ b/erpnext/templates/pages/home.py @@ -15,15 +15,38 @@ def get_context(context): if route: item.route = '/' + route - context.title = homepage.title or homepage.company - - # show atleast 3 products - if len(homepage.products) < 3: - for i in range(3 - len(homepage.products)): - homepage.append('products', { - 'item_code': 'product-{0}'.format(i), - 'item_name': frappe._('Product {0}').format(i), - 'route': '#' - }) - + homepage.title = homepage.title or homepage.company + context.title = homepage.title context.homepage = homepage + + if homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section: + homepage.hero_section_doc = frappe.get_doc('Homepage Section', homepage.hero_section) + + if homepage.slideshow: + doc = frappe.get_doc('Website Slideshow', homepage.slideshow) + context.slideshow = homepage.slideshow + context.slideshow_header = doc.header + context.slides = doc.slideshow_items + + context.blogs = frappe.get_all('Blog Post', + fields=['title', 'blogger', 'blog_intro', 'route'], + filters={ + 'published': 1 + }, + order_by='modified desc', + limit=3 + ) + + # filter out homepage section which is used as hero section + homepage_hero_section = homepage.hero_section_based_on == 'Homepage Section' and homepage.hero_section + homepage_sections = frappe.get_all('Homepage Section', + filters=[['name', '!=', homepage_hero_section]] if homepage_hero_section else None, + order_by='section_order asc' + ) + context.homepage_sections = [frappe.get_doc('Homepage Section', name) for name in homepage_sections] + + context.metatags = context.metatags or frappe._dict({}) + context.metatags.image = homepage.hero_image or None + context.metatags.description = homepage.description or None + + context.explore_link = '/all-products' diff --git a/erpnext/templates/pages/material_request_info.html b/erpnext/templates/pages/material_request_info.html index ff3bd65b67d..9d189895b63 100644 --- a/erpnext/templates/pages/material_request_info.html +++ b/erpnext/templates/pages/material_request_info.html @@ -12,7 +12,7 @@ {% endblock %} {% block header_actions %} -{{ _("Print") }} +{{ _("Print") }} {% endblock %} {% block page_content %} @@ -70,5 +70,5 @@ {% endif %} {% endfor %} - + {% endblock %} \ No newline at end of file diff --git a/erpnext/templates/pages/non_profit/leave-chapter.html b/erpnext/templates/pages/non_profit/leave-chapter.html index 009c7af7903..bc4242f9196 100644 --- a/erpnext/templates/pages/non_profit/leave-chapter.html +++ b/erpnext/templates/pages/non_profit/leave-chapter.html @@ -9,7 +9,7 @@ - diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html index 64fd32a992a..67a8fed8ab1 100644 --- a/erpnext/templates/pages/order.html +++ b/erpnext/templates/pages/order.html @@ -8,23 +8,22 @@ {% block title %}{{ doc.name }}{% endblock %} {% block header %} -

{{ doc.name }}

+

{{ doc.name }}

{% endblock %} {% block header_actions %} -{{ _("Print") }} +{{ _("Print") }} {% endblock %} {% block page_content %}
-
- +
{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
-
+
{{ frappe.utils.formatdate(doc.transaction_date, 'medium') }} {% if doc.valid_till %}

@@ -34,16 +33,14 @@

-

-{% if doc.doctype == 'Supplier Quotation' %} - {{ doc.supplier_name}} -{% else %} - {{ doc.customer_name}} -{% endif %} -{% if doc.contact_display %} -
- {{ doc.contact_display }} -{% endif %} +

+ {%- set party_name = doc.supplier_name if doc.doctype == 'Supplier Quotation' else doc.customer_name %} + {{ party_name }} + + {% if doc.contact_display and doc.contact_display != party_name %} +
+ {{ doc.contact_display }} + {% endif %}

{% if doc._header %} @@ -55,7 +52,7 @@
-
+
{{ _("Item") }}
@@ -67,7 +64,7 @@
{% for d in doc.items %}
-
+
{{ item_name_and_description(d) }}
@@ -85,11 +82,10 @@
-
-
-
+
+ {% include "erpnext/templates/includes/order/order_taxes.html" %} - +
@@ -115,7 +111,7 @@
- +

Available Points: {{ available_loyalty_points }}

{% endif %} diff --git a/erpnext/templates/pages/product_search.html b/erpnext/templates/pages/product_search.html index f9efd485d32..6a5425bbf8c 100644 --- a/erpnext/templates/pages/product_search.html +++ b/erpnext/templates/pages/product_search.html @@ -25,7 +25,7 @@ frappe.ready(function() {
diff --git a/erpnext/templates/pages/projects.html b/erpnext/templates/pages/projects.html index baa2ae62cce..7e294e076b3 100644 --- a/erpnext/templates/pages/projects.html +++ b/erpnext/templates/pages/projects.html @@ -20,11 +20,11 @@ aria-valuemin="0" aria-valuemax="100" style="width:{{ doc.percent_complete|round|int }}%;">
-{% endif %} +{% endif %}

diff --git a/erpnext/templates/pages/task_info.html b/erpnext/templates/pages/task_info.html index 6cfac28da6a..6cd6a7e51af 100644 --- a/erpnext/templates/pages/task_info.html +++ b/erpnext/templates/pages/task_info.html @@ -20,7 +20,7 @@

- + {{ __("Cancel") }}
@@ -91,7 +91,7 @@ {% endfor %}
- {{ __("Add Comment") }} + {{ __("Add Comment") }}