From b3799989616f28b505f6cf363368dabee1a3230a Mon Sep 17 00:00:00 2001 From: deepak-mnt Date: Thu, 26 Apr 2018 17:00:08 +0530 Subject: [PATCH 01/79] Added Update Cost Center Number button and it's functionality --- .../doctype/cost_center/cost_center.js | 51 +++++++++++++++- .../doctype/cost_center/cost_center.json | 59 ++++++++++++++++--- .../doctype/cost_center/cost_center.py | 46 ++++++++++++++- 3 files changed, 147 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js index cbc37fff8d..a5bcaf47c1 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.js +++ b/erpnext/accounts/doctype/cost_center/cost_center.js @@ -15,8 +15,57 @@ frappe.ui.form.on('Cost Center', { } } }) + }, + refresh: function(frm) { + if (!frm.is_new()) { + frm.add_custom_button(__('Update Cost Center Number'), function () { + frm.trigger("update_cost_center_number"); + }); + } + }, + update_cost_center_number: function(frm) { + var d = new frappe.ui.Dialog({ + title: __('Update Cost Center Number'), + fields: [ + { + "label": 'Cost Center Number', + "fieldname": "cost_center_number", + "fieldtype": "Data", + "reqd": 1 + } + ], + primary_action: function() { + var data = d.get_values(); + if(data.cost_center_number === frm.doc.cost_center_number) { + d.hide(); + return; + } + frappe.call({ + method: "erpnext.accounts.doctype.cost_center.cost_center.update_number_field", + args: { + doctype_name: frm.doc.doctype, + name: frm.doc.name, + field_name: d.fields[0].fieldname, + field_value: data.cost_center_number, + company: frm.doc.company + }, + callback: function(r) { + if(!r.exc) { + if(r.message) { + frappe.set_route("Form", "Cost Center", r.message); + } else { + me.set_value("cost_center_number", data.cost_center_number); + } + d.hide(); + } + } + }); + }, + primary_action_label: __('Update') + }); + d.show(); } -}) +}); cur_frm.cscript.refresh = function(doc, cdt, cdn) { var intro_txt = ''; diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json index 9eddeec04f..4da21f11fe 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.json +++ b/erpnext/accounts/doctype/cost_center/cost_center.json @@ -1,5 +1,6 @@ { "allow_copy": 1, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, "autoname": "field:cost_center_name", @@ -13,6 +14,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -38,9 +40,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -68,9 +72,42 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "cost_center_number", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Cost Center Number", + "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_on_submit": 0, "bold": 0, "collapsible": 0, @@ -99,9 +136,11 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -130,9 +169,11 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -157,10 +198,12 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -188,9 +231,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -218,9 +263,11 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -248,9 +295,11 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -279,22 +328,23 @@ "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-money", "idx": 1, "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2017-02-17 16:22:27.129572", + "modified": "2018-04-26 15:26:25.325778", "modified_by": "Administrator", "module": "Accounts", "name": "Cost Center", @@ -302,7 +352,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -322,7 +371,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -342,7 +390,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -362,7 +409,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -382,7 +428,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index fe1e9076de..3fb1f75c2d 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from frappe.utils import cint, cstr from frappe.utils.nestedset import NestedSet class CostCenter(NestedSet): @@ -66,4 +67,47 @@ class CostCenter(NestedSet): " - ".join(newdn.split(" - ")[:-1])) def on_doctype_update(): - frappe.db.add_index("Cost Center", ["lft", "rgt"]) \ No newline at end of file + frappe.db.add_index("Cost Center", ["lft", "rgt"]) + +def get_doc_name_autoname(field_value, doc_title, name, company): + if company: + name_split=name.split("-") + parts = [doc_title.strip(), name_split[len(name_split)-1].strip()] + else: + parts = [doc_title.strip()] + if cstr(field_value).strip(): + parts.insert(0, cstr(field_value).strip()) + return ' - '.join(parts) + +def validate_field_number(doctype_name, name, field_value, company, field_name): + if field_value: + if company: + doctype_with_same_number = frappe.db.get_value(doctype_name, + {field_name: field_value, "company": company, "name": ["!=", name]}) + else: + doctype_with_same_number = frappe.db.get_value(doctype_name, + {field_name: field_value, "name": ["!=", name]}) + if doctype_with_same_number: + frappe.throw(_("{0} Number {1} already used in account {2}") + .format(doctype_name, field_value, doctype_with_same_number)) + +@frappe.whitelist() +def update_number_field(doctype_name, name, field_name, field_value, company): + + doc_title = frappe.db.get_value(doctype_name, name, frappe.scrub(doctype_name)+"_name") + + validate_field_number(doctype_name, name, field_value, company, field_name) + + frappe.db.set_value(doctype_name, name, field_name, field_value) + + if doc_title[0].isdigit(): + separator = " - " if " - " in doc_title else " " + doc_title = doc_title.split(separator, 1)[1] + + frappe.db.set_value(doctype_name, name, frappe.scrub(doctype_name)+"_name", doc_title) + + new_name = get_doc_name_autoname(field_value, doc_title, name, company) + + if name != new_name: + frappe.rename_doc(doctype_name, name, new_name) + return new_name \ No newline at end of file From b712d1905d5b5357b8738759cc6262a4fb25c002 Mon Sep 17 00:00:00 2001 From: deepak-mnt Date: Thu, 26 Apr 2018 17:04:06 +0530 Subject: [PATCH 02/79] Override after_rename method to fetch cost center number on changing from title --- .../doctype/cost_center/cost_center.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py index 3fb1f75c2d..24af0ce376 100644 --- a/erpnext/accounts/doctype/cost_center/cost_center.py +++ b/erpnext/accounts/doctype/cost_center/cost_center.py @@ -63,8 +63,25 @@ class CostCenter(NestedSet): super(CostCenter, self).after_rename(olddn, newdn, merge) if not merge: - frappe.db.set_value("Cost Center", newdn, "cost_center_name", - " - ".join(newdn.split(" - ")[:-1])) + new_cost_center = frappe.db.get_value("Cost Center", newdn, ["cost_center_name", "cost_center_number"], as_dict=1) + + # exclude company abbr + new_parts = newdn.split(" - ")[:-1] + # update cost center number and remove from parts + if new_parts[0][0].isdigit(): + if len(new_parts) == 1: + new_parts = newdn.split(" ") + if new_cost_center.cost_center_number != new_parts[0]: + validate_field_number("Cost Center", self.name, new_parts[0], self.company, "cost_center_number") + self.cost_center_number = new_parts[0] + self.db_set("cost_center_number", new_parts[0]) + new_parts = new_parts[1:] + + # update cost center name + cost_center_name = " - ".join(new_parts) + if new_cost_center.cost_center_name != cost_center_name: + self.cost_center_name = cost_center_name + self.db_set("cost_center_name", cost_center_name) def on_doctype_update(): frappe.db.add_index("Cost Center", ["lft", "rgt"]) From e4cda0380aa5dd07f33752b49a3dba15c2e2cf15 Mon Sep 17 00:00:00 2001 From: Ranjith Date: Mon, 7 May 2018 13:54:49 +0530 Subject: [PATCH 03/79] Staffing Plan - validations, get employee count on designation change, calc estimated cost --- .../hr/doctype/staffing_plan/staffing_plan.js | 88 +++++++++- .../doctype/staffing_plan/staffing_plan.json | 5 +- .../hr/doctype/staffing_plan/staffing_plan.py | 47 +++++- .../staffing_plan_detail.json | 156 +++++++++++------- 4 files changed, 228 insertions(+), 68 deletions(-) diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js index 3cadfc56c5..17f03e67bf 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js @@ -2,7 +2,91 @@ // For license information, please see license.txt frappe.ui.form.on('Staffing Plan', { - refresh: function(frm) { - + setup: function(frm) { + frm.set_query("designation", "staffing_details", function() { + let designations = []; + $.each(frm.doc.staffing_details, function(index, staff_detail) { + if(staff_detail.designation){ + designations.push(staff_detail.designation) + } + }) + // Filter out designations already selected in Staffing Plan Detail + return { + filters: [ + ['Designation', 'name', 'not in', designations], + ] + } + }); } }); + +frappe.ui.form.on('Staffing Plan Detail', { + designation: function(frm, cdt, cdn) { + let child = locals[cdt][cdn] + if(child.designation){ + frappe.call({ + "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_current_employee_count", + args: { + designation: child.designation + }, + callback: function (data) { + if(data.message){ + frappe.model.set_value(cdt, cdn, 'current_count', data.message); + } + else{ // No employees for this designation + frappe.model.set_value(cdt, cdn, 'current_count', 0); + } + } + }); + } + }, + + number_of_positions: function(frm, cdt, cdn) { + set_vacancies(frm, cdt, cdn); + }, + + current_count: function(frm, cdt, cdn) { + set_vacancies(frm, cdt, cdn); + }, + + estimated_cost_per_position: function(frm, cdt, cdn) { + let child = locals[cdt][cdn]; + set_total_estimated_cost(frm, cdt, cdn); + } + +}); + +var set_vacancies = function(frm, cdt, cdn) { + let child = locals[cdt][cdn] + if(child.number_of_positions) { + frappe.model.set_value(cdt, cdn, 'vacancies', child.number_of_positions - child.current_count); + } + else{ + frappe.model.set_value(cdt, cdn, 'vacancies', 0); + } + set_total_estimated_cost(frm, cdt, cdn); +} + +// Note: Estimated Cost is calculated on number of Vacancies +var set_total_estimated_cost = function(frm, cdt, cdn) { + let child = locals[cdt][cdn] + if(child.number_of_positions && child.estimated_cost_per_position) { + frappe.model.set_value(cdt, cdn, 'total_estimated_cost', child.vacancies * child.estimated_cost_per_position); + } + else { + frappe.model.set_value(cdt, cdn, 'total_estimated_cost', 0); + } + set_total_estimated_budget(frm); +}; + +var set_total_estimated_budget = function(frm) { + let estimated_budget = 0.0 + if(frm.doc.staffing_details) { + $.each(frm.doc.staffing_details, function(index, staff_detail) { + if(staff_detail.total_estimated_cost){ + estimated_budget += staff_detail.total_estimated_cost + } + }) + frm.set_value('total_estimated_budget', estimated_budget); + } +} diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.json b/erpnext/hr/doctype/staffing_plan/staffing_plan.json index a5d26e6d4f..229cc05ddd 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.json +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.json @@ -268,6 +268,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "default": "0.00", "fieldname": "total_estimated_budget", "fieldtype": "Currency", "hidden": 0, @@ -284,7 +285,7 @@ "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, @@ -335,7 +336,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 18:45:16.729979", + "modified": "2018-04-18 19:10:34.394249", "modified_by": "Administrator", "module": "HR", "name": "Staffing Plan", diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 510d2dcc49..588e536b42 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -5,6 +5,51 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ +from frappe.utils import getdate, nowdate class StaffingPlan(Document): - pass + def validate(self): + # Validate Dates + if self.from_date and self.to_date and self.from_date > self.to_date: + frappe.throw(_("From Date cannot be greater than To Date")) + + # Validate if any submitted Staffing Plan exist for Designations in this plan + # and spd.vacancies>0 ? + for detail in self.get("staffing_details"): + overlap = (frappe.db.sql("""select spd.parent \ + from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name \ + where spd.designation='{0}' and sp.docstatus=1 \ + and sp.to_date >= '{1}' and sp.from_date <='{2}'""".format(detail.designation, self.from_date, self.to_date))) + + if overlap and overlap [0][0]: + frappe.throw(_("Staffing Plan {0} already exist for designation {1}".format(overlap[0][0], detail.designation))) + +@frappe.whitelist() +def get_current_employee_count(designation): + if not designation: + return False + employee_count = frappe.db.sql("""select count(*) from `tabEmployee` where \ + designation = '{0}' and status='Active'""".format(designation))[0][0] + return employee_count + +@frappe.whitelist() +def get_active_staffing_plan_and_vacancies(company, designation, department=None, date=getdate(nowdate())): + if not company or not designation: + frappe.throw(_("Please select Company and Designation")) + + conditions = "spd.designation='{0}' and sp.docstatus=1 and \ + sp.company='{1}'".format(designation, company) + + if(department): #Department is an optional field + conditions += " and sp.department='{0}'".format(department) + + if(date): #ToDo: Date should be mandatory? + conditions += " and '{0}' between sp.from_date and sp.to_date".format(date) + + staffing_plan = frappe.db.sql("""select spd.parent, spd.vacancies \ + from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name + where {0}""".format(conditions)) + + # Only a signle staffing plan can be active for a designation on given date + return staffing_plan[0] if staffing_plan else False diff --git a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json index 7c395647be..eb77b43914 100644 --- a/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json +++ b/erpnext/hr/doctype/staffing_plan_detail/staffing_plan_detail.json @@ -75,68 +75,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "current_count", - "fieldtype": "Int", - "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": "Current Count", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "vacancies", - "fieldtype": "Int", - "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": "Vacancies", - "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_on_submit": 0, @@ -190,6 +128,36 @@ "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "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, @@ -198,6 +166,68 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "current_count", + "fieldtype": "Int", + "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": "Current Count", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "vacancies", + "fieldtype": "Int", + "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": "Vacancies", + "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 } ], "has_web_view": 0, @@ -210,7 +240,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 18:39:52.783341", + "modified": "2018-04-15 16:09:12.622186", "modified_by": "Administrator", "module": "HR", "name": "Staffing Plan Detail", From f220e89d9f9848c318a58543b468a487a9f2d7bb Mon Sep 17 00:00:00 2001 From: Ranjith Date: Mon, 7 May 2018 13:58:54 +0530 Subject: [PATCH 04/79] Job Opening - get planned opening, validate vacancies by Staffing Plan --- erpnext/hr/doctype/job_opening/job_opening.js | 39 +++++++++++++++++++ .../hr/doctype/job_opening/job_opening.json | 35 ++++++++++++++++- erpnext/hr/doctype/job_opening/job_opening.py | 15 +++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/job_opening/job_opening.js b/erpnext/hr/doctype/job_opening/job_opening.js index e69de29bb2..b024310339 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.js +++ b/erpnext/hr/doctype/job_opening/job_opening.js @@ -0,0 +1,39 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Job Opening', { + designation: function(frm) { + if(frm.doc.designation && frm.doc.company){ + frappe.call({ + "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_active_staffing_plan_and_vacancies", + args: { + company: frm.doc.company, + designation: frm.doc.designation, + department: frm.doc.department, + date: frappe.datetime.now_date() // ToDo - Date in Job Opening? + }, + callback: function (data) { + if(data.message){ + frm.set_value('staffing_plan', data.message[0]); + frm.set_value('planned_vacancies', data.message[1]); + } + else{ + frm.set_value('staffing_plan', ""); + frm.set_value('planned_vacancies', 0); + frappe.show_alert({ + indicator: 'orange', + message: __('No Staffing Plans found for this Designation') + }); + } + } + }); + } + else{ + frm.set_value('staffing_plan', ""); + frm.set_value('planned_vacancies', 0); + } + }, + company: function(frm) { + frm.set_value('designation', ""); + } +}); diff --git a/erpnext/hr/doctype/job_opening/job_opening.json b/erpnext/hr/doctype/job_opening/job_opening.json index de15114a43..a877119809 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.json +++ b/erpnext/hr/doctype/job_opening/job_opening.json @@ -224,7 +224,38 @@ "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, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "planned_vacancies", + "fieldtype": "Int", + "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": "Planned number of Positions", + "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, @@ -369,7 +400,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 18:52:56.109392", + "modified": "2018-04-18 19:27:15.004385", "modified_by": "Administrator", "module": "HR", "name": "Job Opening", diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py index 60c911a016..d3d662a743 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.py +++ b/erpnext/hr/doctype/job_opening/job_opening.py @@ -8,6 +8,7 @@ import frappe from frappe.website.website_generator import WebsiteGenerator from frappe import _ +from erpnext.hr.doctype.staffing_plan.staffing_plan import get_current_employee_count, get_active_staffing_plan_and_vacancies class JobOpening(WebsiteGenerator): website = frappe._dict( @@ -20,6 +21,20 @@ class JobOpening(WebsiteGenerator): if not self.route: self.route = frappe.scrub(self.job_title).replace('_', '-') + if self.staffing_plan: + self.validate_current_vacancies() + + def validate_current_vacancies(self): + current_count = get_current_employee_count(self.designation) + current_count+= frappe.db.sql("""select count(*) from `tabJob Opening` \ + where designation = '{0}' and status='Open'""".format(self.designation))[0][0] + + vacancies = get_active_staffing_plan_and_vacancies(self.company, self.designation, self.department)[1] + # set staffing_plan too? + if vacancies and vacancies <= current_count: + frappe.throw(_("Job Openings for designation {0} already opened or hiring \ + completed as per Staffing Plan {1}".format(self.designation, self.staffing_plan))) + def get_context(self, context): context.parents = [{'route': 'jobs', 'title': _('All Jobs') }] From c36578e8d34cc3b3f534c2d5071d3560b8f0980d Mon Sep 17 00:00:00 2001 From: Ranjith Date: Mon, 7 May 2018 14:19:40 +0530 Subject: [PATCH 05/79] Job Opening - display vacancies only on staffing plan --- erpnext/hr/doctype/job_opening/job_opening.json | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/erpnext/hr/doctype/job_opening/job_opening.json b/erpnext/hr/doctype/job_opening/job_opening.json index a877119809..79064390d1 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.json +++ b/erpnext/hr/doctype/job_opening/job_opening.json @@ -41,7 +41,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -73,7 +72,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -104,7 +102,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -134,7 +131,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -166,7 +162,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -198,7 +193,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -230,7 +224,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -239,6 +232,7 @@ "bold": 0, "collapsible": 0, "columns": 0, + "depends_on": "staffing_plan", "fieldname": "planned_vacancies", "fieldtype": "Int", "hidden": 0, @@ -261,7 +255,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -291,7 +284,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -322,7 +314,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -354,7 +345,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 1 }, { @@ -385,7 +375,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -400,7 +389,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-18 19:27:15.004385", + "modified": "2018-05-07 14:16:50.300247", "modified_by": "Administrator", "module": "HR", "name": "Job Opening", @@ -408,6 +397,7 @@ "permissions": [ { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -427,6 +417,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, From 3e47fbb2f0b1019c3eb8ee1edf48e7b828837015 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 8 May 2018 16:05:04 +0530 Subject: [PATCH 06/79] assign salary structure custom button --- .../doctype/salary_structure/salary_structure.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index 8e5f8e651f..3de01cd303 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -47,8 +47,18 @@ frappe.ui.form.on('Salary Structure', { frm.fields_dict['earnings'].grid.set_column_disp("default_amount", false); frm.fields_dict['deductions'].grid.set_column_disp("default_amount", false); - frm.add_custom_button(__("Preview Salary Slip"), - function() { frm.trigger('preview_salary_slip'); }, "fa fa-sitemap", "btn-default"); + frm.add_custom_button(__("Preview Salary Slip"), function() { + frm.trigger('preview_salary_slip'); + }); + + if(frm.doc.docstatus==1) { + frm.add_custom_button(__("Assign Salary Structure"), function() { + var doc = frappe.model.get_new_doc('Salary Structure Assignment'); + doc.salary_structure = frm.doc.name; + doc.company = frm.doc.company; + frappe.set_route('Form', 'Salary Structure Assignment', doc.name); + }); + } }, salary_slip_based_on_timesheet: function(frm) { From 3881e98000cdf69f72f5d0c2e190b51b3a7ad06c Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 8 May 2018 18:36:14 +0530 Subject: [PATCH 07/79] Staffing plan for group company --- erpnext/hr/doctype/job_opening/job_opening.js | 45 +++++++++---------- erpnext/hr/doctype/job_opening/job_opening.py | 35 ++++++++++----- .../hr/doctype/staffing_plan/staffing_plan.js | 5 ++- .../hr/doctype/staffing_plan/staffing_plan.py | 37 +++++++++------ 4 files changed, 73 insertions(+), 49 deletions(-) diff --git a/erpnext/hr/doctype/job_opening/job_opening.js b/erpnext/hr/doctype/job_opening/job_opening.js index b024310339..960f5b3c65 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.js +++ b/erpnext/hr/doctype/job_opening/job_opening.js @@ -2,38 +2,37 @@ // For license information, please see license.txt frappe.ui.form.on('Job Opening', { - designation: function(frm) { - if(frm.doc.designation && frm.doc.company){ + designation: function(frm) { + if(frm.doc.designation && frm.doc.company){ frappe.call({ "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_active_staffing_plan_and_vacancies", args: { - company: frm.doc.company, - designation: frm.doc.designation, - department: frm.doc.department, - date: frappe.datetime.now_date() // ToDo - Date in Job Opening? + company: frm.doc.company, + designation: frm.doc.designation, + department: frm.doc.department, + date: frappe.datetime.now_date() // ToDo - Date in Job Opening? }, callback: function (data) { if(data.message){ frm.set_value('staffing_plan', data.message[0]); - frm.set_value('planned_vacancies', data.message[1]); + frm.set_value('planned_vacancies', data.message[1]); + } else { + frm.set_value('staffing_plan', ""); + frm.set_value('planned_vacancies', 0); + frappe.show_alert({ + indicator: 'orange', + message: __('No Staffing Plans found for this Designation') + }); } - else{ - frm.set_value('staffing_plan', ""); - frm.set_value('planned_vacancies', 0); - frappe.show_alert({ - indicator: 'orange', - message: __('No Staffing Plans found for this Designation') - }); - } } }); } - else{ - frm.set_value('staffing_plan', ""); - frm.set_value('planned_vacancies', 0); - } - }, - company: function(frm) { - frm.set_value('designation', ""); - } + else{ + frm.set_value('staffing_plan', ""); + frm.set_value('planned_vacancies', 0); + } + }, + company: function(frm) { + frm.set_value('designation', ""); + } }); diff --git a/erpnext/hr/doctype/job_opening/job_opening.py b/erpnext/hr/doctype/job_opening/job_opening.py index d3d662a743..b579d6f24a 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.py +++ b/erpnext/hr/doctype/job_opening/job_opening.py @@ -20,20 +20,33 @@ class JobOpening(WebsiteGenerator): def validate(self): if not self.route: self.route = frappe.scrub(self.job_title).replace('_', '-') - - if self.staffing_plan: - self.validate_current_vacancies() + self.validate_current_vacancies() def validate_current_vacancies(self): - current_count = get_current_employee_count(self.designation) - current_count+= frappe.db.sql("""select count(*) from `tabJob Opening` \ - where designation = '{0}' and status='Open'""".format(self.designation))[0][0] + if not self.staffing_plan: + vacancies = get_active_staffing_plan_and_vacancies(self.company, + self.designation, self.department) + if vacancies: + self.staffing_plan = vacancies[0] + self.planned_vacancies = vacancies[1] + elif not self.planned_vacancies: + planned_vacancies = frappe.db.sql(""" + select vacancies from `tabStaffing Plan Detail` + where parent=%s and designation=%s""", (self.staffing_plan, self.designation)) + self.planned_vacancies = planned_vacancies[0][0] if planned_vacancies else None - vacancies = get_active_staffing_plan_and_vacancies(self.company, self.designation, self.department)[1] - # set staffing_plan too? - if vacancies and vacancies <= current_count: - frappe.throw(_("Job Openings for designation {0} already opened or hiring \ - completed as per Staffing Plan {1}".format(self.designation, self.staffing_plan))) + if self.staffing_plan and self.planned_vacancies: + staffing_plan_company = frappe.db.get_value("Staffing Plan", self.staffing_plan, "company") + lft, rgt = frappe.db.get_value("Company", staffing_plan_company, ["lft", "rgt"]) + + current_count = get_current_employee_count(self.designation, staffing_plan_company) + current_count+= frappe.db.sql("""select count(*) from `tabJob Opening` \ + where designation=%s and status='Open' + and company in (select name from tabCompany where lft>=%s and rgt<=%s) + """, (self.designation, lft, rgt))[0][0] + + if self.planned_vacancies <= current_count: + frappe.throw(_("Job Openings for designation {0} and company {1} already opened or hiring completed as per Staffing Plan {2}".format(self.designation, staffing_plan_company, self.staffing_plan))) def get_context(self, context): context.parents = [{'route': 'jobs', 'title': _('All Jobs') }] diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js index 17f03e67bf..1c1a720088 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js @@ -23,11 +23,12 @@ frappe.ui.form.on('Staffing Plan', { frappe.ui.form.on('Staffing Plan Detail', { designation: function(frm, cdt, cdn) { let child = locals[cdt][cdn] - if(child.designation){ + if(frm.doc.company && child.designation){ frappe.call({ "method": "erpnext.hr.doctype.staffing_plan.staffing_plan.get_current_employee_count", args: { - designation: child.designation + designation: child.designation, + company: frm.doc.company }, callback: function (data) { if(data.message){ diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py index 588e536b42..37ff5cbc90 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py @@ -20,36 +20,47 @@ class StaffingPlan(Document): overlap = (frappe.db.sql("""select spd.parent \ from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name \ where spd.designation='{0}' and sp.docstatus=1 \ - and sp.to_date >= '{1}' and sp.from_date <='{2}'""".format(detail.designation, self.from_date, self.to_date))) + and sp.to_date >= '{1}' and sp.from_date <='{2}'""" + .format(detail.designation, self.from_date, self.to_date))) if overlap and overlap [0][0]: - frappe.throw(_("Staffing Plan {0} already exist for designation {1}".format(overlap[0][0], detail.designation))) + frappe.throw(_("Staffing Plan {0} already exist for designation {1}" + .format(overlap[0][0], detail.designation))) @frappe.whitelist() -def get_current_employee_count(designation): +def get_current_employee_count(designation, company): if not designation: return False - employee_count = frappe.db.sql("""select count(*) from `tabEmployee` where \ - designation = '{0}' and status='Active'""".format(designation))[0][0] + + lft, rgt = frappe.db.get_value("Company", company, ["lft", "rgt"]) + employee_count = frappe.db.sql("""select count(*) from `tabEmployee` + where designation = %s and status='Active' + and company in (select name from tabCompany where lft>=%s and rgt<=%s) + """, (designation, lft, rgt))[0][0] return employee_count -@frappe.whitelist() def get_active_staffing_plan_and_vacancies(company, designation, department=None, date=getdate(nowdate())): if not company or not designation: frappe.throw(_("Please select Company and Designation")) - conditions = "spd.designation='{0}' and sp.docstatus=1 and \ - sp.company='{1}'".format(designation, company) - + conditions = "" if(department): #Department is an optional field - conditions += " and sp.department='{0}'".format(department) + conditions += " and sp.department='{0}'".format(frappe.db.escape(department)) if(date): #ToDo: Date should be mandatory? conditions += " and '{0}' between sp.from_date and sp.to_date".format(date) - staffing_plan = frappe.db.sql("""select spd.parent, spd.vacancies \ + staffing_plan = frappe.db.sql(""" + select sp.name, spd.vacancies from `tabStaffing Plan Detail` spd join `tabStaffing Plan` sp on spd.parent=sp.name - where {0}""".format(conditions)) + where company=%s and spd.designation=%s and sp.docstatus=1 {0} + """.format(conditions), (company, designation)) + + if not staffing_plan: + parent_company = frappe.db.get_value("Company", company, "parent_company") + if parent_company: + staffing_plan = get_active_staffing_plan_and_vacancies(parent_company, + designation, department, date) # Only a signle staffing plan can be active for a designation on given date - return staffing_plan[0] if staffing_plan else False + return staffing_plan[0] if staffing_plan else None From a948726dddea7f2e3d2ee91910deb7065dee69e0 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:16:57 +0530 Subject: [PATCH 08/79] Added Finance Book doctype --- .../accounts/doctype/finance_book/__init__.py | 0 .../doctype/finance_book/finance_book.js | 8 + .../doctype/finance_book/finance_book.json | 165 ++++++++++++++++++ .../doctype/finance_book/finance_book.py | 10 ++ .../doctype/finance_book/test_finance_book.js | 23 +++ .../doctype/finance_book/test_finance_book.py | 10 ++ 6 files changed, 216 insertions(+) create mode 100644 erpnext/accounts/doctype/finance_book/__init__.py create mode 100644 erpnext/accounts/doctype/finance_book/finance_book.js create mode 100644 erpnext/accounts/doctype/finance_book/finance_book.json create mode 100644 erpnext/accounts/doctype/finance_book/finance_book.py create mode 100644 erpnext/accounts/doctype/finance_book/test_finance_book.js create mode 100644 erpnext/accounts/doctype/finance_book/test_finance_book.py diff --git a/erpnext/accounts/doctype/finance_book/__init__.py b/erpnext/accounts/doctype/finance_book/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/accounts/doctype/finance_book/finance_book.js b/erpnext/accounts/doctype/finance_book/finance_book.js new file mode 100644 index 0000000000..71191bb0cf --- /dev/null +++ b/erpnext/accounts/doctype/finance_book/finance_book.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Finance Book', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/accounts/doctype/finance_book/finance_book.json b/erpnext/accounts/doctype/finance_book/finance_book.json new file mode 100644 index 0000000000..d13a484028 --- /dev/null +++ b/erpnext/accounts/doctype/finance_book/finance_book.json @@ -0,0 +1,165 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 0, + "autoname": "field:finance_book_name", + "beta": 0, + "creation": "2018-04-13 17:42:43.252224", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book_name", + "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": "Name", + "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_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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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-book", + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-04-23 02:15:07.997774", + "modified_by": "Administrator", + "module": "Accounts", + "name": "Finance Book", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + }, + { + "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": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "amend": 0, + "cancel": 0, + "create": 0, + "delete": 0, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Auditor", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 0 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "search_fields": "finance_book_name", + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_seen": 1 +} \ No newline at end of file diff --git a/erpnext/accounts/doctype/finance_book/finance_book.py b/erpnext/accounts/doctype/finance_book/finance_book.py new file mode 100644 index 0000000000..bc9fce2ce8 --- /dev/null +++ b/erpnext/accounts/doctype/finance_book/finance_book.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class FinanceBook(Document): + pass diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.js b/erpnext/accounts/doctype/finance_book/test_finance_book.js new file mode 100644 index 0000000000..9fb7d4fcc8 --- /dev/null +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Finance Book", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Finance Book + () => frappe.tests.make('Finance Book', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.py b/erpnext/accounts/doctype/finance_book/test_finance_book.py new file mode 100644 index 0000000000..771e8130c5 --- /dev/null +++ b/erpnext/accounts/doctype/finance_book/test_finance_book.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestFinanceBook(unittest.TestCase): + pass From 30f9e0f54732d217670337c44369eb80134f1f19 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:17:42 +0530 Subject: [PATCH 09/79] Set finance book in make_gl_entries --- erpnext/accounts/doctype/journal_entry/journal_entry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index f9ffbcdbd7..51308e5008 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -460,7 +460,8 @@ class JournalEntry(AccountsController): "against_voucher": d.reference_name, "remarks": self.remark, "cost_center": d.cost_center, - "project": d.project + "project": d.project, + "finance_book": self.finance_book }) ) From a6d3cbfbc51f6f66452910a6a44c806f5651b049 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:19:14 +0530 Subject: [PATCH 10/79] Added finance book field to GL Entry doctype --- .../accounts/doctype/gl_entry/gl_entry.json | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json index 441266199a..4b3396828b 100644 --- a/erpnext/accounts/doctype/gl_entry/gl_entry.json +++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json @@ -41,6 +41,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -72,6 +73,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -104,6 +106,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -134,6 +137,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -164,6 +168,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -196,6 +201,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -229,6 +235,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -262,6 +269,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -293,6 +301,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -324,6 +333,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -355,6 +365,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -386,6 +397,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -418,6 +430,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -450,6 +463,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -482,6 +496,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -514,6 +529,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -545,6 +561,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -576,6 +593,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -608,6 +626,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -640,6 +659,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -672,6 +692,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -704,6 +725,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book", + "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": "Finance Book", + "length": 0, + "no_copy": 0, + "options": "Finance Book", + "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 } ], @@ -718,7 +772,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-20 12:40:09.611951", + "modified": "2018-04-23 02:15:22.297509", "modified_by": "Administrator", "module": "Accounts", "name": "GL Entry", @@ -726,7 +780,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -746,7 +799,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -766,7 +818,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -794,4 +845,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} +} \ No newline at end of file From 460d96f16d3557ce501789e226ba1fefb3ce1718 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:29:22 +0530 Subject: [PATCH 11/79] Added finance book filter to General Ledger --- .../report/general_ledger/general_ledger.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 5e8f9cfc28..91879a8c1f 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -11,6 +11,20 @@ frappe.query_reports["General Ledger"] = { "default": frappe.defaults.get_user_default("Company"), "reqd": 1 }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + "get_query": function() { + var company = frappe.query_report_filters_by_name.company.get_value(); + return { + "filters": { + "company": company, + } + }; + } + }, { "fieldname":"from_date", "label": __("From Date"), From 57856b475c6c9ba12dfb578f53976167f3372910 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:29:48 +0530 Subject: [PATCH 12/79] Added finance book filter logic in general_ledger.py --- erpnext/accounts/report/general_ledger/general_ledger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 7fd653e39b..6c16a840a0 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -164,6 +164,11 @@ def get_conditions(filters): if filters.get("project"): conditions.append("project=%(project)s") + if filters.get("finance_book"): + conditions.append("finance_book=%(finance_book)s") + else: + conditions.append("ifnull(finance_book, '')=''") + from frappe.desk.reportview import build_match_conditions match_conditions = build_match_conditions("GL Entry") From cddda8ac5fc8f5cd7aa23b8fc0ae867daa25bff3 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:49:30 +0530 Subject: [PATCH 13/79] Added finance book filter in accounts_payable.js --- .../report/accounts_payable/accounts_payable.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index 63ef83263c..cd6a8fc5f8 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -10,6 +10,20 @@ frappe.query_reports["Accounts Payable"] = { "options": "Company", "default": frappe.defaults.get_user_default("Company") }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + "get_query": function() { + var company = frappe.query_report_filters_by_name.company.get_value(); + return { + "filters": { + "company": company, + } + }; + } + }, { "fieldname":"supplier", "label": __("Supplier"), From 70ac2d2c51e5598fce968d18ef8e780c212c899a Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:56:50 +0530 Subject: [PATCH 14/79] Added finance book filter in accounts_receivable.js --- .../accounts_receivable/accounts_receivable.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index ec1e9f9d61..776835e86d 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -10,6 +10,20 @@ frappe.query_reports["Accounts Receivable"] = { "options": "Company", "default": frappe.defaults.get_user_default("Company") }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + "get_query": function() { + var company = frappe.query_report_filters_by_name.company.get_value(); + return { + "filters": { + "company": company, + } + }; + } + }, { "fieldname":"customer", "label": __("Customer"), From 40559d5f5cabe904df830384ea4f3d29fe008985 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 02:57:10 +0530 Subject: [PATCH 15/79] Added finance book filter logic to ReceivablePayable --- .../report/accounts_receivable/accounts_receivable.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index 61096837f5..eb14513d79 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -321,6 +321,13 @@ class ReceivablePayableReport(object): conditions.append("company=%s") values.append(self.filters.company) + if self.filters.finance_book: + conditions.append("finance_book=%s") + values.append(self.filters.finance_book) + else: + conditions.append("ifnull(finance_book,'')=%s") + values.append('') + if self.filters.get(party_type_field): conditions.append("party=%s") values.append(self.filters.get(party_type_field)) From 8cbbdfdf309b3c31ed57062fad0c5acfef33773e Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 03:36:02 +0530 Subject: [PATCH 16/79] Added finance book filter to financial_statements.js --- erpnext/public/js/financial_statements.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 29e3999f0a..94518f83da 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -70,6 +70,20 @@ function get_filters(){ "default": frappe.defaults.get_user_default("Company"), "reqd": 1 }, + { + "fieldname":"finance_book", + "label": __("Finance Book"), + "fieldtype": "Link", + "options": "Finance Book", + "get_query": function() { + var company = frappe.query_report_filters_by_name.company.get_value(); + return { + "filters": { + "company": company, + } + }; + } + }, { "fieldname":"from_fiscal_year", "label": __("Start Year"), From a78a0e241fb6e02089d7708d5477e20148eb0725 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 23 Apr 2018 04:10:01 +0530 Subject: [PATCH 17/79] Added finance book filtering logic to financial_statements.py --- erpnext/accounts/report/financial_statements.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index dd47ca0b37..87b7ba1536 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -375,6 +375,11 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): additional_conditions.append("project = '%s'" % (frappe.db.escape(filters.get("project")))) if filters.get("cost_center"): additional_conditions.append(get_cost_center_cond(filters.get("cost_center"))) + if filters.get("finance_book"): + additional_conditions.append("finance_book = '%s'" % frappe.db.escape(filters.get("finance_book"))) + else: + additional_conditions.append("ifnull(finance_book, '') = ''") + return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else "" From b9fed2aa38c322354999f4d828eec0e40193a0e3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Wed, 9 May 2018 15:10:29 +0530 Subject: [PATCH 18/79] Removed company from Finance Book and related changes --- .../doctype/finance_book/finance_book.json | 34 +------------------ .../doctype/journal_entry/journal_entry.json | 34 ++++++++++++++++++- .../accounts_payable/accounts_payable.js | 10 +----- .../accounts_receivable.js | 10 +----- .../accounts_receivable.py | 2 +- .../accounts/report/financial_statements.py | 3 +- .../report/general_ledger/general_ledger.js | 10 +----- .../report/general_ledger/general_ledger.py | 2 +- erpnext/public/js/financial_statements.js | 10 +----- 9 files changed, 42 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/finance_book/finance_book.json b/erpnext/accounts/doctype/finance_book/finance_book.json index d13a484028..c9fb843cc3 100644 --- a/erpnext/accounts/doctype/finance_book/finance_book.json +++ b/erpnext/accounts/doctype/finance_book/finance_book.json @@ -43,38 +43,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 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_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "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, @@ -88,7 +56,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-23 02:15:07.997774", + "modified": "2018-05-09 14:55:01.394387", "modified_by": "Administrator", "module": "Accounts", "name": "Finance Book", diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json index 916c71f20f..a975aa0756 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.json +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json @@ -238,6 +238,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book", + "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": "Finance Book", + "length": 0, + "no_copy": 0, + "options": "Finance Book", + "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_on_submit": 0, @@ -1553,7 +1585,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-05-05 13:11:33.696498", + "modified": "2018-05-09 14:56:08.687994", "modified_by": "Administrator", "module": "Accounts", "name": "Journal Entry", diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js index cd6a8fc5f8..f39df3da3d 100644 --- a/erpnext/accounts/report/accounts_payable/accounts_payable.js +++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js @@ -14,15 +14,7 @@ frappe.query_reports["Accounts Payable"] = { "fieldname":"finance_book", "label": __("Finance Book"), "fieldtype": "Link", - "options": "Finance Book", - "get_query": function() { - var company = frappe.query_report_filters_by_name.company.get_value(); - return { - "filters": { - "company": company, - } - }; - } + "options": "Finance Book" }, { "fieldname":"supplier", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js index 776835e86d..2caf3590fb 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js @@ -14,15 +14,7 @@ frappe.query_reports["Accounts Receivable"] = { "fieldname":"finance_book", "label": __("Finance Book"), "fieldtype": "Link", - "options": "Finance Book", - "get_query": function() { - var company = frappe.query_report_filters_by_name.company.get_value(); - return { - "filters": { - "company": company, - } - }; - } + "options": "Finance Book" }, { "fieldname":"customer", diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index eb14513d79..fc76be406e 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -322,7 +322,7 @@ class ReceivablePayableReport(object): values.append(self.filters.company) if self.filters.finance_book: - conditions.append("finance_book=%s") + conditions.append("finance_book in (%s, '')") values.append(self.filters.finance_book) else: conditions.append("ifnull(finance_book,'')=%s") diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py index 87b7ba1536..ea22bacf21 100644 --- a/erpnext/accounts/report/financial_statements.py +++ b/erpnext/accounts/report/financial_statements.py @@ -376,7 +376,8 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters): if filters.get("cost_center"): additional_conditions.append(get_cost_center_cond(filters.get("cost_center"))) if filters.get("finance_book"): - additional_conditions.append("finance_book = '%s'" % frappe.db.escape(filters.get("finance_book"))) + additional_conditions.append("finance_book in ('%s', '')" % + frappe.db.escape(filters.get("finance_book"))) else: additional_conditions.append("ifnull(finance_book, '') = ''") diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js index 91879a8c1f..9a774ce6b9 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.js +++ b/erpnext/accounts/report/general_ledger/general_ledger.js @@ -15,15 +15,7 @@ frappe.query_reports["General Ledger"] = { "fieldname":"finance_book", "label": __("Finance Book"), "fieldtype": "Link", - "options": "Finance Book", - "get_query": function() { - var company = frappe.query_report_filters_by_name.company.get_value(); - return { - "filters": { - "company": company, - } - }; - } + "options": "Finance Book" }, { "fieldname":"from_date", diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py index 6c16a840a0..2d0bd52fd0 100644 --- a/erpnext/accounts/report/general_ledger/general_ledger.py +++ b/erpnext/accounts/report/general_ledger/general_ledger.py @@ -165,7 +165,7 @@ def get_conditions(filters): conditions.append("project=%(project)s") if filters.get("finance_book"): - conditions.append("finance_book=%(finance_book)s") + conditions.append("finance_book in (%(finance_book)s, '')") else: conditions.append("ifnull(finance_book, '')=''") diff --git a/erpnext/public/js/financial_statements.js b/erpnext/public/js/financial_statements.js index 94518f83da..7e31c573c5 100644 --- a/erpnext/public/js/financial_statements.js +++ b/erpnext/public/js/financial_statements.js @@ -74,15 +74,7 @@ function get_filters(){ "fieldname":"finance_book", "label": __("Finance Book"), "fieldtype": "Link", - "options": "Finance Book", - "get_query": function() { - var company = frappe.query_report_filters_by_name.company.get_value(); - return { - "filters": { - "company": company, - } - }; - } + "options": "Finance Book" }, { "fieldname":"from_fiscal_year", From 96121c598ed4e0a0f164a5cacf73cdf381d5ed3f Mon Sep 17 00:00:00 2001 From: Ranjith Date: Thu, 10 May 2018 15:02:00 +0530 Subject: [PATCH 19/79] Tax Declaration, Proof Submission, field company --- .../employee_tax_exemption_declaration.json | 43 ++++++++++++++--- ...ployee_tax_exemption_proof_submission.json | 46 +++++++++++++++---- 2 files changed, 72 insertions(+), 17 deletions(-) diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json index ebfce2b9e3..3c361eea60 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json @@ -42,7 +42,37 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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 }, { @@ -72,7 +102,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -104,7 +133,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -135,7 +163,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -165,7 +192,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -197,7 +223,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -211,7 +236,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 19:23:54.363578", + "modified": "2018-05-10 13:26:25.241545", "modified_by": "Administrator", "module": "HR", "name": "Employee Tax Exemption Declaration", @@ -220,6 +245,7 @@ "permissions": [ { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -239,6 +265,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -258,6 +285,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -277,6 +305,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json index 41d9681b1b..51f23aa55e 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json +++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json @@ -42,7 +42,37 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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 }, { @@ -72,7 +102,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -104,7 +133,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -135,7 +163,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -165,7 +192,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -197,7 +223,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -228,7 +253,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -259,7 +283,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -290,7 +313,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -304,7 +326,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 19:21:59.969371", + "modified": "2018-05-10 13:26:53.030547", "modified_by": "Administrator", "module": "HR", "name": "Employee Tax Exemption Proof Submission", @@ -313,6 +335,7 @@ "permissions": [ { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -332,6 +355,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -351,6 +375,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -370,6 +395,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, From e1c556b7e00bd0d243fb34c1995a990fc8b87ed5 Mon Sep 17 00:00:00 2001 From: Ranjith Date: Thu, 10 May 2018 15:02:57 +0530 Subject: [PATCH 20/79] Doctype Employee Tax Exemption Sub Category --- .../__init__.py | 0 .../employee_tax_exemption_sub_category.js | 8 + .../employee_tax_exemption_sub_category.json | 194 ++++++++++++++++++ .../employee_tax_exemption_sub_category.py | 10 + ...est_employee_tax_exemption_sub_category.js | 23 +++ ...est_employee_tax_exemption_sub_category.py | 10 + 6 files changed, 245 insertions(+) create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js create mode 100644 erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js new file mode 100644 index 0000000000..8a83a2739c --- /dev/null +++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Tax Exemption Sub Category', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json new file mode 100644 index 0000000000..dc99785067 --- /dev/null +++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.json @@ -0,0 +1,194 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 1, + "allow_rename": 1, + "autoname": "Prompt", + "beta": 0, + "creation": "2018-05-09 12:47:26.983095", + "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, + "fieldname": "exemption_category", + "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": "Tax Exemption Category", + "length": 0, + "no_copy": 0, + "options": "Employee Tax Exemption Category", + "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, + "fieldname": "max_amount", + "fieldtype": "Currency", + "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": "Max Amount", + "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, + "fieldname": "is_active", + "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": "Is Active", + "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, + "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": 0, + "max_attachments": 0, + "modified": "2018-05-09 13:25:01.595240", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Tax Exemption Sub Category", + "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": 0, + "write": 1 + }, + { + "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": "HR Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "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": "HR User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "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/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py new file mode 100644 index 0000000000..cd58136d8d --- /dev/null +++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/employee_tax_exemption_sub_category.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class EmployeeTaxExemptionSubCategory(Document): + pass diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js new file mode 100644 index 0000000000..8a1a6d151d --- /dev/null +++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Employee Tax Exemption Sub Category + () => frappe.tests.make('Employee Tax Exemption Sub Category', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py new file mode 100644 index 0000000000..5d705567a2 --- /dev/null +++ b/erpnext/hr/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestEmployeeTaxExemptionSubCategory(unittest.TestCase): + pass From 85d2d571c56306fb7fe15d42a6f5059a5df9c278 Mon Sep 17 00:00:00 2001 From: Ranjith Date: Thu, 10 May 2018 15:04:32 +0530 Subject: [PATCH 21/79] update hr menu, field subcategory in child table --- erpnext/config/hr.py | 8 ++-- ...ee_tax_exemption_declaration_category.json | 41 +++++++++++++++--- ...tax_exemption_proof_submission_detail.json | 42 +++++++++++++++---- 3 files changed, 74 insertions(+), 17 deletions(-) diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 4e9e91ef3b..6ec5c26e7d 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -23,10 +23,6 @@ def get_data(): "type": "doctype", "name": "Attendance Request", }, - { - "type": "doctype", - "name": "Attendance Request", - }, { "type": "doctype", "name": "Upload Attendance", @@ -131,6 +127,10 @@ def get_data(): "type": "doctype", "name": "Employee Tax Exemption Category", }, + { + "type": "doctype", + "name": "Employee Tax Exemption Sub Category" + } ] }, { diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json b/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json index 48a5dd09db..bdaf9bf3d0 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration_category/employee_tax_exemption_declaration_category.json @@ -18,7 +18,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "exemption_category", + "fieldname": "exemption_sub_category", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -27,10 +27,41 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Exemption Category", + "label": "Exemption Sub Category", "length": 0, "no_copy": 0, - "options": "Employee Tax Exemption Category", + "options": "Employee Tax Exemption Sub Category", + "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, + "fieldname": "exemption_category", + "fieldtype": "Read Only", + "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": "Exemption Category", + "length": 0, + "no_copy": 0, + "options": "exemption_sub_category.exemption_category", "permlevel": 0, "precision": "", "print_hide": 0, @@ -41,7 +72,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -72,7 +102,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -86,7 +115,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 19:24:18.076613", + "modified": "2018-05-09 13:30:44.363393", "modified_by": "Administrator", "module": "HR", "name": "Employee Tax Exemption Declaration Category", diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json b/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json index 509e425270..3725900772 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json +++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission_detail/employee_tax_exemption_proof_submission_detail.json @@ -18,7 +18,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "exemption_category", + "fieldname": "exemption_sub_category", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -27,10 +27,41 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Exemption Category", + "label": "Exemption Sub Category", "length": 0, "no_copy": 0, - "options": "Employee Tax Exemption Category", + "options": "Employee Tax Exemption Sub Category", + "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, + "fieldname": "exemption_category", + "fieldtype": "Read Only", + "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": "Exemption Category", + "length": 0, + "no_copy": 0, + "options": "exemption_sub_category.exemption_category", "permlevel": 0, "precision": "", "print_hide": 0, @@ -41,7 +72,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -73,7 +103,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -104,7 +133,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -118,7 +146,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-13 17:19:03.006149", + "modified": "2018-05-09 13:53:22.563316", "modified_by": "Administrator", "module": "HR", "name": "Employee Tax Exemption Proof Submission Detail", From 5a8e6427f4d1d8e58ffc1cca359afe91d1383b35 Mon Sep 17 00:00:00 2001 From: Ranjith Date: Thu, 10 May 2018 15:06:49 +0530 Subject: [PATCH 22/79] Tax Declaration, Proof Submission, validation --- .../employee_tax_exemption_declaration.js | 35 +++++++++++++++++-- .../employee_tax_exemption_declaration.py | 13 ++++++- ...employee_tax_exemption_proof_submission.js | 35 +++++++++++++++++-- ...employee_tax_exemption_proof_submission.py | 13 ++++++- erpnext/hr/utils.py | 13 +++++++ 5 files changed, 103 insertions(+), 6 deletions(-) diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js index d204efc5bf..b31bf0ec45 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js @@ -2,7 +2,38 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Tax Exemption Declaration', { - refresh: function(frm) { - + setup: function(frm) { + frm.set_query('employee', function() { + return { + filters: { + 'status': "Active" + } + } + }); + frm.set_query('payroll_period', function() { + if(frm.doc.employee && frm.doc.company){ + return { + filters: { + 'company': frm.doc.company + } + } + }else { + frappe.msgprint(__("Please select Employee")); + } + }); + frm.set_query('exemption_sub_category', 'declarations', function() { + return { + filters: { + 'is_active': 1 + } + } + }); + }, + employee: function(frm){ + if(frm.doc.employee){ + frm.add_fetch('employee', 'company', 'company'); + }else{ + frm.set_value('company', ''); + } } }); diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py index 1a5f195d02..52746d4cff 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py @@ -5,6 +5,17 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ +from erpnext.hr.utils import validate_tax_declaration class EmployeeTaxExemptionDeclaration(Document): - pass + def validate(self): + validate_tax_declaration(self.declarations) + + def before_submit(self): + if frappe.db.exists({"doctype": "Employee Tax Exemption Declaration", + "employee": self.employee, + "payroll_period": self.payroll_period, + "docstatus": 1}): + frappe.throw(_("Tax Declaration of {0} for period {1} already submitted.")\ + .format(self.employee, self.payroll_period), frappe.DocstatusTransitionError) diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js index d8036c48e9..99bec14b18 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js +++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js @@ -2,7 +2,38 @@ // For license information, please see license.txt frappe.ui.form.on('Employee Tax Exemption Proof Submission', { - refresh: function(frm) { - + setup: function(frm) { + frm.set_query('employee', function() { + return { + filters: { + 'status': "Active" + } + } + }); + frm.set_query('payroll_period', function() { + if(frm.doc.employee && frm.doc.company){ + return { + filters: { + 'company': frm.doc.company + } + } + }else { + frappe.msgprint(__("Please select Employee")); + } + }); + frm.set_query('exemption_sub_category', 'tax_exemption_proofs', function() { + return { + filters: { + 'is_active': 1 + } + } + }); + }, + employee: function(frm){ + if(frm.doc.employee){ + frm.add_fetch('employee', 'company', 'company'); + }else{ + frm.set_value('company', ''); + } } }); diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py index 1c31cc4080..a0c003cdc6 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py +++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py @@ -5,6 +5,17 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe import _ +from erpnext.hr.utils import validate_tax_declaration class EmployeeTaxExemptionProofSubmission(Document): - pass + def validate(self): + validate_tax_declaration(self.tax_exemption_proofs) + #TODO: allow multiple? + # def before_submit(self): + # if frappe.db.exists({"doctype": "Employee Tax Exemption Proof Submission", + # "employee": self.employee, + # "payroll_period": self.payroll_period, + # "docstatus": 1}): + # frappe.throw(_("Proof Submission of {0} for period {1} already submitted.")\ + # .format(self.employee, self.payroll_period), frappe.DocstatusTransitionError) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 057f406e80..040134b3bc 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -49,3 +49,16 @@ def update_employee(employee, details, cancel=False): new_data = get_datetime(new_data) setattr(employee, item.fieldname, new_data) return employee + +def validate_tax_declaration(declarations): + subcategories = [] + for declaration in declarations: + if declaration.exemption_sub_category in subcategories: + frappe.throw(_("More than one selection for {0} not \ + allowed").format(declaration.exemption_sub_category), frappe.ValidationError) + subcategories.append(declaration.exemption_sub_category) + max_amount = frappe.db.get_value("Employee Tax Exemption Sub Category", \ + declaration.exemption_sub_category, "max_amount") + if declaration.amount > max_amount: + frappe.throw(_("Max exemption amount for {0} is {1}").format(\ + declaration.exemption_sub_category, max_amount), frappe.ValidationError) From ded636097e7651aa619faeff86341c55fb9d50f5 Mon Sep 17 00:00:00 2001 From: Ranjith Date: Thu, 10 May 2018 15:42:04 +0530 Subject: [PATCH 23/79] Tax Declaration, Proof Submission tests --- ...test_employee_tax_exemption_declaration.py | 106 +++++++++++++++++- ...employee_tax_exemption_proof_submission.py | 46 +++++++- 2 files changed, 150 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py index 48f561a167..84970d85fd 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py +++ b/erpnext/hr/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.py @@ -5,6 +5,110 @@ from __future__ import unicode_literals import frappe import unittest +from erpnext.hr.doctype.salary_structure.test_salary_structure import make_employee class TestEmployeeTaxExemptionDeclaration(unittest.TestCase): - pass + def setup(self): + make_employee("employee@taxexepmtion.com") + make_employee("employee1@taxexepmtion.com") + create_payroll_period() + create_exemption_category() + frappe.db.sql("""delete from `tabEmployee Tax Exemption Declaration`""") + + def test_exemption_amount_greater_than_category_max(self): + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), + "payroll_period": "Test Payroll Period", + "declarations": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 150000)] + }) + self.assertRaises(frappe.ValidationError, declaration.save) + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Declaration", + "payroll_period": "Test Payroll Period", + "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), + "declarations": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 90000)] + }) + self.assertTrue(declaration.save) + + def test_duplicate_category_in_declaration(self): + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), + "payroll_period": "Test Payroll Period", + "declarations": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 100000), + dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 50000), + ] + }) + self.assertRaises(frappe.ValidationError, declaration.save) + + def test_duplicate_submission_for_payroll_period(self): + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), + "payroll_period": "Test Payroll Period", + "declarations": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 100000), + dict(exemption_sub_category = "_Test1 Sub Category", + exemption_category = "_Test Category", + amount = 50000), + ] + }) + self.assertTrue(declaration.submit) + duplicate_declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Declaration", + "employee": frappe.get_value("Employee", {"user_id":"employee@taxexepmtion.com"}, "name"), + "payroll_period": "Test Payroll Period", + "declarations": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 100000) + ] + }) + self.assertRaises(frappe.DocstatusTransitionError, duplicate_declaration.submit) + duplicate_declaration.employee = frappe.get_value("Employee", {"user_id":"employee1@taxexepmtion.com"}, "name") + self.assertTrue(duplicate_declaration.submit) + +def create_payroll_period(): + if not frappe.db.exists("Payroll Period", "_Test Payroll Period"): + from datetime import date + payroll_period = frappe.get_doc(dict( + doctype = 'Payroll Period', + name = "_Test Payroll Period", + company = "_Test Company", + periods = [ + dict(start_date = date(date.today().year, 1, 1), + end_date = date(date.today().year, 12, 31)) + ] + )).insert() + +def create_exemption_category(): + if not frappe.db.exists("Employee Tax Exemption Category", "_Test Category"): + category = frappe.get_doc({ + "doctype": "Employee Tax Exemption Category", + "name": "_Test Category", + "deduction_component": "_Test Tax", + "max_amount": 100000 + }).insert() + if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test Category"): + frappe.get_doc({ + "doctype": "Employee Tax Exemption Sub Category", + "name": "_Test Sub Category", + "exemption_category": "_Test Category", + "max_amount": 100000 + }).insert() + if not frappe.db.exists("Employee Tax Exemption Sub Category", "_Test Category"): + frappe.get_doc({ + "doctype": "Employee Tax Exemption Sub Category", + "name": "_Test1 Sub Category", + "exemption_category": "_Test Category", + "max_amount": 50000 + }).insert() diff --git a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py index 1dc090f0bb..4b5777bcab 100644 --- a/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py +++ b/erpnext/hr/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.py @@ -5,6 +5,50 @@ from __future__ import unicode_literals import frappe import unittest +from erpnext.hr.doctype.employee_tax_exemption_declaration.test_employee_tax_exemption_declaration import create_exemption_category, create_payroll_period class TestEmployeeTaxExemptionProofSubmission(unittest.TestCase): - pass + def setup(self): + make_employee("employee@proofsubmission.com") + create_payroll_period() + create_exemption_category() + frappe.db.sql("""delete from `tabEmployee Tax Exemption Proof Submission`""") + + def test_exemption_amount_lesser_than_category_max(self): + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Proof Submission", + "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), + "payroll_period": "Test Payroll Period", + "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", + type_of_proof = "Test Proof", + exemption_category = "_Test Category", + amount = 150000)] + }) + self.assertRaises(frappe.ValidationError, declaration.save) + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Proof Submission", + "payroll_period": "Test Payroll Period", + "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), + "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", + type_of_proof = "Test Proof", + exemption_category = "_Test Category", + amount = 100000)] + }) + self.assertTrue(declaration.save) + self.assertTrue(declaration.submit) + + def test_duplicate_category_in_proof_submission(self): + declaration = frappe.get_doc({ + "doctype": "Employee Tax Exemption Proof Submission", + "employee": frappe.get_value("Employee", {"user_id":"employee@proofsubmission.com"}, "name"), + "payroll_period": "Test Payroll Period", + "tax_exemption_proofs": [dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + type_of_proof = "Test Proof", + amount = 100000), + dict(exemption_sub_category = "_Test Sub Category", + exemption_category = "_Test Category", + amount = 50000), + ] + }) + self.assertRaises(frappe.ValidationError, declaration.save) From 6c92cff22657908498a6ee208965a75593fadf44 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Fri, 11 May 2018 07:52:50 +0530 Subject: [PATCH 24/79] Accounting Period autoname --- .../doctype/accounting_period/accounting_period.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 31f18490a3..030189d88f 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -7,4 +7,9 @@ import frappe from frappe.model.document import Document class AccountingPeriod(Document): - pass + def validate(self): + self.validate_overlap() + + def autoname(self): + company_abbr = frappe.db.get_value("Company", self.company, "abbr") + self.name = " - ".join([self.period_name, company_abbr]) From 9502476c6753cb8c628d48e2b534d48e4ec2625b Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Fri, 11 May 2018 07:53:12 +0530 Subject: [PATCH 25/79] Accounting Period overlap validation --- .../accounting_period/accounting_period.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 030189d88f..306bf9177e 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -13,3 +13,22 @@ class AccountingPeriod(Document): def autoname(self): company_abbr = frappe.db.get_value("Company", self.company, "abbr") self.name = " - ".join([self.period_name, company_abbr]) + + def validate_overlap(self): + existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period` + where ( + (%(start_date)s between start_date and end_date) + or (%(end_date)s between start_date and end_date) + or (start_date between %(start_date)s and %(end_date)s) + or (end_date between %(start_date)s and %(end_date)s) + ) and name!=%(name)s and company=%(company)s""", + { + "start_date": self.start_date, + "end_date": self.end_date, + "name": self.name, + "company": self.company + }, as_dict=True) + + if len(existing_accounting_period) > 0: + frappe.throw("Accounting Period overlaps with {0}".format(existing_accounting_period[0].get("name"))) + From c53e35368d232f3fd02ab1cfff24eead50630c03 Mon Sep 17 00:00:00 2001 From: Pawan Mehta Date: Fri, 11 May 2018 21:03:50 +0530 Subject: [PATCH 26/79] Shift Management - Documentation and Tests (#13997) * [fix] #13996 * codacy fix --- .../shift-assignment-calendar.png | Bin 0 -> 42303 bytes .../img/human-resources/shift-assignment.png | Bin 0 -> 118184 bytes .../img/human-resources/shift-request.png | Bin 0 -> 29626 bytes .../assets/img/human-resources/shift-type.png | Bin 0 -> 28733 bytes .../en/human-resources/shift-management.md | 44 ++++++++++++++++++ .../shift_request/test_shift_request.py | 20 +++++++- .../hr/doctype/shift_type/test_shift_type.py | 9 +++- 7 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 erpnext/docs/assets/img/human-resources/shift-assignment-calendar.png create mode 100644 erpnext/docs/assets/img/human-resources/shift-assignment.png create mode 100644 erpnext/docs/assets/img/human-resources/shift-request.png create mode 100644 erpnext/docs/assets/img/human-resources/shift-type.png create mode 100644 erpnext/docs/user/manual/en/human-resources/shift-management.md diff --git a/erpnext/docs/assets/img/human-resources/shift-assignment-calendar.png b/erpnext/docs/assets/img/human-resources/shift-assignment-calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..7c8ec779c0ecf6ef20981b8964f94516f8fc3024 GIT binary patch literal 42303 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 IlD7>12kqNC*Z=?k literal 0 HcmV?d00001 diff --git a/erpnext/docs/assets/img/human-resources/shift-assignment.png b/erpnext/docs/assets/img/human-resources/shift-assignment.png new file mode 100644 index 0000000000000000000000000000000000000000..39c2857efcdc07e717eea6652dff567e4875cb3a GIT binary patch literal 118184 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

BokD=G*RbYaw4Sp& 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 z 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 XeKuN8^RNZg&i*JWD^e!(+4p||GGIMx literal 0 HcmV?d00001 diff --git a/erpnext/docs/assets/img/human-resources/shift-type.png b/erpnext/docs/assets/img/human-resources/shift-type.png new file mode 100644 index 0000000000000000000000000000000000000000..e03e45c64be298f6c4b46c5847003ad9e4971297 GIT binary patch literal 28733 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 literal 0 HcmV?d00001 diff --git a/erpnext/docs/user/manual/en/human-resources/shift-management.md b/erpnext/docs/user/manual/en/human-resources/shift-management.md new file mode 100644 index 0000000000..2279538cc2 --- /dev/null +++ b/erpnext/docs/user/manual/en/human-resources/shift-management.md @@ -0,0 +1,44 @@ +# Shift Management + +Shift Management section of Human Resources helps your Organization manage shifts of your employees. + +To use Shift Management in ERPNext, + + 1. Set Up a Shift Type. + 2. Enter Shift Request. + 3. View and Manage Shift Assignments. + +### Shift Type + +The Shift Type Set Up allows you to define the different types of Shifts in your Organization. + +To create a new Shift Type go to: + +Human Resources > Shift Management > Shift Type + +* Enter Shift Type, Start Time and End Time for quick entry. + + Shift Type + +### Shift Request + +Shift Request is used by an employee to request for a particular Shift Type. + +To create a new Shift Request Log go to: + +Human Resources > Shift Management > Shift Request + +* Enter Shift Type, Employee, Company, From Date and To Date. + + Shift Request + +### Shift Assignment + +* Once the Shift Request is submitted it automatically creates the Shift Assignments for an Employee. + + Shift Assignment + +* You can also view Calendar view of Shift Assignments. + + Shift Assignment Calendar \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py index 0b8bcb811c..d32443a952 100644 --- a/erpnext/hr/doctype/shift_request/test_shift_request.py +++ b/erpnext/hr/doctype/shift_request/test_shift_request.py @@ -5,6 +5,24 @@ from __future__ import unicode_literals import frappe import unittest +from frappe.utils import nowdate class TestShiftRequest(unittest.TestCase): - pass + def test_make_shift_request(self): + shift_request = frappe.get_doc({ + "doctype": "Shift Request", + "shift_type": "Day Shift", + "company": "_Test Company", + "employee": "_T-Employee-00001", + "employee_name": "_Test Employee", + "start_date": nowdate(), + "end_date": nowdate() + }) + shift_request.insert() + shift_request.submit() + shift_assignment = frappe.db.sql("""select employee + from `tabShift Assignment` + where shift_request = %s""", shift_request.name) + if shift_assignment: + employee = shift_assignment[0][0] + self.assertEqual(shift_request.employee, employee) \ No newline at end of file diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py index bc4f0eafcd..d5afdf519b 100644 --- a/erpnext/hr/doctype/shift_type/test_shift_type.py +++ b/erpnext/hr/doctype/shift_type/test_shift_type.py @@ -7,4 +7,11 @@ import frappe import unittest class TestShiftType(unittest.TestCase): - pass + def test_make_shift_type(self): + shift_type = frappe.get_doc({ + "doctype": "Shift Type", + "name": "Day Shift", + "start_time": "9:00:00", + "end_time": "18:00:00" + }) + shift_type.insert() From 46b23f8e6e8d2b7e19f88912c742c6a33b3f7011 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Fri, 11 May 2018 21:05:24 +0530 Subject: [PATCH 27/79] Salary Structure Refactor, Formula on Salary Component Master (#13967) * Salary structure refactor * Salary Structure Assignment - filters applied * Formula on Salary Component Master * Salary Structure - filter updated, Salary Component - fields re-arranged * Payroll Entry - get_employee_list fix * Salary Structure Assignment - Validate Duplicate Assignment * Salary Structure Assignment - filters for salary structure --- .../hr/doctype/payroll_entry/payroll_entry.py | 5 +- .../salary_component/salary_component.json | 316 +++++++++++++++++- erpnext/hr/doctype/salary_slip/salary_slip.py | 12 +- .../doctype/salary_slip/test_salary_slip.py | 37 +- .../salary_structure/salary_structure.js | 41 +++ .../salary_structure_assignment.js | 33 +- .../salary_structure_assignment.py | 11 +- .../doctype/timesheet/test_timesheet.py | 19 +- 8 files changed, 430 insertions(+), 44 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index 1025bc7dae..e1b841f9b9 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -40,15 +40,16 @@ class PayrollEntry(Document): {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) if sal_struct: - cond += "and t2.parent IN %(sal_struct)s " + cond += "and t2.salary_structure IN %(sal_struct)s " emp_list = frappe.db.sql(""" select t1.name as employee, t1.employee_name, t1.department, t1.designation from - `tabEmployee` t1, `tabSalary Structure Employee` t2 + `tabEmployee` t1, `tabSalary Structure Assignment` t2 where t1.docstatus!=2 and t1.name = t2.employee + and t2.docstatus = 1 %s """% cond, {"sal_struct": sal_struct}, as_dict=True) return emp_list diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json index 27b4bef036..5f875a9948 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.json +++ b/erpnext/hr/doctype/salary_component/salary_component.json @@ -610,6 +610,320 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "condition_and_formula", + "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": "Condition and Formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "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": "Condition", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", + "fieldname": "statistical_component", + "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": "Statistical Component", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depends_on_lwp", + "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": "Depends on Leave Without Pay", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "do_not_include_in_total", + "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": "Do not include in total", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "amount_based_on_formula", + "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": "Amount based on formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==0", + "fieldname": "formula", + "fieldtype": "Code", + "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": "Formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==1", + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_28", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "help", + "fieldtype": "HTML", + "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": "Help", + "length": 0, + "no_copy": 0, + "options": "

Help

\n\n

Notes:

\n\n
    \n
  1. Use field base for using base salary of the Employee
  2. \n
  3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
  4. \n
  5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
  6. \n
  7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
  8. \n
  9. Direct Amount can also be entered based on Condtion. See example 3
\n\n

Examples

\n
    \n
  1. Calculating Basic Salary based on base\n
    Condition: base < 10000
    \n
    Formula: base * .2
  2. \n
  3. Calculating HRA based on Basic SalaryBS \n
    Condition: BS > 2000
    \n
    Formula: BS * .1
  4. \n
  5. Calculating TDS based on Employment Typeemployment_type \n
    Condition: employment_type==\"Intern\"
    \n
    Amount: 1000
  6. \n
", + "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, @@ -623,7 +937,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-27 13:23:34.503504", + "modified": "2018-05-09 17:35:11.073733", "modified_by": "Administrator", "module": "HR", "name": "Salary Component", diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 75eb73b532..99de580a3f 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -104,8 +104,8 @@ class SalarySlip(TransactionBase): '''Returns data for evaluating formula''' data = frappe._dict() - data.update(frappe.get_doc("Salary Structure Employee", - {"employee": self.employee, "parent": self.salary_structure}).as_dict()) + data.update(frappe.get_doc("Salary Structure Assignment", + {"employee": self.employee, "salary_structure": self.salary_structure}).as_dict()) data.update(frappe.get_doc("Employee", self.employee).as_dict()) data.update(self.as_dict()) @@ -166,10 +166,10 @@ class SalarySlip(TransactionBase): if self.payroll_frequency: cond = """and payroll_frequency = '%(payroll_frequency)s'""" % {"payroll_frequency": self.payroll_frequency} - st_name = frappe.db.sql("""select parent from `tabSalary Structure Employee` + st_name = frappe.db.sql("""select salary_structure from `tabSalary Structure Assignment` where employee=%s and (from_date <= %s or from_date <= %s) and (to_date is null or to_date >= %s or to_date >= %s) - and parent in (select name from `tabSalary Structure` + and salary_structure in (select name from `tabSalary Structure` where is_active = 'Yes'%s) """% ('%s', '%s', '%s','%s','%s', cond),(self.employee, self.start_date, joining_date, self.end_date, relieving_date)) @@ -327,7 +327,7 @@ class SalarySlip(TransactionBase): def sum_components(self, component_type, total_field): joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, ["date_of_joining", "relieving_date"]) - + if not relieving_date: relieving_date = getdate(self.end_date) @@ -463,4 +463,4 @@ def unlink_ref_doc_from_salary_slip(ref_no): if linked_ss: for ss in linked_ss: ss_doc = frappe.get_doc("Salary Slip", ss) - frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "") \ No newline at end of file + frappe.db.set_value("Salary Slip", ss_doc.name, "journal_entry", "") diff --git a/erpnext/hr/doctype/salary_slip/test_salary_slip.py b/erpnext/hr/doctype/salary_slip/test_salary_slip.py index cced29d7d9..ae58298bff 100644 --- a/erpnext/hr/doctype/salary_slip/test_salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/test_salary_slip.py @@ -7,6 +7,7 @@ import frappe import erpnext import calendar from erpnext.accounts.utils import get_fiscal_year +from frappe.utils.make_random import get_random from frappe.utils import getdate, nowdate, add_days, add_months, flt from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip from erpnext.hr.doctype.payroll_entry.test_payroll_entry import get_salary_component_account @@ -272,33 +273,31 @@ def make_salary_structure(sal_struct, payroll_frequency, employee): frappe.get_doc({ "doctype": "Salary Structure", "name": sal_struct, - "company": erpnext.get_default_company(), - "employees": get_employee_details(employee), + "company": "_Test Company", "earnings": get_earnings_component(), "deductions": get_deductions_component(), "payroll_frequency": payroll_frequency, - "payment_account": frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name") + "payment_account": get_random("Account") }).insert() - elif not frappe.db.get_value("Salary Structure Employee",{'parent':sal_struct, 'employee':employee},'name'): + create_salary_structure_assignment(employee, sal_struct) + + elif not frappe.db.get_value("Salary Structure Assignment",{'salary_structure':sal_struct, 'employee':employee},'name'): sal_struct = frappe.get_doc("Salary Structure", sal_struct) - sal_struct.append("employees", {"employee": employee, - "employee_name": employee, - "base": 32000, - "variable": 3200, - "from_date": add_months(nowdate(),-1) - }) - sal_struct.save() + create_salary_structure_assignment(employee, sal_struct) sal_struct = sal_struct.name return sal_struct -def get_employee_details(employee): - return [{"employee": employee, - "base": 50000, - "variable": 5000, - "from_date": add_months(nowdate(),-1) - } - ] +def create_salary_structure_assignment(employee, salary_structure): + salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") + salary_structure_assignment.employee = employee + salary_structure_assignment.base = 50000 + salary_structure_assignment.variable = 5000 + salary_structure_assignment.from_date = add_months(nowdate(), -1) + salary_structure_assignment.salary_structure = salary_structure + salary_structure_assignment.company = erpnext.get_default_company() + salary_structure_assignment.save(ignore_permissions=True) + return salary_structure_assignment def get_earnings_component(): return [ @@ -353,4 +352,4 @@ def get_deductions_component(): "formula": 'base*.1', "idx": 3 } - ] \ No newline at end of file + ] diff --git a/erpnext/hr/doctype/salary_structure/salary_structure.js b/erpnext/hr/doctype/salary_structure/salary_structure.js index 3de01cd303..6f5c923a35 100755 --- a/erpnext/hr/doctype/salary_structure/salary_structure.js +++ b/erpnext/hr/doctype/salary_structure/salary_structure.js @@ -165,5 +165,46 @@ frappe.ui.form.on('Salary Detail', { deductions_remove: function(frm) { calculate_totals(frm.doc); + }, + + salary_component: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if(child.salary_component){ + frappe.call({ + method: "frappe.client.get", + args: { + doctype: "Salary Component", + name: child.salary_component + }, + callback: function(data) { + if(data.message){ + var result = data.message; + frappe.model.set_value(cdt, cdn, 'condition',result.condition); + frappe.model.set_value(cdt, cdn, 'amount_based_on_formula',result.amount_based_on_formula); + if(result.amount_based_on_formula == 1){ + frappe.model.set_value(cdt, cdn, 'formula',result.formula); + } + else{ + frappe.model.set_value(cdt, cdn, 'amount',result.amount); + } + frappe.model.set_value(cdt, cdn, 'statistical_component',result.statistical_component); + frappe.model.set_value(cdt, cdn, 'depends_on_lwp',result.depends_on_lwp); + frappe.model.set_value(cdt, cdn, 'do_not_include_in_total',result.do_not_include_in_total); + refresh_field("earnings"); + refresh_field("deductions"); + } + } + }); + } + }, + + amount_based_on_formula: function(frm, cdt, cdn) { + var child = locals[cdt][cdn]; + if(child.amount_based_on_formula == 1){ + frappe.model.set_value(cdt, cdn, 'amount', null); + } + else{ + frappe.model.set_value(cdt, cdn, 'formula', null); + } } }) diff --git a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js index e7c6598653..af4ca3a3b5 100644 --- a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js +++ b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js @@ -11,9 +11,36 @@ frappe.ui.form.on('Salary Structure Assignment', { } } }); + frm.set_query("salary_structure", function() { + return { + filters: { + company: frm.doc.company, + is_active: "Yes", + docstatus: 1 + } + } + }); }, - - refresh: function(frm) { - + employee: function(frm) { + if(frm.doc.employee){ + frappe.call({ + method: "frappe.client.get_value", + args:{ + doctype: "Employee", + fieldname: "company", + filters:{ + name: frm.doc.employee + } + }, + callback: function(data) { + if(data.message){ + frm.set_value("company", data.message.company); + } + } + }); + } + else{ + frm.set_value("company", null); + } } }); diff --git a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.py b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.py index c9269d7c92..ee2920be2c 100644 --- a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.py +++ b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.py @@ -11,6 +11,7 @@ from frappe.model.document import Document class SalaryStructureAssignment(Document): def validate(self): self.validate_dates() + self.validate_duplicate_assignments() def validate_dates(self): joining_date, relieving_date = frappe.db.get_value("Employee", self.employee, @@ -33,10 +34,14 @@ class SalaryStructureAssignment(Document): .format(self.to_date, relieving_date)) def validate_duplicate_assignments(self): + if not self.name: + # hack! if name is null, it could cause problems with != + self.name = "New "+self.doctype assignment = frappe.db.sql(""" select name from `tabSalary Structure Assignment` where employee=%(employee)s - and name != %(salary_struct)s + and name != %(name)s + and docstatus != 2 and ( (%(from_date)s between from_date and ifnull(to_date, '2199-12-31')) or (%(to_date)s between from_date and ifnull(to_date, '2199-12-31')) @@ -45,8 +50,8 @@ class SalaryStructureAssignment(Document): 'employee': self.employee, 'from_date': self.from_date, 'to_date': (self.to_date or '2199-12-31'), - 'salary_struct': self.salary_struct + 'name': self.name }) if assignment: - frappe.throw(_("Active Salary Structure Assignment {0} found for employee {1} for the given dates").format(assignment[0][0], self.employee)) \ No newline at end of file + frappe.throw(_("Active Salary Structure Assignment {0} found for employee {1} for the given dates").format(assignment[0][0], self.employee)) diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py index 2458db0cba..d2017c5712 100644 --- a/erpnext/projects/doctype/timesheet/test_timesheet.py +++ b/erpnext/projects/doctype/timesheet/test_timesheet.py @@ -118,30 +118,21 @@ class TestTimesheet(unittest.TestCase): def make_salary_structure(employee): - name = frappe.db.get_value('Salary Structure Employee', {'employee': employee}, 'parent') + name = frappe.db.get_value('Salary Structure Assignment', {'employee': employee}, 'salary_structure') if name: salary_structure = frappe.get_doc('Salary Structure', name) else: salary_structure = frappe.new_doc("Salary Structure") salary_structure.name = "Timesheet Salary Structure Test" salary_structure.salary_slip_based_on_timesheet = 1 - salary_structure.from_date = add_days(nowdate(), -30) salary_structure.salary_component = "Basic" salary_structure.hour_rate = 50.0 salary_structure.company = "_Test Company" salary_structure.payment_account = get_random("Account") - salary_structure.set('employees', []) salary_structure.set('earnings', []) salary_structure.set('deductions', []) - es = salary_structure.append('employees', { - "employee": employee, - "base": 1200, - "from_date": add_months(nowdate(),-1) - }) - - es = salary_structure.append('earnings', { "salary_component": "_Test Allowance", "amount": 100 @@ -154,6 +145,14 @@ def make_salary_structure(employee): salary_structure.save(ignore_permissions=True) + salary_structure_assignment = frappe.new_doc("Salary Structure Assignment") + salary_structure_assignment.employee = employee + salary_structure_assignment.base = 1200 + salary_structure_assignment.from_date = add_months(nowdate(), -1) + salary_structure_assignment.salary_structure = salary_structure.name + salary_structure_assignment.company = "_Test Company" + salary_structure_assignment.save(ignore_permissions=True) + return salary_structure def make_timesheet(employee, simulate=False, billable = 0, activity_type="_Test Activity Type", project=None, task=None, company=None): From a35dad7610d7ebb6d822956b91dedc40fe8948e6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Thu, 10 May 2018 19:47:41 +0530 Subject: [PATCH 28/79] add get leave period method --- erpnext/hr/utils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 057f406e80..be8c948add 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -49,3 +49,9 @@ def update_employee(employee, details, cancel=False): new_data = get_datetime(new_data) setattr(employee, item.fieldname, new_data) return employee + + +def get_leave_period(from_date, to_date, company): + return frappe.db.sql("""select name from `tabLeave Period` + where is_active=1 and company=%s + and to_date >= %s and from_date <= %s""", (company, to_date, from_date)) From c1acbdfb690f4ce7d410c2ae342fff205b07ac3c Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 12 May 2018 17:45:22 +0530 Subject: [PATCH 29/79] [fix] overlapping dates --- erpnext/hr/utils.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index be8c948add..686f79170c 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -50,8 +50,19 @@ def update_employee(employee, details, cancel=False): setattr(employee, item.fieldname, new_data) return employee - def get_leave_period(from_date, to_date, company): - return frappe.db.sql("""select name from `tabLeave Period` - where is_active=1 and company=%s - and to_date >= %s and from_date <= %s""", (company, to_date, from_date)) + leave_period = frappe.db.sql(""" + select name, from_date, to_date + from `tabLeave Period` + where company=%(company)s and is_active=1 + and (from_date between %(from_date)s and %(to_date)s + or to_date between %(from_date)s and %(to_date)s + or (from_date < %(from_date)s and to_date > %(to_date)s)) + """, { + "from_date": from_date, + "to_date": to_date, + "company": company + }, as_dict=1) + + if leave_period: + return leave_period From 3387a35fed08c5129abad7d434a6e1fbab24629d Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Thu, 19 Apr 2018 18:23:31 +0530 Subject: [PATCH 30/79] rename supplier-type to supplier-group --- .../__init__.py | 0 .../supplier_group.js} | 4 ++-- .../supplier_group.json} | 21 +++++++++---------- .../doctype/supplier_group/supplier_group.py | 10 +++++++++ .../doctype/supplier_group/test_records.json | 7 +++++++ .../test_supplier_group.js} | 6 +++--- .../supplier_group/test_supplier_group.py | 7 +++++++ erpnext/setup/doctype/supplier_type/README.md | 1 - .../doctype/supplier_type/supplier_type.py | 10 --------- .../doctype/supplier_type/test_records.json | 6 ------ .../supplier_type/test_supplier_type.py | 7 ------- 11 files changed, 39 insertions(+), 40 deletions(-) rename erpnext/setup/doctype/{supplier_type => supplier_group}/__init__.py (100%) rename erpnext/setup/doctype/{supplier_type/supplier_type.js => supplier_group/supplier_group.js} (74%) rename erpnext/setup/doctype/{supplier_type/supplier_type.json => supplier_group/supplier_group.json} (94%) create mode 100644 erpnext/setup/doctype/supplier_group/supplier_group.py create mode 100644 erpnext/setup/doctype/supplier_group/test_records.json rename erpnext/setup/doctype/{supplier_type/test_supplier_type.js => supplier_group/test_supplier_group.js} (71%) create mode 100644 erpnext/setup/doctype/supplier_group/test_supplier_group.py delete mode 100644 erpnext/setup/doctype/supplier_type/README.md delete mode 100644 erpnext/setup/doctype/supplier_type/supplier_type.py delete mode 100644 erpnext/setup/doctype/supplier_type/test_records.json delete mode 100644 erpnext/setup/doctype/supplier_type/test_supplier_type.py diff --git a/erpnext/setup/doctype/supplier_type/__init__.py b/erpnext/setup/doctype/supplier_group/__init__.py similarity index 100% rename from erpnext/setup/doctype/supplier_type/__init__.py rename to erpnext/setup/doctype/supplier_group/__init__.py diff --git a/erpnext/setup/doctype/supplier_type/supplier_type.js b/erpnext/setup/doctype/supplier_group/supplier_group.js similarity index 74% rename from erpnext/setup/doctype/supplier_type/supplier_type.js rename to erpnext/setup/doctype/supplier_group/supplier_group.js index f1c5d70640..10e5c04dcc 100644 --- a/erpnext/setup/doctype/supplier_type/supplier_type.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -1,5 +1,5 @@ -// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -// License: GNU General Public License v3. See license.txt +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt cur_frm.cscript.refresh = function(doc) { cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit.")) diff --git a/erpnext/setup/doctype/supplier_type/supplier_type.json b/erpnext/setup/doctype/supplier_group/supplier_group.json similarity index 94% rename from erpnext/setup/doctype/supplier_type/supplier_type.json rename to erpnext/setup/doctype/supplier_group/supplier_group.json index d7d7084d7f..35fafac4ff 100644 --- a/erpnext/setup/doctype/supplier_type/supplier_type.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -3,7 +3,7 @@ "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, - "autoname": "field:supplier_type", + "autoname": "field:supplier_group_name", "beta": 0, "creation": "2013-01-10 16:34:24", "custom": 0, @@ -18,7 +18,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "supplier_type", + "fieldname": "supplier_group_name", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -27,7 +27,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Supplier Type", + "label": "Supplier Group Name", "length": 0, "no_copy": 0, "oldfieldname": "supplier_type", @@ -41,6 +41,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -71,6 +72,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -102,6 +104,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -131,6 +134,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -163,6 +167,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -177,15 +182,14 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-09-04 18:54:10.093500", + "modified": "2018-04-19 07:45:03.495577", "modified_by": "Administrator", "module": "Setup", - "name": "Supplier Type", + "name": "Supplier Group", "owner": "Administrator", "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -205,7 +209,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -225,7 +228,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -245,7 +247,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -265,7 +266,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -285,7 +285,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.py b/erpnext/setup/doctype/supplier_group/supplier_group.py new file mode 100644 index 0000000000..53ab215190 --- /dev/null +++ b/erpnext/setup/doctype/supplier_group/supplier_group.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class SupplierGroup(Document): + pass diff --git a/erpnext/setup/doctype/supplier_group/test_records.json b/erpnext/setup/doctype/supplier_group/test_records.json new file mode 100644 index 0000000000..1681e2a647 --- /dev/null +++ b/erpnext/setup/doctype/supplier_group/test_records.json @@ -0,0 +1,7 @@ +[ + { + "doctype": "Supplier Group", + "supplier_group_name": "_Test Supplier Group" + } +] + \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_type/test_supplier_type.js b/erpnext/setup/doctype/supplier_group/test_supplier_group.js similarity index 71% rename from erpnext/setup/doctype/supplier_type/test_supplier_type.js rename to erpnext/setup/doctype/supplier_group/test_supplier_group.js index 085dddd0b6..976dd2cc4f 100644 --- a/erpnext/setup/doctype/supplier_type/test_supplier_type.js +++ b/erpnext/setup/doctype/supplier_group/test_supplier_group.js @@ -2,15 +2,15 @@ // rename this file from _test_[name] to test_[name] to activate // and remove above this line -QUnit.test("test: Supplier Type", function (assert) { +QUnit.test("test: Supplier Group", function (assert) { let done = assert.async(); // number of asserts assert.expect(1); frappe.run_serially([ - // insert a new Supplier Type - () => frappe.tests.make('Supplier Type', [ + // insert a new Supplier Group + () => frappe.tests.make('Supplier Group', [ // values to be set {key: 'value'} ]), diff --git a/erpnext/setup/doctype/supplier_group/test_supplier_group.py b/erpnext/setup/doctype/supplier_group/test_supplier_group.py new file mode 100644 index 0000000000..0e3d23d6bd --- /dev/null +++ b/erpnext/setup/doctype/supplier_group/test_supplier_group.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +test_records = frappe.get_test_records('Supplier Group') diff --git a/erpnext/setup/doctype/supplier_type/README.md b/erpnext/setup/doctype/supplier_type/README.md deleted file mode 100644 index 0da4c708ac..0000000000 --- a/erpnext/setup/doctype/supplier_type/README.md +++ /dev/null @@ -1 +0,0 @@ -Supplier classification. \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_type/supplier_type.py b/erpnext/setup/doctype/supplier_type/supplier_type.py deleted file mode 100644 index 0d237469e3..0000000000 --- a/erpnext/setup/doctype/supplier_type/supplier_type.py +++ /dev/null @@ -1,10 +0,0 @@ -# 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 - -from frappe.model.document import Document - -class SupplierType(Document): - pass \ No newline at end of file diff --git a/erpnext/setup/doctype/supplier_type/test_records.json b/erpnext/setup/doctype/supplier_type/test_records.json deleted file mode 100644 index fd632c5afd..0000000000 --- a/erpnext/setup/doctype/supplier_type/test_records.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "doctype": "Supplier Type", - "supplier_type": "_Test Supplier Type" - } -] diff --git a/erpnext/setup/doctype/supplier_type/test_supplier_type.py b/erpnext/setup/doctype/supplier_type/test_supplier_type.py deleted file mode 100644 index 9fa8a44b1d..0000000000 --- a/erpnext/setup/doctype/supplier_type/test_supplier_type.py +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt -from __future__ import unicode_literals - - -import frappe -test_records = frappe.get_test_records('Supplier Type') \ No newline at end of file From 4437a5ed18cd4affd29d6fa5770cda65fb25577a Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Thu, 19 Apr 2018 18:30:16 +0530 Subject: [PATCH 31/79] json level changes --- .../doctype/pricing_rule/pricing_rule.json | 60 +++++++++--- .../accounts/doctype/tax_rule/tax_rule.json | 66 +++++++++++-- .../buying_settings/buying_settings.json | 97 +++++++++++++++++-- erpnext/buying/doctype/supplier/supplier.json | 66 ++++++------- .../buying/doctype/supplier/test_records.json | 14 +-- 5 files changed, 238 insertions(+), 65 deletions(-) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json index 1c30fa4357..ac5990427e 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json @@ -39,6 +39,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -69,6 +70,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -100,6 +102,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -131,6 +134,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -162,6 +166,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -193,6 +198,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -222,6 +228,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -253,6 +260,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -282,6 +290,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -311,6 +320,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -340,6 +350,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -369,6 +380,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -398,6 +410,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -419,7 +432,7 @@ "label": "Applicable For", "length": 0, "no_copy": 0, - "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Type", + "options": "\nCustomer\nCustomer Group\nTerritory\nSales Partner\nCampaign\nSupplier\nSupplier Group", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -429,6 +442,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -460,6 +474,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -491,6 +506,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -522,6 +538,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -553,6 +570,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -584,6 +602,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -615,6 +634,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -623,8 +643,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval:doc.applicable_for==\"Supplier Type\"", - "fieldname": "supplier_type", + "depends_on": "eval:doc.applicable_for==\"Supplier Group\"", + "fieldname": "supplier_group", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -633,10 +653,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Supplier Type", + "label": "Supplier Group", "length": 0, "no_copy": 0, - "options": "Supplier Type", + "options": "Supplier Group", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -646,6 +666,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -675,6 +696,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -704,6 +726,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -733,6 +756,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -762,6 +786,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -791,6 +816,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -821,6 +847,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -850,6 +877,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -878,6 +906,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -908,6 +937,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -940,6 +970,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -971,6 +1002,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1003,6 +1035,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1032,6 +1065,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1064,6 +1098,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1093,6 +1128,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1124,6 +1160,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1152,6 +1189,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1182,6 +1220,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1212,6 +1251,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1243,6 +1283,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1273,6 +1314,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1302,6 +1344,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -1316,7 +1359,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-02-12 17:21:22.559996", + "modified": "2018-04-19 07:52:41.024618", "modified_by": "Administrator", "module": "Accounts", "name": "Pricing Rule", @@ -1324,7 +1367,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1344,7 +1386,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1364,7 +1405,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1384,7 +1424,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1404,7 +1443,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.json b/erpnext/accounts/doctype/tax_rule/tax_rule.json index a746dafa04..56ca055770 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.json +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.json @@ -1,5 +1,6 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 0, "autoname": "TR.####", @@ -12,6 +13,7 @@ "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -40,9 +42,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -69,9 +73,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -97,9 +103,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -128,9 +136,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -159,9 +169,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -188,9 +200,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -219,9 +233,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -250,9 +266,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -279,9 +297,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -308,9 +328,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -337,9 +359,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -367,9 +391,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -395,9 +421,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -426,15 +454,17 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, "depends_on": "eval:doc.tax_type==\"Purchase\"", - "fieldname": "supplier_type", + "fieldname": "supplier_group", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -443,10 +473,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Supplier Type", + "label": "Supplier Group", "length": 0, "no_copy": 0, - "options": "Supplier Type", + "options": "Supplier Group", "permlevel": 0, "precision": "", "print_hide": 0, @@ -457,9 +487,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -486,9 +518,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -515,9 +549,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -544,9 +580,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -574,9 +612,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -603,9 +643,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -632,9 +674,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -660,9 +704,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -689,9 +735,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -717,9 +765,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -747,9 +797,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -775,9 +827,11 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -805,20 +859,21 @@ "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, - "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-02-17 16:22:42.501765", + "modified": "2018-04-19 07:53:43.020808", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Rule", @@ -827,7 +882,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json index bb2b47def7..c0684f939a 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.json +++ b/erpnext/buying/doctype/buying_settings/buying_settings.json @@ -1,188 +1,263 @@ { "allow_copy": 0, + "allow_guest_to_view": 0, "allow_import": 0, "allow_rename": 0, + "beta": 0, "creation": "2013-06-25 11:04:03", "custom": 0, "description": "Settings for Buying Module", "docstatus": 0, "doctype": "DocType", "document_type": "Other", + "editable_grid": 0, "fields": [ { + "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "default": "Supplier Name", "fieldname": "supp_master_name", "fieldtype": "Select", "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": "Supplier Naming By", + "length": 0, "no_copy": 0, "options": "Supplier Name\nNaming Series", "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_on_submit": 0, "bold": 0, "collapsible": 0, - "fieldname": "supplier_type", + "columns": 0, + "fieldname": "supplier_group", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, + "ignore_xss_filter": 0, "in_filter": 0, + "in_global_search": 0, "in_list_view": 0, - "label": "Default Supplier Type", + "in_standard_filter": 0, + "label": "Default Supplier Group", + "length": 0, "no_copy": 0, - "options": "Supplier Type", + "options": "Supplier Group", "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "buying_price_list", "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": "Default Buying Price List", + "length": 0, "no_copy": 0, "options": "Price List", "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "column_break_3", "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, "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "po_required", "fieldtype": "Select", "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": "Purchase Order Required", + "length": 0, "no_copy": 0, "options": "No\nYes", "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "pr_required", "fieldtype": "Select", "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": "Purchase Receipt Required", + "length": 0, "no_copy": 0, "options": "No\nYes", "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "maintain_same_rate", "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": "Maintain same rate throughout purchase cycle", + "length": 0, "no_copy": 0, "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_on_submit": 0, "bold": 0, "collapsible": 0, + "columns": 0, "fieldname": "allow_multiple_items", "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": "Allow Item to be added multiple times in a transaction", + "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_on_submit": 0, @@ -213,19 +288,22 @@ "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-cog", "idx": 1, + "image_view": 0, "in_create": 0, - "in_dialog": 0, "is_submittable": 0, "issingle": 1, "istable": 0, - "modified": "2017-12-27 15:20:06.052342", + "max_attachments": 0, + "modified": "2018-04-19 07:56:42.888821", "modified_by": "Administrator", "module": "Buying", "name": "Buying Settings", @@ -233,7 +311,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 0, @@ -252,6 +329,10 @@ "write": 1 } ], + "quick_entry": 0, "read_only": 0, - "read_only_onload": 0 + "read_only_onload": 0, + "show_name_in_global_search": 0, + "track_changes": 0, + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index afb8899dcb..d342e115b7 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -43,7 +43,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -76,7 +76,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 1, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -108,7 +108,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -140,7 +140,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -171,7 +171,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -267,7 +267,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -296,7 +296,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -306,7 +306,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "supplier_type", + "fieldname": "supplier_group", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -315,12 +315,12 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 1, - "label": "Supplier Type", + "label": "Supplier Group", "length": 0, "no_copy": 0, "oldfieldname": "supplier_type", "oldfieldtype": "Link", - "options": "Supplier Type", + "options": "Supplier Group", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -330,7 +330,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -362,7 +362,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -394,7 +394,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -425,7 +425,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -456,7 +456,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -487,7 +487,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -518,7 +518,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -614,7 +614,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -645,7 +645,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -675,7 +675,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -706,7 +706,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -737,7 +737,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -770,7 +770,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -803,7 +803,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -833,7 +833,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -862,7 +862,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -893,7 +893,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -924,7 +924,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -957,7 +957,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -988,7 +988,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -1021,7 +1021,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "translatable": 0, "unique": 0 }, { @@ -1163,7 +1163,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-19 12:19:52.519026", + "modified": "2018-05-11 12:19:52.519026", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", @@ -1307,7 +1307,7 @@ "quick_entry": 1, "read_only": 0, "read_only_onload": 0, - "search_fields": "supplier_name, supplier_type", + "search_fields": "supplier_name, supplier_group", "show_name_in_global_search": 1, "sort_order": "ASC", "title_field": "supplier_name", diff --git a/erpnext/buying/doctype/supplier/test_records.json b/erpnext/buying/doctype/supplier/test_records.json index 2536721326..9c019898cb 100644 --- a/erpnext/buying/doctype/supplier/test_records.json +++ b/erpnext/buying/doctype/supplier/test_records.json @@ -2,39 +2,39 @@ { "doctype": "Supplier", "supplier_name": "_Test Supplier With Template 1", - "supplier_type": "_Test Supplier Type", + "supplier_group": "_Test Supplier Group", "payment_terms": "_Test Payment Term Template 3" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier P", - "supplier_type": "_Test Supplier Type" + "supplier_group": "_Test Supplier Group" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier with Country", - "supplier_type": "_Test Supplier Type", + "supplier_group": "_Test Supplier Group", "country": "Greece" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier", - "supplier_type": "_Test Supplier Type" + "supplier_group": "_Test Supplier Group" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier 1", - "supplier_type": "_Test Supplier Type" + "supplier_group": "_Test Supplier Group" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier 2", - "supplier_type": "_Test Supplier Type" + "supplier_group": "_Test Supplier Group" }, { "doctype": "Supplier", "supplier_name": "_Test Supplier USD", - "supplier_type": "_Test Supplier Type", + "supplier_group": "_Test Supplier Group", "accounts": [{ "company": "_Test Company", "account": "_Test Payable USD - _TC" From 0a50554417995dcc3abab5df4c7b970558380dc7 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Thu, 19 Apr 2018 18:32:48 +0530 Subject: [PATCH 32/79] doctype level changes --- .../opening_invoice_creation_tool.py | 8 ++++---- .../doctype/pricing_rule/pricing_rule.js | 6 +++--- .../doctype/pricing_rule/pricing_rule.py | 16 ++++++++-------- erpnext/accounts/doctype/tax_rule/tax_rule.js | 2 +- erpnext/accounts/doctype/tax_rule/tax_rule.py | 6 +++--- .../doctype/buying_settings/buying_settings.py | 2 +- .../request_for_quotation.js | 14 +++++++------- .../test_request_for_quotation.py | 2 +- erpnext/buying/doctype/supplier/supplier_list.js | 2 +- erpnext/buying/doctype/supplier/test_supplier.js | 4 ++-- erpnext/buying/doctype/supplier/test_supplier.py | 10 +++++----- .../selling/doctype/sales_order/sales_order.py | 4 ++-- .../stock/doctype/delivery_note/delivery_note.js | 2 +- 13 files changed, 39 insertions(+), 39 deletions(-) diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py index 753540eb42..5712bf83ab 100644 --- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py +++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py @@ -120,12 +120,12 @@ class OpeningInvoiceCreationTool(Document): if party_type == "Customer": party_doc.customer_name = party else: - supplier_type = frappe.db.get_single_value("Buying Settings", "supplier_type") - if not supplier_type: - frappe.throw(_("Please Set Supplier Type in Buying Settings.")) + supplier_group = frappe.db.get_single_value("Buying Settings", "supplier_group") + if not supplier_group: + frappe.throw(_("Please Set Supplier Group in Buying Settings.")) party_doc.supplier_name = party - party_doc.supplier_type = supplier_type + party_doc.supplier_group = supplier_group party_doc.flags.ignore_mandatory = True party_doc.save(ignore_permissions=True) diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js index dcbf354c16..4be09deb4b 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.js +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.js @@ -33,7 +33,7 @@ frappe.ui.form.on("Pricing Rule", "refresh", function(frm) { ${__("Pricing Rule is first selected based on 'Apply On' field, which can be Item, Item Group or Brand.")}
  • - ${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Type, Campaign, Sales Partner etc.")} + ${__("Then Pricing Rules are filtered out based on Customer, Customer Group, Territory, Supplier, Supplier Group, Campaign, Sales Partner etc.")}
  • ${__('Pricing Rules are further filtered based on quantity.')} @@ -51,7 +51,7 @@ frappe.ui.form.on("Pricing Rule", "refresh", function(frm) { ${__('Customer > Customer Group > Territory')}
  • - ${__('Supplier > Supplier Type')} + ${__('Supplier > Supplier Group')}
  • @@ -75,7 +75,7 @@ cur_frm.cscript.set_options_for_applicable_for = function() { options = $.merge(options, ["Customer", "Customer Group", "Territory", "Sales Partner", "Campaign"]); } if(cur_frm.doc.buying) { - $.merge(options, ["Supplier", "Supplier Type"]); + $.merge(options, ["Supplier", "Supplier Group"]); } set_field_options("applicable_for", options.join("\n")); diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py index e047b6d67e..9b73fcd968 100644 --- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py +++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py @@ -41,7 +41,7 @@ class PricingRule(Document): throw(_("Selling must be checked, if Applicable For is selected as {0}" .format(self.applicable_for))) - if not self.buying and self.applicable_for in ["Supplier", "Supplier Type"]: + if not self.buying and self.applicable_for in ["Supplier", "Supplier Group"]: throw(_("Buying must be checked, if Applicable For is selected as {0}" .format(self.applicable_for))) @@ -85,7 +85,7 @@ def apply_pricing_rule(args): "customer_group": "something", "territory": "something", "supplier": "something", - "supplier_type": "something", + "supplier_group": "something", "currency": "something", "conversion_rate": "something", "price_list": "something", @@ -165,10 +165,10 @@ def get_pricing_rule_for_item(args): if customer: args.customer_group, args.territory = customer - args.supplier = args.supplier_type = None + args.supplier = args.supplier_group = None - elif args.supplier and not args.supplier_type: - args.supplier_type = frappe.db.get_value("Supplier", args.supplier, "supplier_type") + elif args.supplier and not args.supplier_group: + args.supplier_group = frappe.db.get_value("Supplier", args.supplier, "supplier_group") args.customer = args.customer_group = args.territory = None pricing_rules = get_pricing_rules(args) @@ -258,7 +258,7 @@ def get_pricing_rules(args): conditions = item_variant_condition = "" values = {"item_code": args.get("item_code"), "brand": args.get("brand")} - for field in ["company", "customer", "supplier", "supplier_type", "campaign", "sales_partner"]: + for field in ["company", "customer", "supplier", "supplier_group", "campaign", "sales_partner"]: if args.get(field): conditions += " and ifnull("+field+", '') in (%("+field+")s, '')" values[field] = args.get(field) @@ -324,11 +324,11 @@ def filter_pricing_rules(args, pricing_rules): # apply internal priority all_fields = ["item_code", "item_group", "brand", "customer", "customer_group", "territory", - "supplier", "supplier_type", "campaign", "sales_partner", "variant_of"] + "supplier", "supplier_group", "campaign", "sales_partner", "variant_of"] if len(pricing_rules) > 1: for field_set in [["item_code", "variant_of", "item_group", "brand"], - ["customer", "customer_group", "territory"], ["supplier", "supplier_type"]]: + ["customer", "customer_group", "territory"], ["supplier", "supplier_group"]]: remaining_fields = list(set(all_fields) - set(field_set)) if if_all_rules_same(pricing_rules, remaining_fields): pricing_rules = apply_internal_priority(pricing_rules, field_set, args) diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.js b/erpnext/accounts/doctype/tax_rule/tax_rule.js index 84c42a3da3..370890e4d8 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.js +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.js @@ -2,7 +2,7 @@ // License: GNU General Public License v3. See license.txt cur_frm.add_fetch("customer", "customer_group", "customer_group" ); -cur_frm.add_fetch("supplier", "supplier_type", "supplier_type" ); +cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" ); frappe.ui.form.on("Tax Rule", "tax_type", function(frm) { frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales"); diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py index ddef65799b..56bd2870ba 100644 --- a/erpnext/accounts/doctype/tax_rule/tax_rule.py +++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py @@ -30,7 +30,7 @@ class TaxRule(Document): def validate_tax_template(self): if self.tax_type== "Sales": - self.purchase_tax_template = self.supplier = self.supplier_type = None + self.purchase_tax_template = self.supplier = self.supplier_group = None if self.customer: self.customer_group = None @@ -38,7 +38,7 @@ class TaxRule(Document): self.sales_tax_template = self.customer = self.customer_group = None if self.supplier: - self.supplier_type = None + self.supplier_group = None if not (self.sales_tax_template or self.purchase_tax_template): frappe.throw(_("Tax Template is mandatory.")) @@ -53,7 +53,7 @@ class TaxRule(Document): "customer": self.customer, "customer_group": self.customer_group, "supplier": self.supplier, - "supplier_type": self.supplier_type, + "supplier_group": self.supplier_group, "billing_city": self.billing_city, "billing_county": self.billing_county, "billing_state": self.billing_state, diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py index 1f0184b82c..a634a0908b 100644 --- a/erpnext/buying/doctype/buying_settings/buying_settings.py +++ b/erpnext/buying/doctype/buying_settings/buying_settings.py @@ -10,7 +10,7 @@ from frappe.model.document import Document class BuyingSettings(Document): def validate(self): - for key in ["supplier_type", "supp_master_name", "maintain_same_rate", "buying_price_list"]: + for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]: frappe.db.set_default(key, self.get(key, "")) from erpnext.setup.doctype.naming_series.naming_series import set_by_naming_series diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js index de87e04443..2ff80755df 100644 --- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js +++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js @@ -61,11 +61,11 @@ frappe.ui.form.on("Request for Quotation",{ fields: [ { "fieldtype": "Select", "label": __("Get Suppliers By"), "fieldname": "search_type", - "options": "Tag\nSupplier Type", "reqd": 1 }, - { "fieldtype": "Link", "label": __("Supplier Type"), - "fieldname": "supplier_type", - "options": "Supplier Type", "reqd": 0, - "depends_on": "eval:doc.search_type == 'Supplier Type'"}, + "options": "Tag\nSupplier Group", "reqd": 1 }, + { "fieldtype": "Link", "label": __("Supplier Group"), + "fieldname": "supplier_group", + "options": "Supplier Group", "reqd": 0, + "depends_on": "eval:doc.search_type == 'Supplier Group'"}, { "fieldtype": "Data", "label": __("Tag"), "fieldname": "tag", "reqd": 0, "depends_on": "eval:doc.search_type == 'Tag'" }, @@ -121,14 +121,14 @@ frappe.ui.form.on("Request for Quotation",{ }, callback: load_suppliers }); - } else if (args.supplier_type) { + } else if (args.supplier_group) { return frappe.call({ method: "frappe.client.get_list", args: { doctype: "Supplier", order_by: "name", fields: ["name"], - filters: [["Supplier", "supplier_type", "=", args.supplier_type]] + filters: [["Supplier", "supplier_group_name", "=", args.supplier_group]] }, callback: load_suppliers diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py index 7eb741f06a..dbd9f02278 100644 --- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py +++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py @@ -56,7 +56,7 @@ class TestRequestforQuotation(unittest.TestCase): frappe.delete_doc_if_exists("Supplier", "_Test Supplier '1", force=1) supplier = frappe.new_doc("Supplier") supplier.supplier_name = "_Test Supplier '1" - supplier.supplier_type = "_Test Supplier Type" + supplier.supplier_group = "_Test Supplier Group" supplier.insert() rfq = make_request_for_quotation(supplier_wt_appos) diff --git a/erpnext/buying/doctype/supplier/supplier_list.js b/erpnext/buying/doctype/supplier/supplier_list.js index ab25d2c8fc..d99e3f8f0f 100644 --- a/erpnext/buying/doctype/supplier/supplier_list.js +++ b/erpnext/buying/doctype/supplier/supplier_list.js @@ -1,3 +1,3 @@ frappe.listview_settings['Supplier'] = { - add_fields: ["supplier_name", "supplier_type", "image"], + add_fields: ["supplier_name", "supplier_group", "image"], }; diff --git a/erpnext/buying/doctype/supplier/test_supplier.js b/erpnext/buying/doctype/supplier/test_supplier.js index 51e3c09788..bf7c192c91 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.js +++ b/erpnext/buying/doctype/supplier/test_supplier.js @@ -7,7 +7,7 @@ QUnit.test("test: supplier", function(assert) { () => { return frappe.tests.make('Supplier', [ {supplier_name: 'Test Supplier'}, - {supplier_type: 'Hardware'}, + {supplier_group: 'Hardware'}, {country: 'India'}, {default_currency: 'INR'}, {accounts: [ @@ -66,7 +66,7 @@ QUnit.test("test: supplier", function(assert) { () => { assert.ok(cur_frm.doc.supplier_name == 'Test Supplier', "Name correct"); - assert.ok(cur_frm.doc.supplier_type == 'Hardware', "Type correct"); + assert.ok(cur_frm.doc.supplier_group == 'Hardware', "Type correct"); assert.ok(cur_frm.doc.default_currency == 'INR', "Currency correct"); assert.ok(cur_frm.doc.accounts[0].account == 'Creditors - '+frappe.get_abbr('For Testing'), " Account Head abbr correct"); assert.ok($('.address-box:nth-child(3) p').text().includes('Shipping City 3'), "Address correct"); diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py index 16dda5c5ff..2211db7e0d 100644 --- a/erpnext/buying/doctype/supplier/test_supplier.py +++ b/erpnext/buying/doctype/supplier/test_supplier.py @@ -36,14 +36,14 @@ class TestSupplier(unittest.TestCase): frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "") - # Set credit limit for the supplier type instead of supplier and evaluate the due date - frappe.db.set_value("Supplier Type", "_Test Supplier Type", "payment_terms", "_Test Payment Term Template 3") + # Set credit limit for the supplier group instead of supplier and evaluate the due date + frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") self.assertEqual(due_date, "2016-02-21") - # Payment terms for Supplier Type instead of supplier and evaluate the due date - frappe.db.set_value("Supplier Type", "_Test Supplier Type", "payment_terms", "_Test Payment Term Template 1") + # Payment terms for Supplier Group instead of supplier and evaluate the due date + frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1") # Leap year due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1") @@ -53,7 +53,7 @@ class TestSupplier(unittest.TestCase): self.assertEqual(due_date, "2017-02-28") # Supplier with no default Payment Terms Template - frappe.db.set_value("Supplier Type", "_Test Supplier Type", "payment_terms", "") + frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "") frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "") due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier") diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py index 40c2cf0358..72723eb115 100755 --- a/erpnext/selling/doctype/sales_order/sales_order.py +++ b/erpnext/selling/doctype/sales_order/sales_order.py @@ -755,9 +755,9 @@ def make_purchase_order_for_drop_shipment(source_name, for_supplier, target_doc= def get_supplier(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") if supp_master_name == "Supplier Name": - fields = ["name", "supplier_type"] + fields = ["name", "supplier_group"] else: - fields = ["name", "supplier_name", "supplier_type"] + fields = ["name", "supplier_name", "supplier_group"] fields = ", ".join(fields) return frappe.db.sql("""select {field} from `tabSupplier` diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js index e6029aa4ea..7ee0c5c2d0 100644 --- a/erpnext/stock/doctype/delivery_note/delivery_note.js +++ b/erpnext/stock/doctype/delivery_note/delivery_note.js @@ -34,7 +34,7 @@ frappe.ui.form.on("Delivery Note", { frm.set_query('transporter_name', function(doc) { return { - filters: { 'supplier_type': "transporter" } + filters: { 'supplier_group': "transporter" } } }); From 890707854d7efca4a875b33725dd7f96c5ac5e68 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Thu, 19 Apr 2018 18:37:29 +0530 Subject: [PATCH 33/79] config/utility level changes --- erpnext/accounts/party.py | 24 +++++++------- .../accounts_receivable.py | 12 +++---- .../accounts_receivable_summary.py | 6 ++-- .../purchase_register/purchase_register.py | 8 ++--- .../purchase_analytics/purchase_analytics.js | 32 +++++++++---------- erpnext/config/buying.py | 4 +-- erpnext/demo/setup/setup_data.py | 2 +- .../operations/install_fixtures.py | 16 +++++----- erpnext/startup/report_data_map.py | 6 ++-- erpnext/utilities/user_progress_utils.py | 2 +- 10 files changed, 56 insertions(+), 56 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 6f272cf833..652272dbe1 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -47,7 +47,7 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= set_contact_details(out, party, party_type) set_other_values(out, party, party_type) set_price_list(out, party, party_type, price_list) - out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_type) + out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group) out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) if not out.get("currency"): @@ -105,7 +105,7 @@ def set_other_values(out, party, party_type): if party_type=="Customer": to_copy = ["customer_name", "customer_group", "territory", "language"] else: - to_copy = ["supplier_name", "supplier_type", "language"] + to_copy = ["supplier_name", "supplier_group", "language"] for f in to_copy: out[f] = party.get(f) @@ -169,7 +169,7 @@ def set_account_and_due_date(party, account, party_type, company, posting_date, def get_party_account(party_type, party, company): """Returns the account for the given `party`. Will first search in party (Customer / Supplier) record, if not found, - will search in group (Customer Group / Supplier Type), + will search in group (Customer Group / Supplier Group), finally will return default.""" if not company: frappe.throw(_("Please select a Company")) @@ -181,7 +181,7 @@ def get_party_account(party_type, party, company): {"parenttype": party_type, "parent": party, "company": company}, "account") if not account and party_type in ['Customer', 'Supplier']: - party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Type" + party_group_doctype = "Customer Group" if party_type=="Customer" else "Supplier Group" group = frappe.db.get_value(party_type, party, scrub(party_group_doctype)) account = frappe.db.get_value("Party Account", {"parenttype": party_group_doctype, "parent": group, "company": company}, "account") @@ -274,8 +274,8 @@ def get_due_date(posting_date, party_type, party, company=None, bill_date=None): due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d") else: if party_type == "Supplier": - supplier_type = frappe.db.get_value(party_type, party, fieldname="supplier_type") - template_name = frappe.db.get_value("Supplier Type", supplier_type, fieldname="payment_terms") + supplier_group = frappe.db.get_value(party_type, party, fieldname="supplier_group") + template_name = frappe.db.get_value("Supplier Group", supplier_group, fieldname="payment_terms") if template_name: due_date = get_due_date_from_template(template_name, posting_date, bill_date).strftime("%Y-%m-%d") # If due date is calculated from bill_date, check this condition @@ -321,7 +321,7 @@ def validate_due_date(posting_date, due_date, party_type, party, company=None, b .format(formatdate(default_due_date))) @frappe.whitelist() -def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_type=None, +def set_taxes(party, party_type, posting_date, company, customer_group=None, supplier_group=None, billing_address=None, shipping_address=None, use_for_shopping_cart=None): from erpnext.accounts.doctype.tax_rule.tax_rule import get_tax_template, get_party_details args = { @@ -332,8 +332,8 @@ def set_taxes(party, party_type, posting_date, company, customer_group=None, sup if customer_group: args['customer_group'] = customer_group - if supplier_type: - args['supplier_type'] = supplier_type + if supplier_group: + args['supplier_group'] = supplier_group if billing_address or shipping_address: args.update(get_party_details(party, party_type, {"billing_address": billing_address, \ @@ -371,10 +371,10 @@ def get_pyt_term_template(party_name, party_type, company=None): customer.customer_group, fieldname='payment_terms') else: supplier = frappe.db.get_value("Supplier", party_name, - fieldname=['payment_terms', "supplier_type"], as_dict=1) + fieldname=['payment_terms', "supplier_group"], as_dict=1) template = supplier.payment_terms - if not template and supplier.supplier_type: - template = frappe.db.get_value("Supplier Type", supplier.supplier_type, fieldname='payment_terms') + if not template and supplier.supplier_group: + template = frappe.db.get_value("Supplier Group", supplier.supplier_group, fieldname='payment_terms') if not template and company: template = frappe.db.get_value("Company", company, fieldname='payment_terms') diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py index fc76be406e..ca806bfc4a 100644 --- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py +++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py @@ -112,7 +112,7 @@ class ReceivablePayableReport(object): _("Customer Group") + ":Link/Customer Group:120" ] if args.get("party_type") == "Supplier": - columns += [_("Supplier Type") + ":Link/Supplier Type:80"] + columns += [_("Supplier Group") + ":Link/Supplier Group:80"] columns.append(_("Remarks") + "::200") @@ -194,11 +194,11 @@ class ReceivablePayableReport(object): # Delivery Note row += [voucher_details.get(gle.voucher_no, {}).get("delivery_note")] - # customer territory / supplier type + # customer territory / supplier group if args.get("party_type") == "Customer": row += [self.get_territory(gle.party), self.get_customer_group(gle.party)] if args.get("party_type") == "Supplier": - row += [self.get_supplier_type(gle.party)] + row += [self.get_supplier_group(gle.party)] row.append(gle.remarks) data.append(row) @@ -260,15 +260,15 @@ class ReceivablePayableReport(object): def get_customer_group(self, party_name): return self.get_party_map("Customer").get(party_name, {}).get("customer_group") or "" - def get_supplier_type(self, party_name): - return self.get_party_map("Supplier").get(party_name, {}).get("supplier_type") or "" + def get_supplier_group(self, party_name): + return self.get_party_map("Supplier").get(party_name, {}).get("supplier_group") or "" def get_party_map(self, party_type): if not hasattr(self, "party_map"): if party_type == "Customer": select_fields = "name, customer_name, territory, customer_group" elif party_type == "Supplier": - select_fields = "name, supplier_name, supplier_type" + select_fields = "name, supplier_name, supplier_group" self.party_map = dict(((r.name, r) for r in frappe.db.sql("select {0} from `tab{1}`" .format(select_fields, party_type), as_dict=True))) diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py index 08b24fb147..9b50960f4b 100644 --- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py +++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py @@ -35,7 +35,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): _("Customer Group") + ":Link/Customer Group:120" ] if args.get("party_type") == "Supplier": - columns += [_("Supplier Type") + ":Link/Supplier Type:80"] + columns += [_("Supplier Group") + ":Link/Supplier Group:80"] columns.append({ "fieldname": "currency", @@ -66,7 +66,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): if args.get("party_type") == "Customer": row += [self.get_territory(party), self.get_customer_group(party)] if args.get("party_type") == "Supplier": - row += [self.get_supplier_type(party)] + row += [self.get_supplier_group(party)] row.append(party_dict.currency) data.append(row) @@ -113,7 +113,7 @@ class AccountsReceivableSummary(ReceivablePayableReport): "outstanding_amt", "age", "range1", "range2", "range3", "range4", "currency"] if args.get("party_type") == "Supplier": - cols += ["supplier_type", "remarks"] + cols += ["supplier_group", "remarks"] if args.get("party_type") == "Customer": cols += ["territory", "customer_group", "remarks"] diff --git a/erpnext/accounts/report/purchase_register/purchase_register.py b/erpnext/accounts/report/purchase_register/purchase_register.py index cf599a0ccc..7a298b322f 100644 --- a/erpnext/accounts/report/purchase_register/purchase_register.py +++ b/erpnext/accounts/report/purchase_register/purchase_register.py @@ -42,7 +42,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum row.append(inv.get(col)) row += [ - supplier_details.get(inv.supplier), # supplier_type + supplier_details.get(inv.supplier), # supplier_group inv.tax_id, inv.credit_to, inv.mode_of_payment, ", ".join(project), inv.bill_no, inv.bill_date, inv.remarks, ", ".join(purchase_order), ", ".join(purchase_receipt), company_currency @@ -83,7 +83,7 @@ def get_columns(invoice_list, additional_table_columns): columns += additional_table_columns columns += [ - _("Supplier Type") + ":Link/Supplier Type:120", _("Tax Id") + "::80", _("Payable Account") + ":Link/Account:120", + _("Supplier Group") + ":Link/Supplier Group:120", _("Tax Id") + "::80", _("Payable Account") + ":Link/Account:120", _("Mode of Payment") + ":Link/Mode of Payment:80", _("Project") + ":Link/Project:80", _("Bill No") + "::120", _("Bill Date") + ":Date:80", _("Remarks") + "::150", _("Purchase Order") + ":Link/Purchase Order:100", @@ -229,8 +229,8 @@ def get_account_details(invoice_list): def get_supplier_details(suppliers): supplier_details = {} - for supp in frappe.db.sql("""select name, supplier_type from `tabSupplier` + for supp in frappe.db.sql("""select name, supplier_group from `tabSupplier` where name in (%s)""" % ", ".join(["%s"]*len(suppliers)), tuple(suppliers), as_dict=1): - supplier_details.setdefault(supp.name, supp.supplier_type) + supplier_details.setdefault(supp.name, supp.supplier_group) return supplier_details diff --git a/erpnext/buying/page/purchase_analytics/purchase_analytics.js b/erpnext/buying/page/purchase_analytics/purchase_analytics.js index 442a8bbd3e..df5790e826 100644 --- a/erpnext/buying/page/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/page/purchase_analytics/purchase_analytics.js @@ -19,7 +19,7 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ title: __("Purchase Analytics"), parent: $(wrapper).find('.layout-main'), page: wrapper.page, - doctypes: ["Item", "Item Group", "Supplier", "Supplier Type", "Company", "Fiscal Year", + doctypes: ["Item", "Item Group", "Supplier", "Supplier Group", "Company", "Fiscal Year", "Purchase Invoice", "Purchase Invoice Item", "Purchase Order", "Purchase Order Item[Purchase Analytics]", "Purchase Receipt", "Purchase Receipt Item[Purchase Analytics]"], @@ -27,11 +27,11 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ }); this.tree_grids = { - "Supplier Type": { - label: __("Supplier Type / Supplier"), + "Supplier Group": { + label: __("Supplier Group / Supplier"), show: true, item_key: "supplier", - parent_field: "parent_supplier_type", + parent_field: "parent_supplier_group", formatter: function(item) { return item.supplier_name ? item.supplier_name + " (" + item.name + ")" : item.name; } @@ -77,7 +77,7 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ }, filters: [ {fieldtype:"Select", label: __("Tree Type"), fieldname: "tree_type", - options:["Supplier Type", "Supplier", "Item Group", "Item"], + options:["Supplier Group", "Supplier", "Item Group", "Item"], filter: function(val, item, opts, me) { return me.apply_zero_filter(val, item, opts, me); }}, @@ -110,22 +110,22 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ var me = this; if (!this.tl) { // add 'Not Set' Supplier & Item - // Add 'All Supplier Types' Supplier Type + // Add 'All Supplier Groups' Supplier Group // (Supplier / Item are not mandatory!!) - // Set parent supplier type for tree view + // Set parent supplier group for tree view - $.each(frappe.report_dump.data["Supplier Type"], function(i, v) { - v['parent_supplier_type'] = __("All Supplier Types") + $.each(frappe.report_dump.data["Supplier Group"], function(i, v) { + v['parent_supplier_group'] = __("All Supplier Groups") }) - frappe.report_dump.data["Supplier Type"] = [{ - name: __("All Supplier Types"), - id: "All Supplier Types", - }].concat(frappe.report_dump.data["Supplier Type"]); + frappe.report_dump.data["Supplier Group"] = [{ + name: __("All Supplier Groups"), + id: "All Supplier Groups", + }].concat(frappe.report_dump.data["Supplier Group"]); frappe.report_dump.data["Supplier"].push({ name: __("Not Set"), - parent_supplier_type: __("All Supplier Types"), + parent_supplier_group: __("All Supplier Groups"), id: "Not Set", }); @@ -144,8 +144,8 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ if(!this.data || me.item_type != me.tree_type) { if(me.tree_type=='Supplier') { var items = frappe.report_dump.data["Supplier"]; - } if(me.tree_type=='Supplier Type') { - var items = this.prepare_tree("Supplier", "Supplier Type"); + } if(me.tree_type=='Supplier Group') { + var items = this.prepare_tree("Supplier", "Supplier Group"); } else if(me.tree_type=="Item Group") { var items = this.prepare_tree("Item", "Item Group"); } else if(me.tree_type=="Item") { diff --git a/erpnext/config/buying.py b/erpnext/config/buying.py index ba29125ca0..e20d514a23 100644 --- a/erpnext/config/buying.py +++ b/erpnext/config/buying.py @@ -39,8 +39,8 @@ def get_data(): }, { "type": "doctype", - "name": "Supplier Type", - "description": _("Supplier Type master.") + "name": "Supplier Group", + "description": _("Supplier Group master.") }, { "type": "doctype", diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py index 4ed4df9176..42d079db34 100644 --- a/erpnext/demo/setup/setup_data.py +++ b/erpnext/demo/setup/setup_data.py @@ -275,7 +275,7 @@ def setup_supplier(): frappe.get_doc({ "doctype": "Supplier", "supplier_name": s, - "supplier_type": random.choice(["Services", "Raw Material"]), + "supplier_group": random.choice(["Services", "Raw Material"]), }).insert() def setup_warehouse(): diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 8f761e5946..2c294af2a1 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -147,14 +147,14 @@ def install(country=None): {'doctype': 'Customer Group', 'customer_group_name': _('Non Profit'), 'is_group': 0, 'parent_customer_group': _('All Customer Groups')}, {'doctype': 'Customer Group', 'customer_group_name': _('Government'), 'is_group': 0, 'parent_customer_group': _('All Customer Groups')}, - # supplier type - {'doctype': 'Supplier Type', 'supplier_type': _('Services')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Local')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Raw Material')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Electrical')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Hardware')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Pharmaceutical')}, - {'doctype': 'Supplier Type', 'supplier_type': _('Distributor')}, + # supplier group + {'doctype': 'Supplier Group', 'supplier_group_name': _('Services')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Local')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Raw Material')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Electrical')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Hardware')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Pharmaceutical')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Distributor')}, # Sales Person {'doctype': 'Sales Person', 'sales_person_name': _('Sales Team'), 'is_group': 1, "parent_sales_person": ""}, diff --git a/erpnext/startup/report_data_map.py b/erpnext/startup/report_data_map.py index daf6fdd3bd..3720017804 100644 --- a/erpnext/startup/report_data_map.py +++ b/erpnext/startup/report_data_map.py @@ -208,14 +208,14 @@ data_map = { }, "Supplier": { "columns": ["name", "if(supplier_name=name, '', supplier_name) as supplier_name", - "supplier_type as parent_supplier_type"], + "supplier_group as parent_supplier_group"], "conditions": ["docstatus < 2"], "order_by": "name", "links": { - "parent_supplier_type": ["Supplier Type", "name"], + "parent_supplier_group": ["Supplier Group", "name"], } }, - "Supplier Type": { + "Supplier Group": { "columns": ["name"], "conditions": ["docstatus < 2"], "order_by": "name" diff --git a/erpnext/utilities/user_progress_utils.py b/erpnext/utilities/user_progress_utils.py index 5bd855f719..20e533e91a 100644 --- a/erpnext/utilities/user_progress_utils.py +++ b/erpnext/utilities/user_progress_utils.py @@ -63,7 +63,7 @@ def create_suppliers(args_data): doc = frappe.get_doc({ "doctype":"Supplier", "supplier_name": supplier, - "supplier_type": _("Local"), + "supplier_group": _("Local"), "company": defaults.get("company") }).insert() From 2e0809801d35812d197ffe669aa580071bfeb36c Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Thu, 19 Apr 2018 18:37:53 +0530 Subject: [PATCH 34/79] controller and other changes --- erpnext/controllers/queries.py | 4 ++-- erpnext/controllers/trends.py | 12 ++++++------ erpnext/hub_node/__init__.py | 2 +- erpnext/public/js/conf.js | 2 +- erpnext/public/js/controllers/transaction.js | 2 +- erpnext/public/js/hub/hub_form.js | 4 ++-- erpnext/public/js/purchase_trends_filters.js | 2 +- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py index fd13c1353e..3bf720fd5d 100644 --- a/erpnext/controllers/queries.py +++ b/erpnext/controllers/queries.py @@ -102,9 +102,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters): def supplier_query(doctype, txt, searchfield, start, page_len, filters): supp_master_name = frappe.defaults.get_user_default("supp_master_name") if supp_master_name == "Supplier Name": - fields = ["name", "supplier_type"] + fields = ["name", "supplier_group"] else: - fields = ["name", "supplier_name", "supplier_type"] + fields = ["name", "supplier_name", "supplier_group"] fields = ", ".join(fields) return frappe.db.sql("""select {field} from `tabSupplier` diff --git a/erpnext/controllers/trends.py b/erpnext/controllers/trends.py index ef698c2a16..28a8fddfac 100644 --- a/erpnext/controllers/trends.py +++ b/erpnext/controllers/trends.py @@ -235,16 +235,16 @@ def based_wise_columns_query(based_on, trans): based_on_details["addl_tables"] = '' elif based_on == 'Supplier': - based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Type:Link/Supplier Type:140"] - based_on_details["based_on_select"] = "t1.supplier, t3.supplier_type," + based_on_details["based_on_cols"] = ["Supplier:Link/Supplier:120", "Supplier Group:Link/Supplier Group:140"] + based_on_details["based_on_select"] = "t1.supplier, t3.supplier_group," based_on_details["based_on_group_by"] = 't1.supplier' based_on_details["addl_tables"] = ',`tabSupplier` t3' based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name" - elif based_on == 'Supplier Type': - based_on_details["based_on_cols"] = ["Supplier Type:Link/Supplier Type:140"] - based_on_details["based_on_select"] = "t3.supplier_type," - based_on_details["based_on_group_by"] = 't3.supplier_type' + elif based_on == 'Supplier Group': + based_on_details["based_on_cols"] = ["Supplier Group:Link/Supplier Group:140"] + based_on_details["based_on_select"] = "t3.supplier_group," + based_on_details["based_on_group_by"] = 't3.supplier_group' based_on_details["addl_tables"] = ',`tabSupplier` t3' based_on_details["addl_tables_relational_cond"] = " and t1.supplier = t3.name" diff --git a/erpnext/hub_node/__init__.py b/erpnext/hub_node/__init__.py index 89ee926d55..65b1386466 100644 --- a/erpnext/hub_node/__init__.py +++ b/erpnext/hub_node/__init__.py @@ -187,7 +187,7 @@ def make_supplier(supplier): supplier_doc = frappe.get_doc({ 'doctype': 'Supplier', 'supplier_name': supplier.supplier_name, - 'supplier_type': supplier.supplier_type, + 'supplier_group': supplier.supplier_group, 'supplier_email': supplier.supplier_email }).insert() else: diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js index d256c08e13..08f8d43e50 100644 --- a/erpnext/public/js/conf.js +++ b/erpnext/public/js/conf.js @@ -49,7 +49,7 @@ $.extend(frappe.create_routes, { $.extend(frappe.breadcrumbs.preferred, { "Item Group": "Stock", "Customer Group": "Selling", - "Supplier Type": "Buying", + "Supplier Group": "Buying", "Territory": "Selling", "Sales Person": "Selling", "Sales Partner": "Selling", diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js index 1551b1dfc3..eb4a7001f3 100644 --- a/erpnext/public/js/controllers/transaction.js +++ b/erpnext/public/js/controllers/transaction.js @@ -980,7 +980,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({ "customer_group": me.frm.doc.customer_group, "territory": me.frm.doc.territory, "supplier": me.frm.doc.supplier, - "supplier_type": me.frm.doc.supplier_type, + "supplier_group": me.frm.doc.supplier_group, "currency": me.frm.doc.currency, "conversion_rate": me.frm.doc.conversion_rate, "price_list": me.frm.doc.selling_price_list || me.frm.doc.buying_price_list, diff --git a/erpnext/public/js/hub/hub_form.js b/erpnext/public/js/hub/hub_form.js index 62c44fcb33..4a8c4eb3ec 100644 --- a/erpnext/public/js/hub/hub_form.js +++ b/erpnext/public/js/hub/hub_form.js @@ -444,8 +444,8 @@ erpnext.hub.ItemPage = class ItemPage extends erpnext.hub.HubDetailsPage { { label: __('Supplier Name'), fieldtype: 'Data', fieldname: 'supplier_name', default: item.company_name }, { label: __('Supplier Email'), fieldtype: 'Data', fieldname: 'supplier_email', default: item.seller }, { fieldtype: 'Column Break' }, - { label: __('Supplier Type'), fieldname: 'supplier_type', - fieldtype: 'Link', options: 'Supplier Type' } + { label: __('Supplier Group'), fieldname: 'supplier_group', + fieldtype: 'Link', options: 'Supplier Group' } ]; fields = fields.map(f => { f.reqd = 1; return f; }); diff --git a/erpnext/public/js/purchase_trends_filters.js b/erpnext/public/js/purchase_trends_filters.js index 117d4eb08c..595a138f12 100644 --- a/erpnext/public/js/purchase_trends_filters.js +++ b/erpnext/public/js/purchase_trends_filters.js @@ -23,7 +23,7 @@ erpnext.get_purchase_trends_filters = function() { { "value": "Item", "label": __("Item") }, { "value": "Item Group", "label": __("Item Group") }, { "value": "Supplier", "label": __("Supplier") }, - { "value": "Supplier Type", "label": __("Supplier Type") }, + { "value": "Supplier Group", "label": __("Supplier Group") }, { "value": "Project", "label": __("Project") } ], "default": "Item" From 540d7aa635080677e2678ab70712e0310646b747 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 20 Apr 2018 09:39:29 +0530 Subject: [PATCH 35/79] patch to reload_doc and rename field --- erpnext/patches.txt | 1 + .../rename_supplier_type_to_supplier_group.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3fffade9bb..a8696fa24b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -527,3 +527,4 @@ erpnext.patches.v11_0.rename_field_max_days_allowed erpnext.patches.v11_0.create_salary_structure_assignments erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company +erpnext.patches.v11_0.rename_supplier_type_to_supplier_group diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py new file mode 100644 index 0000000000..b5b1bf4090 --- /dev/null +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -0,0 +1,17 @@ +import frappe +from frappe.model.rename_doc import rename_doc +from frappe.model.utils.rename_field import rename_field + +def execute(): + if frappe.db.table_exists("Supplier Type") and not frappe.db.table_exists("Supplier Group"): + rename_doc("DocType", "Supplier Type", "Supplier Group", force=True) + frappe.reload_doc('setup', 'doctype', 'supplier_group') + frappe.reload_doc("accounts", "doctype", "pricing_rule") + frappe.reload_doc("accounts", "doctype", "tax_rule") + frappe.reload_doc("buying", "doctype", "buying_settings") + frappe.reload_doc("buying", "doctype", "supplier") + rename_field("Supplier Group", "supplier_type", "supplier_group_name") + rename_field("Supplier", "supplier_type", "supplier_group") + rename_field("Buying Settings", "supplier_type", "supplier_group") + rename_field("Pricing Rule", "supplier_type", "supplier_group") + rename_field("Tax Rule", "supplier_type", "supplier_group") From c88ce364cef3450b81eb8ed00adfad50378a474f Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 20 Apr 2018 11:06:15 +0530 Subject: [PATCH 36/79] treeview implemented for supplier group --- .../rename_supplier_type_to_supplier_group.py | 17 ++ .../supplier_group/supplier_group.json | 161 +++++++++++++++++- .../supplier_group/supplier_group_tree.js | 3 + 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 erpnext/setup/doctype/supplier_group/supplier_group_tree.js diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py index b5b1bf4090..4db5704ad6 100644 --- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -1,6 +1,8 @@ import frappe from frappe.model.rename_doc import rename_doc from frappe.model.utils.rename_field import rename_field +from frappe import _ +from frappe.utils.nestedset import rebuild_tree def execute(): if frappe.db.table_exists("Supplier Type") and not frappe.db.table_exists("Supplier Group"): @@ -15,3 +17,18 @@ def execute(): rename_field("Buying Settings", "supplier_type", "supplier_group") rename_field("Pricing Rule", "supplier_type", "supplier_group") rename_field("Tax Rule", "supplier_type", "supplier_group") + + build_tree() + +def build_tree(): + if not frappe.db.exists("Supplier Group", _('All Supplier Groups')): + frappe.get_doc({ + 'doctype': 'Supplier Group', + 'supplier_group_name': _('All Supplier Groups'), + 'is_group': 1 + }).insert(ignore_permissions=True) + + frappe.db.sql("""update `tabSupplier Group` set parent_supplier_group = '{0}' + where is_group = 0""".format(_('All Supplier Groups'))) + + rebuild_tree("Supplier Group", "parent_supplier_group") diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.json b/erpnext/setup/doctype/supplier_group/supplier_group.json index 35fafac4ff..dc8b2631f1 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.json +++ b/erpnext/setup/doctype/supplier_group/supplier_group.json @@ -25,7 +25,7 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 1, + "in_list_view": 0, "in_standard_filter": 0, "label": "Supplier Group Name", "length": 0, @@ -44,6 +44,69 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "parent_supplier_group", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "Parent Supplier Group", + "length": 0, + "no_copy": 0, + "options": "Supplier Group", + "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_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "is_group", + "fieldtype": "Check", + "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": "Is Group", + "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_on_submit": 0, @@ -169,6 +232,100 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "lft", + "fieldtype": "Int", + "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": "lft", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rgt", + "fieldtype": "Int", + "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": "rgt", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "old_parent", + "fieldtype": "Link", + "hidden": 1, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Old Parent", + "length": 0, + "no_copy": 1, + "options": "Supplier Group", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "translatable": 0, + "unique": 0 } ], "has_web_view": 0, @@ -182,7 +339,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-19 07:45:03.495577", + "modified": "2018-04-20 00:54:48.724120", "modified_by": "Administrator", "module": "Setup", "name": "Supplier Group", diff --git a/erpnext/setup/doctype/supplier_group/supplier_group_tree.js b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js new file mode 100644 index 0000000000..24328bdd5a --- /dev/null +++ b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js @@ -0,0 +1,3 @@ +frappe.treeview_settings["Supplier Group"] = { + ignore_fields:["parent_supplier_group"] +} \ No newline at end of file From 93d6b555d78170b522f43720727c593a09677fa6 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 20 Apr 2018 11:07:19 +0530 Subject: [PATCH 37/79] added fixtures for supplier-group, changed analytics report accordingly --- .../page/purchase_analytics/purchase_analytics.js | 12 ------------ .../setup_wizard/operations/install_fixtures.py | 15 ++++++++------- erpnext/startup/report_data_map.py | 2 +- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/erpnext/buying/page/purchase_analytics/purchase_analytics.js b/erpnext/buying/page/purchase_analytics/purchase_analytics.js index df5790e826..b503113837 100644 --- a/erpnext/buying/page/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/page/purchase_analytics/purchase_analytics.js @@ -110,19 +110,7 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ var me = this; if (!this.tl) { // add 'Not Set' Supplier & Item - // Add 'All Supplier Groups' Supplier Group // (Supplier / Item are not mandatory!!) - // Set parent supplier group for tree view - - $.each(frappe.report_dump.data["Supplier Group"], function(i, v) { - v['parent_supplier_group'] = __("All Supplier Groups") - }) - - frappe.report_dump.data["Supplier Group"] = [{ - name: __("All Supplier Groups"), - id: "All Supplier Groups", - }].concat(frappe.report_dump.data["Supplier Group"]); - frappe.report_dump.data["Supplier"].push({ name: __("Not Set"), parent_supplier_group: __("All Supplier Groups"), diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py index 2c294af2a1..c686ed3080 100644 --- a/erpnext/setup/setup_wizard/operations/install_fixtures.py +++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py @@ -148,13 +148,14 @@ def install(country=None): {'doctype': 'Customer Group', 'customer_group_name': _('Government'), 'is_group': 0, 'parent_customer_group': _('All Customer Groups')}, # supplier group - {'doctype': 'Supplier Group', 'supplier_group_name': _('Services')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Local')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Raw Material')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Electrical')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Hardware')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Pharmaceutical')}, - {'doctype': 'Supplier Group', 'supplier_group_name': _('Distributor')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('All Supplier Groups'), 'is_group': 1, 'name': _('All Supplier Groups'), 'parent_supplier_group': ''}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Services'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Local'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Raw Material'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Electrical'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Hardware'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Pharmaceutical'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, + {'doctype': 'Supplier Group', 'supplier_group_name': _('Distributor'), 'is_group': 0, 'parent_supplier_group': _('All Supplier Groups')}, # Sales Person {'doctype': 'Sales Person', 'sales_person_name': _('Sales Team'), 'is_group': 1, "parent_sales_person": ""}, diff --git a/erpnext/startup/report_data_map.py b/erpnext/startup/report_data_map.py index 3720017804..5de7998c7d 100644 --- a/erpnext/startup/report_data_map.py +++ b/erpnext/startup/report_data_map.py @@ -216,7 +216,7 @@ data_map = { } }, "Supplier Group": { - "columns": ["name"], + "columns": ["name", "parent_supplier_group"], "conditions": ["docstatus < 2"], "order_by": "name" }, From f665189ab3e8b209dfefdc9578d5065df7323e72 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 20 Apr 2018 16:00:55 +0530 Subject: [PATCH 38/79] tree based changes --- .../doctype/supplier_group/supplier_group.js | 22 ++++++++++++++++++- .../doctype/supplier_group/supplier_group.py | 16 +++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index 10e5c04dcc..ea92726e2a 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -2,7 +2,27 @@ // For license information, please see license.txt cur_frm.cscript.refresh = function(doc) { - cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit.")) + cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit.")); + cur_frm.cscript.set_root_readonly(doc); +} + +cur_frm.cscript.set_root_readonly = function(doc) { + // read-only for root customer group + if(!doc.parent_supplier_group) { + cur_frm.set_read_only(); + cur_frm.set_intro(__("This is a root supplier group and cannot be edited.")); + } else { + cur_frm.set_intro(null); + } +} + +//get query select Customer Group +cur_frm.fields_dict['parent_supplier_group'].get_query = function(doc,cdt,cdn) { + return { + filters: { + 'is_group': 1 + } + } } cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) { diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.py b/erpnext/setup/doctype/supplier_group/supplier_group.py index 53ab215190..747560a533 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.py +++ b/erpnext/setup/doctype/supplier_group/supplier_group.py @@ -4,7 +4,17 @@ from __future__ import unicode_literals import frappe -from frappe.model.document import Document +from frappe.utils.nestedset import NestedSet -class SupplierGroup(Document): - pass +class SupplierGroup(NestedSet): + nsm_parent_field = 'parent_supplier_group'; + + def update_nsm_model(self): + frappe.utils.nestedset.update_nsm(self) + + def on_update(self): + self.update_nsm_model() + self.validate_one_root() + + def on_trash(self): + self.update_nsm_model() From 4bd7cdb018201db7835e19e0d736a340dae485d7 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Wed, 9 May 2018 15:55:33 +0530 Subject: [PATCH 39/79] Salary Structure Assignment - filters applied --- .../salary_structure_assignment.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js index af4ca3a3b5..56a05e0495 100644 --- a/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js +++ b/erpnext/hr/doctype/salary_structure_assignment/salary_structure_assignment.js @@ -15,8 +15,8 @@ frappe.ui.form.on('Salary Structure Assignment', { return { filters: { company: frm.doc.company, - is_active: "Yes", - docstatus: 1 + docstatus: 1, + is_active: "Yes" } } }); From b5c5c471b461ddc8a329a5fd462ae4ecd0f8070b Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Wed, 9 May 2018 17:23:06 +0530 Subject: [PATCH 40/79] Formula on Salary Component Master --- .../salary_component/salary_component.json | 223 +++++++++++++++++- 1 file changed, 222 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json index 5f875a9948..f1f5946342 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.json +++ b/erpnext/hr/doctype/salary_component/salary_component.json @@ -108,6 +108,131 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "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": "Condition", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", + "fieldname": "statistical_component", + "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": "Statistical Component", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depends_on_lwp", + "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": "Depends on Leave Without Pay", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "do_not_include_in_total", + "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": "Do not include in total", + "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_on_submit": 0, @@ -138,6 +263,102 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "amount_based_on_formula", + "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": "Amount based on formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==0", + "fieldname": "formula", + "fieldtype": "Code", + "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": "Formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==1", + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "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_on_submit": 0, @@ -972,4 +1193,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} \ No newline at end of file +} From a5028c8e54c5bc4fc1fc66fd8538c38d99dc9870 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Wed, 9 May 2018 17:40:27 +0530 Subject: [PATCH 41/79] Salary Structure - filter updated, Salary Component - fields re-arranged --- .../salary_component/salary_component.json | 535 ++++++++++-------- 1 file changed, 314 insertions(+), 221 deletions(-) diff --git a/erpnext/hr/doctype/salary_component/salary_component.json b/erpnext/hr/doctype/salary_component/salary_component.json index f1f5946342..f08b1d7a0b 100644 --- a/erpnext/hr/doctype/salary_component/salary_component.json +++ b/erpnext/hr/doctype/salary_component/salary_component.json @@ -108,131 +108,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "condition", - "fieldtype": "Code", - "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": "Condition", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", - "fieldname": "statistical_component", - "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": "Statistical Component", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "depends_on_lwp", - "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": "Depends on Leave Without Pay", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "do_not_include_in_total", - "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": "Do not include in total", - "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_on_submit": 0, @@ -263,102 +138,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "amount_based_on_formula", - "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": "Amount based on formula", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.amount_based_on_formula!==0", - "fieldname": "formula", - "fieldtype": "Code", - "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": "Formula", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:doc.amount_based_on_formula!==1", - "fieldname": "amount", - "fieldtype": "Currency", - "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": "Amount", - "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_on_submit": 0, @@ -1114,6 +893,320 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "help", + "fieldtype": "HTML", + "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": "Help", + "length": 0, + "no_copy": 0, + "options": "

    Help

    \n\n

    Notes:

    \n\n
      \n
    1. Use field base for using base salary of the Employee
    2. \n
    3. Use Salary Component abbreviations in conditions and formulas. BS = Basic Salary
    4. \n
    5. Use field name for employee details in conditions and formulas. Employment Type = employment_typeBranch = branch
    6. \n
    7. Use field name from Salary Slip in conditions and formulas. Payment Days = payment_daysLeave without pay = leave_without_pay
    8. \n
    9. Direct Amount can also be entered based on Condtion. See example 3
    \n\n

    Examples

    \n
      \n
    1. Calculating Basic Salary based on base\n
      Condition: base < 10000
      \n
      Formula: base * .2
    2. \n
    3. Calculating HRA based on Basic SalaryBS \n
      Condition: BS > 2000
      \n
      Formula: BS * .1
    4. \n
    5. Calculating TDS based on Employment Typeemployment_type \n
      Condition: employment_type==\"Intern\"
      \n
      Amount: 1000
    6. \n
    ", + "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_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "condition_and_formula", + "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": "Condition and Formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "condition", + "fieldtype": "Code", + "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": "Condition", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "If selected, the value specified or calculated in this component will not contribute to the earnings or deductions. However, it's value can be referenced by other components that can be added or deducted. ", + "fieldname": "statistical_component", + "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": "Statistical Component", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depends_on_lwp", + "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": "Depends on Leave Without Pay", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "do_not_include_in_total", + "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": "Do not include in total", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "1", + "fieldname": "amount_based_on_formula", + "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": "Amount based on formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==0", + "fieldname": "formula", + "fieldtype": "Code", + "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": "Formula", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:doc.amount_based_on_formula!==1", + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_28", + "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_on_submit": 0, From 08b60a1a62fb8cc98248f540c2c03da6117f6b13 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Thu, 10 May 2018 13:16:24 +0530 Subject: [PATCH 42/79] New Doctype - Additional Salary Component --- erpnext/config/hr.py | 4 + .../additional_salary_component/__init__.py | 0 .../additional_salary_component.js | 21 + .../additional_salary_component.json | 364 ++++++++++++++++++ .../additional_salary_component.py | 26 ++ .../test_additional_salary_component.js | 23 ++ .../test_additional_salary_component.py | 10 + 7 files changed, 448 insertions(+) create mode 100644 erpnext/hr/doctype/additional_salary_component/__init__.py create mode 100644 erpnext/hr/doctype/additional_salary_component/additional_salary_component.js create mode 100644 erpnext/hr/doctype/additional_salary_component/additional_salary_component.json create mode 100644 erpnext/hr/doctype/additional_salary_component/additional_salary_component.py create mode 100644 erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.js create mode 100644 erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py diff --git a/erpnext/config/hr.py b/erpnext/config/hr.py index 4e9e91ef3b..e1c589fe09 100644 --- a/erpnext/config/hr.py +++ b/erpnext/config/hr.py @@ -123,6 +123,10 @@ def get_data(): "type": "doctype", "name": "Salary Component", }, + { + "type": "doctype", + "name": "Additional Salary Component", + }, { "type": "doctype", "name": "Salary Structure", diff --git a/erpnext/hr/doctype/additional_salary_component/__init__.py b/erpnext/hr/doctype/additional_salary_component/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js new file mode 100644 index 0000000000..36bfdf2a08 --- /dev/null +++ b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js @@ -0,0 +1,21 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Additional Salary Component', { + setup: function(frm) { + frm.set_query("salary_component", function() { + return { + filters: { + type: "earning" + } + } + }); + frm.set_query("employee", function() { + return { + filters: { + company: frm.doc.company + } + } + }); + } +}); diff --git a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.json b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.json new file mode 100644 index 0000000000..47b090469e --- /dev/null +++ b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.json @@ -0,0 +1,364 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:salary_component", + "beta": 0, + "creation": "2018-05-10 12:04:08.396461", + "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, + "fieldname": "company", + "fieldtype": "Link", + "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": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "fieldtype": "Link", + "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": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "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": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "salary_component", + "fieldtype": "Link", + "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": "Salary Component", + "length": 0, + "no_copy": 0, + "options": "Salary Component", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_date", + "fieldtype": "Date", + "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 Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "to_date", + "fieldtype": "Date", + "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": "To Date", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amount", + "fieldtype": "Currency", + "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": "Amount", + "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_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": "Additional Salary Component", + "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": "2018-05-10 12:54:06.155708", + "modified_by": "Administrator", + "module": "HR", + "name": "Additional Salary Component", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "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 + }, + { + "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": "HR User", + "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", + "title_field": "employee", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py new file mode 100644 index 0000000000..0591082b4a --- /dev/null +++ b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate +from erpnext.hr.utils import validate_dates + +class AdditionalSalaryComponent(Document): + def validate(self): + # self.validate_dates() + validate_dates(self, self.from_date, self.to_date) + + # def validate_dates(self): + # date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, + # ["date_of_joining", "relieving_date"]) + # if getdate(self.from_date) > getdate(to_date): + # frappe.throw(_("To date can not be less than from date")) + # elif getdate(self.from_date) > getdate(nowdate()): + # frappe.throw(_("Future dates not allowed")) + # elif date_of_joining and getdate(self.from_date) < getdate(date_of_joining): + # frappe.throw(_("From date can not be less than employee's joining date")) + # elif relieving_date and getdate(to_date) > getdate(relieving_date): + # frappe.throw(_("To date can not greater than employee's relieving date")) diff --git a/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.js b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.js new file mode 100644 index 0000000000..118290bcfa --- /dev/null +++ b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Additional Salary Component", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Additional Salary Component + () => frappe.tests.make('Additional Salary Component', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py new file mode 100644 index 0000000000..6859da1c0b --- /dev/null +++ b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestAdditionalSalaryComponent(unittest.TestCase): + pass From d0b0c87f4034261d3431e83eb0fdab24b50e2289 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Fri, 11 May 2018 11:33:07 +0530 Subject: [PATCH 43/79] get additional salary component in salary slip --- .../additional_salary_component.py | 80 +++++++++++++++---- erpnext/hr/doctype/salary_slip/salary_slip.py | 7 ++ 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py index 0591082b4a..216dea0403 100644 --- a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py +++ b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.py @@ -5,22 +5,72 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.utils import formatdate, format_datetime, getdate, get_datetime, nowdate -from erpnext.hr.utils import validate_dates +from frappe import _ +from frappe.utils import getdate, date_diff class AdditionalSalaryComponent(Document): def validate(self): - # self.validate_dates() - validate_dates(self, self.from_date, self.to_date) + self.validate_dates() + if self.amount <= 0: + frappe.throw(_("Amount should be greater than zero.")) - # def validate_dates(self): - # date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, - # ["date_of_joining", "relieving_date"]) - # if getdate(self.from_date) > getdate(to_date): - # frappe.throw(_("To date can not be less than from date")) - # elif getdate(self.from_date) > getdate(nowdate()): - # frappe.throw(_("Future dates not allowed")) - # elif date_of_joining and getdate(self.from_date) < getdate(date_of_joining): - # frappe.throw(_("From date can not be less than employee's joining date")) - # elif relieving_date and getdate(to_date) > getdate(relieving_date): - # frappe.throw(_("To date can not greater than employee's relieving date")) + def validate_dates(self): + date_of_joining, relieving_date = frappe.db.get_value("Employee", self.employee, + ["date_of_joining", "relieving_date"]) + if getdate(self.from_date) > getdate(self.to_date): + frappe.throw(_("To date can not be less than from date")) + elif date_of_joining and getdate(self.from_date) < getdate(date_of_joining): + frappe.throw(_("From date can not be less than employee's joining date")) + elif relieving_date and getdate(self.to_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) + end_date = getdate(sal_end_date) + total_days = date_diff(getdate(self.to_date), getdate(self.from_date)) + 1 + amount_per_day = self.amount / total_days + if getdate(sal_start_date) <= getdate(self.from_date): + start_date = getdate(self.from_date) + if getdate(sal_end_date) > getdate(self.to_date): + end_date = getdate(self.end_date) + no_of_days = date_diff(getdate(end_date), getdate(start_date)) + return amount_per_day * no_of_days + + + + +@frappe.whitelist() +def get_additional_salary_component(employee, start_date, end_date): + additional_components = frappe.db.sql(""" + select name from `tabAdditional Salary Component` + where employee=%(employee)s + and docstatus = 1 + and ( + (%(from_date)s between from_date and to_date) + or (%(to_date)s between from_date and to_date) + or (from_date between %(from_date)s and %(to_date)s) + )""", { + 'employee': employee, + 'from_date': start_date, + 'to_date': end_date + }) + + if additional_components: + additional_components_array = [] + for additional_component in additional_components: + struct_row = {} + additional_components_dict = {} + additional_component_obj = frappe.get_doc("Additional Salary Component", additional_component[0]) + amount = additional_component_obj.get_amount(start_date, end_date) + salary_component = frappe.get_doc("Salary Component", additional_component_obj.salary_component) + struct_row['depends_on_lwp'] = salary_component.depends_on_lwp + struct_row['salary_component'] = salary_component.name + struct_row['abbr'] = salary_component.salary_component_abbr + struct_row['do_not_include_in_total'] = salary_component.do_not_include_in_total + additional_components_dict['amount'] = amount + additional_components_dict['struct_row'] = struct_row + additional_components_array.append(additional_components_dict) + + if len(additional_components_array) > 0: + return additional_components_array + return False diff --git a/erpnext/hr/doctype/salary_slip/salary_slip.py b/erpnext/hr/doctype/salary_slip/salary_slip.py index 99de580a3f..984a78c75f 100644 --- a/erpnext/hr/doctype/salary_slip/salary_slip.py +++ b/erpnext/hr/doctype/salary_slip/salary_slip.py @@ -12,6 +12,7 @@ from erpnext.hr.doctype.payroll_entry.payroll_entry import get_start_end_dates from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee from erpnext.utilities.transaction_base import TransactionBase from frappe.utils.background_jobs import enqueue +from erpnext.hr.doctype.additional_salary_component.additional_salary_component import get_additional_salary_component class SalarySlip(TransactionBase): def autoname(self): @@ -58,6 +59,12 @@ class SalarySlip(TransactionBase): if amount and struct_row.statistical_component == 0: self.update_component_row(struct_row, amount, key) + additional_components = get_additional_salary_component(self.employee, self.start_date, self.end_date) + if additional_components: + for additional_component in additional_components: + additional_component = frappe._dict(additional_component) + self.update_component_row(frappe._dict(additional_component.struct_row), additional_component.amount, "earnings") + def update_component_row(self, struct_row, amount, key): component_row = None for d in self.get(key): From 236be226484abc538b79fca4f03d4aac26dd6f8b Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Mon, 14 May 2018 13:32:50 +0530 Subject: [PATCH 44/79] Payroll entry - get employee fix --- erpnext/hr/doctype/payroll_entry/payroll_entry.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.py b/erpnext/hr/doctype/payroll_entry/payroll_entry.py index e1b841f9b9..0dd2b4cb07 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.py +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.py @@ -38,19 +38,19 @@ class PayrollEntry(Document): ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s {condition}""".format(condition=condition), {"company": self.company, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet}) - if sal_struct: cond += "and t2.salary_structure IN %(sal_struct)s " + cond += "and ((%(from_date)s between t2.from_date and ifnull(t2.to_date, '2199-12-31')) or (%(to_date)s between t2.from_date and ifnull(t2.to_date, '2199-12-31')) or (t2.from_date between %(from_date)s and %(to_date)s))" emp_list = frappe.db.sql(""" select - t1.name as employee, t1.employee_name, t1.department, t1.designation + t1.name as employee, t1.employee_name, t1.department, t1.designation, t2.name from `tabEmployee` t1, `tabSalary Structure Assignment` t2 where t1.docstatus!=2 and t1.name = t2.employee and t2.docstatus = 1 - %s """% cond, {"sal_struct": sal_struct}, as_dict=True) + %s """% cond, {"sal_struct": sal_struct, "from_date": self.start_date, "to_date": self.end_date}, as_dict=True) return emp_list def fill_employee_details(self): From 39097c7fbe963c5df9c5df4a1166266993efad61 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Mon, 7 May 2018 14:54:25 +0530 Subject: [PATCH 45/79] codacy fix --- .../purchase_analytics/purchase_analytics.js | 11 ++++++----- .../doctype/supplier_group/supplier_group.js | 16 ++++++++-------- .../supplier_group/supplier_group_tree.js | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/erpnext/buying/page/purchase_analytics/purchase_analytics.js b/erpnext/buying/page/purchase_analytics/purchase_analytics.js index b503113837..06764a3c60 100644 --- a/erpnext/buying/page/purchase_analytics/purchase_analytics.js +++ b/erpnext/buying/page/purchase_analytics/purchase_analytics.js @@ -130,14 +130,15 @@ erpnext.PurchaseAnalytics = frappe.views.TreeGridReport.extend({ if(!this.data || me.item_type != me.tree_type) { + var items; if(me.tree_type=='Supplier') { - var items = frappe.report_dump.data["Supplier"]; - } if(me.tree_type=='Supplier Group') { - var items = this.prepare_tree("Supplier", "Supplier Group"); + items = frappe.report_dump.data["Supplier"]; + } else if(me.tree_type=='Supplier Group') { + items = this.prepare_tree("Supplier", "Supplier Group"); } else if(me.tree_type=="Item Group") { - var items = this.prepare_tree("Item", "Item Group"); + items = this.prepare_tree("Item", "Item Group"); } else if(me.tree_type=="Item") { - var items = frappe.report_dump.data["Item"]; + items = frappe.report_dump.data["Item"]; } me.item_type = me.tree_type diff --git a/erpnext/setup/doctype/supplier_group/supplier_group.js b/erpnext/setup/doctype/supplier_group/supplier_group.js index ea92726e2a..ac5bda6e2c 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group.js @@ -4,7 +4,7 @@ cur_frm.cscript.refresh = function(doc) { cur_frm.set_intro(doc.__islocal ? "" : __("There is nothing to edit.")); cur_frm.cscript.set_root_readonly(doc); -} +}; cur_frm.cscript.set_root_readonly = function(doc) { // read-only for root customer group @@ -14,16 +14,16 @@ cur_frm.cscript.set_root_readonly = function(doc) { } else { cur_frm.set_intro(null); } -} +}; -//get query select Customer Group -cur_frm.fields_dict['parent_supplier_group'].get_query = function(doc,cdt,cdn) { +// get query select Customer Group +cur_frm.fields_dict['parent_supplier_group'].get_query = function() { return { filters: { 'is_group': 1 } - } -} + }; +}; cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(doc, cdt, cdn) { var d = locals[cdt][cdn]; @@ -33,5 +33,5 @@ cur_frm.fields_dict['accounts'].grid.get_field('account').get_query = function(d 'company': d.company, "is_group": 0 } - } -} + }; +}; diff --git a/erpnext/setup/doctype/supplier_group/supplier_group_tree.js b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js index 24328bdd5a..73926992e8 100644 --- a/erpnext/setup/doctype/supplier_group/supplier_group_tree.js +++ b/erpnext/setup/doctype/supplier_group/supplier_group_tree.js @@ -1,3 +1,3 @@ frappe.treeview_settings["Supplier Group"] = { ignore_fields:["parent_supplier_group"] -} \ No newline at end of file +}; \ No newline at end of file From 513f13df272de2ceb190d898204753eafbeedf47 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Mon, 14 May 2018 16:05:16 +0530 Subject: [PATCH 46/79] patch fix --- .../patches/v11_0/rename_supplier_type_to_supplier_group.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py index 4db5704ad6..f0907af333 100644 --- a/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py +++ b/erpnext/patches/v11_0/rename_supplier_type_to_supplier_group.py @@ -21,6 +21,9 @@ def execute(): build_tree() def build_tree(): + frappe.db.sql("""update `tabSupplier Group` set parent_supplier_group = '{0}' + where is_group = 0""".format(_('All Supplier Groups'))) + if not frappe.db.exists("Supplier Group", _('All Supplier Groups')): frappe.get_doc({ 'doctype': 'Supplier Group', @@ -28,7 +31,4 @@ def build_tree(): 'is_group': 1 }).insert(ignore_permissions=True) - frappe.db.sql("""update `tabSupplier Group` set parent_supplier_group = '{0}' - where is_group = 0""".format(_('All Supplier Groups'))) - rebuild_tree("Supplier Group", "parent_supplier_group") From 366ca92ba75b21cb3e986b9bb46f60dd114bb3f6 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 18 Apr 2018 16:11:38 +0530 Subject: [PATCH 47/79] move default_currency on top --- erpnext/setup/doctype/company/company.json | 86 +++++++++++----------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index 66b1433eb1..d236dda873 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -456,7 +456,38 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "default_letter_head", + "fieldname": "default_currency", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 1, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Default Currency", + "length": 0, + "no_copy": 0, + "options": "Currency", + "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": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "default_letter_head", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -465,10 +496,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Default Letter Head", + "label": "Default Letter Head", "length": 0, "no_copy": 0, - "options": "Letter Head", + "options": "Letter Head", "permlevel": 0, "precision": "", "print_hide": 0, @@ -488,7 +519,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "default_holiday_list", + "fieldname": "default_holiday_list", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -497,12 +528,12 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Default Holiday List", + "label": "Default Holiday List", "length": 0, "no_copy": 0, - "options": "Holiday List", + "options": "Holiday List", "permlevel": 0, - "precision": "", + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -520,57 +551,26 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "default_terms", + "fieldname": "default_terms", "fieldtype": "Link", "hidden": 0, - "ignore_user_permissions": 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": "Default Terms", + "label": "Default Terms", "length": 0, "no_copy": 0, - "options": "Terms and Conditions", + "options": "Terms and Conditions", "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "default_currency", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 1, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Default Currency", - "length": 0, - "no_copy": 0, - "options": "Currency", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, From 39668609ce0df12132a622ca274d36f596d3a8cc Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Wed, 18 Apr 2018 18:13:24 +0530 Subject: [PATCH 48/79] improv department tree --- erpnext/hr/doctype/department/department.py | 21 ++++++++++++++- .../hr/doctype/department/department_tree.js | 26 ++++++++++++++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index fda1b69de2..f1e0aeed8e 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -21,4 +21,23 @@ class Department(NestedSet): delete_events(self.doctype, self.name) def on_doctype_update(): - frappe.db.add_index("Department", ["lft", "rgt"]) \ No newline at end of file + frappe.db.add_index("Department", ["lft", "rgt"]) + +@frappe.whitelist() +def get_children(doctype, parent=None, company=None, is_root=False): + condition = '' + if company == parent: + condition = 'name="All Departments"' + elif company: + condition = "parent_department='{0}' and company='{1}'".format(parent, company) + else: + condition = "parent_department = '{0}'".format(parent) + + return frappe.db.sql(""" + select + name as value, + is_group as expandable + from `tab{doctype}` + where + {condition} + order by name""".format(doctype=doctype, condition=condition), as_dict=1) diff --git a/erpnext/hr/doctype/department/department_tree.js b/erpnext/hr/doctype/department/department_tree.js index 5652ad61a0..1f891fd1e5 100644 --- a/erpnext/hr/doctype/department/department_tree.js +++ b/erpnext/hr/doctype/department/department_tree.js @@ -1,3 +1,27 @@ frappe.treeview_settings["Department"] = { - ignore_fields:["parent_department"] + ignore_fields:["parent_department"], + get_tree_nodes: 'erpnext.hr.doctype.department.department.get_children', + filters: [ + { + fieldname: "company", + fieldtype:"Link", + options: "Company", + label: __("Company"), + }, + ], + breadcrumb: "HR", + root_label: "All Departments", + get_tree_root: true, + menu_items: [ + { + label: __("New Department"), + action: function() { + frappe.new_doc("Department", true); + }, + condition: 'frappe.boot.user.can_create.indexOf("Department") !== -1' + } + ], + onload: function(treeview) { + treeview.make_tree(); + } }; \ No newline at end of file From 9a8f5bd6c73b58b77a2ca8a18a882cc0c60723e5 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 11 May 2018 12:36:54 +0530 Subject: [PATCH 49/79] set department name with company abbr --- erpnext/hr/doctype/department/department.json | 4 ++-- erpnext/hr/doctype/department/department.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/department/department.json b/erpnext/hr/doctype/department/department.json index a1c8dc16ff..664679d3f1 100644 --- a/erpnext/hr/doctype/department/department.json +++ b/erpnext/hr/doctype/department/department.json @@ -3,7 +3,7 @@ "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, - "autoname": "field:department_name", + "autoname": "", "beta": 0, "creation": "2013-02-05 11:48:26", "custom": 0, @@ -434,7 +434,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-18 12:02:42.572599", + "modified": "2018-05-11 12:18:18.839182", "modified_by": "Administrator", "module": "HR", "name": "Department", diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py index f1e0aeed8e..19994ae5ef 100644 --- a/erpnext/hr/doctype/department/department.py +++ b/erpnext/hr/doctype/department/department.py @@ -10,6 +10,10 @@ from frappe.model.document import Document class Department(NestedSet): nsm_parent_field = 'parent_department' + def autoname(self): + abbr = frappe.db.get_value('Company', self.company, 'abbr') + self.name = '{0} - {1}'.format(self.department_name, abbr) + def update_nsm_model(self): frappe.utils.nestedset.update_nsm(self) From 03ad0f2f4a0a73a715de65b7900ec8ffa1f00b90 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Fri, 11 May 2018 16:57:44 +0530 Subject: [PATCH 50/79] set query for department based on company --- erpnext/assets/doctype/asset/asset.js | 8 +++ .../doctype/instructor/instructor.js | 72 ++++++++++++------- erpnext/hr/doctype/employee/employee.js | 9 +++ erpnext/hr/doctype/job_opening/job_opening.js | 9 +++ .../leave_control_panel.js | 17 ++++- .../hr/doctype/leave_period/leave_period.js | 10 ++- .../hr/doctype/payroll_entry/payroll_entry.js | 8 +++ .../hr/doctype/staffing_plan/staffing_plan.js | 8 +++ 8 files changed, 112 insertions(+), 29 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 236c4f9f9b..f4a01aefca 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -23,6 +23,14 @@ frappe.ui.form.on('Asset', { } }; }); + + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); }, refresh: function(frm) { diff --git a/erpnext/education/doctype/instructor/instructor.js b/erpnext/education/doctype/instructor/instructor.js index 62d73b32ee..f9c7a2a13d 100644 --- a/erpnext/education/doctype/instructor/instructor.js +++ b/erpnext/education/doctype/instructor/instructor.js @@ -1,31 +1,53 @@ cur_frm.add_fetch("employee", "department", "department"); cur_frm.add_fetch("employee", "image", "image"); -frappe.ui.form.on("Instructor", "refresh", function(frm) { - if(!frm.doc.__islocal) { - frm.add_custom_button(__("Student Group"), function() { - frappe.route_options = { - instructor: frm.doc.name - } - frappe.set_route("List", "Student Group"); +frappe.ui.form.on("Instructor", { + employee: function(frm) { + if(!frm.doc.employee) return; + frappe.db.get_value('Employee', {name: frm.doc.employee}, 'company', (company) => { + frm.set_query("department", function() { + return { + "filters": { + "company": company, + } + }; + }); + + frm.set_query("department", "instructor_log", function() { + return { + "filters": { + "company": company, + } + }; + }); }); - frm.add_custom_button(__("Course Schedule"), function() { - frappe.route_options = { - instructor: frm.doc.name - } - frappe.set_route("List", "Course Schedule"); - }); - frm.add_custom_button(__("As Examiner"), function() { - frappe.route_options = { - examiner: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); - }, __("Assessment Plan")); - frm.add_custom_button(__("As Supervisor"), function() { - frappe.route_options = { - supervisor: frm.doc.name - } - frappe.set_route("List", "Assessment Plan"); - }, __("Assessment Plan")); + }, + refresh: function(frm) { + if(!frm.doc.__islocal) { + frm.add_custom_button(__("Student Group"), function() { + frappe.route_options = { + instructor: frm.doc.name + } + frappe.set_route("List", "Student Group"); + }); + frm.add_custom_button(__("Course Schedule"), function() { + frappe.route_options = { + instructor: frm.doc.name + } + frappe.set_route("List", "Course Schedule"); + }); + frm.add_custom_button(__("As Examiner"), function() { + frappe.route_options = { + examiner: frm.doc.name + } + frappe.set_route("List", "Assessment Plan"); + }, __("Assessment Plan")); + frm.add_custom_button(__("As Supervisor"), function() { + frappe.route_options = { + supervisor: frm.doc.name + } + frappe.set_route("List", "Assessment Plan"); + }, __("Assessment Plan")); + } } }); diff --git a/erpnext/hr/doctype/employee/employee.js b/erpnext/hr/doctype/employee/employee.js index 240411dcfa..6f6873ac1d 100755 --- a/erpnext/hr/doctype/employee/employee.js +++ b/erpnext/hr/doctype/employee/employee.js @@ -37,6 +37,15 @@ erpnext.hr.EmployeeController = frappe.ui.form.Controller.extend({ }); frappe.ui.form.on('Employee',{ + onload:function(frm) { + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); + }, prefered_contact_email:function(frm){ frm.events.update_contact(frm) }, diff --git a/erpnext/hr/doctype/job_opening/job_opening.js b/erpnext/hr/doctype/job_opening/job_opening.js index 960f5b3c65..7b0e447a9e 100644 --- a/erpnext/hr/doctype/job_opening/job_opening.js +++ b/erpnext/hr/doctype/job_opening/job_opening.js @@ -2,6 +2,15 @@ // For license information, please see license.txt frappe.ui.form.on('Job Opening', { + onload: function(frm) { + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); + }, designation: function(frm) { if(frm.doc.designation && frm.doc.company){ frappe.call({ diff --git a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js index 0eb6414406..7aeb8ea65d 100644 --- a/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js +++ b/erpnext/hr/doctype/leave_control_panel/leave_control_panel.js @@ -26,6 +26,19 @@ cur_frm.cscript.allocation_type = function (doc, cdt, cdn) { refresh_field('no_of_days'); } -frappe.ui.form.on("Leave Control Panel", "refresh", function (frm) { - frm.disable_save(); +frappe.ui.form.on("Leave Control Panel", { + company: function(frm) { + if(frm.doc.company) { + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); + } + }, + refresh: function(frm) { + frm.disable_save(); + } }); \ No newline at end of file diff --git a/erpnext/hr/doctype/leave_period/leave_period.js b/erpnext/hr/doctype/leave_period/leave_period.js index 2a6010e44f..a54147d18a 100644 --- a/erpnext/hr/doctype/leave_period/leave_period.js +++ b/erpnext/hr/doctype/leave_period/leave_period.js @@ -2,7 +2,13 @@ // For license information, please see license.txt frappe.ui.form.on('Leave Period', { - refresh: function(frm) { - + onload: function(frm) { + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); } }); diff --git a/erpnext/hr/doctype/payroll_entry/payroll_entry.js b/erpnext/hr/doctype/payroll_entry/payroll_entry.js index 1e6dc510b0..d02e1f1dc3 100644 --- a/erpnext/hr/doctype/payroll_entry/payroll_entry.js +++ b/erpnext/hr/doctype/payroll_entry/payroll_entry.js @@ -9,6 +9,14 @@ frappe.ui.form.on('Payroll Entry', { frm.doc.posting_date = frappe.datetime.nowdate(); } frm.toggle_reqd(['payroll_frequency'], !frm.doc.salary_slip_based_on_timesheet); + + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); }, refresh: function(frm) { diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.js b/erpnext/hr/doctype/staffing_plan/staffing_plan.js index 1c1a720088..ca57d9f19d 100644 --- a/erpnext/hr/doctype/staffing_plan/staffing_plan.js +++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.js @@ -17,6 +17,14 @@ frappe.ui.form.on('Staffing Plan', { ] } }); + + frm.set_query("department", function() { + return { + "filters": { + "company": frm.doc.company, + } + }; + }); } }); From d25a264e5bd5258cb6dea894e18d1393fd814da1 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Sun, 13 May 2018 14:34:20 +0530 Subject: [PATCH 51/79] create and update department records patch --- erpnext/patches.txt | 1 + ...ate_department_records_for_each_company.py | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 erpnext/patches/v11_0/create_department_records_for_each_company.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 3fffade9bb..4bbb3e9e70 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -527,3 +527,4 @@ erpnext.patches.v11_0.rename_field_max_days_allowed erpnext.patches.v11_0.create_salary_structure_assignments erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company +erpnext.patches.v11_0.create_department_records_for_each_company diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py new file mode 100644 index 0000000000..514c709071 --- /dev/null +++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py @@ -0,0 +1,51 @@ +import frappe +from frappe.utils.nestedset import rebuild_tree + +def execute(): + frappe.reload_doc("hr", "doctype", "department") + companies = frappe.db.get_all("Company", fields=["name", "abbr"]) + departments = frappe.db.get_all("Department") + comp_dict = {} + + # create a blank list for each company + for company in companies: + comp_dict[company.name] = {} + + for department in departments: + # skip root node + if department.name == "All Departments": + continue + + # for each company, create a copy of the doc + department_doc = frappe.get_doc("Department", department) + for company in companies: + copy_doc = frappe.copy_doc(department_doc) + copy_doc.update({"company": company.name}) + copy_doc.insert() + + # append list of new department for each company + comp_dict[company.name][department.name] = copy_doc.name + + rebuild_tree('Department', 'parent_department') + doctypes = ["Asset", "Employee", "Leave Period", "Payroll Entry", "Staffing Plan", "Job Opening"] + + for d in doctypes: + update_records(d, comp_dict) + +def update_records(doctype, comp_dict): + when_then = [] + for company in comp_dict: + records = comp_dict[company] + + for department in records: + when_then.append(''' + WHEN company = "%s" and department = "%s" + THEN "%s" + '''%(company, department, records[department])) + + frappe.db.sql(""" + update + `tab%s` + set + department = CASE %s END + """%(doctype, " ".join(when_then)), debug=1) From 06fd51bf696a0344c6ebc8df77f2ebe307e93871 Mon Sep 17 00:00:00 2001 From: Zlash65 Date: Mon, 14 May 2018 17:16:27 +0530 Subject: [PATCH 52/79] patch fix for instructor --- ...ate_department_records_for_each_company.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/patches/v11_0/create_department_records_for_each_company.py b/erpnext/patches/v11_0/create_department_records_for_each_company.py index 514c709071..94071857d7 100644 --- a/erpnext/patches/v11_0/create_department_records_for_each_company.py +++ b/erpnext/patches/v11_0/create_department_records_for_each_company.py @@ -32,6 +32,8 @@ def execute(): for d in doctypes: update_records(d, comp_dict) + update_instructors(comp_dict) + def update_records(doctype, comp_dict): when_then = [] for company in comp_dict: @@ -48,4 +50,24 @@ def update_records(doctype, comp_dict): `tab%s` set department = CASE %s END - """%(doctype, " ".join(when_then)), debug=1) + """%(doctype, " ".join(when_then))) + +def update_instructors(comp_dict): + when_then = [] + emp_details = frappe.get_all("Employee", fields=["name", "company"]) + + for employee in emp_details: + records = comp_dict[employee.company] + + for department in records: + when_then.append(''' + WHEN employee = "%s" and department = "%s" + THEN "%s" + '''%(employee.name, department, records[department])) + + frappe.db.sql(""" + update + `tabInstructor` + set + department = CASE %s END + """%(" ".join(when_then))) From 32b3aa257c65ac0182b07e79bdc987a258e99564 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 14 May 2018 18:02:24 +0530 Subject: [PATCH 53/79] [Fix] Indentation for accounting_period overlap --- .../accounting_period/accounting_period.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 306bf9177e..8760d8f35e 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -15,20 +15,20 @@ class AccountingPeriod(Document): self.name = " - ".join([self.period_name, company_abbr]) def validate_overlap(self): - existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period` - where ( - (%(start_date)s between start_date and end_date) - or (%(end_date)s between start_date and end_date) - or (start_date between %(start_date)s and %(end_date)s) - or (end_date between %(start_date)s and %(end_date)s) - ) and name!=%(name)s and company=%(company)s""", - { - "start_date": self.start_date, - "end_date": self.end_date, - "name": self.name, - "company": self.company - }, as_dict=True) + existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period` + where ( + (%(start_date)s between start_date and end_date) + or (%(end_date)s between start_date and end_date) + or (start_date between %(start_date)s and %(end_date)s) + or (end_date between %(start_date)s and %(end_date)s) + ) and name!=%(name)s and company=%(company)s""", + { + "start_date": self.start_date, + "end_date": self.end_date, + "name": self.name, + "company": self.company + }, as_dict=True) - if len(existing_accounting_period) > 0: - frappe.throw("Accounting Period overlaps with {0}".format(existing_accounting_period[0].get("name"))) + if len(existing_accounting_period) > 0: + frappe.throw("Accounting Period overlaps with {0}".format(existing_accounting_period[0].get("name"))) From 2fe38c3ff5b89c72e5a15f365e6a005370bc0c8f Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 14 May 2018 18:06:34 +0530 Subject: [PATCH 54/79] Bootstrap documents for closing --- .../accounting_period/accounting_period.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index 8760d8f35e..f2ba578965 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -10,6 +10,9 @@ class AccountingPeriod(Document): def validate(self): self.validate_overlap() + def before_insert(self): + self.bootstrap_doctypes_for_closing() + def autoname(self): company_abbr = frappe.db.get_value("Company", self.company, "abbr") self.name = " - ".join([self.period_name, company_abbr]) @@ -32,3 +35,20 @@ class AccountingPeriod(Document): if len(existing_accounting_period) > 0: frappe.throw("Accounting Period overlaps with {0}".format(existing_accounting_period[0].get("name"))) + def get_doctypes_for_closing(self): + docs_for_closing = [] + #if not self.closed_documents or len(self.closed_documents) == 0: + doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", "Bank Reconciliation", "Asset", "Purchase Order", "Sales Order", "Leave Application", "Leave Allocation", "Stock Entry"] + closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes] + for closed_doctype in closed_doctypes: + docs_for_closing.append(closed_doctype) + + return docs_for_closing + + def bootstrap_doctypes_for_closing(self): + if self.closed_documents.length == 0: + for doctype_for_closing in self.get_doctypes_for_closing(): + self.append('closed_documents', { + "document_type": doctype_for_closing.document_type, + "closed": doctype_for_closing.closed + }) From 52571a80daef6bba3ffeb10712183347bd69ad41 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 14 May 2018 18:06:50 +0530 Subject: [PATCH 55/79] Bootstrap documents for closing (js) --- .../accounting_period/accounting_period.js | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.js b/erpnext/accounts/doctype/accounting_period/accounting_period.js index 1fb57eabcf..e3d805a168 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.js +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.js @@ -2,7 +2,23 @@ // For license information, please see license.txt frappe.ui.form.on('Accounting Period', { - refresh: function(frm) { - + onload: function(frm) { + if(frm.doc.closed_documents.length === 0 || (frm.doc.closed_documents.length === 1 && frm.doc.closed_documents[0].document_type == undefined)) { + frappe.call({ + method: "get_doctypes_for_closing", + doc:frm.doc, + callback: function(r) { + if(r.message) { + cur_frm.clear_table("closed_documents"); + r.message.forEach(function(element) { + var c = frm.add_child("closed_documents"); + c.document_type = element.document_type; + c.closed = element.closed; + }); + refresh_field("closed_documents"); + } + } + }); + } } }); From caaebb9d2ac1c3b6cd421a23619e799f80fe7116 Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 14 May 2018 18:11:09 +0530 Subject: [PATCH 56/79] [WIP] Accounting Period Test cases --- .../test_accounting_period.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py index 99694d2136..cc2e6a9fc6 100644 --- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py @@ -7,4 +7,21 @@ import frappe import unittest class TestAccountingPeriod(unittest.TestCase): - pass + def test_overlap(self): + ap1 = create_accounting_period({"start_date":"2018-04-01", "end_date":"2018-06-30", "company":"Wind Power LLC"}) + ap1.save() + ap2 = create_accounting_period({"start_date":"2018-06-30", "end_date":"2018-07-10", "company":"Wind Power LLC"}) + self.assertRaises(frappe.OverlapError, accounting_period_2.save()) + + def tearDown(self): + pass + + +def create_accounting_period(**args): + accounting_period = frappe.new_doc("Accounting Period") + accounting_period.start_date = args.start_date or frappe.utils.datetime.date(2018, 4, 1) + accounting_period.end_date = args.end_date or frappe.utils.datetime.date(2018, 6, 30) + accounting_period.company = args.company + accounting_period.period_name = "_Test_Period_Name_1" + + return accounting_period From 21b2df41accea57653f39891ab0bd30b801ed6dc Mon Sep 17 00:00:00 2001 From: Gaurav Naik Date: Mon, 14 May 2018 18:15:47 +0530 Subject: [PATCH 57/79] [Fix] len() instead of .length --- erpnext/accounts/doctype/accounting_period/accounting_period.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py index f2ba578965..32441db2c1 100644 --- a/erpnext/accounts/doctype/accounting_period/accounting_period.py +++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py @@ -46,7 +46,7 @@ class AccountingPeriod(Document): return docs_for_closing def bootstrap_doctypes_for_closing(self): - if self.closed_documents.length == 0: + if len(self.closed_documents) == 0: for doctype_for_closing in self.get_doctypes_for_closing(): self.append('closed_documents', { "document_type": doctype_for_closing.document_type, From 47039e801e27a83984c1bcc851bc526c227235e4 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Mon, 14 May 2018 18:54:10 +0530 Subject: [PATCH 58/79] Remove unused import --- .../test_additional_salary_component.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py index 6859da1c0b..eda2f79dc9 100644 --- a/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py +++ b/erpnext/hr/doctype/additional_salary_component/test_additional_salary_component.py @@ -2,8 +2,6 @@ # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors # See license.txt from __future__ import unicode_literals - -import frappe import unittest class TestAdditionalSalaryComponent(unittest.TestCase): From ab842541887cc8c0cda245cef88940e911731b97 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Thu, 26 Apr 2018 19:18:29 +0530 Subject: [PATCH 59/79] Asset linked to purchase receipt and serial no --- .../purchase_invoice/purchase_invoice.py | 17 -- .../purchase_invoice_item.json | 2 +- erpnext/assets/doctype/asset/asset.js | 3 +- erpnext/assets/doctype/asset/asset.json | 109 ++++++++++- erpnext/assets/doctype/asset/asset.py | 25 ++- .../assets/doctype/asset/asset_dashboard.py | 7 + .../asset_maintenance/asset_maintenance.json | 49 ++++- .../asset_maintenance/asset_maintenance.py | 3 + .../asset_movement/asset_movement.json | 171 +++++++++++++++++- .../doctype/asset_movement/asset_movement.py | 17 +- erpnext/controllers/accounts_controller.py | 52 +++--- erpnext/controllers/buying_controller.py | 126 +++++++++++++ erpnext/setup/doctype/company/company.js | 8 + erpnext/stock/doctype/item/item.json | 12 +- erpnext/stock/doctype/item/item.py | 2 +- .../purchase_receipt/purchase_receipt.js | 14 ++ .../purchase_receipt/purchase_receipt.py | 2 + .../purchase_receipt_dashboard.py | 3 +- .../purchase_receipt/test_purchase_receipt.py | 42 ++++- .../purchase_receipt_item.json | 66 ++++++- .../stock/doctype/serial_no/serial_no.json | 78 +++++++- erpnext/stock/doctype/serial_no/serial_no.py | 56 +++--- erpnext/stock/get_item_details.py | 4 + 23 files changed, 763 insertions(+), 105 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 67b41a3248..d2cc4eea9f 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -305,24 +305,8 @@ class PurchaseInvoice(BuyingController): self.make_gl_entries() self.update_project() - self.update_fixed_asset() update_linked_invoice(self.doctype, self.name, self.inter_company_invoice_reference) - def update_fixed_asset(self): - for d in self.get("items"): - if d.is_fixed_asset: - asset = frappe.get_doc("Asset", d.asset) - if self.docstatus==1: - asset.purchase_invoice = self.name - asset.purchase_date = self.posting_date - asset.supplier = self.supplier - else: - asset.purchase_invoice = None - asset.supplier = None - - asset.flags.ignore_validate_update_after_submit = True - asset.save() - def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False): if not self.grand_total: return @@ -636,7 +620,6 @@ class PurchaseInvoice(BuyingController): self.make_gl_entries_on_cancel() self.update_project() - self.update_fixed_asset() frappe.db.set(self, 'status', 'Cancelled') unlink_inter_company_invoice(self.doctype, self.name, self.inter_company_invoice_reference) diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json index d1f99ab3eb..ef9b2f69b6 100755 --- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json +++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json @@ -2258,7 +2258,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-22 15:15:25.297672", + "modified": "2018-04-23 14:07:33.576495", "modified_by": "Administrator", "module": "Accounts", "name": "Purchase Invoice Item", diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index f4a01aefca..edc1e364f5 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -181,7 +181,8 @@ frappe.ui.form.on('Asset', { args: { "asset": frm.doc.name, "item_code": frm.doc.item_code, - "company": frm.doc.company + "company": frm.doc.company, + "serial_no": frm.doc.serial_no }, method: "erpnext.assets.doctype.asset.asset.make_sales_invoice", callback: function(r) { diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 49b574d3b0..63b9167cc6 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -40,6 +40,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -71,6 +72,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -102,6 +104,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -133,6 +136,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -165,6 +169,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -196,6 +201,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -228,6 +234,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -260,6 +267,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -292,6 +300,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -322,6 +331,38 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "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": "Serial No", + "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 }, { @@ -351,6 +392,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -382,6 +424,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -413,6 +456,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -444,6 +488,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -474,6 +519,39 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_receipt", + "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": "Purchase Receipt", + "length": 0, + "no_copy": 1, + "options": "Purchase Receipt", + "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 }, { @@ -505,6 +583,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -535,6 +614,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -565,6 +645,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -595,6 +676,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -626,6 +708,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -655,6 +738,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -686,6 +770,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -715,6 +800,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -747,6 +833,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -779,6 +866,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -808,6 +896,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -838,6 +927,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -869,6 +959,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -902,6 +993,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -933,6 +1025,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -963,6 +1056,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -992,6 +1086,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1023,6 +1118,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1053,6 +1149,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1084,6 +1181,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1115,6 +1213,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1146,6 +1245,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1176,6 +1276,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1207,6 +1308,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1237,6 +1339,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -1251,7 +1354,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-01-05 09:53:05.945328", + "modified": "2018-04-19 20:03:13.669957", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -1260,7 +1363,6 @@ "permissions": [ { "amend": 1, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -1280,7 +1382,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -1307,4 +1408,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index baffdd757d..a9cc924b8f 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -10,6 +10,7 @@ from frappe.model.document import Document from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_depreciation_accounts +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class Asset(Document): def validate(self): @@ -26,12 +27,16 @@ class Asset(Document): def on_submit(self): self.set_status() + self.update_stock_movement() def on_cancel(self): self.validate_cancellation() self.delete_depreciation_entries() self.set_status() + def on_update(self): + self.update_serial_nos() + def validate_item(self): item = frappe.db.get_value("Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1) @@ -219,6 +224,9 @@ class Asset(Document): if self.purchase_invoice: frappe.throw(_("Please cancel Purchase Invoice {0} first").format(self.purchase_invoice)) + if self.purchase_receipt: + frappe.throw(_("Please cancel Purchase Receipt {0} first").format(self.purchase_receipt)) + def delete_depreciation_entries(self): for d in self.get("schedules"): if d.journal_entry: @@ -250,6 +258,20 @@ class Asset(Document): status = "Cancelled" return status + def update_serial_nos(self): + if self.serial_no: + serial_nos = get_serial_nos(self.serial_no) + frappe.db.sql(""" update `tabSerial No` set asset = '%s' where + name in(%s)"""%(self.name, ','.join(['%s'] * len(serial_nos))), tuple(serial_nos)) + + def update_stock_movement(self): + asset_movement = frappe.db.get_value('Asset Movement', + {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name') + + if asset_movement: + doc = frappe.get_doc('Asset Movement', asset_movement) + doc.submit() + def update_maintenance_status(): assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1}) @@ -280,7 +302,7 @@ def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, post return pi @frappe.whitelist() -def make_sales_invoice(asset, item_code, company): +def make_sales_invoice(asset, item_code, company, serial_no): si = frappe.new_doc("Sales Invoice") si.company = company si.currency = frappe.db.get_value("Company", company, "default_currency") @@ -290,6 +312,7 @@ def make_sales_invoice(asset, item_code, company): "is_fixed_asset": 1, "asset": asset, "income_account": disposal_account, + "serial_no": serial_no, "cost_center": depreciation_cost_center, "qty": 1 }) diff --git a/erpnext/assets/doctype/asset/asset_dashboard.py b/erpnext/assets/doctype/asset/asset_dashboard.py index 94dad1b311..89699f3edb 100644 --- a/erpnext/assets/doctype/asset/asset_dashboard.py +++ b/erpnext/assets/doctype/asset/asset_dashboard.py @@ -1,6 +1,9 @@ def get_data(): return { 'fieldname': 'asset_name', + 'non_standard_fieldnames': { + 'Asset Movement': 'asset' + }, 'transactions': [ { 'label': ['Maintenance'], @@ -9,6 +12,10 @@ def get_data(): { 'label': ['Repair'], 'items': ['Asset Repair'] + }, + { + 'label': ['Movement'], + 'items': ['Asset Movement'] } ] } \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json index 72d96b0db0..f36fe4e078 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.json @@ -42,6 +42,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -73,6 +74,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -104,6 +106,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -135,6 +138,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -164,6 +168,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -195,6 +200,39 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "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": "Serial No", + "length": 0, + "no_copy": 0, + "options": "asset_name.serial_no", + "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 }, { @@ -224,6 +262,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -255,6 +294,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -284,6 +324,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -315,6 +356,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -346,6 +388,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -376,6 +419,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -407,6 +451,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -420,7 +465,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-01 15:13:29.816396", + "modified": "2018-04-20 08:39:27.072622", "modified_by": "Administrator", "module": "Assets", "name": "Asset Maintenance", @@ -429,7 +474,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -449,7 +493,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py index 7551eae229..b30685f108 100644 --- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py +++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py @@ -11,6 +11,9 @@ from frappe.utils import add_days, add_months, add_years, getdate, nowdate class AssetMaintenance(Document): def validate(self): + if not self.serial_no: + self.serial_no = frappe.db.get_value("Asset", self.asset_name, 'serial_no') + for task in self.get('asset_maintenance_tasks'): if task.end_date and (getdate(task.start_date) >= getdate(task.end_date)): throw(_("Start date should be less than end date for task {0}").format(task.maintenance_task)) diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 0c05552962..3c3a1dc9cd 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -12,6 +12,39 @@ "document_type": "", "editable_grid": 0, "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Transfer", + "fieldname": "purpose", + "fieldtype": "Select", + "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": "Purpose", + "length": 0, + "no_copy": 0, + "options": "Receipt\nTransfer", + "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_on_submit": 0, @@ -41,6 +74,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -71,6 +105,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -102,6 +137,38 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "serial_no", + "fieldtype": "Small Text", + "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": "Serial No", + "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 }, { @@ -131,6 +198,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -162,6 +230,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -193,6 +262,102 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference", + "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": "Reference", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_doctype", + "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": "Reference DocType", + "length": 0, + "no_copy": 1, + "options": "DocType", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "reference_name", + "fieldtype": "Dynamic 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": "Reference Name", + "length": 0, + "no_copy": 1, + "options": "reference_doctype", + "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 }, { @@ -223,6 +388,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -236,7 +402,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-10-19 16:08:17.389257", + "modified": "2018-04-20 15:45:54.156501", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", @@ -245,7 +411,6 @@ "permissions": [ { "amend": 1, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -265,7 +430,6 @@ }, { "amend": 1, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -285,7 +449,6 @@ }, { "amend": 1, - "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 574c49992b..42ed249959 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -11,19 +11,22 @@ class AssetMovement(Document): def validate(self): self.validate_asset() self.validate_warehouses() - + def validate_asset(self): - status, company = frappe.db.get_value("Asset", self.asset, ["status", "company"]) - if status in ("Draft", "Scrapped", "Sold"): + status, company, serial_no = frappe.db.get_value("Asset", self.asset, ["status", "company", "serial_no"]) + if self.purpose == 'Transfer' and status in ("Draft", "Scrapped", "Sold"): frappe.throw(_("{0} asset cannot be transferred").format(status)) - + if company != self.company: frappe.throw(_("Asset {0} does not belong to company {1}").format(self.asset, self.company)) - + + if serial_no and not self.serial_no: + self.serial_no = serial_no + def validate_warehouses(self): - if not self.source_warehouse: + if self.purpose == 'Transfer' and not self.source_warehouse: self.source_warehouse = frappe.db.get_value("Asset", self.asset, "warehouse") - + if self.source_warehouse == self.target_warehouse: frappe.throw(_("Source and Target Warehouse cannot be same")) diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index c70cfcd811..40028afb01 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -600,40 +600,36 @@ class AccountsController(TransactionBase): if d.qty > 1: frappe.throw(_("Row #{0}: Qty must be 1, as item is a fixed asset. Please use separate row for multiple qty.").format(d.idx)) - if d.meta.get_field("asset"): - if not d.asset: - frappe.throw(_("Row #{0}: Asset is mandatory for fixed asset purchase/sale") - .format(d.idx)) - else: - asset = frappe.get_doc("Asset", d.asset) + if d.meta.get_field("asset") and d.asset: + asset = frappe.get_doc("Asset", d.asset) - if asset.company != self.company: - frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") - .format(d.idx, d.asset, self.company)) + if asset.company != self.company: + frappe.throw(_("Row #{0}: Asset {1} does not belong to company {2}") + .format(d.idx, d.asset, self.company)) - elif asset.item_code != d.item_code: - frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") - .format(d.idx, d.asset, d.item_code)) + elif asset.item_code != d.item_code: + frappe.throw(_("Row #{0}: Asset {1} does not linked to Item {2}") + .format(d.idx, d.asset, d.item_code)) - elif asset.docstatus != 1: - frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) + elif asset.docstatus != 1: + frappe.throw(_("Row #{0}: Asset {1} must be submitted").format(d.idx, d.asset)) - elif self.doctype == "Purchase Invoice": - if asset.status != "Submitted": - frappe.throw(_("Row #{0}: Asset {1} is already {2}") - .format(d.idx, d.asset, asset.status)) - elif getdate(asset.purchase_date) != getdate(self.posting_date): - frappe.throw(_("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, asset.purchase_date, d.asset)) - elif asset.is_existing_asset: - frappe.throw(_("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format(d.idx, d.asset)) + elif self.doctype == "Purchase Invoice": + if asset.status != "Submitted": + frappe.throw(_("Row #{0}: Asset {1} is already {2}") + .format(d.idx, d.asset, asset.status)) + elif getdate(asset.purchase_date) != getdate(self.posting_date): + frappe.throw(_("Row #{0}: Posting Date must be same as purchase date {1} of asset {2}").format(d.idx, asset.purchase_date, d.asset)) + elif asset.is_existing_asset: + frappe.throw(_("Row #{0}: Purchase Invoice cannot be made against an existing asset {1}").format(d.idx, d.asset)) - elif self.docstatus=="Sales Invoice" and self.docstatus == 1: - if self.update_stock: - frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) + elif self.docstatus=="Sales Invoice" and self.docstatus == 1: + if self.update_stock: + frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale")) - elif asset.status in ("Scrapped", "Cancelled", "Sold"): - frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") - .format(d.idx, d.asset, asset.status)) + elif asset.status in ("Scrapped", "Cancelled", "Sold"): + frappe.throw(_("Row #{0}: Asset {1} cannot be submitted, it is already {2}") + .format(d.idx, d.asset, asset.status)) def delink_advance_entries(self, linked_doc_name): total_allocated_amount = 0 diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index de6ed79351..c340901e3b 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -11,6 +11,7 @@ from erpnext.stock.get_item_details import get_conversion_factor from erpnext.buying.utils import validate_for_items, update_last_purchase_rate from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items +from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos from erpnext.controllers.stock_controller import StockController @@ -439,6 +440,11 @@ class BuyingController(StockController): if self.get('is_return'): return + if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: + if self.doctype == 'Purchase Receipt': + self.process_fixed_asset() + self.update_fixed_asset() + update_last_purchase_rate(self, is_submit = 1) def on_cancel(self): @@ -446,6 +452,118 @@ class BuyingController(StockController): return update_last_purchase_rate(self, is_submit = 0) + if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: + if self.doctype == 'Purchase Receipt': + self.delete_linked_asset() + self.update_fixed_asset() + + def process_fixed_asset(self): + if not self.doctype in ['Purchase Receipt', 'Purchase Invoice']: + return + + asset_items = [d.item_code for d in self.items if d.is_fixed_asset] + if asset_items: + self.make_serial_nos_for_asset(asset_items) + + def make_serial_nos_for_asset(self, asset_items): + items_data = get_asset_item_details(asset_items) + + for d in self.items: + if d.is_fixed_asset: + item_data = items_data.get(d.item_code) + + if item_data.get('has_serial_no'): + # If item has serial no + if item_data.get('serial_no_series') and not d.serial_no: + serial_nos = get_auto_serial_nos(item_data.get('serial_no_series'), d.qty) + elif d.serial_no: + serial_nos = d.serial_no + elif not d.serial_no: + frappe.throw(_("Serial no is mandatory for the item {0}").format(d.item_code)) + + auto_make_serial_nos({ + 'serial_no': serial_nos, + 'item_code': d.item_code, + 'via_stock_ledger': False, + 'company': self.company, + 'actual_qty': d.qty, + 'purchase_document_type': self.doctype, + 'purchase_document_no': self.name + }) + d.db_set('serial_no', serial_nos) + + if not d.asset: + asset = self.make_asset(d) + d.db_set('asset', asset) + + if d.asset: + self.make_asset_movement(d) + + def make_asset(self, row): + asset = frappe.get_doc({ + 'doctype': 'Asset', + 'item_code': row.item_code, + 'asset_name': '{0} - {1}'.format(self.name, row.item_code), + 'warehouse': row.warehouse, + 'serial_no': row.serial_no, + 'company': self.company, + 'purchase_date': self.posting_date, + 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, + 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None + }) + + asset.flags.ignore_validate = True + asset.flags.ignore_mandatory = True + asset.insert() + + frappe.msgprint(_("Asset {0} created").format(asset.name)) + return asset.name + + def make_asset_movement(self, row): + asset_movement = frappe.get_doc({ + 'doctype': 'Asset Movement', + 'asset': row.asset, + 'source_warehouse': '', + 'target_warehouse': row.warehouse, + 'purpose': 'Receipt', + 'serial_no': row.serial_no, + 'company': self.company, + 'transaction_date': self.posting_date, + 'reference_doctype': self.doctype, + 'reference_name': self.name + }).insert() + + return asset_movement.name + + def update_fixed_asset(self): + field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' + + for d in self.get("items"): + if d.is_fixed_asset and d.asset: + asset = frappe.get_doc("Asset", d.asset) + if self.docstatus in [0, 1] and not asset.get(field): + asset.set(field, self.name) + asset.purchase_date = self.posting_date + asset.supplier = self.supplier + else: + asset.set(field, None) + asset.supplier = None + + asset.flags.ignore_validate_update_after_submit = True + if asset.docstatus == 0: + asset.flags.ignore_validate = True + + asset.save() + + def delete_linked_asset(self): + if not self.doctype in ['Purchase Receipt', 'Purchase Invoice']: + return + + if self.doctype == 'Purchase Invoice' and self.get('update_stock'): + return + + frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s and docstatus = 0", self.name) + frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) def validate_schedule_date(self): if not self.schedule_date: @@ -480,3 +598,11 @@ def get_items_from_bom(item_code, bom, exploded_item=1): msgprint(_("Specified BOM {0} does not exist for Item {1}").format(bom, item_code), raise_exception=1) return bom_items + +def get_asset_item_details(asset_items): + asset_items_data = {} + for d in frappe.get_all('Item', fields = ["name", "has_serial_no", "serial_no_series"], + filters = {'name': ('in', asset_items)}): + asset_items_data.setdefault(d.name, d) + + return asset_items_data diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index e164d6949f..d4095772cd 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -5,6 +5,14 @@ frappe.provide("erpnext.company"); frappe.ui.form.on("Company", { setup: function(frm) { + frm.fields_dict.fixed_asset_account.get_query = function() { + return { + filters: { + account_type: "Fixed Asset", + company: frm.doc.name + } + } + } erpnext.company.setup_queries(frm); }, diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 21900ad440..251717e127 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -1285,7 +1285,7 @@ "collapsible": 1, "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no", "columns": 0, - "depends_on": "is_stock_item", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", "fieldname": "serial_nos_and_batches", "fieldtype": "Section Break", "hidden": 0, @@ -1513,7 +1513,7 @@ "collapsible": 0, "columns": 0, "default": "", - "depends_on": "eval:doc.is_stock_item", + "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset", "description": "", "fieldname": "has_serial_no", "fieldtype": "Check", @@ -3725,7 +3725,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -3745,7 +3744,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3765,7 +3763,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3785,7 +3782,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3805,7 +3801,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3825,7 +3820,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3845,7 +3839,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -3865,7 +3858,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index 123e73f652..fafdaab51f 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -444,7 +444,7 @@ class Item(WebsiteGenerator): _("Conversion factor for default Unit of Measure must be 1 in row {0}").format(d.idx)) def validate_item_type(self): - if self.has_serial_no == 1 and self.is_stock_item == 0: + if self.has_serial_no == 1 and self.is_stock_item == 0 and not self.is_fixed_asset: msgprint(_("'Has Serial No' can not be 'Yes' for non-stock item"), raise_exception=1) if self.has_serial_no == 0 and self.serial_no_series: diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js index 791b2532f7..69d2f2a3d7 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js @@ -65,6 +65,20 @@ erpnext.stock.PurchaseReceiptController = erpnext.buying.BuyingController.extend if (erpnext.is_perpetual_inventory_enabled(this.frm.doc.company)) { this.show_general_ledger(); } + + this.frm.add_custom_button(__('Asset'), function() { + frappe.route_options = { + purchase_receipt: me.frm.doc.name, + }; + frappe.set_route("List", "Asset"); + }, __("View")); + + this.frm.add_custom_button(__('Asset Movement'), function() { + frappe.route_options = { + reference_name: me.frm.doc.name, + }; + frappe.set_route("List", "Asset Movement"); + }, __("View")); } if(!this.frm.doc.is_return && this.frm.doc.status!="Closed") { diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 46eee31e90..c7083be286 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -393,6 +393,8 @@ def make_purchase_invoice(source_name, target_doc=None): "parent": "purchase_receipt", "purchase_order_item": "po_detail", "purchase_order": "purchase_order", + "is_fixed_asset": "is_fixed_asset", + "asset": "asset", }, "postprocess": update_item, "filter": lambda d: abs(d.qty) - abs(invoiced_qty_map.get(d.name, 0))<=0 diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py index 9ade1afd8a..bcedd7179a 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_dashboard.py @@ -5,6 +5,7 @@ def get_data(): 'fieldname': 'purchase_receipt_no', 'non_standard_fieldnames': { 'Purchase Invoice': 'purchase_receipt', + 'Asset': 'purchase_receipt', 'Landed Cost Voucher': 'receipt_document', 'Subscription': 'reference_document' }, @@ -16,7 +17,7 @@ def get_data(): 'transactions': [ { 'label': _('Related'), - 'items': ['Purchase Invoice', 'Landed Cost Voucher'] + 'items': ['Purchase Invoice', 'Landed Cost Voucher', 'Asset'] }, { 'label': _('Reference'), diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py index d9f40cca90..ffcc954df1 100644 --- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py @@ -289,13 +289,53 @@ class TestPurchaseReceipt(unittest.TestCase): serial_no=serial_no, basic_rate=100, do_not_submit=True) self.assertRaises(SerialNoDuplicateError, se.submit) + def test_serialized_asset_item(self): + asset_item = "Test Serialized Asset Item" + + if not frappe.db.exists('Item', asset_item): + asset_category = frappe.get_all('Asset Category') + + if asset_category: + asset_category = asset_category[0].name + + if not asset_category: + doc = frappe.get_doc({ + 'doctype': 'Asset Category', + 'asset_category_name': 'Test Asset Category', + 'depreciation_method': 'Straight Line', + 'total_number_of_depreciations': 12, + 'frequency_of_depreciation': 1, + 'accounts': [{ + 'company_name': '_Test Company', + 'fixed_asset_account': '_Test Fixed Asset - _TC', + 'accumulated_depreciation_account': 'Depreciation - _TC', + 'depreciation_expense_account': 'Depreciation - _TC' + }] + }).insert() + + asset_category = doc.name + + asset_item = make_item(asset_item, {'is_stock_item':0, + 'stock_uom': 'Box', 'is_fixed_asset': 1, 'has_serial_no': 1, + 'asset_category': asset_category, 'serial_no_series': 'ABC.###'}) + + pr = make_purchase_receipt(item_code=asset_item, qty=3) + asset = frappe.db.get_value('Asset', {'purchase_receipt': pr.name}, 'name') + asset_movement = frappe.db.get_value('Asset Movement', {'reference_name': pr.name}, 'name') + serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') + + self.assertEquals(len(serial_nos), 3) + pr.cancel() + serial_nos = frappe.get_all('Serial No', {'asset': asset}, 'name') or [] + self.assertEquals(len(serial_nos), 0) + frappe.db.sql("delete from `tabAsset Category`") + frappe.db.sql("delete from `tabAsset`") def get_gl_entries(voucher_type, voucher_no): return frappe.db.sql("""select account, debit, credit from `tabGL Entry` where voucher_type=%s and voucher_no=%s order by account desc""", (voucher_type, voucher_no), as_dict=1) - def make_purchase_receipt(**args): frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1) pr = frappe.new_doc("Purchase Receipt") diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index 754bd71879..a7b0a03c0a 100755 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -1577,6 +1577,70 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_fixed_asset", + "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": "Is Fixed Asset", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_fixed_asset", + "fieldname": "asset", + "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": "Asset", + "length": 0, + "no_copy": 1, + "options": "Asset", + "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_on_submit": 0, @@ -2395,7 +2459,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-02-22 15:15:38.793425", + "modified": "2018-04-23 14:07:48.438379", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index b37713be96..fa4fa694c1 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -41,6 +41,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -69,6 +70,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -100,6 +102,7 @@ "reqd": 1, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -132,6 +135,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -165,6 +169,39 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset", + "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": "Asset", + "length": 0, + "no_copy": 1, + "options": "Asset", + "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 }, { @@ -193,6 +230,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -222,6 +260,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -253,6 +292,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "300px" }, @@ -287,6 +327,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -319,6 +360,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -348,6 +390,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -376,6 +419,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -407,6 +451,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -437,6 +482,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -468,6 +514,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -497,6 +544,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -529,6 +577,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -557,6 +606,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -588,6 +638,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -617,6 +668,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -647,6 +699,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -677,6 +730,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -707,6 +761,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -738,6 +793,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -767,6 +823,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -799,6 +856,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -827,6 +885,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -860,6 +919,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -891,6 +951,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -921,6 +982,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -952,6 +1014,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -981,6 +1044,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1009,6 +1073,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -1042,6 +1107,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "150px" }, @@ -1074,6 +1140,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "150px" }, @@ -1103,6 +1170,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -1135,6 +1203,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "150px" }, @@ -1167,6 +1236,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "150px" }, @@ -1197,6 +1267,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1226,6 +1297,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1256,6 +1328,7 @@ "reqd": 1, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -1270,7 +1343,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-05-15 18:22:23.685286", + "modified": "2018-04-19 20:25:52.066995", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", @@ -1278,7 +1351,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1298,7 +1370,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -1318,7 +1389,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 98f15a831a..17bf1bb1c3 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe +from frappe.model.naming import make_autoname from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate from frappe import _, ValidationError @@ -276,24 +277,31 @@ def allow_serial_nos_with_different_item(sle_serial_no, sle): def update_serial_nos(sle, item_det): if sle.is_cancelled == "No" and not sle.serial_no and sle.actual_qty > 0 \ and item_det.has_serial_no == 1 and item_det.serial_no_series: - from frappe.model.naming import make_autoname - serial_nos = [] - for i in range(cint(sle.actual_qty)): - serial_nos.append(make_autoname(item_det.serial_no_series, "Serial No")) - frappe.db.set(sle, "serial_no", "\n".join(serial_nos)) + serial_nos = get_auto_serial_nos(item_det.serial_no_series, sle.actual_qty) + frappe.db.set(sle, "serial_no", serial_nos) validate_serial_no(sle, item_det) if sle.serial_no: - serial_nos = get_serial_nos(sle.serial_no) - for serial_no in serial_nos: - if frappe.db.exists("Serial No", serial_no): - sr = frappe.get_doc("Serial No", serial_no) - sr.via_stock_ledger = True - sr.item_code = sle.item_code - sr.warehouse = sle.warehouse if sle.actual_qty > 0 else None - sr.save(ignore_permissions=True) - elif sle.actual_qty > 0: - make_serial_no(serial_no, sle) + auto_make_serial_nos(sle) + +def get_auto_serial_nos(serial_no_series, qty): + serial_nos = [] + for i in range(cint(qty)): + serial_nos.append(make_autoname(serial_no_series, "Serial No")) + + return "\n".join(serial_nos) + +def auto_make_serial_nos(args): + serial_nos = get_serial_nos(args.get('serial_no')) + for serial_no in serial_nos: + if frappe.db.exists("Serial No", serial_no): + sr = frappe.get_doc("Serial No", serial_no) + sr.via_stock_ledger = True + sr.item_code = args.get('item_code') + sr.warehouse = args.get('warehouse') if args.get('actual_qty', 0) > 0 else None + sr.save(ignore_permissions=True) + elif args.get('actual_qty', 0) > 0: + make_serial_no(serial_no, args) def get_item_details(item_code): return frappe.db.sql("""select name, has_batch_no, docstatus, @@ -304,20 +312,26 @@ def get_serial_nos(serial_no): return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n') if s.strip()] -def make_serial_no(serial_no, sle): +def make_serial_no(serial_no, args): sr = frappe.new_doc("Serial No") sr.warehouse = None sr.dont_update_if_missing.append("warehouse") sr.flags.ignore_permissions = True sr.serial_no = serial_no - sr.item_code = sle.item_code - sr.company = sle.company - sr.via_stock_ledger = True + sr.item_code = args.get('item_code') + sr.company = args.get('company') + sr.via_stock_ledger = args.get('via_stock_ledger') or True sr.insert() - sr.warehouse = sle.warehouse - sr.save() + if args.get('purchase_document_type'): + sr.purchase_document_type = args.get('purchase_document_type') + sr.purchase_document_no = args.get('purchase_document_no') + + if args.get('warehouse'): + sr.warehouse = args.get('warehouse') + sr.save() + frappe.msgprint(_("Serial No {0} created").format(sr.name)) return sr.name diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 7e456dd3d3..0d03b4d2ec 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -287,6 +287,10 @@ def get_default_income_account(args, item): or frappe.db.get_value("Item Group", item.item_group, "default_income_account")) def get_default_expense_account(args, item): + if item and item.is_fixed_asset: + return frappe.db.get_value("Company", args.company, "fixed_asset_account") + if account: return account + return (item.expense_account or args.expense_account or frappe.db.get_value("Item Group", item.item_group, "default_expense_account")) From c6deb13fb424817b499b725e429ab0481fe7ac97 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 May 2018 15:58:41 +0530 Subject: [PATCH 60/79] Added account fields in the company, fixes in auto creation of the asset from PR --- erpnext/accounts/doctype/account/account.json | 4 +- erpnext/assets/doctype/asset/asset.json | 37 ++- erpnext/assets/doctype/asset/asset.py | 14 +- erpnext/assets/doctype/location/__init__.py | 0 erpnext/assets/doctype/location/location.js | 8 + erpnext/assets/doctype/location/location.json | 213 ++++++++++++++++++ erpnext/assets/doctype/location/location.py | 10 + .../assets/doctype/location/test_location.js | 23 ++ .../assets/doctype/location/test_location.py | 10 + erpnext/config/assets.py | 5 + erpnext/controllers/buying_controller.py | 42 ++-- erpnext/setup/doctype/company/company.js | 11 +- erpnext/setup/doctype/company/company.json | 98 +++++++- erpnext/stock/doctype/item/item.js | 19 +- erpnext/stock/doctype/item/item.json | 36 ++- erpnext/stock/doctype/item/item.py | 9 + .../purchase_receipt_item.json | 35 ++- erpnext/stock/doctype/serial_no/serial_no.py | 1 + erpnext/stock/get_item_details.py | 4 - 19 files changed, 525 insertions(+), 54 deletions(-) create mode 100644 erpnext/assets/doctype/location/__init__.py create mode 100644 erpnext/assets/doctype/location/location.js create mode 100644 erpnext/assets/doctype/location/location.json create mode 100644 erpnext/assets/doctype/location/location.py create mode 100644 erpnext/assets/doctype/location/test_location.js create mode 100644 erpnext/assets/doctype/location/test_location.py diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json index 668164ca64..de28a59f8c 100644 --- a/erpnext/accounts/doctype/account/account.json +++ b/erpnext/accounts/doctype/account/account.json @@ -412,7 +412,7 @@ "no_copy": 0, "oldfieldname": "account_type", "oldfieldtype": "Select", - "options": "\nAccumulated Depreciation\nBank\nCash\nChargeable\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nTax\nTemporary", + "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nTax\nTemporary", "permlevel": 0, "print_hide": 0, "print_hide_if_no_value": 0, @@ -625,7 +625,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-02 11:00:34.108490", + "modified": "2018-05-07 15:37:25.962506", "modified_by": "Administrator", "module": "Accounts", "name": "Account", diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 63b9167cc6..536b4dc522 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -3,7 +3,7 @@ "allow_guest_to_view": 0, "allow_import": 1, "allow_rename": 1, - "autoname": "field:asset_name", + "autoname": "naming_series:", "beta": 0, "creation": "2016-03-01 17:01:27.920130", "custom": 0, @@ -12,6 +12,39 @@ "document_type": "Document", "editable_grid": 0, "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "AST", + "fieldname": "naming_series", + "fieldtype": "Select", + "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": "Naming Series", + "length": 0, + "no_copy": 0, + "options": "AST\nAT", + "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_on_submit": 0, @@ -1354,7 +1387,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-19 20:03:13.669957", + "modified": "2018-05-07 15:25:06.456992", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a9cc924b8f..adcc986f66 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -10,7 +10,6 @@ from frappe.model.document import Document from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_depreciation_accounts -from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos class Asset(Document): def validate(self): @@ -34,9 +33,6 @@ class Asset(Document): self.delete_depreciation_entries() self.set_status() - def on_update(self): - self.update_serial_nos() - def validate_item(self): item = frappe.db.get_value("Item", self.item_code, ["is_fixed_asset", "is_stock_item", "disabled"], as_dict=1) @@ -258,12 +254,6 @@ class Asset(Document): status = "Cancelled" return status - def update_serial_nos(self): - if self.serial_no: - serial_nos = get_serial_nos(self.serial_no) - frappe.db.sql(""" update `tabSerial No` set asset = '%s' where - name in(%s)"""%(self.name, ','.join(['%s'] * len(serial_nos))), tuple(serial_nos)) - def update_stock_movement(self): asset_movement = frappe.db.get_value('Asset Movement', {'asset': self.name, 'reference_name': self.purchase_receipt, 'docstatus': 0}, 'name') @@ -282,6 +272,10 @@ def update_maintenance_status(): if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}): asset.set_status('Out of Order') +def get_asset_naming_series(): + meta = frappe.get_meta('Asset') + return meta.get_field("naming_series").options + @frappe.whitelist() def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, posting_date): pi = frappe.new_doc("Purchase Invoice") diff --git a/erpnext/assets/doctype/location/__init__.py b/erpnext/assets/doctype/location/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/location/location.js b/erpnext/assets/doctype/location/location.js new file mode 100644 index 0000000000..c3783dfae7 --- /dev/null +++ b/erpnext/assets/doctype/location/location.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Location', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/assets/doctype/location/location.json b/erpnext/assets/doctype/location/location.json new file mode 100644 index 0000000000..13ef66224d --- /dev/null +++ b/erpnext/assets/doctype/location/location.json @@ -0,0 +1,213 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "field:location_name", + "beta": 0, + "creation": "2018-05-07 12:49:22.595974", + "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, + "fieldname": "location_name", + "fieldtype": "Data", + "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": "Location Name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_group", + "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": "Is Group", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "parent_location", + "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": "Parent Location", + "length": 0, + "no_copy": 0, + "options": "Location", + "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": 1, + "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": 0, + "max_attachments": 0, + "modified": "2018-05-07 12:54:49.527782", + "modified_by": "Administrator", + "module": "Assets", + "name": "Location", + "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": 0, + "write": 1 + }, + { + "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": "Stock User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "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": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + }, + { + "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": "Stock Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "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/assets/doctype/location/location.py b/erpnext/assets/doctype/location/location.py new file mode 100644 index 0000000000..2483ca1b9d --- /dev/null +++ b/erpnext/assets/doctype/location/location.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class Location(Document): + pass diff --git a/erpnext/assets/doctype/location/test_location.js b/erpnext/assets/doctype/location/test_location.js new file mode 100644 index 0000000000..236b5c65b0 --- /dev/null +++ b/erpnext/assets/doctype/location/test_location.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Location", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Location + () => frappe.tests.make('Location', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/location/test_location.py b/erpnext/assets/doctype/location/test_location.py new file mode 100644 index 0000000000..9a46fd93ef --- /dev/null +++ b/erpnext/assets/doctype/location/test_location.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestLocation(unittest.TestCase): + pass diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py index 73c1aee87a..be522469e6 100644 --- a/erpnext/config/assets.py +++ b/erpnext/config/assets.py @@ -14,6 +14,11 @@ def get_data(): "type": "doctype", "name": "Asset Category", }, + { + "type": "doctype", + "label": _("Asset Location"), + "name": "Location", + }, { "type": "doctype", "name": "Asset Settings", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index c340901e3b..798eee2b9d 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -441,9 +441,10 @@ class BuyingController(StockController): return if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: - if self.doctype == 'Purchase Receipt': - self.process_fixed_asset() - self.update_fixed_asset() + field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' + + self.process_fixed_asset() + self.update_fixed_asset(field) update_last_purchase_rate(self, is_submit = 1) @@ -453,12 +454,13 @@ class BuyingController(StockController): update_last_purchase_rate(self, is_submit = 0) if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: - if self.doctype == 'Purchase Receipt': - self.delete_linked_asset() - self.update_fixed_asset() + field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' + + self.delete_linked_asset(field) + self.update_fixed_asset(field) def process_fixed_asset(self): - if not self.doctype in ['Purchase Receipt', 'Purchase Invoice']: + if self.doctype == 'Purchase Invoice' and not self.update_stock: return asset_items = [d.item_code for d in self.items if d.is_fixed_asset] @@ -471,6 +473,9 @@ class BuyingController(StockController): for d in self.items: if d.is_fixed_asset: item_data = items_data.get(d.item_code) + if not d.asset: + asset = self.make_asset(d) + d.db_set('asset', asset) if item_data.get('has_serial_no'): # If item has serial no @@ -488,14 +493,11 @@ class BuyingController(StockController): 'company': self.company, 'actual_qty': d.qty, 'purchase_document_type': self.doctype, - 'purchase_document_no': self.name + 'purchase_document_no': self.name, + 'asset': d.asset }) d.db_set('serial_no', serial_nos) - if not d.asset: - asset = self.make_asset(d) - d.db_set('asset', asset) - if d.asset: self.make_asset_movement(d) @@ -503,9 +505,9 @@ class BuyingController(StockController): asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, - 'asset_name': '{0} - {1}'.format(self.name, row.item_code), + 'asset_name': row.item_name, + 'naming_series': frappe.db.get_value('Item', row.item_code, 'asset_naming_series') or 'AST', 'warehouse': row.warehouse, - 'serial_no': row.serial_no, 'company': self.company, 'purchase_date': self.posting_date, 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, @@ -535,9 +537,7 @@ class BuyingController(StockController): return asset_movement.name - def update_fixed_asset(self): - field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' - + def update_fixed_asset(self, field): for d in self.get("items"): if d.is_fixed_asset and d.asset: asset = frappe.get_doc("Asset", d.asset) @@ -555,13 +555,11 @@ class BuyingController(StockController): asset.save() - def delete_linked_asset(self): - if not self.doctype in ['Purchase Receipt', 'Purchase Invoice']: - return - - if self.doctype == 'Purchase Invoice' and self.get('update_stock'): + def delete_linked_asset(self, field): + if self.doctype == 'Purchase Invoice' and not self.get('update_stock'): return + frappe.db.sql("delete from `tabAsset` where {0} = %s and docstatus = 0".format(field), self.name) frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s and docstatus = 0", self.name) frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index d4095772cd..5ca2885547 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -5,14 +5,6 @@ frappe.provide("erpnext.company"); frappe.ui.form.on("Company", { setup: function(frm) { - frm.fields_dict.fixed_asset_account.get_query = function() { - return { - filters: { - account_type: "Fixed Asset", - company: frm.doc.name - } - } - } erpnext.company.setup_queries(frm); }, @@ -215,6 +207,9 @@ erpnext.company.setup_queries = function(frm) { ["round_off_cost_center", {}], ["depreciation_cost_center", {}], ["default_employee_advance_account", {"root_type": "Asset"}], + ["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}], + ["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}], + ["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}] ], function(i, v) { erpnext.company.set_custom_query(frm, v); }); diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json index d236dda873..07051eeba8 100644 --- a/erpnext/setup/doctype/company/company.json +++ b/erpnext/setup/doctype/company/company.json @@ -1785,6 +1785,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "expenses_included_in_asset_valuation", + "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": "Expenses Included In Asset Valuation", + "length": 0, + "no_copy": 0, + "options": "Account", + "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_on_submit": 0, @@ -1879,6 +1911,70 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "capital_work_in_progress_account", + "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": "Capital Work In Progress Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_received_but_not_billed", + "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": "Asset Received But Not Billed", + "length": 0, + "no_copy": 0, + "options": "Account", + "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_on_submit": 0, @@ -2400,7 +2496,7 @@ "istable": 0, "max_attachments": 0, "menu_index": 0, - "modified": "2018-05-05 13:08:07.351655", + "modified": "2018-05-07 15:35:06.736602", "modified_by": "Administrator", "module": "Setup", "name": "Company", diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js index f98cbb6268..fbf0deddc7 100644 --- a/erpnext/stock/doctype/item/item.js +++ b/erpnext/stock/doctype/item/item.js @@ -19,7 +19,9 @@ frappe.ui.form.on("Item", { // should never check Private frm.fields_dict["website_image"].df.is_private = 0; - + if (frm.doc.is_fixed_asset) { + frm.trigger("set_asset_naming_series"); + } }, refresh: function(frm) { @@ -124,7 +126,20 @@ frappe.ui.form.on("Item", { }, is_fixed_asset: function(frm) { - frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); + frm.call({ + method: "set_asset_naming_series", + doc: frm.doc, + callback: function() { + frm.set_value("is_stock_item", frm.doc.is_fixed_asset ? 0 : 1); + frm.trigger("set_asset_naming_series"); + } + }) + }, + + set_asset_naming_series: function(frm) { + if (frm.doc.__onload && frm.doc.__onload.asset_naming_series) { + frm.set_df_property("asset_naming_series", "options", frm.doc.__onload.asset_naming_series); + } }, page_name: frappe.utils.warn_page_name_change, diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json index 251717e127..b8621719c4 100644 --- a/erpnext/stock/doctype/item/item.json +++ b/erpnext/stock/doctype/item/item.json @@ -395,7 +395,7 @@ "search_index": 0, "set_only_once": 0, "translatable": 0, - "unique": 0 + "unique": 0 }, { "allow_bulk_edit": 0, @@ -559,6 +559,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_fixed_asset", + "fieldname": "asset_naming_series", + "fieldtype": "Select", + "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": "Asset Naming Series", + "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_on_submit": 0, @@ -3717,7 +3749,7 @@ "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2018-04-30 12:21:48.715529", + "modified": "2018-05-07 14:54:24.479267", "modified_by": "Administrator", "module": "Stock", "name": "Item", diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py index fafdaab51f..94b907b914 100644 --- a/erpnext/stock/doctype/item/item.py +++ b/erpnext/stock/doctype/item/item.py @@ -16,6 +16,7 @@ from frappe.utils import (cint, cstr, flt, formatdate, get_timestamp, getdate, from frappe.utils.html_utils import clean_html from frappe.website.doctype.website_slideshow.website_slideshow import \ get_slideshow + from frappe.website.render import clear_cache from frappe.website.website_generator import WebsiteGenerator @@ -42,10 +43,18 @@ class Item(WebsiteGenerator): super(Item, self).onload() self.set_onload('stock_exists', self.stock_ledger_created()) + self.set_asset_naming_series() if self.is_fixed_asset: asset = frappe.db.get_all("Asset", filters={"item_code": self.name, "docstatus": 1}, limit=1) self.set_onload("asset_exists", True if asset else False) + def set_asset_naming_series(self): + if not hasattr(self, '_asset_naming_series'): + from erpnext.assets.doctype.asset.asset import get_asset_naming_series + self._asset_naming_series = get_asset_naming_series() + + self.set_onload('asset_naming_series', self._asset_naming_series) + def autoname(self): if frappe.db.get_default("item_naming_by") == "Naming Series": if self.variant_of: diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json index a7b0a03c0a..87c9a757bf 100755 --- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json +++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json @@ -1641,6 +1641,39 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_fixed_asset", + "fieldname": "asset_location", + "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": "Asset Location", + "length": 0, + "no_copy": 0, + "options": "Location", + "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_on_submit": 0, @@ -2459,7 +2492,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-04-23 14:07:48.438379", + "modified": "2018-05-07 13:42:05.061386", "modified_by": "Administrator", "module": "Stock", "name": "Purchase Receipt Item", diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 17bf1bb1c3..6ee679ab82 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -322,6 +322,7 @@ def make_serial_no(serial_no, args): sr.item_code = args.get('item_code') sr.company = args.get('company') sr.via_stock_ledger = args.get('via_stock_ledger') or True + sr.asset = args.get('asset') sr.insert() if args.get('purchase_document_type'): diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py index 0d03b4d2ec..7e456dd3d3 100644 --- a/erpnext/stock/get_item_details.py +++ b/erpnext/stock/get_item_details.py @@ -287,10 +287,6 @@ def get_default_income_account(args, item): or frappe.db.get_value("Item Group", item.item_group, "default_income_account")) def get_default_expense_account(args, item): - if item and item.is_fixed_asset: - return frappe.db.get_value("Company", args.company, "fixed_asset_account") - if account: return account - return (item.expense_account or args.expense_account or frappe.db.get_value("Item Group", item.item_group, "default_expense_account")) From af0599541867c130e51ac14dc13f1e970d296267 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 May 2018 18:46:53 +0530 Subject: [PATCH 61/79] Asset accounting --- .../purchase_invoice/purchase_invoice.py | 43 +++++++++++++++++++ erpnext/controllers/buying_controller.py | 24 +++++++---- .../purchase_receipt/purchase_receipt.py | 31 +++++++++++++ erpnext/stock/doctype/serial_no/serial_no.py | 2 +- 4 files changed, 91 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index d2cc4eea9f..fea126945c 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -422,6 +422,49 @@ 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 item.is_fixed_asset and not self.update_stock: + asset_accounts = self.get_company_default(["asset_received_but_not_billed", + "expenses_included_in_asset_valuation", "capital_work_in_progress_account"]) + + asset_amount = flt(item.net_amount) + flt(item.item_tax_amount/self.conversion_rate) + base_asset_amount = flt(item.base_net_amount + item.item_tax_amount) + + if not self.update_stock: + asset_rbnb_currency = get_account_currency(asset_accounts[0]) + + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[0], + "against": self.supplier, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": base_asset_amount, + "debit_in_account_currency": (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + })) + else: + cwip_account_currency = get_account_currency(asset_accounts[2]) + + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[2], + "against": self.supplier, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": base_asset_amount, + "debit_in_account_currency": (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + })) + + asset_eiiav_currency = get_account_currency(asset_accounts[0]) + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[1], + "against": self.supplier, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "cost_center": item.cost_center, + "credit": item.item_tax_amount, + "credit_in_account_currency": (item.item_tax_amount + if asset_eiiav_currency == self.company_currency else + item.item_tax_amount / self.conversion_rate) + })) + else: gl_entries.append( self.get_gl_dict({ diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 798eee2b9d..a1bc6d7aa7 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -79,7 +79,7 @@ class BuyingController(StockController): break def validate_stock_or_nonstock_items(self): - if self.meta.get_field("taxes") and not self.get_stock_items(): + if self.meta.get_field("taxes") and not self.get_stock_items() and not self.get_asset_items(): tax_for_valuation = [d for d in self.get("taxes") if d.category in ["Valuation", "Valuation and Total"]] @@ -88,6 +88,9 @@ class BuyingController(StockController): d.category = 'Total' msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) + def get_asset_items(self): + return [d.item_code for d in self.items if d.is_fixed_asset] + def set_landed_cost_voucher_amount(self): for d in self.get("items"): lc_voucher_data = frappe.db.sql("""select sum(applicable_charges), cost_center @@ -112,7 +115,7 @@ class BuyingController(StockController): TODO: rename item_tax_amount to valuation_tax_amount """ - stock_items = self.get_stock_items() + stock_items = self.get_stock_items() + self.get_asset_items() stock_items_qty, stock_items_amount = 0, 0 last_stock_item_idx = 1 @@ -456,14 +459,14 @@ class BuyingController(StockController): if self.doctype in ['Purchase Receipt', 'Purchase Invoice']: field = 'purchase_invoice' if self.doctype == 'Purchase Invoice' else 'purchase_receipt' - self.delete_linked_asset(field) - self.update_fixed_asset(field) + self.delete_linked_asset() + self.update_fixed_asset(field, delete_asset=True) def process_fixed_asset(self): if self.doctype == 'Purchase Invoice' and not self.update_stock: return - asset_items = [d.item_code for d in self.items if d.is_fixed_asset] + asset_items = self.get_asset_items() if asset_items: self.make_serial_nos_for_asset(asset_items) @@ -537,10 +540,16 @@ class BuyingController(StockController): return asset_movement.name - def update_fixed_asset(self, field): + def update_fixed_asset(self, field, delete_asset = False): for d in self.get("items"): if d.is_fixed_asset and d.asset: asset = frappe.get_doc("Asset", d.asset) + + if delete_asset and asset.docstatus == 0: + frappe.delete_doc("Asset", asset.name) + d.db_set('asset', None) + continue + if self.docstatus in [0, 1] and not asset.get(field): asset.set(field, self.name) asset.purchase_date = self.posting_date @@ -555,11 +564,10 @@ class BuyingController(StockController): asset.save() - def delete_linked_asset(self, field): + def delete_linked_asset(self): if self.doctype == 'Purchase Invoice' and not self.get('update_stock'): return - frappe.db.sql("delete from `tabAsset` where {0} = %s and docstatus = 0".format(field), self.name) frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s and docstatus = 0", self.name) frappe.db.sql("delete from `tabSerial No` where purchase_document_no=%s", self.name) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index c7083be286..984bf33b4b 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -253,6 +253,37 @@ class PurchaseReceipt(BuyingController): d.rejected_warehouse not in warehouse_with_no_account: warehouse_with_no_account.append(d.warehouse) + elif d.is_fixed_asset: + asset_accounts = self.get_company_default(["capital_work_in_progress_account", + "asset_received_but_not_billed"]) + + # CWIP entry + asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) + base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) + + cwip_account_currency = get_account_currency(asset_accounts[0]) + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[0], + "against": asset_accounts[1], + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "debit": base_asset_amount, + "debit_in_account_currency": (base_asset_amount + if cwip_account_currency == self.company_currency else asset_amount) + })) + + # Asset received but not billed + asset_rbnb_currency = get_account_currency(asset_accounts[1]) + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[1], + "against": asset_accounts[0], + "cost_center": d.cost_center, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "credit": base_asset_amount, + "credit_in_account_currency": (base_asset_amount + if asset_rbnb_currency == self.company_currency else asset_amount) + })) + # Cost center-wise amount breakup for other charges included for valuation valuation_tax = {} for tax in self.get("taxes"): diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py index 6ee679ab82..333963bd66 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.py +++ b/erpnext/stock/doctype/serial_no/serial_no.py @@ -323,12 +323,12 @@ def make_serial_no(serial_no, args): sr.company = args.get('company') sr.via_stock_ledger = args.get('via_stock_ledger') or True sr.asset = args.get('asset') - sr.insert() if args.get('purchase_document_type'): sr.purchase_document_type = args.get('purchase_document_type') sr.purchase_document_no = args.get('purchase_document_no') + sr.insert() if args.get('warehouse'): sr.warehouse = args.get('warehouse') sr.save() From 255cd44abae989f0223721e71c96e6b8a3b04ff9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 7 May 2018 19:22:15 +0530 Subject: [PATCH 62/79] Treeview for Location --- erpnext/assets/doctype/location/location.json | 99 ++++++++++++++++++- erpnext/assets/doctype/location/location.py | 41 +++++++- .../assets/doctype/location/location_tree.js | 28 ++++++ 3 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 erpnext/assets/doctype/location/location_tree.js diff --git a/erpnext/assets/doctype/location/location.json b/erpnext/assets/doctype/location/location.json index 13ef66224d..d83fdf3667 100644 --- a/erpnext/assets/doctype/location/location.json +++ b/erpnext/assets/doctype/location/location.json @@ -83,7 +83,7 @@ "columns": 0, "fieldname": "parent_location", "fieldtype": "Link", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -98,7 +98,7 @@ "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, @@ -106,6 +106,99 @@ "set_only_once": 0, "translatable": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "lft", + "fieldtype": "Int", + "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": "lft", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "rgt", + "fieldtype": "Int", + "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": "rgt", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "old_parent", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Old Parent", + "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 } ], "has_web_view": 0, @@ -118,7 +211,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-07 12:54:49.527782", + "modified": "2018-05-07 19:21:06.051414", "modified_by": "Administrator", "module": "Assets", "name": "Location", diff --git a/erpnext/assets/doctype/location/location.py b/erpnext/assets/doctype/location/location.py index 2483ca1b9d..9d05720a0d 100644 --- a/erpnext/assets/doctype/location/location.py +++ b/erpnext/assets/doctype/location/location.py @@ -5,6 +5,43 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document +from frappe.utils.nestedset import NestedSet -class Location(Document): - pass +class Location(NestedSet): + def on_update(self): + self.update_nsm_model() + + def on_trash(self): + self.update_nsm_model() + + def update_nsm_model(self): + frappe.utils.nestedset.update_nsm(self) + +@frappe.whitelist() +def get_children(doctype, parent=None, location=None, is_root=False): + if parent == None or parent == "All Locations": + parent = "" + + return frappe.db.sql(""" + select + name as value, + is_group as expandable + from + `tab{doctype}` comp + where + ifnull(parent_location, "")="{parent}" + """.format( + doctype = frappe.db.escape(doctype), + parent=frappe.db.escape(parent) + ), as_dict=1) + +@frappe.whitelist() +def add_node(): + from frappe.desk.treeview import make_tree_args + args = frappe.form_dict + args = make_tree_args(**args) + + if args.parent_location == 'All Locations': + args.parent_location = None + + frappe.get_doc(args).insert() \ No newline at end of file diff --git a/erpnext/assets/doctype/location/location_tree.js b/erpnext/assets/doctype/location/location_tree.js new file mode 100644 index 0000000000..523dd63ced --- /dev/null +++ b/erpnext/assets/doctype/location/location_tree.js @@ -0,0 +1,28 @@ +frappe.treeview_settings["Location"] = { + ignore_fields:["parent_location"], + get_tree_nodes: 'erpnext.assets.doctype.location.location.get_children', + add_tree_node: 'erpnext.assets.doctype.location.location.add_node', + filters: [ + { + fieldname: "location", + fieldtype:"Link", + options: "Location", + label: __("Location") + }, + ], + breadcrumb: "Assets", + root_label: "All Locations", + get_tree_root: false, + menu_items: [ + { + label: __("New Location"), + action: function() { + frappe.new_doc("Location", true); + }, + condition: 'frappe.boot.user.can_create.indexOf("Location") !== -1' + } + ], + onload: function(treeview) { + treeview.make_tree(); + } +}; \ No newline at end of file From f41e1ed191bf383356169549c8571e9ced8251e1 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 8 May 2018 12:38:28 +0530 Subject: [PATCH 63/79] Added default asset accounts in COA, CWIP account in asset category --- .../verified/standard_chart_of_accounts.py | 11 +++++- ...d_chart_of_accounts_with_account_number.py | 12 ++++++ .../purchase_invoice/purchase_invoice.py | 34 ++++++++--------- .../doctype/asset_category/asset_category.js | 11 ++++++ .../doctype/asset_category/asset_category.py | 7 ++++ .../asset_category_account.json | 38 ++++++++++++++++++- erpnext/setup/doctype/company/company.py | 3 ++ .../purchase_receipt/purchase_receipt.py | 7 +++- 8 files changed, 102 insertions(+), 21 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py index 5452040fb6..6e1637165a 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts.py @@ -63,7 +63,10 @@ def get(): }, _("Accumulated Depreciation"): { "account_type": "Accumulated Depreciation" - } + }, + _("CWIP Account"): { + "account_type": "Capital Work in Progress", + } }, _("Investments"): { "is_group": 1 @@ -81,6 +84,9 @@ def get(): _("Cost of Goods Sold"): { "account_type": "Cost of Goods Sold" }, + _("Expenses Included In Asset Valuation"): { + "account_type": "Expenses Included In Asset Valuation" + }, _("Expenses Included In Valuation"): { "account_type": "Expenses Included In Valuation" }, @@ -146,6 +152,9 @@ def get(): _("Stock Received But Not Billed"): { "account_type": "Stock Received But Not Billed" }, + _("Asset Received But Not Billed"): { + "account_type": "Asset Received But Not Billed" + } }, _("Duties and Taxes"): { "account_type": "Tax", diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py index bad84533a5..5ed3e45086 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/standard_chart_of_accounts_with_account_number.py @@ -85,6 +85,10 @@ def get(): "account_type": "Accumulated Depreciation", "account_number": "1780" }, + _("CWIP Account"): { + "account_type": "Capital Work in Progress", + "account_number": "1790" + }, "account_number": "1700" }, _("Investments"): { @@ -108,6 +112,10 @@ def get(): "account_type": "Cost of Goods Sold", "account_number": "5111" }, + _("Expenses Included In Asset Valuation"): { + "account_type": "Expenses Included In Asset Valuation", + "account_number": "5112" + }, _("Expenses Included In Valuation"): { "account_type": "Expenses Included In Valuation", "account_number": "5118" @@ -228,6 +236,10 @@ def get(): "account_type": "Stock Received But Not Billed", "account_number": "2210" }, + _("Asset Received But Not Billed"): { + "account_type": "Asset Received But Not Billed", + "account_number": "2211" + }, "account_number": "2200" }, _("Duties and Taxes"): { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fea126945c..fd0054ace2 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -19,6 +19,7 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente from frappe.model.mapper import get_mapped_doc from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_invoice,\ unlink_inter_company_invoice +from erpnext.assets.doctype.asset_category.asset_category import get_cwip_account form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -423,7 +424,7 @@ class PurchaseInvoice(BuyingController): "credit": flt(item.rm_supp_cost) }, warehouse_account[self.supplier_warehouse]["account_currency"])) - elif item.is_fixed_asset and not self.update_stock: + elif item.is_fixed_asset: asset_accounts = self.get_company_default(["asset_received_but_not_billed", "expenses_included_in_asset_valuation", "capital_work_in_progress_account"]) @@ -432,7 +433,6 @@ class PurchaseInvoice(BuyingController): if not self.update_stock: asset_rbnb_currency = get_account_currency(asset_accounts[0]) - gl_entries.append(self.get_gl_dict({ "account": asset_accounts[0], "against": self.supplier, @@ -442,10 +442,10 @@ class PurchaseInvoice(BuyingController): if asset_rbnb_currency == self.company_currency else asset_amount) })) else: - cwip_account_currency = get_account_currency(asset_accounts[2]) - + cwip_account = get_cwip_account(item.item_code, self.company) or asset_accounts[2] + cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ - "account": asset_accounts[2], + "account": cwip_account, "against": self.supplier, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "debit": base_asset_amount, @@ -453,18 +453,18 @@ class PurchaseInvoice(BuyingController): if cwip_account_currency == self.company_currency else asset_amount) })) - asset_eiiav_currency = get_account_currency(asset_accounts[0]) - gl_entries.append(self.get_gl_dict({ - "account": asset_accounts[1], - "against": self.supplier, - "remarks": self.get("remarks") or _("Accounting Entry for Asset"), - "cost_center": item.cost_center, - "credit": item.item_tax_amount, - "credit_in_account_currency": (item.item_tax_amount - if asset_eiiav_currency == self.company_currency else - item.item_tax_amount / self.conversion_rate) - })) - + if item.item_tax_amount: + asset_eiiav_currency = get_account_currency(asset_accounts[0]) + gl_entries.append(self.get_gl_dict({ + "account": asset_accounts[1], + "against": self.supplier, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "cost_center": item.cost_center, + "credit": item.item_tax_amount, + "credit_in_account_currency": (item.item_tax_amount + if asset_eiiav_currency == self.company_currency else + item.item_tax_amount / self.conversion_rate) + })) else: gl_entries.append( self.get_gl_dict({ diff --git a/erpnext/assets/doctype/asset_category/asset_category.js b/erpnext/assets/doctype/asset_category/asset_category.js index aafe8a69a0..6f0c428c4d 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.js +++ b/erpnext/assets/doctype/asset_category/asset_category.js @@ -40,5 +40,16 @@ frappe.ui.form.on('Asset Category', { }; }); + frm.set_query('capital_work_in_progress_account', 'accounts', function(doc, cdt, cdn) { + var d = locals[cdt][cdn]; + return { + "filters": { + "account_type": "Capital Work in Progress", + "is_group": 0, + "company": d.company_name + } + }; + }); + } }); \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 542bd12861..4ffd20b159 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -13,3 +13,10 @@ class AssetCategory(Document): for field in ("total_number_of_depreciations", "frequency_of_depreciation"): if cint(self.get(field))<1: frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) + +def get_cwip_account(item_code, company): + asset_category = frappe.db.get_value('Item', item_code, 'asset_category') + cwip_account = frappe.db.get_value('Asset Category Account', + {'parent': asset_category, 'company_name': company}, 'capital_work_in_progress_account') + + return cwip_account or None \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category_account/asset_category_account.json b/erpnext/assets/doctype/asset_category_account/asset_category_account.json index 679cc5271d..3cace59a4c 100644 --- a/erpnext/assets/doctype/asset_category_account/asset_category_account.json +++ b/erpnext/assets/doctype/asset_category_account/asset_category_account.json @@ -41,6 +41,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -72,6 +73,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -103,6 +105,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -134,6 +137,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "capital_work_in_progress_account", + "fieldtype": "Link", + "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": "Capital Work In Progress Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "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 } ], @@ -147,7 +183,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-11-28 16:54:12.252271", + "modified": "2018-05-08 11:41:09.678234", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category Account", diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py index 1b68b8a6b6..9040bb7c8f 100644 --- a/erpnext/setup/doctype/company/company.py +++ b/erpnext/setup/doctype/company/company.py @@ -168,6 +168,9 @@ class Company(NestedSet): self._set_default_account("round_off_account", "Round Off") self._set_default_account("accumulated_depreciation_account", "Accumulated Depreciation") self._set_default_account("depreciation_expense_account", "Depreciation") + self._set_default_account("capital_work_in_progress_account", "Capital Work in Progress") + self._set_default_account("asset_received_but_not_billed", "Asset Received But Not Billed") + self._set_default_account("expenses_included_in_asset_valuation", "Expenses Included In Asset Valuation") if self.enable_perpetual_inventory: self._set_default_account("stock_received_but_not_billed", "Stock Received But Not Billed") diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index 984bf33b4b..ab86c762cd 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -13,6 +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_category.asset_category import get_cwip_account form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -258,12 +259,14 @@ class PurchaseReceipt(BuyingController): "asset_received_but_not_billed"]) # CWIP entry + cwip_account = get_cwip_account(d.item_code, self.company) or asset_accounts[0] + asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) - cwip_account_currency = get_account_currency(asset_accounts[0]) + cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ - "account": asset_accounts[0], + "account": cwip_account, "against": asset_accounts[1], "cost_center": d.cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), From f2684ae83a8748ca796a4ec5b2407e1a742ef127 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 8 May 2018 13:22:22 +0530 Subject: [PATCH 64/79] Commonify get_asset_category_account and get_fixed_asset_account method --- .../purchase_invoice/purchase_invoice.js | 3 ++- .../purchase_invoice/purchase_invoice.py | 20 ++++--------------- erpnext/assets/doctype/asset/asset.py | 4 ++-- .../doctype/asset_category/asset_category.py | 17 +++++++++++----- erpnext/controllers/buying_controller.py | 6 +++++- .../purchase_receipt/purchase_receipt.py | 5 +++-- 6 files changed, 28 insertions(+), 27 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js index e7fdd64a6d..d2c41938fe 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js @@ -205,9 +205,10 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({ var row = locals[cdt][cdn]; if(row.asset) { frappe.call({ - method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.get_fixed_asset_account", + method: "erpnext.assets.doctype.asset_category.asset_category.get_asset_category_account", args: { "asset": row.asset, + "fieldname": "fixed_asset_account", "account": row.expense_account }, callback: function(r, rt) { diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index fd0054ace2..3e375e5051 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -19,7 +19,7 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente from frappe.model.mapper import get_mapped_doc from erpnext.accounts.doctype.sales_invoice.sales_invoice import validate_inter_company_party, update_linked_invoice,\ unlink_inter_company_invoice -from erpnext.assets.doctype.asset_category.asset_category import get_cwip_account +from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -442,7 +442,9 @@ class PurchaseInvoice(BuyingController): if asset_rbnb_currency == self.company_currency else asset_amount) })) else: - cwip_account = get_cwip_account(item.item_code, self.company) or asset_accounts[2] + cwip_account = get_asset_category_account(item.asset, + 'capital_work_in_progress_account') or asset_accounts[2] + cwip_account_currency = get_account_currency(cwip_account) gl_entries.append(self.get_gl_dict({ "account": cwip_account, @@ -734,20 +736,6 @@ def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc return make_return_doc("Purchase Invoice", source_name, target_doc) -@frappe.whitelist() -def get_fixed_asset_account(asset, account=None): - if account: - if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": - account=None - - if not account: - asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) - - account = frappe.db.get_value("Asset Category Account", - filters={"parent": asset_category, "company_name": company}, fieldname="fixed_asset_account") - - return account - @frappe.whitelist() def make_stock_entry(source_name, target_doc=None): doc = get_mapped_doc("Purchase Invoice", source_name, { diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index adcc986f66..ef6e376eae 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -7,7 +7,7 @@ import frappe from frappe import _ from frappe.utils import flt, add_months, cint, nowdate, getdate, today, date_diff from frappe.model.document import Document -from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import get_fixed_asset_account +from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_depreciation_accounts @@ -287,7 +287,7 @@ def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, post "item_code": item_code, "is_fixed_asset": 1, "asset": asset, - "expense_account": get_fixed_asset_account(asset), + "expense_account": get_asset_category_account(asset, 'fixed_asset_account'), "qty": 1, "price_list_rate": gross_purchase_amount, "rate": gross_purchase_amount diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index 4ffd20b159..d1dd8ed7e2 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -14,9 +14,16 @@ class AssetCategory(Document): if cint(self.get(field))<1: frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) -def get_cwip_account(item_code, company): - asset_category = frappe.db.get_value('Item', item_code, 'asset_category') - cwip_account = frappe.db.get_value('Asset Category Account', - {'parent': asset_category, 'company_name': company}, 'capital_work_in_progress_account') +@frappe.whitelist() +def get_asset_category_account(asset, fieldname, account=None): + if account: + if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": + account=None - return cwip_account or None \ No newline at end of file + if not account: + asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + + account = frappe.db.get_value("Asset Category Account", + filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) + + return account \ No newline at end of file diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index a1bc6d7aa7..80bec0e889 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -505,11 +505,15 @@ class BuyingController(StockController): self.make_asset_movement(d) def make_asset(self, row): + item_data = frappe.db.get_value('Item', + row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) + asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, 'asset_name': row.item_name, - 'naming_series': frappe.db.get_value('Item', row.item_code, 'asset_naming_series') or 'AST', + 'naming_series': item_data.get('asset_naming_series') or 'AST', + 'asset_category': item_data.get('asset_category'), 'warehouse': row.warehouse, 'company': self.company, 'purchase_date': self.posting_date, diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index ab86c762cd..573f7ed8f3 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_category.asset_category import get_cwip_account +from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account form_grid_templates = { "items": "templates/form_grid/item_grid.html" @@ -259,7 +259,8 @@ class PurchaseReceipt(BuyingController): "asset_received_but_not_billed"]) # CWIP entry - cwip_account = get_cwip_account(d.item_code, self.company) or asset_accounts[0] + cwip_account = get_asset_category_account(d.asset, + 'capital_work_in_progress_account') or asset_accounts[0] asset_amount = flt(d.net_amount) + flt(d.item_tax_amount/self.conversion_rate) base_asset_amount = flt(d.base_net_amount + d.item_tax_amount) From 0ea6fe4397051f3d7094dd2059913918f999a3e4 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 8 May 2018 23:31:58 +0530 Subject: [PATCH 65/79] Added new doctype Finance Book Detail in asset, asset category --- erpnext/assets/doctype/asset/asset.js | 4 +- erpnext/assets/doctype/asset/asset.json | 377 ++++++++++++++++-- erpnext/assets/doctype/asset/asset.py | 141 +++---- .../asset_category/asset_category.json | 65 ++- .../doctype/asset_finance_book/__init__.py | 0 .../asset_finance_book.json | 318 +++++++++++++++ .../asset_finance_book/asset_finance_book.py | 10 + .../depreciation_schedule.json | 62 ++- erpnext/controllers/buying_controller.py | 1 + 9 files changed, 859 insertions(+), 119 deletions(-) create mode 100644 erpnext/assets/doctype/asset_finance_book/__init__.py create mode 100644 erpnext/assets/doctype/asset_finance_book/asset_finance_book.json create mode 100644 erpnext/assets/doctype/asset_finance_book/asset_finance_book.py diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index edc1e364f5..b8bcc91d2a 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -131,9 +131,7 @@ frappe.ui.form.on('Asset', { }, callback: function(r, rt) { if(r.message) { - $.each(r.message, function(field, value) { - frm.set_value(field, value); - }) + frm.set_value('finance_books', r.message); } } }) diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 536b4dc522..a47c645359 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -42,7 +42,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -73,7 +72,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -105,7 +103,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -137,7 +134,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -169,7 +165,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -202,7 +197,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -234,7 +228,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -267,7 +260,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -300,7 +292,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -333,7 +324,37 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "custodian", + "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": "Custodian", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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 }, { @@ -364,7 +385,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -395,7 +415,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -425,7 +444,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -457,7 +475,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -489,7 +506,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -521,7 +537,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -552,7 +567,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -584,7 +598,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -616,7 +629,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -647,7 +659,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -678,7 +689,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -709,7 +719,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -741,7 +750,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -771,7 +779,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -803,7 +810,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -833,7 +839,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -866,7 +871,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -899,7 +903,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -929,7 +932,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -960,7 +962,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -992,7 +993,66 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_books", + "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": "Finance Books", + "length": 0, + "no_copy": 0, + "options": "Asset Finance Book", + "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": "section_break_33", + "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, "unique": 0 }, { @@ -1026,7 +1086,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1058,7 +1117,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1089,7 +1147,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1119,7 +1176,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1151,7 +1207,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1182,7 +1237,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1214,7 +1268,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1246,7 +1299,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1278,7 +1330,245 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "insurance_details", + "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": "Insurance details", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "policy_number", + "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": "Policy number", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "insurer", + "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": "Insurer", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "insured_value", + "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": "Insured value", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_48", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "insurance_start_date", + "fieldtype": "Date", + "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": "Insurance Start Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "insurance_end_date", + "fieldtype": "Date", + "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": "Insurance End Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "comprehensive_insurance", + "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": "Comprehensive Insurance", + "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, "unique": 0 }, { @@ -1309,7 +1599,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1341,7 +1630,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1372,7 +1660,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -1387,7 +1674,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-07 15:25:06.456992", + "modified": "2018-05-09 11:09:56.407423", "modified_by": "Administrator", "module": "Assets", "name": "Asset", @@ -1396,6 +1683,7 @@ "permissions": [ { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -1415,6 +1703,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index ef6e376eae..a6078c4209 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -16,11 +16,11 @@ class Asset(Document): self.status = self.get_status() self.validate_item() self.set_missing_values() - self.validate_asset_values() + # self.validate_asset_values() if self.calculate_depreciation: self.make_depreciation_schedule() self.set_accumulated_depreciation() - get_depreciation_accounts(self) + # get_depreciation_accounts(self) if self.get("schedules"): self.validate_expected_value_after_useful_life() @@ -46,11 +46,9 @@ class Asset(Document): frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code)) def set_missing_values(self): - if self.item_code: - item_details = get_item_details(self.item_code) - for field, value in item_details.items(): - if not self.get(field): - self.set(field, value) + if self.item_code and not self.finance_books: + finance_books = get_item_details(self.item_code) + self.set('finance_books', finance_books) def validate_asset_values(self): if not flt(self.gross_purchase_amount): @@ -65,8 +63,6 @@ class Asset(Document): if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 self.number_of_depreciations_booked = 0 - if not self.next_depreciation_date: - frappe.throw(_("Next Depreciation Date is mandatory for new asset")) else: depreciable_amount = flt(self.gross_purchase_amount) - flt(self.expected_value_after_useful_life) if flt(self.opening_accumulated_depreciation) > depreciable_amount: @@ -94,66 +90,69 @@ class Asset(Document): if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.available_for_use_date): frappe.throw(_("Next Depreciation Date cannot be before Available-for-use Date")) - if (flt(self.value_after_depreciation) > flt(self.expected_value_after_useful_life) - and not self.next_depreciation_date and self.calculate_depreciation): - frappe.throw(_("Please set Next Depreciation Date")) - def make_depreciation_schedule(self): if self.depreciation_method != 'Manual': self.schedules = [] - if not self.get("schedules") and self.next_depreciation_date: - value_after_depreciation = flt(self.value_after_depreciation) + if not self.get("schedules"): + total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) - number_of_pending_depreciations = cint(self.total_number_of_depreciations) - \ - cint(self.number_of_depreciations_booked) - if number_of_pending_depreciations: - next_depr_date = getdate(add_months(self.available_for_use_date, - number_of_pending_depreciations * 12)) - if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 - and getdate(self.next_depreciation_date) < next_depr_date): + for d in self.get('finance_books'): + d.value_after_depreciation = ((flt(self.gross_purchase_amount * d.total_number_of_depreciations) / + total_depreciations) - flt(d.opening_accumulated_depreciation)) + value_after_depreciation = flt(d.value_after_depreciation) - number_of_pending_depreciations += 1 - for n in range(number_of_pending_depreciations): - if n == range(number_of_pending_depreciations)[-1]: - schedule_date = add_months(self.available_for_use_date, n * 12) - previous_scheduled_date = add_months(self.next_depreciation_date, (n-1) * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - previous_scheduled_date, schedule_date) + number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ + cint(d.number_of_depreciations_booked) + if number_of_pending_depreciations: + next_depr_date = getdate(add_months(self.available_for_use_date, + number_of_pending_depreciations * 12)) + if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 + and getdate(d.start_date) < next_depr_date): - elif n == range(number_of_pending_depreciations)[0]: - schedule_date = self.next_depreciation_date - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - self.available_for_use_date, schedule_date) + number_of_pending_depreciations += 1 + for n in range(number_of_pending_depreciations): + if n == range(number_of_pending_depreciations)[-1]: + schedule_date = add_months(self.available_for_use_date, n * 12) + previous_scheduled_date = add_months(d.start_date, (n-1) * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + previous_scheduled_date, schedule_date) - else: - schedule_date = add_months(self.next_depreciation_date, n * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation) + elif n == range(number_of_pending_depreciations)[0]: + schedule_date = d.start_date + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + self.available_for_use_date, schedule_date) - if value_after_depreciation != 0: - value_after_depreciation -= flt(depreciation_amount) + else: + schedule_date = add_months(d.start_date, n * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount - }) - else: - for n in range(number_of_pending_depreciations): - schedule_date = add_months(self.next_depreciation_date, - n * cint(self.frequency_of_depreciation)) + if value_after_depreciation != 0: + value_after_depreciation -= flt(depreciation_amount) - depreciation_amount = self.get_depreciation_amount(value_after_depreciation) - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount) + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + 'finance_book_id': d.name + }) + else: + for n in range(number_of_pending_depreciations): + schedule_date = add_months(d.start_date, + n * cint(d.frequency_of_depreciation)) - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount - }) + depreciation_amount = self.get_depreciation_amount(value_after_depreciation, d) + if depreciation_amount: + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + 'finance_book_id': d.name + }) def set_accumulated_depreciation(self): accumulated_depreciation = flt(self.opening_accumulated_depreciation) @@ -171,13 +170,13 @@ class Asset(Document): d.accumulated_depreciation_amount = flt(accumulated_depreciation, d.precision("accumulated_depreciation_amount")) - def get_depreciation_amount(self, depreciable_value): - if self.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(self.value_after_depreciation) - - flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - - cint(self.number_of_depreciations_booked)) + def get_depreciation_amount(self, depreciable_value, row): + if row.depreciation_method in ("Straight Line", "Manual"): + depreciation_amount = (flt(row.value_after_depreciation) - + flt(self.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - + cint(row.number_of_depreciations_booked)) else: - factor = 200.0 / self.total_number_of_depreciations + factor = 200.0 / row.total_number_of_depreciations depreciation_amount = flt(depreciable_value * factor / 100, 0) value_after_depreciation = flt(depreciable_value) - depreciation_amount @@ -345,11 +344,15 @@ def get_item_details(item_code): if not asset_category: frappe.throw(_("Please enter Asset Category in Item {0}").format(item_code)) - ret = frappe.db.get_value("Asset Category", asset_category, - ["depreciation_method", "total_number_of_depreciations", "frequency_of_depreciation"], as_dict=1) + asset_category_doc = frappe.get_doc('Asset Category', asset_category) + books = [] + for d in asset_category_doc.finance_books: + books.append({ + 'finance_book': d.finance_book, + 'depreciation_method': d.depreciation_method, + 'total_number_of_depreciations': d.total_number_of_depreciations, + 'frequency_of_depreciation': d.frequency_of_depreciation, + 'start_date': nowdate() + }) - ret.update({ - "asset_category": asset_category - }) - - return ret + return books diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json index 3331d05e85..d9776b8d91 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.json +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -164,6 +164,67 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book_detail", + "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": "Finance Book Detail", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_books", + "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": "Finance Books", + "length": 0, + "no_copy": 0, + "options": "Asset Finance Book", + "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, @@ -236,7 +297,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2017-12-01 15:14:25.645077", + "modified": "2018-05-09 11:09:55.806482", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category", @@ -312,4 +373,4 @@ "sort_order": "DESC", "track_changes": 0, "track_seen": 0 -} +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_finance_book/__init__.py b/erpnext/assets/doctype/asset_finance_book/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json new file mode 100644 index 0000000000..92991e9c85 --- /dev/null +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -0,0 +1,318 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-08 14:44:37.095570", + "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, + "depends_on": "", + "fieldname": "finance_book", + "fieldtype": "Data", + "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": "Finance Book", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depreciation_method", + "fieldtype": "Select", + "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": "Depreciation Method", + "length": 0, + "no_copy": 0, + "options": "\nStraight Line\nDouble Declining Balance\nManual", + "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": "total_number_of_depreciations", + "fieldtype": "Int", + "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": "Total Number of Depreciations", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "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": "Value After Depreciation", + "length": 0, + "no_copy": 0, + "options": "Company:company:default_currency", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_5", + "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, + "label": "", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "frequency_of_depreciation", + "fieldtype": "Int", + "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": "Frequency of Depreciation (Months)", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval:parent.doctype == 'Asset'", + "fieldname": "start_date", + "fieldtype": "Date", + "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": "Start Date", + "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", + "depends_on": "eval:parent.is_existing_asset", + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "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": "Opening Accumulated Depreciation", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: (parent.is_existing_asset && doc.opening_accumulated_depreciation)", + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "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": "Number of Depreciations Booked", + "length": 0, + "no_copy": 1, + "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 + } + ], + "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": "2018-05-09 11:10:00.923786", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Finance Book", + "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": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py new file mode 100644 index 0000000000..bdc2acfb79 --- /dev/null +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class AssetFinanceBook(Document): + pass diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json index 330347240d..17b4aecc04 100644 --- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -196,6 +196,66 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book", + "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": "Finance Book", + "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": "finance_book_id", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Finance Book Id", + "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, + "unique": 0 } ], "has_web_view": 0, @@ -208,7 +268,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2017-10-19 16:30:13.738170", + "modified": "2018-05-08 15:24:57.955533", "modified_by": "Administrator", "module": "Assets", "name": "Depreciation Schedule", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index 80bec0e889..d4399ad518 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -517,6 +517,7 @@ class BuyingController(StockController): 'warehouse': row.warehouse, 'company': self.company, 'purchase_date': self.posting_date, + 'calculate_depreciation': 1, 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None }) From aa7b43427004af20002ae2a1d4ae7b13736093ca Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 11 May 2018 01:56:05 +0530 Subject: [PATCH 66/79] Create asset adjustment doctype, post gl entry for the asset --- erpnext/assets/doctype/asset/asset.js | 28 +- erpnext/assets/doctype/asset/asset.json | 693 +++++++++--------- erpnext/assets/doctype/asset/asset.py | 322 +++++--- .../doctype/asset_adjustment/__init__.py | 0 .../asset_adjustment/asset_adjustment.js | 8 + .../asset_adjustment/asset_adjustment.json | 438 +++++++++++ .../asset_adjustment/asset_adjustment.py | 10 + .../asset_adjustment/test_asset_adjustment.js | 23 + .../asset_adjustment/test_asset_adjustment.py | 10 + .../asset_category/asset_category.json | 4 +- .../doctype/asset_category/asset_category.py | 17 +- .../asset_category_account.json | 13 +- .../asset_finance_book.json | 116 ++- .../asset_movement/asset_movement.json | 367 +++++++--- .../doctype/asset_movement/asset_movement.py | 41 +- .../depreciation_schedule.json | 94 ++- erpnext/config/assets.py | 5 +- erpnext/controllers/accounts_controller.py | 7 +- erpnext/controllers/buying_controller.py | 16 +- erpnext/hooks.py | 3 +- .../stock/doctype/serial_no/serial_no.json | 262 +++++-- .../stock/doctype/serial_no/test_serial_no.js | 23 + 22 files changed, 1695 insertions(+), 805 deletions(-) create mode 100644 erpnext/assets/doctype/asset_adjustment/__init__.py create mode 100644 erpnext/assets/doctype/asset_adjustment/asset_adjustment.js create mode 100644 erpnext/assets/doctype/asset_adjustment/asset_adjustment.json create mode 100644 erpnext/assets/doctype/asset_adjustment/asset_adjustment.py create mode 100644 erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.js create mode 100644 erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.py create mode 100644 erpnext/stock/doctype/serial_no/test_serial_no.js diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index b8bcc91d2a..4daeae2880 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -57,6 +57,19 @@ frappe.ui.form.on('Asset', { erpnext.asset.restore_asset(frm); }); } + + if (frm.doc.purchase_receipt) { + frm.add_custom_button("General Ledger", function() { + frappe.route_options = { + "voucher_no": frm.doc.name, + "from_date": frm.doc.available_for_use_date, + "to_date": frm.doc.available_for_use_date, + "company": frm.doc.company + }; + frappe.set_route("query-report", "General Ledger"); + }); + } + if (frm.doc.status=='Submitted' && !frm.doc.is_existing_asset && !frm.doc.purchase_invoice) { frm.add_custom_button(__("Purchase Invoice"), function() { frm.trigger("make_purchase_invoice"); @@ -139,7 +152,7 @@ frappe.ui.form.on('Asset', { }, is_existing_asset: function(frm) { - frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); + // frm.toggle_reqd("next_depreciation_date", (!frm.doc.is_existing_asset && frm.doc.calculate_depreciation)); }, opening_accumulated_depreciation: function(frm) { @@ -289,15 +302,14 @@ erpnext.asset.transfer_asset = function(frm) { title: __("Transfer Asset"), fields: [ { - "label": __("Target Warehouse"), - "fieldname": "target_warehouse", + "label": __("Target Location"), + "fieldname": "target_location", "fieldtype": "Link", - "options": "Warehouse", + "options": "Location", "get_query": function () { return { filters: [ - ["Warehouse", "company", "in", ["", cstr(frm.doc.company)]], - ["Warehouse", "is_group", "=", 0] + ["Location", "is_group", "=", 0] ] } }, @@ -324,8 +336,8 @@ erpnext.asset.transfer_asset = function(frm) { args: { "asset": frm.doc.name, "transaction_date": args.transfer_date, - "source_warehouse": frm.doc.warehouse, - "target_warehouse": args.target_warehouse, + "source_warehouse": frm.doc.location, + "target_warehouse": args.target_location, "company": frm.doc.company } }, diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index a47c645359..9a05cad65c 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -167,38 +167,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Draft", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Status", - "length": 0, - "no_copy": 1, - "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order", - "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, @@ -326,37 +294,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "custodian", - "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": "Custodian", - "length": 0, - "no_copy": 0, - "options": "Employee", - "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": 1, @@ -387,36 +324,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Small Text", - "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": "Serial No", - "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, @@ -483,7 +390,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "warehouse", + "fieldname": "location", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, @@ -491,11 +398,11 @@ "in_filter": 0, "in_global_search": 0, "in_list_view": 0, - "in_standard_filter": 1, - "label": "Warehouse", + "in_standard_filter": 0, + "label": "Location", "length": 0, "no_copy": 0, - "options": "Warehouse", + "options": "Location", "permlevel": 0, "precision": "", "print_hide": 0, @@ -508,6 +415,37 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "custodian", + "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": "Custodian", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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, @@ -569,128 +507,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_receipt", - "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": "Purchase Receipt", - "length": 0, - "no_copy": 1, - "options": "Purchase Receipt", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "purchase_invoice", - "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": "Purchase Invoice", - "length": 0, - "no_copy": 1, - "options": "Purchase Invoice", - "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": "available_for_use_date", - "fieldtype": "Date", - "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": "Available-for-use Date", - "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, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "is_existing_asset", - "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": "Is Existing Asset", - "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, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -812,6 +628,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "available_for_use_date", + "fieldtype": "Date", + "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": "Available-for-use Date", + "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, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -841,99 +687,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "expected_value_after_useful_life", - "fieldtype": "Currency", - "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": "Expected Value After Useful Life", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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, - "depends_on": "is_existing_asset", - "fieldname": "opening_accumulated_depreciation", - "fieldtype": "Currency", - "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": "Opening Accumulated Depreciation", - "length": 0, - "no_copy": 1, - "options": "Company:company:default_currency", - "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": "section_break_20", - "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, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -968,7 +721,100 @@ "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "is_existing_asset", + "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": "Is Existing Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "is_existing_asset", + "fieldname": "opening_accumulated_depreciation", + "fieldtype": "Currency", + "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": "Opening Accumulated Depreciation", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "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, + "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", + "fieldname": "number_of_depreciations_booked", + "fieldtype": "Int", + "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": "Number of Depreciations Booked", + "length": 0, + "no_copy": 1, + "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, "depends_on": "calculate_depreciation", "fieldname": "section_break_23", @@ -1034,7 +880,7 @@ "columns": 0, "fieldname": "section_break_33", "fieldtype": "Section Break", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, @@ -1178,37 +1024,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)", - "fieldname": "number_of_depreciations_booked", - "fieldtype": "Int", - "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": "Number of Depreciations Booked", - "length": 0, - "no_copy": 1, - "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, @@ -1336,7 +1151,7 @@ "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, - "collapsible": 0, + "collapsible": 1, "columns": 0, "fieldname": "insurance_details", "fieldtype": "Section Break", @@ -1632,6 +1447,190 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "other_details", + "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": "Other Details", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "Draft", + "fieldname": "status", + "fieldtype": "Select", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Status", + "length": 0, + "no_copy": 1, + "options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt", + "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, + "default": "0", + "fieldname": "booked_fixed_asset", + "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": "Booked Fixed Asset", + "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": "column_break_51", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_receipt", + "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": "Purchase Receipt", + "length": 0, + "no_copy": 1, + "options": "Purchase Receipt", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_invoice", + "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": "Purchase Invoice", + "length": 0, + "no_copy": 1, + "options": "Purchase Invoice", + "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, @@ -1674,7 +1673,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-09 11:09:56.407423", + "modified": "2018-05-11 01:48:18.711485", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index a6078c4209..bf70fbc17d 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -10,17 +10,20 @@ from frappe.model.document import Document from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account from erpnext.assets.doctype.asset.depreciation \ import get_disposal_account_and_cost_center, get_depreciation_accounts +from erpnext.accounts.general_ledger import make_gl_entries +from erpnext.accounts.utils import get_account_currency +from erpnext.controllers.accounts_controller import AccountsController -class Asset(Document): +class Asset(AccountsController): def validate(self): self.status = self.get_status() self.validate_item() self.set_missing_values() - # self.validate_asset_values() + self.validate_asset_values() if self.calculate_depreciation: self.make_depreciation_schedule() self.set_accumulated_depreciation() - # get_depreciation_accounts(self) + get_depreciation_accounts(self) if self.get("schedules"): self.validate_expected_value_after_useful_life() @@ -46,8 +49,11 @@ class Asset(Document): frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code)) def set_missing_values(self): - if self.item_code and not self.finance_books: - finance_books = get_item_details(self.item_code) + if not self.asset_category: + self.asset_category = frappe.db.get_value("Item", self.item_code, "asset_category") + + if self.item_code and not self.get('finance_books'): + finance_books = get_item_details(self.item_code, self.asset_category) self.set('finance_books', finance_books) def validate_asset_values(self): @@ -57,14 +63,105 @@ class Asset(Document): if not self.calculate_depreciation: return - if flt(self.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): - frappe.throw(_("Expected Value After Useful Life must be less than Gross Purchase Amount")) + self.value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) + + if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(nowdate()): + frappe.throw(_("Available-for-use Date is entered as past date")) + + def make_depreciation_schedule(self): + if self.depreciation_method != 'Manual': + self.schedules = [] + + if not self.get("schedules") and self.available_for_use_date: + total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) + + for d in self.get('finance_books'): + self.validate_asset_finance_books(d) + + value_after_depreciation = flt(self.value_after_depreciation) + d.value_after_depreciation = value_after_depreciation + + no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked) + end_date = add_months(d.depreciation_start_date, + no_of_depreciations * cint(d.frequency_of_depreciation)) + + total_days = date_diff(end_date, self.available_for_use_date) + rate_per_day = value_after_depreciation / total_days + + number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ + cint(self.number_of_depreciations_booked) + + from_date = self.available_for_use_date + if number_of_pending_depreciations: + next_depr_date = getdate(add_months(self.available_for_use_date, + number_of_pending_depreciations * 12)) + if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 + and getdate(d.depreciation_start_date) < next_depr_date): + + number_of_pending_depreciations += 1 + for n in range(number_of_pending_depreciations): + if n == range(number_of_pending_depreciations)[-1]: + schedule_date = add_months(self.available_for_use_date, n * 12) + previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + row, previous_scheduled_date, schedule_date) + + elif n == range(number_of_pending_depreciations)[0]: + schedule_date = d.depreciation_start_date + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, + row, self.available_for_use_date, schedule_date) + + else: + schedule_date = add_months(d.depreciation_start_date, n * 12) + depreciation_amount = \ + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, row) + + if value_after_depreciation != 0: + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + else: + for n in range(number_of_pending_depreciations): + schedule_date = add_months(d.depreciation_start_date, + n * cint(d.frequency_of_depreciation)) + + if d.depreciation_method in ("Straight Line", "Manual"): + days = date_diff(schedule_date, from_date) + depreciation_amount = days * rate_per_day + from_date = schedule_date + else: + depreciation_amount = self.get_depreciation_amount(value_after_depreciation,d) + + if depreciation_amount: + value_after_depreciation -= flt(depreciation_amount) + + self.append("schedules", { + "schedule_date": schedule_date, + "depreciation_amount": depreciation_amount, + "depreciation_method": d.depreciation_method, + "finance_book": d.finance_book, + "finance_book_id": d.idx + }) + + def validate_asset_finance_books(self, row): + if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount): + frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount") + .format(row.idx)) if not self.is_existing_asset: self.opening_accumulated_depreciation = 0 self.number_of_depreciations_booked = 0 else: - depreciable_amount = flt(self.gross_purchase_amount) - flt(self.expected_value_after_useful_life) + depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life) if flt(self.opening_accumulated_depreciation) > depreciable_amount: frappe.throw(_("Opening Accumulated Depreciation must be less than equal to {0}") .format(depreciable_amount)) @@ -75,95 +172,38 @@ class Asset(Document): else: self.number_of_depreciations_booked = 0 - if cint(self.number_of_depreciations_booked) > cint(self.total_number_of_depreciations): + if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations): frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations")) - self.value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) + if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(nowdate()): + frappe.msgprint(_("Depreciation Row {0}: Depreciation Start Date is entered as past date") + .format(row.idx), title=_('Warning'), indicator='red') - if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(nowdate()): - frappe.msgprint(_("Next Depreciation Date is entered as past date"), title=_('Warning'), indicator='red') + if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date): + frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date") + .format(row.idx)) - if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.purchase_date): - frappe.throw(_("Next Depreciation Date cannot be before Purchase Date")) - - if self.next_depreciation_date and getdate(self.next_depreciation_date) < getdate(self.available_for_use_date): - frappe.throw(_("Next Depreciation Date cannot be before Available-for-use Date")) - - def make_depreciation_schedule(self): - - if self.depreciation_method != 'Manual': - self.schedules = [] - - if not self.get("schedules"): - total_depreciations = sum([d.total_number_of_depreciations for d in self.get('finance_books')]) - - for d in self.get('finance_books'): - d.value_after_depreciation = ((flt(self.gross_purchase_amount * d.total_number_of_depreciations) / - total_depreciations) - flt(d.opening_accumulated_depreciation)) - value_after_depreciation = flt(d.value_after_depreciation) - - number_of_pending_depreciations = cint(d.total_number_of_depreciations) - \ - cint(d.number_of_depreciations_booked) - if number_of_pending_depreciations: - next_depr_date = getdate(add_months(self.available_for_use_date, - number_of_pending_depreciations * 12)) - if (cint(frappe.db.get_value("Asset Settings", None, "schedule_based_on_fiscal_year")) == 1 - and getdate(d.start_date) < next_depr_date): - - number_of_pending_depreciations += 1 - for n in range(number_of_pending_depreciations): - if n == range(number_of_pending_depreciations)[-1]: - schedule_date = add_months(self.available_for_use_date, n * 12) - previous_scheduled_date = add_months(d.start_date, (n-1) * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - previous_scheduled_date, schedule_date) - - elif n == range(number_of_pending_depreciations)[0]: - schedule_date = d.start_date - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - self.available_for_use_date, schedule_date) - - else: - schedule_date = add_months(d.start_date, n * 12) - depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation) - - if value_after_depreciation != 0: - value_after_depreciation -= flt(depreciation_amount) - - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - 'finance_book_id': d.name - }) - else: - for n in range(number_of_pending_depreciations): - schedule_date = add_months(d.start_date, - n * cint(d.frequency_of_depreciation)) - - depreciation_amount = self.get_depreciation_amount(value_after_depreciation, d) - if depreciation_amount: - value_after_depreciation -= flt(depreciation_amount) - - self.append("schedules", { - "schedule_date": schedule_date, - "depreciation_amount": depreciation_amount, - 'finance_book_id': d.name - }) + if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.available_for_use_date): + frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") + .format(row.idx)) def set_accumulated_depreciation(self): - accumulated_depreciation = flt(self.opening_accumulated_depreciation) value_after_depreciation = flt(self.value_after_depreciation) + straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] + finance_books = [] + for i, d in enumerate(self.get("schedules")): + if d.finance_book_id not in finance_books: + accumulated_depreciation = flt(self.opening_accumulated_depreciation) + finance_books.append(d.finance_book_id) + depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount")) value_after_depreciation -= flt(depreciation_amount) - if i==len(self.get("schedules"))-1 and self.depreciation_method == "Straight Line": + if straight_line_idx and i == max(straight_line_idx) - 1: + book = self.get('finance_books')[d.finance_book_id - 1] depreciation_amount += flt(value_after_depreciation - - flt(self.expected_value_after_useful_life), d.precision("depreciation_amount")) + flt(book.expected_value_after_useful_life), d.precision("depreciation_amount")) d.depreciation_amount = depreciation_amount accumulated_depreciation += d.depreciation_amount @@ -171,46 +211,44 @@ class Asset(Document): d.precision("accumulated_depreciation_amount")) def get_depreciation_amount(self, depreciable_value, row): - if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(row.value_after_depreciation) - - flt(self.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - - cint(row.number_of_depreciations_booked)) - else: - factor = 200.0 / row.total_number_of_depreciations - depreciation_amount = flt(depreciable_value * factor / 100, 0) + percentage_value = 100.0 if row.depreciation_method == 'Written Down Value' else 200.0 - value_after_depreciation = flt(depreciable_value) - depreciation_amount - if value_after_depreciation < flt(self.expected_value_after_useful_life): - depreciation_amount = flt(depreciable_value) - flt(self.expected_value_after_useful_life) + factor = percentage_value / row.total_number_of_depreciations + depreciation_amount = flt(depreciable_value * factor / 100, 0) + + value_after_depreciation = flt(depreciable_value) - depreciation_amount + if value_after_depreciation < flt(row.expected_value_after_useful_life): + depreciation_amount = flt(depreciable_value) - flt(row.expected_value_after_useful_life) return depreciation_amount - def get_depreciation_amount_prorata_temporis(self, depreciable_value, start_date=None, end_date=None): + def get_depreciation_amount_prorata_temporis(self, depreciable_value, row, start_date=None, end_date=None): if start_date and end_date: prorata_temporis = min(abs(flt(date_diff(str(end_date), str(start_date)))) / flt(frappe.db.get_value("Asset Settings", None, "number_of_days_in_fiscal_year")), 1) else: prorata_temporis = 1 - if self.depreciation_method in ("Straight Line", "Manual"): + if row.depreciation_method in ("Straight Line", "Manual"): depreciation_amount = (flt(self.value_after_depreciation) - - flt(self.expected_value_after_useful_life)) / (cint(self.total_number_of_depreciations) - + flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) * prorata_temporis - - return depreciation_amount else: - self.get_depreciation_amount(depreciable_value) + depreciation_amount = self.get_depreciation_amount(depreciable_value, row) + + return depreciation_amount def validate_expected_value_after_useful_life(self): - accumulated_depreciation_after_full_schedule = \ - max([d.accumulated_depreciation_amount for d in self.get("schedules")]) + for row in self.get('finance_books'): + accumulated_depreciation_after_full_schedule = \ + max([d.accumulated_depreciation_amount for d in self.get("schedules") if d.finance_book_id == row.idx]) - asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - - flt(accumulated_depreciation_after_full_schedule), - self.precision('expected_value_after_useful_life')) + asset_value_after_full_schedule = flt(flt(self.gross_purchase_amount) - + flt(accumulated_depreciation_after_full_schedule), + self.precision('gross_purchase_amount')) - if self.expected_value_after_useful_life < asset_value_after_full_schedule: - frappe.throw(_("Expected value after useful life must be greater than or equal to {0}") - .format(asset_value_after_full_schedule)) + if row.expected_value_after_useful_life < asset_value_after_full_schedule: + frappe.throw(_("Depreciation Row {0}: Expected value after useful life must be greater than or equal to {1}") + .format(row.idx, asset_value_after_full_schedule)) def validate_cancellation(self): if self.status not in ("Submitted", "Partially Depreciated", "Fully Depreciated"): @@ -243,9 +281,15 @@ class Asset(Document): status = "Draft" elif self.docstatus == 1: status = "Submitted" + expected_value_after_useful_life = flt(sum([d.expected_value_after_useful_life + for d in self.get('finance_books')])) + + value_after_depreciation = flt(sum([d.value_after_depreciation + for d in self.get('finance_books')])) + if self.journal_entry_for_scrap: status = "Scrapped" - elif flt(self.value_after_depreciation) <= flt(self.expected_value_after_useful_life): + elif flt(value_after_depreciation) <= expected_value_after_useful_life: status = "Fully Depreciated" elif flt(self.value_after_depreciation) < flt(self.gross_purchase_amount): status = 'Partially Depreciated' @@ -261,6 +305,37 @@ class Asset(Document): doc = frappe.get_doc('Asset Movement', asset_movement) doc.submit() + def make_gl_entries(self): + if self.purchase_receipt: + from erpnext.accounts.general_ledger import make_gl_entries + + gl_entries = [] + + cwip_account = get_cwip_account(self.name, self.asset_category, self.company) + fixed_aseet_account = get_asset_category_account(self.name, 'fixed_asset_account', + asset_category = self.asset_category, company = self.company) + + gl_entries.append(self.get_gl_dict({ + "account": cwip_account, + "against": fixed_aseet_account, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "posting_date": self.available_for_use_date, + "credit": self.gross_purchase_amount, + "credit_in_account_currency": self.gross_purchase_amount + })) + + gl_entries.append(self.get_gl_dict({ + "account": fixed_aseet_account, + "against": cwip_account, + "remarks": self.get("remarks") or _("Accounting Entry for Asset"), + "posting_date": self.available_for_use_date, + "debit": self.gross_purchase_amount, + "debit_in_account_currency": self.gross_purchase_amount + })) + + make_gl_entries(gl_entries) + self.db_set('booked_fixed_asset', 1) + def update_maintenance_status(): assets = frappe.get_all('Asset', filters = {'docstatus': 1, 'maintenance_required': 1}) @@ -271,6 +346,14 @@ def update_maintenance_status(): if frappe.db.exists('Asset Repair', {'asset_name': asset.name, 'repair_status': 'Pending'}): asset.set_status('Out of Order') +def make_post_gl_entry(): + assets = frappe.db.sql_list(""" select name from `tabAsset` + where ifnull(booked_fixed_asset, 0) = 0 and available_for_use_date = %s""", nowdate()) + + for asset in assets: + doc = frappe.get_doc('Asset', asset) + doc.make_gl_entries() + def get_asset_naming_series(): meta = frappe.get_meta('Asset') return meta.get_field("naming_series").options @@ -295,7 +378,7 @@ def make_purchase_invoice(asset, item_code, gross_purchase_amount, company, post return pi @frappe.whitelist() -def make_sales_invoice(asset, item_code, company, serial_no): +def make_sales_invoice(asset, item_code, company, serial_no=None): si = frappe.new_doc("Sales Invoice") si.company = company si.currency = frappe.db.get_value("Company", company, "default_currency") @@ -338,9 +421,7 @@ def transfer_asset(args): frappe.msgprint(_("Asset Movement record {0} created").format("
    {0}".format(movement_entry.name))) @frappe.whitelist() -def get_item_details(item_code): - asset_category = frappe.db.get_value("Item", item_code, "asset_category") - +def get_item_details(item_code, asset_category=None): if not asset_category: frappe.throw(_("Please enter Asset Category in Item {0}").format(item_code)) @@ -356,3 +437,16 @@ def get_item_details(item_code): }) return books + +def get_cwip_account(asset, asset_category=None, company=None): + cwip_account = get_asset_category_account(asset, 'capital_work_in_progress_account', + asset_category = asset_category, company = company) + + if not cwip_account: + cwip_account = frappe.db.get_value('Company', company, 'capital_work_in_progress_account') + + if not cwip_account: + frappe.throw(_("Set Capital Work In Progress Account in asset category {0} or company {1}") + .format(asset_category, company)) + + return cwip_account diff --git a/erpnext/assets/doctype/asset_adjustment/__init__.py b/erpnext/assets/doctype/asset_adjustment/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js new file mode 100644 index 0000000000..0535743703 --- /dev/null +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Asset Adjustment', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json new file mode 100644 index 0000000000..8a0e95775a --- /dev/null +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json @@ -0,0 +1,438 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-11 00:22:43.695151", + "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, + "fieldname": "asset", + "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": "Asset", + "length": 0, + "no_copy": 0, + "options": "Asset", + "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": "asset_category", + "fieldtype": "Read Only", + "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": "Asset Category", + "length": 0, + "no_copy": 0, + "options": "asset.asset_category", + "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": "finance_book", + "fieldtype": "Link", + "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": "Finance Book", + "length": 0, + "no_copy": 0, + "options": "Finance Book", + "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": "current_asset_value", + "fieldtype": "Currency", + "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": "Current Asset Value", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "journal_entry", + "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": "Journal Entry", + "length": 0, + "no_copy": 0, + "options": "Journal Entry", + "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": "column_break_4", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "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": "Date", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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": "new_asset_value", + "fieldtype": "Currency", + "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": "New Asset Value", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "accumulated_depreciation_account", + "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": "Accumulated Depreciation Account", + "length": 0, + "no_copy": 0, + "options": "Account", + "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": "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": "Asset Adjustment", + "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": "2018-05-11 00:25:07.222408", + "modified_by": "Administrator", + "module": "Assets", + "name": "Asset Adjustment", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "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 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts User", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + }, + { + "amend": 1, + "apply_user_permissions": 0, + "cancel": 1, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "Accounts Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 1, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "asset", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py new file mode 100644 index 0000000000..437f9bd902 --- /dev/null +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class AssetAdjustment(Document): + pass diff --git a/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.js b/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.js new file mode 100644 index 0000000000..29d070af52 --- /dev/null +++ b/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Asset Adjustment", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Asset Adjustment + () => frappe.tests.make('Asset Adjustment', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.py b/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.py new file mode 100644 index 0000000000..209692e04c --- /dev/null +++ b/erpnext/assets/doctype/asset_adjustment/test_asset_adjustment.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestAssetAdjustment(unittest.TestCase): + pass diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json index d9776b8d91..b655b40475 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.json +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -62,7 +62,7 @@ "label": "Depreciation Method", "length": 0, "no_copy": 0, - "options": "\nStraight Line\nDouble Declining Balance\nManual", + "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", "permlevel": 0, "precision": "", "print_hide": 0, @@ -297,7 +297,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-09 11:09:55.806482", + "modified": "2018-05-10 15:12:05.954200", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category", diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index d1dd8ed7e2..fa0bd83117 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -15,15 +15,16 @@ class AssetCategory(Document): frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) @frappe.whitelist() -def get_asset_category_account(asset, fieldname, account=None): - if account: - if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": - account=None +def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): + if not asset_category and company: + if account: + if frappe.db.get_value("Account", account, "account_type") != "Fixed Asset": + account=None - if not account: - asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) + if not account: + asset_category, company = frappe.db.get_value("Asset", asset, ["asset_category", "company"]) - account = frappe.db.get_value("Asset Category Account", - filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) + account = frappe.db.get_value("Asset Category Account", + filters={"parent": asset_category, "company_name": company}, fieldname=fieldname) return account \ No newline at end of file diff --git a/erpnext/assets/doctype/asset_category_account/asset_category_account.json b/erpnext/assets/doctype/asset_category_account/asset_category_account.json index 3cace59a4c..b7df557552 100644 --- a/erpnext/assets/doctype/asset_category_account/asset_category_account.json +++ b/erpnext/assets/doctype/asset_category_account/asset_category_account.json @@ -17,7 +17,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 3, + "columns": 2, "fieldname": "company_name", "fieldtype": "Link", "hidden": 0, @@ -41,7 +41,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -49,7 +48,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 3, + "columns": 2, "fieldname": "fixed_asset_account", "fieldtype": "Link", "hidden": 0, @@ -73,7 +72,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -105,7 +103,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -137,7 +134,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -145,7 +141,7 @@ "allow_on_submit": 0, "bold": 0, "collapsible": 0, - "columns": 0, + "columns": 2, "fieldname": "capital_work_in_progress_account", "fieldtype": "Link", "hidden": 0, @@ -169,7 +165,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -183,7 +178,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-05-08 11:41:09.678234", + "modified": "2018-05-10 17:06:48.839347", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category Account", diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 92991e9c85..351f9d0c66 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -20,7 +20,7 @@ "columns": 0, "depends_on": "", "fieldname": "finance_book", - "fieldtype": "Data", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -31,6 +31,7 @@ "label": "Finance Book", "length": 0, "no_copy": 0, + "options": "Finance Book", "permlevel": 0, "precision": "", "print_hide": 0, @@ -61,7 +62,7 @@ "label": "Depreciation Method", "length": 0, "no_copy": 0, - "options": "\nStraight Line\nDouble Declining Balance\nManual", + "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", "permlevel": 0, "precision": "", "print_hide": 0, @@ -104,37 +105,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "value_after_depreciation", - "fieldtype": "Currency", - "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": "Value After Depreciation", - "length": 0, - "no_copy": 0, - "options": "Company:company:default_currency", - "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, - "unique": 0 - }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -202,7 +172,7 @@ "collapsible": 0, "columns": 0, "depends_on": "eval:parent.doctype == 'Asset'", - "fieldname": "start_date", + "fieldname": "depreciation_start_date", "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, @@ -211,39 +181,7 @@ "in_global_search": 0, "in_list_view": 1, "in_standard_filter": 0, - "label": "Start Date", - "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", - "depends_on": "eval:parent.is_existing_asset", - "fieldname": "opening_accumulated_depreciation", - "fieldtype": "Currency", - "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": "Opening Accumulated Depreciation", + "label": "Depreciation Start Date", "length": 0, "no_copy": 0, "permlevel": 0, @@ -264,9 +202,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "eval: (parent.is_existing_asset && doc.opening_accumulated_depreciation)", - "fieldname": "number_of_depreciations_booked", - "fieldtype": "Int", + "default": "0", + "fieldname": "expected_value_after_useful_life", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -274,9 +212,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Number of Depreciations Booked", + "label": "Expected Value After Useful Life", "length": 0, - "no_copy": 1, + "no_copy": 0, + "options": "Company:company:default_currency", "permlevel": 0, "precision": "", "print_hide": 0, @@ -288,6 +227,37 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "value_after_depreciation", + "fieldtype": "Currency", + "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": "Value After Depreciation", + "length": 0, + "no_copy": 1, + "options": "Company:company:default_currency", + "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, + "unique": 0 } ], "has_web_view": 0, @@ -300,7 +270,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-05-09 11:10:00.923786", + "modified": "2018-05-10 18:05:58.900298", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.json b/erpnext/assets/doctype/asset_movement/asset_movement.json index 3c3a1dc9cd..8adbf57b67 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.json +++ b/erpnext/assets/doctype/asset_movement/asset_movement.json @@ -12,6 +12,37 @@ "document_type": "", "editable_grid": 0, "fields": [ + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 1, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -31,7 +62,7 @@ "label": "Purpose", "length": 0, "no_copy": 0, - "options": "Receipt\nTransfer", + "options": "\nIssue\nReceipt\nTransfer", "permlevel": 0, "precision": "", "print_hide": 0, @@ -39,10 +70,9 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -74,7 +104,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -105,70 +134,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 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_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Company", - "length": 0, - "no_copy": 0, - "options": "Company", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 1, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "serial_no", - "fieldtype": "Small Text", - "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": "Serial No", - "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 }, { @@ -198,7 +163,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -207,51 +171,18 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "source_warehouse", - "fieldtype": "Link", + "fieldname": "quantity", + "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": 1, - "label": "Source Warehouse", + "in_standard_filter": 0, + "label": "Quantity", "length": 0, "no_copy": 0, - "options": "Warehouse", - "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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "target_warehouse", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Target Warehouse", - "length": 0, - "no_copy": 0, - "options": "Warehouse", "permlevel": 0, "precision": "", "print_hide": 0, @@ -259,10 +190,221 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "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": "serial_no", + "fieldtype": "Small Text", + "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": "Serial No", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "source_location", + "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": "Source Location", + "length": 0, + "no_copy": 0, + "options": "Location", + "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": "target_location", + "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": "Target Location", + "length": 0, + "no_copy": 0, + "options": "Location", + "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": "column_break_10", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_employee", + "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": "From Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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": "to_employee", + "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": "To Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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 }, { @@ -293,7 +435,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -325,7 +466,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -357,7 +497,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -388,7 +527,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -402,7 +540,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-20 15:45:54.156501", + "modified": "2018-05-10 23:16:20.791672", "modified_by": "Administrator", "module": "Assets", "name": "Asset Movement", @@ -411,6 +549,7 @@ "permissions": [ { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -430,6 +569,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, @@ -449,6 +589,7 @@ }, { "amend": 1, + "apply_user_permissions": 0, "cancel": 1, "create": 1, "delete": 1, diff --git a/erpnext/assets/doctype/asset_movement/asset_movement.py b/erpnext/assets/doctype/asset_movement/asset_movement.py index 42ed249959..32fc663837 100644 --- a/erpnext/assets/doctype/asset_movement/asset_movement.py +++ b/erpnext/assets/doctype/asset_movement/asset_movement.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos from frappe.model.document import Document class AssetMovement(Document): @@ -23,29 +24,41 @@ class AssetMovement(Document): if serial_no and not self.serial_no: self.serial_no = serial_no - def validate_warehouses(self): - if self.purpose == 'Transfer' and not self.source_warehouse: - self.source_warehouse = frappe.db.get_value("Asset", self.asset, "warehouse") + if self.serial_no and len(get_serial_nos(self.serial_no)) != self.quantity: + frappe.throw(_("Number of serial nos and quantity must be the same")) - if self.source_warehouse == self.target_warehouse: - frappe.throw(_("Source and Target Warehouse cannot be same")) + if not(self.source_location or self.target_location or self.from_employee or self.to_employee): + frappe.throw(_("Either location or employee must be required")) + + def validate_warehouses(self): + if self.purpose in ['Transfer', 'Issue']: + self.source_location = frappe.db.get_value("Asset", self.asset, "location") + + if self.source_location == self.target_location: + frappe.throw(_("Source and Target Location cannot be same")) def on_submit(self): - self.set_latest_warehouse_in_asset() + self.set_latest_location_in_asset() def on_cancel(self): - self.set_latest_warehouse_in_asset() - - def set_latest_warehouse_in_asset(self): - latest_movement_entry = frappe.db.sql("""select target_warehouse from `tabAsset Movement` + self.set_latest_location_in_asset() + + def set_latest_location_in_asset(self): + latest_movement_entry = frappe.db.sql("""select target_location from `tabAsset Movement` where asset=%s and docstatus=1 and company=%s order by transaction_date desc limit 1""", (self.asset, self.company)) if latest_movement_entry: - warehouse = latest_movement_entry[0][0] + location = latest_movement_entry[0][0] else: - warehouse = frappe.db.sql("""select source_warehouse from `tabAsset Movement` + location = frappe.db.sql("""select source_location from `tabAsset Movement` where asset=%s and docstatus=2 and company=%s order by transaction_date asc limit 1""", (self.asset, self.company))[0][0] - - frappe.db.set_value("Asset", self.asset, "warehouse", warehouse) \ No newline at end of file + + frappe.db.set_value("Asset", self.asset, "location", location) + + if self.serial_no: + serial_nos = get_serial_nos(self.serial_no) + + frappe.db.sql(""" update `tabSerial No` set location = %s where name in (%s)""" + %('%s', ','.join(['%s'] * len(serial_nos))), (location, tuple(serial_nos))) diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json index 17b4aecc04..35a2c9dd7f 100644 --- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json +++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json @@ -13,6 +13,37 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "finance_book", + "fieldtype": "Link", + "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": "Finance Book", + "length": 0, + "no_copy": 0, + "options": "Finance Book", + "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, @@ -197,36 +228,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "finance_book", - "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": "Finance Book", - "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, @@ -256,6 +257,37 @@ "search_index": 0, "set_only_once": 0, "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "depreciation_method", + "fieldtype": "Select", + "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": "Depreciation Method", + "length": 0, + "no_copy": 1, + "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", + "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, + "unique": 0 } ], "has_web_view": 0, @@ -268,7 +300,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-05-08 15:24:57.955533", + "modified": "2018-05-10 15:12:41.679436", "modified_by": "Administrator", "module": "Assets", "name": "Depreciation Schedule", diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py index be522469e6..99a7a5c744 100644 --- a/erpnext/config/assets.py +++ b/erpnext/config/assets.py @@ -12,12 +12,11 @@ def get_data(): }, { "type": "doctype", - "name": "Asset Category", + "name": "Location", }, { "type": "doctype", - "label": _("Asset Location"), - "name": "Location", + "name": "Asset Category", }, { "type": "doctype", diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py index 40028afb01..186aad3a5a 100644 --- a/erpnext/controllers/accounts_controller.py +++ b/erpnext/controllers/accounts_controller.py @@ -273,15 +273,16 @@ class AccountsController(TransactionBase): def get_gl_dict(self, args, account_currency=None): """this method populates the common properties of a gl entry record""" - fiscal_years = get_fiscal_years(self.posting_date, company=self.company) + posting_date = args.get('posting_date') or self.get('posting_date') + fiscal_years = get_fiscal_years(posting_date, company=self.company) if len(fiscal_years) > 1: - frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(formatdate(self.posting_date))) + frappe.throw(_("Multiple fiscal years exist for the date {0}. Please set company in Fiscal Year").format(formatdate(posting_date))) else: fiscal_year = fiscal_years[0][0] gl_dict = frappe._dict({ 'company': self.company, - 'posting_date': self.posting_date, + 'posting_date': posting_date, 'fiscal_year': fiscal_year, 'voucher_type': self.doctype, 'voucher_no': self.name, diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index d4399ad518..f33ed25a06 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -11,7 +11,7 @@ from erpnext.stock.get_item_details import get_conversion_factor from erpnext.buying.utils import validate_for_items, update_last_purchase_rate from erpnext.stock.stock_ledger import get_valuation_rate from erpnext.stock.doctype.stock_entry.stock_entry import get_used_alternative_items -from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos +from erpnext.stock.doctype.serial_no.serial_no import get_auto_serial_nos, auto_make_serial_nos, get_serial_nos from erpnext.controllers.stock_controller import StockController @@ -505,6 +505,9 @@ class BuyingController(StockController): self.make_asset_movement(d) def make_asset(self, row): + if not row.asset_location: + frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code)) + item_data = frappe.db.get_value('Item', row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) @@ -512,18 +515,21 @@ class BuyingController(StockController): 'doctype': 'Asset', 'item_code': row.item_code, 'asset_name': row.item_name, + 'status': 'Receipt', 'naming_series': item_data.get('asset_naming_series') or 'AST', 'asset_category': item_data.get('asset_category'), - 'warehouse': row.warehouse, + 'location': row.asset_location, 'company': self.company, 'purchase_date': self.posting_date, 'calculate_depreciation': 1, + 'gross_purchase_amount': flt(row.base_net_amount + row.item_tax_amount), 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None }) asset.flags.ignore_validate = True asset.flags.ignore_mandatory = True + asset.set_missing_values() asset.insert() frappe.msgprint(_("Asset {0} created").format(asset.name)) @@ -533,10 +539,10 @@ class BuyingController(StockController): asset_movement = frappe.get_doc({ 'doctype': 'Asset Movement', 'asset': row.asset, - 'source_warehouse': '', - 'target_warehouse': row.warehouse, + 'target_location': row.asset_location, 'purpose': 'Receipt', 'serial_no': row.serial_no, + 'quantity': len(get_serial_nos(row.serial_no)), 'company': self.company, 'transaction_date': self.posting_date, 'reference_doctype': self.doctype, @@ -559,7 +565,7 @@ class BuyingController(StockController): asset.set(field, self.name) asset.purchase_date = self.posting_date asset.supplier = self.supplier - else: + elif self.docstatus == 2: asset.set(field, None) asset.supplier = None diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 57e83e63be..627455b021 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -234,7 +234,8 @@ scheduler_events = { "erpnext.buying.doctype.supplier_scorecard.supplier_scorecard.refresh_scorecards", "erpnext.setup.doctype.company.company.cache_companies_monthly_sales_history", "erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool.update_latest_price_in_all_boms", - "erpnext.assets.doctype.asset.asset.update_maintenance_status" + "erpnext.assets.doctype.asset.asset.update_maintenance_status", + "erpnext.assets.doctype.asset.asset.make_post_gl_entry" ] } diff --git a/erpnext/stock/doctype/serial_no/serial_no.json b/erpnext/stock/doctype/serial_no/serial_no.json index fa4fa694c1..f84cbef2c8 100644 --- a/erpnext/stock/doctype/serial_no/serial_no.json +++ b/erpnext/stock/doctype/serial_no/serial_no.json @@ -41,7 +41,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -70,7 +69,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -102,7 +100,6 @@ "reqd": 1, "search_index": 1, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -135,7 +132,6 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -169,39 +165,6 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "asset", - "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": "Asset", - "length": 0, - "no_copy": 1, - "options": "Asset", - "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 }, { @@ -230,7 +193,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -260,7 +222,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -292,7 +253,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "300px" }, @@ -327,7 +287,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -360,7 +319,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -390,7 +348,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -419,7 +376,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "50%" }, @@ -451,7 +407,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -482,7 +437,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -514,7 +468,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -544,7 +497,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -577,7 +529,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -606,7 +557,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "50%" }, @@ -638,7 +588,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -668,7 +617,190 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset_details", + "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": "Asset Details", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "asset", + "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": "Asset", + "length": 0, + "no_copy": 1, + "options": "Asset", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "asset", + "fieldname": "asset_status", + "fieldtype": "Select", + "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": "Asset Status", + "length": 0, + "no_copy": 0, + "options": "\nIssue\nReceipt\nTransfer", + "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": "column_break_24", + "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, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "location", + "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": "Location", + "length": 0, + "no_copy": 0, + "options": "Location", + "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": "employee", + "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": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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 }, { @@ -699,7 +831,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -730,7 +861,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -761,7 +891,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -793,7 +922,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -823,7 +951,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -856,7 +983,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -885,7 +1011,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "50%" }, @@ -919,7 +1044,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -951,7 +1075,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -982,7 +1105,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1014,7 +1136,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1044,7 +1165,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1073,7 +1193,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "50%" }, @@ -1107,7 +1226,6 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "150px" }, @@ -1140,7 +1258,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "150px" }, @@ -1170,7 +1287,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "50%" }, @@ -1203,7 +1319,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "150px" }, @@ -1236,7 +1351,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0, "width": "150px" }, @@ -1267,7 +1381,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1297,7 +1410,6 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, - "translatable": 0, "unique": 0 }, { @@ -1328,7 +1440,6 @@ "reqd": 1, "search_index": 1, "set_only_once": 0, - "translatable": 0, "unique": 0 } ], @@ -1343,7 +1454,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-19 20:25:52.066995", + "modified": "2018-05-10 23:38:20.646770", "modified_by": "Administrator", "module": "Stock", "name": "Serial No", @@ -1351,6 +1462,7 @@ "permissions": [ { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1370,6 +1482,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -1389,6 +1502,7 @@ }, { "amend": 0, + "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.js b/erpnext/stock/doctype/serial_no/test_serial_no.js new file mode 100644 index 0000000000..bf8293257c --- /dev/null +++ b/erpnext/stock/doctype/serial_no/test_serial_no.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Serial No", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Serial No + () => frappe.tests.make('Serial No', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); From 16bc853f6abe484a0e99ba259bc9859699f54f1c Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 12 May 2018 12:06:00 +0530 Subject: [PATCH 67/79] Reschedule for future depreciations and booked difference amount in accumulated depreciation account --- .../doctype/journal_entry/journal_entry.py | 14 ++- erpnext/assets/doctype/asset/asset.js | 6 ++ erpnext/assets/doctype/asset/asset.json | 32 ++++++- erpnext/assets/doctype/asset/asset.py | 38 ++++---- erpnext/assets/doctype/asset/depreciation.py | 10 +- .../asset_adjustment/asset_adjustment.js | 24 ++++- .../asset_adjustment/asset_adjustment.json | 93 +++++++++--------- .../asset_adjustment/asset_adjustment.py | 95 ++++++++++++++++++- erpnext/config/assets.py | 4 + erpnext/controllers/buying_controller.py | 4 +- 10 files changed, 247 insertions(+), 73 deletions(-) diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py index 51308e5008..9bbd137159 100644 --- a/erpnext/accounts/doctype/journal_entry/journal_entry.py +++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals import frappe, erpnext, json -from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate +from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint from frappe import msgprint, _, scrub from erpnext.controllers.accounts_controller import AccountsController from erpnext.accounts.utils import get_balance_on, get_account_currency @@ -92,6 +92,7 @@ class JournalEntry(AccountsController): self.unlink_advance_entry_reference() self.unlink_asset_reference() self.unlink_inter_company_jv() + self.unlink_asset_adjustment_entry() def unlink_advance_entry_reference(self): for d in self.get("accounts"): @@ -109,9 +110,12 @@ class JournalEntry(AccountsController): for s in asset.get("schedules"): if s.journal_entry == self.name: s.db_set("journal_entry", None) - asset.value_after_depreciation += s.depreciation_amount - asset.db_set("value_after_depreciation", asset.value_after_depreciation) + idx = cint(s.finance_book_id) or 1 + finance_books = asset.get('finance_books')[idx - 1] + finance_books.value_after_depreciation += s.depreciation_amount + finance_books.db_update() + asset.set_status() def unlink_inter_company_jv(self): @@ -121,6 +125,10 @@ class JournalEntry(AccountsController): frappe.db.set_value("Journal Entry", self.name,\ "inter_company_journal_entry_reference", "") + def unlink_asset_adjustment_entry(self): + frappe.db.sql(""" update `tabAsset Adjustment` + set journal_entry = null where journal_entry = %s""", self.name) + def validate_party(self): for d in self.get("accounts"): account_type = frappe.db.get_value("Account", d.account, "account_type") diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js index 4daeae2880..c05667a767 100644 --- a/erpnext/assets/doctype/asset/asset.js +++ b/erpnext/assets/doctype/asset/asset.js @@ -80,6 +80,12 @@ frappe.ui.form.on('Asset', { frm.trigger("create_asset_maintenance"); }, __("Make")); } + if (frm.doc.status != 'Fully Depreciated') { + frm.add_custom_button(__("Asset Adjustment"), function() { + frm.trigger("create_asset_maintenance"); + }, __("Make")); + } + frm.page.set_inner_btn_group_as_primary(__("Make")); frm.trigger("setup_chart"); } diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json index 9a05cad65c..49a010d7d5 100644 --- a/erpnext/assets/doctype/asset/asset.json +++ b/erpnext/assets/doctype/asset/asset.json @@ -1600,6 +1600,36 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "purchase_receipt_amount", + "fieldtype": "Currency", + "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": "Purchase Receipt Amount", + "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, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, @@ -1673,7 +1703,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-11 01:48:18.711485", + "modified": "2018-05-11 10:41:45.972686", "modified_by": "Administrator", "module": "Assets", "name": "Asset", diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index bf70fbc17d..748849ecb2 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -63,9 +63,6 @@ class Asset(AccountsController): if not self.calculate_depreciation: return - self.value_after_depreciation = (flt(self.gross_purchase_amount) - - flt(self.opening_accumulated_depreciation)) - if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(nowdate()): frappe.throw(_("Available-for-use Date is entered as past date")) @@ -79,7 +76,9 @@ class Asset(AccountsController): for d in self.get('finance_books'): self.validate_asset_finance_books(d) - value_after_depreciation = flt(self.value_after_depreciation) + value_after_depreciation = (flt(self.gross_purchase_amount) - + flt(self.opening_accumulated_depreciation)) + d.value_after_depreciation = value_after_depreciation no_of_depreciations = cint(d.total_number_of_depreciations - 1) - cint(self.number_of_depreciations_booked) @@ -139,7 +138,8 @@ class Asset(AccountsController): depreciation_amount = days * rate_per_day from_date = schedule_date else: - depreciation_amount = self.get_depreciation_amount(value_after_depreciation,d) + depreciation_amount = self.get_depreciation_amount(value_after_depreciation, + d.total_number_of_depreciations, d) if depreciation_amount: value_after_depreciation -= flt(depreciation_amount) @@ -187,21 +187,24 @@ class Asset(AccountsController): frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Available-for-use Date") .format(row.idx)) - def set_accumulated_depreciation(self): - value_after_depreciation = flt(self.value_after_depreciation) + def set_accumulated_depreciation(self, ignore_booked_entry = False): straight_line_idx = [d.idx for d in self.get("schedules") if d.depreciation_method == 'Straight Line'] finance_books = [] for i, d in enumerate(self.get("schedules")): + if ignore_booked_entry and d.journal_entry: + continue + if d.finance_book_id not in finance_books: accumulated_depreciation = flt(self.opening_accumulated_depreciation) + value_after_depreciation = flt(self.get_value_after_depreciation(d.finance_book_id)) finance_books.append(d.finance_book_id) depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount")) value_after_depreciation -= flt(depreciation_amount) if straight_line_idx and i == max(straight_line_idx) - 1: - book = self.get('finance_books')[d.finance_book_id - 1] + book = self.get('finance_books')[cint(d.finance_book_id) - 1] depreciation_amount += flt(value_after_depreciation - flt(book.expected_value_after_useful_life), d.precision("depreciation_amount")) @@ -210,10 +213,13 @@ class Asset(AccountsController): d.accumulated_depreciation_amount = flt(accumulated_depreciation, d.precision("accumulated_depreciation_amount")) - def get_depreciation_amount(self, depreciable_value, row): + def get_value_after_depreciation(self, idx): + return flt(self.get('finance_books')[cint(idx)-1].value_after_depreciation) + + def get_depreciation_amount(self, depreciable_value, total_number_of_depreciations, row): percentage_value = 100.0 if row.depreciation_method == 'Written Down Value' else 200.0 - factor = percentage_value / row.total_number_of_depreciations + factor = percentage_value / total_number_of_depreciations depreciation_amount = flt(depreciable_value * factor / 100, 0) value_after_depreciation = flt(depreciable_value) - depreciation_amount @@ -229,7 +235,7 @@ class Asset(AccountsController): prorata_temporis = 1 if row.depreciation_method in ("Straight Line", "Manual"): - depreciation_amount = (flt(self.value_after_depreciation) - + depreciation_amount = (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (cint(row.total_number_of_depreciations) - cint(self.number_of_depreciations_booked)) * prorata_temporis else: @@ -306,7 +312,7 @@ class Asset(AccountsController): doc.submit() def make_gl_entries(self): - if self.purchase_receipt: + if self.purchase_receipt and self.purchase_receipt_amount: from erpnext.accounts.general_ledger import make_gl_entries gl_entries = [] @@ -320,8 +326,8 @@ class Asset(AccountsController): "against": fixed_aseet_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "credit": self.gross_purchase_amount, - "credit_in_account_currency": self.gross_purchase_amount + "credit": self.purchase_receipt_amount, + "credit_in_account_currency": self.purchase_receipt_amount })) gl_entries.append(self.get_gl_dict({ @@ -329,8 +335,8 @@ class Asset(AccountsController): "against": cwip_account, "remarks": self.get("remarks") or _("Accounting Entry for Asset"), "posting_date": self.available_for_use_date, - "debit": self.gross_purchase_amount, - "debit_in_account_currency": self.gross_purchase_amount + "debit": self.purchase_receipt_amount, + "debit_in_account_currency": self.purchase_receipt_amount })) make_gl_entries(gl_entries) diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py index 92a251e4fa..aacaef5414 100644 --- a/erpnext/assets/doctype/asset/depreciation.py +++ b/erpnext/assets/doctype/asset/depreciation.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ -from frappe.utils import flt, today, getdate +from frappe.utils import flt, today, getdate, cint def post_depreciation_entries(date=None): # Return if automatic booking of asset depreciation is disabled @@ -47,6 +47,7 @@ def make_depreciation_entry(asset_name, date=None): je.naming_series = depreciation_series je.posting_date = d.schedule_date je.company = asset.company + je.finance_book = d.finance_book je.remark = "Depreciation Entry against {0} worth {1}".format(asset_name, d.depreciation_amount) je.append("accounts", { @@ -68,9 +69,12 @@ def make_depreciation_entry(asset_name, date=None): je.submit() d.db_set("journal_entry", je.name) - asset.value_after_depreciation -= d.depreciation_amount + + idx = cint(d.finance_book_id) + finance_books = asset.get('finance_books')[idx - 1] + finance_books.value_after_depreciation -= d.depreciation_amount + finance_books.db_update() - asset.db_set("value_after_depreciation", asset.value_after_depreciation) asset.set_status() return asset diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js index 0535743703..11c02e105f 100644 --- a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.js @@ -2,7 +2,29 @@ // For license information, please see license.txt frappe.ui.form.on('Asset Adjustment', { - refresh: function(frm) { + asset: function(frm) { + frm.trigger("set_current_asset_value"); + }, + finance_book: function(frm) { + frm.trigger("set_current_asset_value"); + }, + + set_current_asset_value: function(frm) { + debugger + if (frm.doc.finance_book && frm.doc.asset) { + frm.call({ + method: "erpnext.assets.doctype.asset_adjustment.asset_adjustment.get_current_asset_value", + args: { + asset: frm.doc.asset, + finance_book: frm.doc.finance_book + }, + callback: function(r) { + if (r.message) { + frm.set_value('current_asset_value', r.message); + } + } + }); + } } }); diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json index 8a0e95775a..faa36efe07 100644 --- a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.json @@ -12,6 +12,37 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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, @@ -38,7 +69,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -100,37 +131,7 @@ "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": "current_asset_value", - "fieldtype": "Currency", - "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": "Current Asset Value", - "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, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -231,27 +232,26 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "company", - "fieldtype": "Link", + "fieldname": "current_asset_value", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_global_search": 0, - "in_list_view": 0, + "in_list_view": 1, "in_standard_filter": 0, - "label": "Company", + "label": "Current Asset Value", "length": 0, "no_copy": 0, - "options": "Company", "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, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -281,7 +281,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -292,8 +292,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "accumulated_depreciation_account", - "fieldtype": "Link", + "fieldname": "difference_amount", + "fieldtype": "Currency", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -301,15 +301,14 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Accumulated Depreciation Account", + "label": "Difference Amount", "length": 0, - "no_copy": 0, - "options": "Account", + "no_copy": 1, "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, @@ -358,7 +357,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-11 00:25:07.222408", + "modified": "2018-05-11 21:45:03.459696", "modified_by": "Administrator", "module": "Assets", "name": "Asset Adjustment", diff --git a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py index 437f9bd902..6b4b752e7f 100644 --- a/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py +++ b/erpnext/assets/doctype/asset_adjustment/asset_adjustment.py @@ -4,7 +4,100 @@ from __future__ import unicode_literals import frappe +from frappe import _ +from frappe.utils import flt, getdate, cint, date_diff +from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts from frappe.model.document import Document class AssetAdjustment(Document): - pass + def validate(self): + self.set_difference_amount() + self.set_current_asset_value() + + def on_submit(self): + self.make_depreciation_entry() + self.reschedule_depreciations() + + def on_cancel(self): + if self.journal_entry: + frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry)) + + def set_difference_amount(self): + self.difference_amount = flt(self.current_asset_value - self.new_asset_value) + + def set_current_asset_value(self): + if not self.current_asset_value and self.asset and self.finance_book: + self.current_asset_value = get_current_asset_value(self.asset, self.finance_book) + + def make_depreciation_entry(self): + asset = frappe.get_doc("Asset", self.asset) + fixed_asset_account, accumulated_depreciation_account, depreciation_expense_account = \ + get_depreciation_accounts(asset) + + depreciation_cost_center, depreciation_series = frappe.db.get_value("Company", asset.company, + ["depreciation_cost_center", "series_for_depreciation_entry"]) + + je = frappe.new_doc("Journal Entry") + je.voucher_type = "Depreciation Entry" + je.naming_series = depreciation_series + je.posting_date = self.date + je.company = self.company + je.remark = "Depreciation Entry against {0} worth {1}".format(self.asset, self.difference_amount) + + je.append("accounts", { + "account": accumulated_depreciation_account, + "credit_in_account_currency": self.difference_amount, + }) + + je.append("accounts", { + "account": depreciation_expense_account, + "debit_in_account_currency": self.difference_amount, + "cost_center": depreciation_cost_center + }) + + je.flags.ignore_permissions = True + je.submit() + + self.db_set("journal_entry", je.name) + + def reschedule_depreciations(self): + asset = frappe.get_doc('Asset', self.asset) + + for d in asset.finance_books: + d.value_after_depreciation = self.new_asset_value + + if d.depreciation_method in ("Straight Line", "Manual"): + end_date = max([s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx]) + total_days = date_diff(end_date, self.date) + rate_per_day = flt(d.value_after_depreciation) / flt(total_days) + from_date = self.date + else: + no_of_depreciations = len([e.name for e in asset.schedules + if (cint(s.finance_book_id) == d.idx and not e.journal_entry)]) + + value_after_depreciation = d.value_after_depreciation + for data in asset.schedules: + if cint(data.finance_book_id) == d.idx and not data.journal_entry: + if d.depreciation_method in ("Straight Line", "Manual"): + days = date_diff(data.schedule_date, from_date) + depreciation_amount = days * rate_per_day + from_date = data.schedule_date + else: + depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, + no_of_depreciations, d) + + if depreciation_amount: + value_after_depreciation -= flt(depreciation_amount) + data.depreciation_amount = depreciation_amount + + d.db_update() + + asset.set_accumulated_depreciation(ignore_booked_entry=True) + for asset_data in asset.schedules: + if not asset_data.journal_entry: + asset_data.db_update() + +@frappe.whitelist() +def get_current_asset_value(asset, finance_book): + return frappe.db.get_value('Asset Finance Book', + {'parent': asset, 'parenttype': 'Asset', 'finance_book': finance_book}, 'value_after_depreciation', debug=1) diff --git a/erpnext/config/assets.py b/erpnext/config/assets.py index 99a7a5c744..d52f1420cd 100644 --- a/erpnext/config/assets.py +++ b/erpnext/config/assets.py @@ -43,6 +43,10 @@ def get_data(): "type": "doctype", "name": "Asset Maintenance Log", }, + { + "type": "doctype", + "name": "Asset Adjustment", + }, { "type": "doctype", "name": "Asset Repair", diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index f33ed25a06..c4e9fdd61e 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -511,6 +511,7 @@ class BuyingController(StockController): item_data = frappe.db.get_value('Item', row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1) + purchase_amount = flt(row.base_net_amount + row.item_tax_amount) asset = frappe.get_doc({ 'doctype': 'Asset', 'item_code': row.item_code, @@ -522,7 +523,8 @@ class BuyingController(StockController): 'company': self.company, 'purchase_date': self.posting_date, 'calculate_depreciation': 1, - 'gross_purchase_amount': flt(row.base_net_amount + row.item_tax_amount), + 'purchase_receipt_amount': purchase_amount, + 'gross_purchase_amount': purchase_amount, 'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None, 'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None }) From 352df959765e8bff3edfdf4f178870fd100cddc7 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 12 May 2018 12:29:04 +0530 Subject: [PATCH 68/79] Patch to make location --- erpnext/patches.txt | 1 + .../v11_0/make_location_from_warehouse.py | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 erpnext/patches/v11_0/make_location_from_warehouse.py diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 4bbb3e9e70..2828d7788b 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -528,3 +528,4 @@ erpnext.patches.v11_0.create_salary_structure_assignments erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company erpnext.patches.v11_0.create_department_records_for_each_company +erpnext.patches.v11_0.make_location_from_warehouse \ No newline at end of file diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py new file mode 100644 index 0000000000..a3c66635b2 --- /dev/null +++ b/erpnext/patches/v11_0/make_location_from_warehouse.py @@ -0,0 +1,27 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe + +def execute(): + frappe.reload_doc('assets', 'doctype', 'location') + frappe.reload_doc('stock', 'doctype', 'warehouse') + + for d in frappe.get_all('Warehouse', + fields = ['warehouse_name', 'is_group', 'parent_warehouse'], order_by="is_group desc"): + try: + loc = frappe.new_doc('Location') + loc.location_name = d.warehouse_name + loc.is_group = d.is_group + loc.flags.ignore_mandatory = True + if d.parent_warehouse: + loc.parent_location = get_parent_warehouse_name(d.parent_warehouse) + + loc.save(ignore_permissions=True) + except frappe.DuplicateEntryError: + continue + +def get_parent_warehouse_name(warehouse): + return frappe.db.get_value('Warehouse', warehouse, 'warehouse_name') + \ No newline at end of file From 11679884e4b7f629bbb39760c2d9ca67792c8dcc Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 12 May 2018 15:05:09 +0530 Subject: [PATCH 69/79] Patch to move asset fields to Asset Finance Book table --- .../asset_category/asset_category.json | 94 +------------------ .../doctype/asset_category/asset_category.py | 7 +- .../asset_finance_book.json | 9 +- ..._asset_finance_book_against_old_entries.py | 46 +++++++++ .../v11_0/make_location_from_warehouse.py | 3 + 5 files changed, 59 insertions(+), 100 deletions(-) create mode 100644 erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py diff --git a/erpnext/assets/doctype/asset_category/asset_category.json b/erpnext/assets/doctype/asset_category/asset_category.json index b655b40475..882cbe2eaa 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.json +++ b/erpnext/assets/doctype/asset_category/asset_category.json @@ -43,38 +43,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Straight Line", - "fieldname": "depreciation_method", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Depreciation Method", - "length": 0, - "no_copy": 0, - "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual", - "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, @@ -104,66 +72,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "total_number_of_depreciations", - "fieldtype": "Int", - "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": "Total Number of Depreciations", - "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, - "fieldname": "frequency_of_depreciation", - "fieldtype": "Int", - "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": "Frequency of Depreciation (Months)", - "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, @@ -297,7 +205,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-10 15:12:05.954200", + "modified": "2018-05-12 14:56:04.116425", "modified_by": "Administrator", "module": "Assets", "name": "Asset Category", diff --git a/erpnext/assets/doctype/asset_category/asset_category.py b/erpnext/assets/doctype/asset_category/asset_category.py index fa0bd83117..bbdc6ec2cf 100644 --- a/erpnext/assets/doctype/asset_category/asset_category.py +++ b/erpnext/assets/doctype/asset_category/asset_category.py @@ -10,9 +10,10 @@ from frappe.model.document import Document class AssetCategory(Document): def validate(self): - for field in ("total_number_of_depreciations", "frequency_of_depreciation"): - if cint(self.get(field))<1: - frappe.throw(_("{0} must be greater than 0").format(self.meta.get_label(field)), frappe.MandatoryError) + for d in self.finance_books: + for field in ("Total Number of Depreciations", "Frequency of Depreciation"): + if cint(d.get(frappe.scrub(field)))<1: + frappe.throw(_("Row {0}: {1} must be greater than 0").format(d.idx, field), frappe.MandatoryError) @frappe.whitelist() def get_asset_category_account(asset, fieldname, account=None, asset_category = None, company = None): diff --git a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json index 351f9d0c66..f75c8510dc 100644 --- a/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json +++ b/erpnext/assets/doctype/asset_finance_book/asset_finance_book.json @@ -70,7 +70,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -100,7 +100,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -160,7 +160,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -203,6 +203,7 @@ "collapsible": 0, "columns": 0, "default": "0", + "depends_on": "eval:parent.doctype == 'Asset'", "fieldname": "expected_value_after_useful_life", "fieldtype": "Currency", "hidden": 0, @@ -270,7 +271,7 @@ "issingle": 0, "istable": 1, "max_attachments": 0, - "modified": "2018-05-10 18:05:58.900298", + "modified": "2018-05-12 14:56:44.800046", "modified_by": "Administrator", "module": "Assets", "name": "Asset Finance Book", diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py new file mode 100644 index 0000000000..75f0ce6450 --- /dev/null +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -0,0 +1,46 @@ +# Copyright (c) 2017, Frappe and Contributors +# License: GNU General Public License v3. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.nestedset import rebuild_tree + +def execute(): + frappe.reload_doc('stock', 'doctype', 'asset_finance_book') + frappe.reload_doc('stock', 'doctype', 'depreciation_schedule') + frappe.reload_doc('assets', 'doctype', 'asset_category') + frappe.reload_doc('assets', 'doctype', 'asset') + frappe.reload_doc('assets', 'doctype', 'asset_movement') + + frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh + set ast.location = wh.warehoue_name where ast.warehoue = wh.name""") + + frappe.db.sql(""" update `tabAsset Movement` ast_mv + set ast_mv.source_location = (select warehoue_name from `tabWarehouse` where name = ast_mv.source_warehouse), + ast_mv.target_location = (select warehoue_name from `tabWarehouse` where name = ast_mv.target_warehouse)""") + + for d in frappe.get_all('Asset'): + doc = frappe.get_doc('Asset', d.name) + fb = doc.append('finance_books', { + 'depreciation_method': doc.depreciation_method, + 'total_number_of_depreciations': doc.total_number_of_depreciations, + 'frequency_of_depreciation': doc.frequency_of_depreciation, + 'depreciation_start_date': doc.next_depreciation_date, + 'expected_value_after_useful_life': doc.expected_value_after_useful_life, + 'value_after_depreciation': doc.value_after_depreciation + }) + + fb.db_update() + + frappe.db.sql(""" update `tabDepreciation Schedule` ds, `tabAsset` ast + set ds.depreciation_method = ast.depreciation_method, ds.finance_book_id = 1 where ds.parent = ast.name """) + + for catergory in frappe.get_all('Asset Category'): + asset_category_doc = frappe.get_doc("Asset Category", catergory) + row = asset_category_doc.append('finance_books', { + 'depreciation_method': asset_category_doc.depreciation_method, + 'total_number_of_depreciations': asset_category_doc.total_number_of_depreciations, + 'frequency_of_depreciation': asset_category_doc.frequency_of_depreciation + }) + + row.db_update() \ No newline at end of file diff --git a/erpnext/patches/v11_0/make_location_from_warehouse.py b/erpnext/patches/v11_0/make_location_from_warehouse.py index a3c66635b2..b838ec98bc 100644 --- a/erpnext/patches/v11_0/make_location_from_warehouse.py +++ b/erpnext/patches/v11_0/make_location_from_warehouse.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from frappe.utils.nestedset import rebuild_tree def execute(): frappe.reload_doc('assets', 'doctype', 'location') @@ -22,6 +23,8 @@ def execute(): except frappe.DuplicateEntryError: continue + rebuild_tree("Location", "parent_location") + def get_parent_warehouse_name(warehouse): return frappe.db.get_value('Warehouse', warehouse, 'warehouse_name') \ No newline at end of file From d644e6da16979bcd56c05b6fbcaffef0a7d89119 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 12 May 2018 15:27:18 +0530 Subject: [PATCH 70/79] Code cleanup --- erpnext/assets/doctype/asset/asset.py | 6 ++-- erpnext/controllers/buying_controller.py | 3 ++ erpnext/patches.txt | 3 +- ..._asset_finance_book_against_old_entries.py | 29 ++++++++++--------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py index 748849ecb2..55a29bc0df 100644 --- a/erpnext/assets/doctype/asset/asset.py +++ b/erpnext/assets/doctype/asset/asset.py @@ -105,18 +105,18 @@ class Asset(AccountsController): previous_scheduled_date = add_months(d.depreciation_start_date, (n-1) * 12) depreciation_amount = \ self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - row, previous_scheduled_date, schedule_date) + d, previous_scheduled_date, schedule_date) elif n == range(number_of_pending_depreciations)[0]: schedule_date = d.depreciation_start_date depreciation_amount = \ self.get_depreciation_amount_prorata_temporis(value_after_depreciation, - row, self.available_for_use_date, schedule_date) + d, self.available_for_use_date, schedule_date) else: schedule_date = add_months(d.depreciation_start_date, n * 12) depreciation_amount = \ - self.get_depreciation_amount_prorata_temporis(value_after_depreciation, row) + self.get_depreciation_amount_prorata_temporis(value_after_depreciation, d) if value_after_depreciation != 0: value_after_depreciation -= flt(depreciation_amount) diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py index c4e9fdd61e..85fb3f0a66 100644 --- a/erpnext/controllers/buying_controller.py +++ b/erpnext/controllers/buying_controller.py @@ -89,6 +89,9 @@ class BuyingController(StockController): msgprint(_('Tax Category has been changed to "Total" because all the Items are non-stock items')) def get_asset_items(self): + if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']: + return [] + return [d.item_code for d in self.items if d.is_fixed_asset] def set_landed_cost_voucher_amount(self): diff --git a/erpnext/patches.txt b/erpnext/patches.txt index 2828d7788b..69b11d65d3 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -528,4 +528,5 @@ erpnext.patches.v11_0.create_salary_structure_assignments erpnext.patches.v11_0.rename_health_insurance erpnext.patches.v11_0.rebuild_tree_for_company erpnext.patches.v11_0.create_department_records_for_each_company -erpnext.patches.v11_0.make_location_from_warehouse \ No newline at end of file +erpnext.patches.v11_0.make_location_from_warehouse +erpnext.patches.v11_0.make_asset_finance_book_against_old_entries \ No newline at end of file diff --git a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py index 75f0ce6450..18622f2301 100644 --- a/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py +++ b/erpnext/patches/v11_0/make_asset_finance_book_against_old_entries.py @@ -6,31 +6,32 @@ import frappe from frappe.utils.nestedset import rebuild_tree def execute(): - frappe.reload_doc('stock', 'doctype', 'asset_finance_book') - frappe.reload_doc('stock', 'doctype', 'depreciation_schedule') + frappe.reload_doc('assets', 'doctype', 'asset_finance_book') + frappe.reload_doc('assets', 'doctype', 'depreciation_schedule') frappe.reload_doc('assets', 'doctype', 'asset_category') frappe.reload_doc('assets', 'doctype', 'asset') frappe.reload_doc('assets', 'doctype', 'asset_movement') frappe.db.sql(""" update `tabAsset` ast, `tabWarehouse` wh - set ast.location = wh.warehoue_name where ast.warehoue = wh.name""") + set ast.location = wh.warehouse_name where ast.warehouse = wh.name""") frappe.db.sql(""" update `tabAsset Movement` ast_mv - set ast_mv.source_location = (select warehoue_name from `tabWarehouse` where name = ast_mv.source_warehouse), - ast_mv.target_location = (select warehoue_name from `tabWarehouse` where name = ast_mv.target_warehouse)""") + set ast_mv.source_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.source_warehouse), + ast_mv.target_location = (select warehouse_name from `tabWarehouse` where name = ast_mv.target_warehouse)""") for d in frappe.get_all('Asset'): doc = frappe.get_doc('Asset', d.name) - fb = doc.append('finance_books', { - 'depreciation_method': doc.depreciation_method, - 'total_number_of_depreciations': doc.total_number_of_depreciations, - 'frequency_of_depreciation': doc.frequency_of_depreciation, - 'depreciation_start_date': doc.next_depreciation_date, - 'expected_value_after_useful_life': doc.expected_value_after_useful_life, - 'value_after_depreciation': doc.value_after_depreciation - }) + if doc.calculate_depreciation: + fb = doc.append('finance_books', { + 'depreciation_method': doc.depreciation_method, + 'total_number_of_depreciations': doc.total_number_of_depreciations, + 'frequency_of_depreciation': doc.frequency_of_depreciation, + 'depreciation_start_date': doc.next_depreciation_date, + 'expected_value_after_useful_life': doc.expected_value_after_useful_life, + 'value_after_depreciation': doc.value_after_depreciation + }) - fb.db_update() + fb.db_update() frappe.db.sql(""" update `tabDepreciation Schedule` ds, `tabAsset` ast set ds.depreciation_method = ast.depreciation_method, ds.finance_book_id = 1 where ds.parent = ast.name """) From 75c53e5022ec9b513b4dadabe174560ea3614984 Mon Sep 17 00:00:00 2001 From: Jamsheer Date: Mon, 14 May 2018 19:17:16 +0530 Subject: [PATCH 71/79] Fix - missing semicolon --- .../additional_salary_component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js index 36bfdf2a08..13ed239b36 100644 --- a/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js +++ b/erpnext/hr/doctype/additional_salary_component/additional_salary_component.js @@ -8,14 +8,14 @@ frappe.ui.form.on('Additional Salary Component', { filters: { type: "earning" } - } + }; }); frm.set_query("employee", function() { return { filters: { company: frm.doc.company } - } + }; }); } }); From 4bd5583e11608344ec31453ff6e7167eb43c2336 Mon Sep 17 00:00:00 2001 From: Shreya Date: Mon, 14 May 2018 16:14:41 +0530 Subject: [PATCH 72/79] Make Available Leave HTML Table --- .../leave_application/leave_application.js | 30 ++++++++++++++++++- .../leave_application/leave_application.py | 26 ++++++++++++++-- .../leave_application_dashboard.html | 30 +++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 erpnext/hr/doctype/leave_application/leave_application_dashboard.html diff --git a/erpnext/hr/doctype/leave_application/leave_application.js b/erpnext/hr/doctype/leave_application/leave_application.js index 242c987e58..76b5ae54fa 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.js +++ b/erpnext/hr/doctype/leave_application/leave_application.js @@ -18,7 +18,7 @@ frappe.ui.form.on("Leave Application", { doctype: frm.doc.doctype } }; - }); + }); frm.set_query("employee", erpnext.queries.employee); }, @@ -27,6 +27,33 @@ frappe.ui.form.on("Leave Application", { frm.toggle_reqd("half_day_date", frm.doc.half_day == 1); }, + make_dashboard: function(frm) { + var leave_details; + if (frm.doc.employee) { + frappe.call({ + method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details", + async: false, + args: { + employee: frm.doc.employee, + date: frm.doc.posting_date + }, + callback: function(r) { + if (!r.exc && r.message) { + leave_details = r.message; + } + } + }); + + $("div").remove(".form-dashboard-section"); + let section = frm.dashboard.add_section( + frappe.render_template('leave_application_dashboard', { + data: leave_details + }) + ); + frm.dashboard.show(); + } + }, + refresh: function(frm) { if (frm.is_new()) { frm.trigger("calculate_total_days"); @@ -43,6 +70,7 @@ frappe.ui.form.on("Leave Application", { }, employee: function(frm) { + frm.trigger("make_dashboard"); frm.trigger("get_leave_balance"); }, diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py index 23514e16e2..304afdd7ed 100755 --- a/erpnext/hr/doctype/leave_application/leave_application.py +++ b/erpnext/hr/doctype/leave_application/leave_application.py @@ -19,6 +19,7 @@ class NotAnOptionalHoliday(frappe.ValidationError): pass from frappe.model.document import Document class LeaveApplication(Document): + def get_feed(self): return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type) @@ -306,6 +307,24 @@ def get_number_of_leave_days(employee, leave_type, from_date, to_date, half_day number_of_days = flt(number_of_days) - flt(get_holidays(employee, from_date, to_date)) return number_of_days +@frappe.whitelist() +def get_leave_details(employee, date): + allocation_records = get_leave_allocation_records(date, employee).get(employee, frappe._dict()) + leave_allocation = {} + for d in allocation_records: + allocation = allocation_records.get(d, frappe._dict()) + date = allocation.to_date + leaves_taken = get_leaves_for_period(employee, d, allocation.from_date, date, status="Approved") + leaves_pending = get_leaves_for_period(employee, d, allocation.from_date, date, status="Open") + remaining_leaves = allocation.total_leaves_allocated - leaves_taken - leaves_pending + leave_allocation[d] = { + "total_leaves": allocation.total_leaves_allocated, + "leaves_taken": leaves_taken, + "pending_leaves": leaves_pending, + "remaining_leaves": remaining_leaves} + + return leave_allocation + @frappe.whitelist() def get_leave_balance_on(employee, leave_type, date, allocation_records=None, consider_all_leaves_in_the_allocation_period=False): @@ -316,16 +335,16 @@ def get_leave_balance_on(employee, leave_type, date, allocation_records=None, if consider_all_leaves_in_the_allocation_period: date = allocation.to_date - leaves_taken = get_approved_leaves_for_period(employee, leave_type, allocation.from_date, date) + leaves_taken = get_leaves_for_period(employee, leave_type, allocation.from_date, date, status=Approved) return flt(allocation.total_leaves_allocated) - flt(leaves_taken) -def get_approved_leaves_for_period(employee, leave_type, from_date, to_date): +def get_leaves_for_period(employee, leave_type, from_date, to_date, status): leave_applications = frappe.db.sql(""" select employee, leave_type, from_date, to_date, total_leave_days from `tabLeave Application` where employee=%(employee)s and leave_type=%(leave_type)s - and docstatus=1 + and status = %(status)s and docstatus=1 and (from_date between %(from_date)s and %(to_date)s or to_date between %(from_date)s and %(to_date)s or (from_date < %(from_date)s and to_date > %(to_date)s)) @@ -333,6 +352,7 @@ def get_approved_leaves_for_period(employee, leave_type, from_date, to_date): "from_date": from_date, "to_date": to_date, "employee": employee, + "status": status, "leave_type": leave_type }, as_dict=1) diff --git a/erpnext/hr/doctype/leave_application/leave_application_dashboard.html b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html new file mode 100644 index 0000000000..95e74a671a --- /dev/null +++ b/erpnext/hr/doctype/leave_application/leave_application_dashboard.html @@ -0,0 +1,30 @@ + +{% if data %} +
    {{ __("Allocated Leaves") }}
    + + + + + + + + + + + + + + {% for(const [key, value] of Object.entries(data)) { %} + + + + + + + + {% } %} + +
    {{ __("Leave Type") }}{{ __("Total Allocated Leaves") }}{{ __("Used Leaves") }}{{ __("Pending Leaves") }}{{ __("Available Leaves") }}
    {%= key %} {%= value["total_leaves"] %} {%= value["leaves_taken"] %} {%= value["pending_leaves"] %} {%= value["remaining_leaves"] %}
    +{% } else { %} +

    No Leaves have been allocated.

    +{% } %} \ No newline at end of file From b698846c300c9f42ea6951d41cbd54f4c19d31cd Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Thu, 10 May 2018 18:07:20 +0530 Subject: [PATCH 73/79] [feature] added the employee onboarding and separation doctype --- erpnext/hr/doctype/employee/employee.json | 66 +- erpnext/hr/doctype/employee/employee.py | 10 + .../employee_boarding_activity/__init__.py | 0 .../employee_boarding_activity.json | 291 +++++++++ .../employee_boarding_activity.py | 10 + .../doctype/employee_onboarding/__init__.py | 0 .../employee_onboarding.js | 59 ++ .../employee_onboarding.json | 569 ++++++++++++++++++ .../employee_onboarding.py | 50 ++ .../test_employee_onboarding.js | 23 + .../test_employee_onboarding.py | 10 + .../employee_onboarding_activity/__init__.py | 0 .../employee_onboarding_activity.json | 290 +++++++++ .../employee_onboarding_activity.py | 10 + .../employee_onboarding_template/__init__.py | 0 .../employee_onboarding_template.js | 8 + .../employee_onboarding_template.json | 284 +++++++++ .../employee_onboarding_template.py | 10 + .../test_employee_onboarding_template.js | 23 + .../test_employee_onboarding_template.py | 10 + .../doctype/employee_separation/__init__.py | 0 .../employee_separation.js | 49 ++ .../employee_separation.json | 567 +++++++++++++++++ .../employee_separation.py | 16 + .../test_employee_separation.js | 23 + .../test_employee_separation.py | 10 + .../employee_separation_template/__init__.py | 0 .../employee_separation_template.js | 8 + .../employee_separation_template.json | 284 +++++++++ .../employee_separation_template.py | 10 + .../test_employee_separation_template.js | 23 + .../test_employee_separation_template.py | 10 + erpnext/hr/utils.py | 100 ++- erpnext/projects/doctype/project/project.json | 97 +-- erpnext/projects/doctype/task/task.json | 73 ++- 35 files changed, 2913 insertions(+), 80 deletions(-) create mode 100644 erpnext/hr/doctype/employee_boarding_activity/__init__.py create mode 100644 erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json create mode 100644 erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py create mode 100644 erpnext/hr/doctype/employee_onboarding/__init__.py create mode 100644 erpnext/hr/doctype/employee_onboarding/employee_onboarding.js create mode 100644 erpnext/hr/doctype/employee_onboarding/employee_onboarding.json create mode 100644 erpnext/hr/doctype/employee_onboarding/employee_onboarding.py create mode 100644 erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js create mode 100644 erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py create mode 100644 erpnext/hr/doctype/employee_onboarding_activity/__init__.py create mode 100644 erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json create mode 100644 erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py create mode 100644 erpnext/hr/doctype/employee_onboarding_template/__init__.py create mode 100644 erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.js create mode 100644 erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.json create mode 100644 erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py create mode 100644 erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js create mode 100644 erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py create mode 100644 erpnext/hr/doctype/employee_separation/__init__.py create mode 100644 erpnext/hr/doctype/employee_separation/employee_separation.js create mode 100644 erpnext/hr/doctype/employee_separation/employee_separation.json create mode 100644 erpnext/hr/doctype/employee_separation/employee_separation.py create mode 100644 erpnext/hr/doctype/employee_separation/test_employee_separation.js create mode 100644 erpnext/hr/doctype/employee_separation/test_employee_separation.py create mode 100644 erpnext/hr/doctype/employee_separation_template/__init__.py create mode 100644 erpnext/hr/doctype/employee_separation_template/employee_separation_template.js create mode 100644 erpnext/hr/doctype/employee_separation_template/employee_separation_template.json create mode 100644 erpnext/hr/doctype/employee_separation_template/employee_separation_template.py create mode 100644 erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js create mode 100644 erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index ef2c75dc83..3527197edc 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -393,6 +393,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "job_applicant", + "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": "Job Applicant", + "length": 0, + "no_copy": 0, + "options": "Job Applicant", + "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_on_submit": 0, @@ -1262,38 +1294,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "leave_policy", - "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 Policy", - "length": 0, - "no_copy": 0, - "options": "Leave Policy", - "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_on_submit": 0, @@ -2766,7 +2766,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-25 09:08:06.852604", + "modified": "2018-05-10 07:52:24.326361", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py index 4cf28a16ec..824ddf5aa7 100755 --- a/erpnext/hr/doctype/employee/employee.py +++ b/erpnext/hr/doctype/employee/employee.py @@ -42,6 +42,8 @@ class Employee(NestedSet): self.validate_status() self.validate_reports_to() self.validate_preferred_email() + if self.job_applicant: + self.validate_onboarding_process() if self.user_id: self.validate_for_enabled_user_id() @@ -164,6 +166,14 @@ class Employee(NestedSet): if self.prefered_contact_email and not self.get(scrub(self.prefered_contact_email)): frappe.msgprint(_("Please enter " + self.prefered_contact_email)) + def validate_onboarding_process(self): + employee_onboarding = frappe.get_all("Employee Onboarding", + filters={"job_applicant": self.job_applicant, "docstatus": 1, "status": ("!=", "Completed")}) + if employee_onboarding: + doc = frappe.get_doc("Employee Onboarding", employee_onboarding[0].name) + doc.validate_employee_creation() + doc.db_set("employee", self.name) + def get_timeline_data(doctype, name): '''Return timeline for attendance''' return dict(frappe.db.sql('''select unix_timestamp(attendance_date), count(*) diff --git a/erpnext/hr/doctype/employee_boarding_activity/__init__.py b/erpnext/hr/doctype/employee_boarding_activity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json new file mode 100644 index 0000000000..95d693994e --- /dev/null +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json @@ -0,0 +1,291 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-09 05:37:18.439763", + "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, + "fieldname": "activity_name", + "fieldtype": "Data", + "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": "Activity Name", + "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_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": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "role", + "fieldtype": "Link", + "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": "Role", + "length": 0, + "no_copy": 0, + "options": "Role", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "task", + "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": "Task", + "length": 0, + "no_copy": 1, + "options": "Task", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "description": "Applicable in the case of Employee Onboarding", + "fieldname": "required_for_employee_creation", + "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": "Required for Employee Creation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "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": "Description", + "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 + } + ], + "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": "2018-05-10 06:54:47.282492", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Boarding Activity", + "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": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py new file mode 100644 index 0000000000..496f1653ba --- /dev/null +++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class EmployeeBoardingActivity(Document): + pass diff --git a/erpnext/hr/doctype/employee_onboarding/__init__.py b/erpnext/hr/doctype/employee_onboarding/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js new file mode 100644 index 0000000000..e95e260fa9 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js @@ -0,0 +1,59 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Onboarding', { + setup: function(frm) { + frm.add_fetch("employee_onboarding_template", "company", "company"); + frm.add_fetch("employee_onboarding_template", "department", "department"); + frm.add_fetch("employee_onboarding_template", "designation", "designation"); + frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade"); + }, + + refresh: function(frm) { + if (frm.doc.employee) { + frm.add_custom_button(__('Employee'), function() { + frappe.set_route("Form", "Employee", frm.doc.employee); + },__("View")); + } + if (frm.doc.project) { + frm.add_custom_button(__('Project'), function() { + frappe.set_route("Form", "Project", frm.doc.project); + },__("View")); + frm.add_custom_button(__('Task'), function() { + frappe.set_route('List', 'Task', {project: frm.doc.project}); + },__("View")); + } + if ((!frm.doc.employee) && (frm.doc.docstatus === 1)) { + frm.add_custom_button(__('Employee'), function () { + frappe.model.open_mapped_doc({ + method: "erpnext.hr.doctype.employee_onboarding.employee_onboarding.make_employee", + frm: frm + }); + }, __("Make")); + frm.page.set_inner_btn_group_as_primary(__("Make")); + } + + }, + + employee_onboarding_template: function(frm) { + frm.set_value("activities" ,""); + if (frm.doc.employee_onboarding_template) { + frappe.call({ + method: "erpnext.hr.utils.get_onboarding_details", + args: { + "parent": frm.doc.employee_onboarding_template, + "parenttype": "Employee Onboarding Template" + }, + callback: function(r) { + if (r.message) { + $.each(r.message, function(i, d) { + var row = frappe.model.add_child(frm.doc, "Employee Boarding Activity", "activities"); + $.extend(row, d); + }); + } + refresh_field("activities"); + } + }); + } + } +}); diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json new file mode 100644 index 0000000000..bd2ec4ce9d --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json @@ -0,0 +1,569 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "EOB.#####", + "beta": 0, + "creation": "2018-05-09 04:57:20.016220", + "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, + "fieldname": "employee_name", + "fieldtype": "Data", + "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": "Employee Name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee", + "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": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "job_offer", + "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": "Job Offer", + "length": 0, + "no_copy": 0, + "options": "Job Offer", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "job_applicant", + "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": "Job Applicant", + "length": 0, + "no_copy": 0, + "options": "Job Applicant", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "date_of_joining", + "fieldtype": "Date", + "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": "Date of Joining", + "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_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "boarding_status", + "fieldtype": "Select", + "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": "Status", + "length": 0, + "no_copy": 0, + "options": "\nPending\nIn Process\nCompleted", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_onboarding_template", + "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": "Employee Onboarding Template", + "length": 0, + "no_copy": 0, + "options": "Employee Onboarding Template", + "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_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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Link", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Link", + "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": "Designation", + "length": 0, + "no_copy": 0, + "options": "Designation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_grade", + "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": "Employee Grade", + "length": 0, + "no_copy": 0, + "options": "Employee Grade", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "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": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "table_for_activity", + "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": "", + "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_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activities", + "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": "Activities", + "length": 0, + "no_copy": 0, + "options": "Employee Boarding Activity", + "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_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": "Employee Onboarding", + "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": "2018-05-10 06:34:21.103617", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Onboarding", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "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": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py new file mode 100644 index 0000000000..3390e8ffe5 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, 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.hr.utils import EmployeeBoardingController +from frappe.model.mapper import get_mapped_doc + + +class EmployeeOnboarding(EmployeeBoardingController): + def validate(): + super(EmployeeOnboarding, self).validate() + + def validate_employee_creation(self): + if self.docstatus != 1: + frappe.throw(_("Submit this to create the Employee record")) + else: + for activity in self.activities: + if not activity.required_for_employee_creation: + continue + else: + task_status = frappe.db.get_value("Task", activity.task, "status") + if task_status not in ["Closed", "Cancelled"]: + frappe.throw(_("All the mandatory Task for employee creation hasn't been done yet.")) + + def on_submit(self): + super(EmployeeOnboarding, self).on_submit() + + def on_cancel(self): + super(EmployeeOnboarding, self).on_cancel() + + +@frappe.whitelist() +def make_employee(source_name, target_doc=None): + doc = frappe.get_doc("Employee Onboarding", source_name) + doc.validate_employee_creation() + def set_missing_values(source, target): + target.personal_email = frappe.db.get_value("Job Applicant", source.job_applicant, "email_id") + target.status = "Active" + doc = get_mapped_doc("Employee Onboarding", source_name, { + "Employee Onboarding": { + "doctype": "Employee", + "field_map": { + "employee_grade": "grade", + }} + }, target_doc, set_missing_values) + return doc + diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js new file mode 100644 index 0000000000..d15cef77dc --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Employee Onboarding", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Employee Onboarding + () => frappe.tests.make('Employee Onboarding', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py new file mode 100644 index 0000000000..b37ae883b6 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestEmployeeOnboarding(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/employee_onboarding_activity/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json new file mode 100644 index 0000000000..4e91b72384 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json @@ -0,0 +1,290 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-09 05:37:18.439763", + "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, + "fieldname": "activity_name", + "fieldtype": "Data", + "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": "Activity Name", + "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_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": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 0, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "role", + "fieldtype": "Link", + "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": "Role", + "length": 0, + "no_copy": 0, + "options": "Role", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_3", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "depends_on": "eval: doc.parenttype == \"Employee Onboarding\"", + "fieldname": "completed", + "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": "Completed", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "required_for_employee_creation", + "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": "Required for Employee Creation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_6", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "description", + "fieldtype": "Text Editor", + "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": "Description", + "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 + } + ], + "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": "2018-05-09 06:15:41.768236", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Onboarding Activity", + "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": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py new file mode 100644 index 0000000000..d170631819 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class EmployeeOnboardingActivity(Document): + pass diff --git a/erpnext/hr/doctype/employee_onboarding_template/__init__.py b/erpnext/hr/doctype/employee_onboarding_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.js new file mode 100644 index 0000000000..2a531f31a2 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Onboarding Template', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.json b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.json new file mode 100644 index 0000000000..d0d3a62ba8 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.json @@ -0,0 +1,284 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "EOBT.#####", + "beta": 0, + "creation": "2018-05-09 05:27:02.393377", + "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, + "fieldname": "company", + "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": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Link", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Link", + "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": "Designation", + "length": 0, + "no_copy": 0, + "options": "Designation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_grade", + "fieldtype": "Link", + "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": "Employee Grade", + "length": 0, + "no_copy": 0, + "options": "Employee Grade", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "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": "Activities", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activities", + "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": "Activities", + "length": 0, + "no_copy": 0, + "options": "Employee Boarding Activity", + "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, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-05-09 07:05:21.051519", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Onboarding Template", + "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": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "designation", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py new file mode 100644 index 0000000000..6f1c316731 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_template/employee_onboarding_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class EmployeeOnboardingTemplate(Document): + pass diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js new file mode 100644 index 0000000000..10912edb6a --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Employee Onboarding Template", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Employee Onboarding Template + () => frappe.tests.make('Employee Onboarding Template', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py new file mode 100644 index 0000000000..f4b5b88342 --- /dev/null +++ b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestEmployeeOnboardingTemplate(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/employee_separation/__init__.py b/erpnext/hr/doctype/employee_separation/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.js b/erpnext/hr/doctype/employee_separation/employee_separation.js new file mode 100644 index 0000000000..33830796b6 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation/employee_separation.js @@ -0,0 +1,49 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Separation', { + setup: function(frm) { + frm.add_fetch("employee_separation_template", "company", "company"); + frm.add_fetch("employee_separation_template", "department", "department"); + frm.add_fetch("employee_separation_template", "designation", "designation"); + frm.add_fetch("employee_separation_template", "employee_grade", "employee_grade"); + }, + + refresh: function(frm) { + if (frm.doc.employee) { + frm.add_custom_button(__('Employee'), function() { + frappe.set_route("Form", "Employee", frm.doc.employee); + },__("View")); + } + if (frm.doc.project) { + frm.add_custom_button(__('Project'), function() { + frappe.set_route("Form", "Project", frm.doc.project); + },__("View")); + frm.add_custom_button(__('Task'), function() { + frappe.set_route('List', 'Task', {project: frm.doc.project}); + },__("View")); + } + }, + + employee_separation_template: function(frm) { + frm.set_value("activities" ,""); + if (frm.doc.employee_separation_template) { + frappe.call({ + method: "erpnext.hr.utils.get_onboarding_details", + args: { + "parent": frm.doc.employee_separation_template, + "parenttype": "Employee Separation Template" + }, + callback: function(r) { + if (r.message) { + $.each(r.message, function(i, d) { + var row = frappe.model.add_child(frm.doc, "Employee Boarding Activity", "activities"); + $.extend(row, d); + }); + } + refresh_field("activities"); + } + }); + } + } +}); diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json new file mode 100644 index 0000000000..7d9cfb1c59 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation/employee_separation.json @@ -0,0 +1,567 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "ES.#####", + "beta": 0, + "creation": "2018-05-10 02:29:16.740490", + "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, + "fieldname": "employee", + "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": "Employee", + "length": 0, + "no_copy": 0, + "options": "Employee", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_name", + "fieldtype": "Data", + "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": "Employee Name", + "length": 0, + "no_copy": 0, + "options": "employee.employee_name", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "resignation_letter_date", + "fieldtype": "Date", + "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": "Resignation Letter Date", + "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_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "boarding_status", + "fieldtype": "Select", + "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": "Status", + "length": 0, + "no_copy": 0, + "options": "\nPending\nIn Process\nCompleted", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "project", + "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": "Project", + "length": 0, + "no_copy": 0, + "options": "Project", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_separation_template", + "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": "Employee Separation Template", + "length": 0, + "no_copy": 0, + "options": "Employee Separation Template", + "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_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_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Link", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Link", + "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": "Designation", + "length": 0, + "no_copy": 0, + "options": "Designation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_grade", + "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": "Employee Grade", + "length": 0, + "no_copy": 0, + "options": "Employee Grade", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "table_for_activity", + "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": "", + "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_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activities", + "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": "Activities", + "length": 0, + "no_copy": 0, + "options": "Employee Boarding Activity", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_14", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "exit_interview", + "fieldtype": "Text Editor", + "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": "Exit Interview Summary", + "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_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": "Employee Separation", + "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": "2018-05-10 06:34:53.649332", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Separation", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 1, + "cancel": 1, + "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": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "employee_name", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.py b/erpnext/hr/doctype/employee_separation/employee_separation.py new file mode 100644 index 0000000000..b908b632c8 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation/employee_separation.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +from erpnext.hr.utils import EmployeeBoardingController + +class EmployeeSeparation(EmployeeBoardingController): + def validate(self): + super(EmployeeSeparation, self).validate() + + def on_submit(self): + super(EmployeeSeparation, self).on_submit() + + def on_cancel(self): + super(EmployeeSeparation, self).on_cancel() diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.js b/erpnext/hr/doctype/employee_separation/test_employee_separation.js new file mode 100644 index 0000000000..d6c635951f --- /dev/null +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Employee Separation", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Employee Separation + () => frappe.tests.make('Employee Separation', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py new file mode 100644 index 0000000000..0773fb6ae9 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestEmployeeSeparation(unittest.TestCase): + pass diff --git a/erpnext/hr/doctype/employee_separation_template/__init__.py b/erpnext/hr/doctype/employee_separation_template/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.js new file mode 100644 index 0000000000..172ff9fc5b --- /dev/null +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.js @@ -0,0 +1,8 @@ +// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Employee Separation Template', { + refresh: function(frm) { + + } +}); diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template.json b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.json new file mode 100644 index 0000000000..f1f440a020 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.json @@ -0,0 +1,284 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "EST.#####", + "beta": 0, + "creation": "2018-05-09 06:31:44.498557", + "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, + "fieldname": "company", + "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": "Company", + "length": 0, + "no_copy": 0, + "options": "Company", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "fieldtype": "Link", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "column_break_7", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "designation", + "fieldtype": "Link", + "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": "Designation", + "length": 0, + "no_copy": 0, + "options": "Designation", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "employee_grade", + "fieldtype": "Link", + "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": "Employee Grade", + "length": 0, + "no_copy": 0, + "options": "Employee Grade", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_7", + "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": "Activities", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "activities", + "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": "Activities", + "length": 0, + "no_copy": 0, + "options": "Employee Boarding Activity", + "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, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2018-05-09 07:05:30.792336", + "modified_by": "Administrator", + "module": "HR", + "name": "Employee Separation Template", + "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": 0, + "write": 1 + } + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "designation", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py new file mode 100644 index 0000000000..0508fc462e --- /dev/null +++ b/erpnext/hr/doctype/employee_separation_template/employee_separation_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class EmployeeSeparationTemplate(Document): + pass diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js new file mode 100644 index 0000000000..66fd450804 --- /dev/null +++ b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: Employee Separation Template", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially([ + // insert a new Employee Separation Template + () => frappe.tests.make('Employee Separation Template', [ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py new file mode 100644 index 0000000000..3fd3d398bd --- /dev/null +++ b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors +# See license.txt +from __future__ import unicode_literals + +import frappe +import unittest + +class TestEmployeeSeparationTemplate(unittest.TestCase): + pass diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 686f79170c..076b1452df 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -6,11 +6,100 @@ import frappe from frappe import _ from frappe.utils import formatdate, format_datetime from frappe.utils import getdate, get_datetime +from frappe.model.document import Document +from frappe.desk.form import assign_to + +class EmployeeBoardingController(Document): + ''' + Create the project and the task for the boarding process + Assign to the concerned person and roles as per the onboarding/separation template + ''' + def validate(self): + # remove the task if linked before submitting the form + if self.amended_from: + for activity in self.activities: + activity.task = '' + + def on_submit(self): + # create the project for the given employee onboarding + project_name = self.doctype + " for " + self.employee_name + if self.doctype == "Employee Onboarding": + project_name += " (" + self.job_applicant + ")" + else: + project_name += " (" + self.employee + ")" + project = frappe.get_doc({ + "doctype": "Project", + "project_name": project_name, + "expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date, + "department": self.department, + "company": self.company + }).insert(ignore_permissions=True) + self.db_set("project", project.name) + + # create the task for the given project and assign to the concerned person + for activity in self.activities: + task = frappe.get_doc({ + "doctype": "Task", + "project": project.name, + "subject": activity.activity_name + " for " + self.employee_name, + "description": activity.description, + "department": self.department, + "company": self.company + }).insert(ignore_permissions=True) + activity.db_set("task", task.name) + users = [activity.user] if activity.user else [] + if activity.role: + user_list = frappe.db.sql_list('''select distinct(parent) from `tabHas Role` + where parenttype='User' and role=%s''', activity.role) + users = users + user_list + + # assign the task the users + if users: + self.assign_task_to_users(task, set(users)) + + def assign_task_to_users(self, task, users): + for user in users: + args = { + 'assign_to' : user, + 'doctype' : task.doctype, + 'name' : task.name, + 'description' : task.description or task.subject, + } + assign_to.add(args) + + def on_cancel(self): + # delete task project + for task in frappe.get_all("Task", filters={"project": self.project}): + frappe.delete_doc("Task", task.name) + frappe.delete_doc("Project", self.project) + self.db_set('project', '') + for activity in self.activities: + activity.db_set("task", "") + + +@frappe.whitelist() +def get_onboarding_details(parent, parenttype): + return frappe.get_list("Employee Boarding Activity", + fields=["activity_name", "role", "user", "required_for_employee_creation", "description"], + filters={"parent": parent, "parenttype": parenttype}, + order_by= "idx") + def set_employee_name(doc): if doc.employee and not doc.employee_name: doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name") +def update_employee(employee, details, cancel=False): + for item in details: + fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype + new_data = item.new if not cancel else item.current + if fieldtype == "Date" and new_data: + new_data = getdate(new_data) + elif fieldtype =="Datetime" and new_data: + new_data = get_datetime(new_data) + setattr(employee, item.fieldname, new_data) + return employee + @frappe.whitelist() def get_employee_fields_label(): fields = [] @@ -39,17 +128,6 @@ def get_employee_field_property(employee, fieldname): else: return False -def update_employee(employee, details, cancel=False): - for item in details: - fieldtype = frappe.get_meta("Employee").get_field(item.fieldname).fieldtype - new_data = item.new if not cancel else item.current - if fieldtype == "Date" and new_data: - new_data = getdate(new_data) - elif fieldtype =="Datetime" and new_data: - new_data = get_datetime(new_data) - setattr(employee, item.fieldname, new_data) - return employee - def get_leave_period(from_date, to_date, company): leave_period = frappe.db.sql(""" select name, from_date, to_date diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json index 43cee681c6..184656bbd0 100644 --- a/erpnext/projects/doctype/project/project.json +++ b/erpnext/projects/doctype/project/project.json @@ -178,6 +178,36 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "fieldname": "percent_complete", + "fieldtype": "Percent", + "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": "% Completed", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "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_on_submit": 0, @@ -208,6 +238,38 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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_on_submit": 0, @@ -305,36 +367,6 @@ "translatable": 0, "unique": 0 }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "percent_complete", - "fieldtype": "Percent", - "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": "% Completed", - "length": 0, - "no_copy": 1, - "permlevel": 0, - "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_on_submit": 0, @@ -1709,7 +1741,7 @@ "issingle": 0, "istable": 0, "max_attachments": 4, - "modified": "2018-03-28 10:19:32.743900", + "modified": "2018-05-10 04:21:25.764015", "modified_by": "Administrator", "module": "Projects", "name": "Project", @@ -1717,7 +1749,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, @@ -1737,7 +1768,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 0, "delete": 0, @@ -1757,7 +1787,6 @@ }, { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json index 8e72d0339e..c1b7aa1c60 100644 --- a/erpnext/projects/doctype/task/task.json +++ b/erpnext/projects/doctype/task/task.json @@ -40,6 +40,7 @@ "reqd": 1, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -72,6 +73,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -103,6 +105,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -133,6 +136,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -166,6 +170,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -198,6 +203,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -229,6 +235,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -260,6 +267,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -292,6 +300,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -326,6 +335,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -357,6 +367,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -386,6 +397,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -418,6 +430,7 @@ "reqd": 0, "search_index": 1, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -449,6 +462,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -480,6 +494,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -512,6 +527,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -545,6 +561,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "300px" }, @@ -578,6 +595,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -610,6 +628,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -641,6 +660,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -675,6 +695,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0, "width": "50%" }, @@ -708,6 +729,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -742,6 +764,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -771,6 +794,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -803,6 +827,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -833,6 +858,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -866,6 +892,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -898,6 +925,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -927,6 +955,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -958,6 +987,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -987,6 +1017,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1019,6 +1050,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1051,6 +1083,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1079,6 +1112,39 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "department", + "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": "Department", + "length": 0, + "no_copy": 0, + "options": "Department", + "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 }, { @@ -1109,6 +1175,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1139,6 +1206,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1169,6 +1237,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 }, { @@ -1199,6 +1268,7 @@ "reqd": 0, "search_index": 0, "set_only_once": 0, + "translatable": 0, "unique": 0 } ], @@ -1214,7 +1284,7 @@ "istable": 0, "max_attachments": 5, "menu_index": 0, - "modified": "2017-11-10 18:37:19.660293", + "modified": "2018-05-10 03:47:12.256088", "modified_by": "Administrator", "module": "Projects", "name": "Task", @@ -1222,7 +1292,6 @@ "permissions": [ { "amend": 0, - "apply_user_permissions": 0, "cancel": 0, "create": 1, "delete": 1, From 094e1841e023a4553ffe7bb29d18d9927bc98f12 Mon Sep 17 00:00:00 2001 From: Manas Solanki Date: Mon, 14 May 2018 20:33:28 +0530 Subject: [PATCH 74/79] minor changes and fixed the conflicts --- erpnext/hr/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index 076b1452df..213d46ea3d 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -22,7 +22,7 @@ class EmployeeBoardingController(Document): def on_submit(self): # create the project for the given employee onboarding - project_name = self.doctype + " for " + self.employee_name + project_name = _(self.doctype) + " : " + self.employee_name if self.doctype == "Employee Onboarding": project_name += " (" + self.job_applicant + ")" else: @@ -41,7 +41,7 @@ class EmployeeBoardingController(Document): task = frappe.get_doc({ "doctype": "Task", "project": project.name, - "subject": activity.activity_name + " for " + self.employee_name, + "subject": activity.activity_name + " : " + self.employee_name, "description": activity.description, "department": self.department, "company": self.company From 2d8a7ee81fb13ec38df7946291b0ab8685febd2a Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 11 May 2018 13:16:16 +0530 Subject: [PATCH 75/79] Bootstrap 'TDS' via fixture --- .../in_standard_chart_of_accounts.json | 3 + erpnext/hr/doctype/employee/employee.json | 2 +- erpnext/regional/india/setup.py | 58 +++++++++++++++---- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json index bc7f965956..f2c767f7cd 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json @@ -139,6 +139,9 @@ "Creditors": { "account_type": "Payable" }, + "TDS": { + "account_type": "Payable" + }, "Payroll Payable": {} }, "Stock Liabilities": { diff --git a/erpnext/hr/doctype/employee/employee.json b/erpnext/hr/doctype/employee/employee.json index 3527197edc..cc3fb7ff20 100644 --- a/erpnext/hr/doctype/employee/employee.json +++ b/erpnext/hr/doctype/employee/employee.json @@ -2766,7 +2766,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-10 07:52:24.326361", + "modified": "2018-05-11 12:48:46.435484", "modified_by": "Administrator", "module": "HR", "name": "Employee", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 0073d3311c..f035249aa4 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -16,7 +16,7 @@ def setup(company=None, patch=True): add_print_formats() if not patch: update_address_template() - make_fixtures() + make_fixtures(company) def update_address_template(): with open(os.path.join(os.path.dirname(__file__), 'address_template.html'), 'r') as f: @@ -189,15 +189,13 @@ def make_custom_fields(): create_custom_fields(custom_fields, ignore_validate = frappe.flags.in_patch) -def make_fixtures(): - docs = [ - {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'}, - {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'}, - {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'}, - {'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'} - ] +def make_fixtures(company=None): + docs = [] + company = company.name if company else frappe.db.get_value("Global Defaults", None, "default_company") + + set_salary_components(docs) + set_tds_account(docs, company) + set_tax_withholding_category(docs, company) for d in docs: try: @@ -206,3 +204,43 @@ def make_fixtures(): doc.insert() except frappe.NameError: pass + +def set_salary_components(docs): + docs.extend([ + {'doctype': 'Salary Component', 'salary_component': 'Professional Tax', 'description': 'Professional Tax', 'type': 'Deduction'}, + {'doctype': 'Salary Component', 'salary_component': 'Provident Fund', 'description': 'Provident fund', 'type': 'Deduction'}, + {'doctype': 'Salary Component', 'salary_component': 'House Rent Allowance', 'description': 'House Rent Allowance', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Basic', 'description': 'Basic', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Arrear', 'description': 'Arrear', 'type': 'Earning'}, + {'doctype': 'Salary Component', 'salary_component': 'Leave Encashment', 'description': 'Leave Encashment', 'type': 'Earning'} + ]) + +def set_tax_withholding_category(docs, company): + accounts = [] + tds_account = frappe.db.get_value("Account", filter={"account_type": "Payable", + "account_name": "TDS", "company": company}) + + if company and tds_account: + accounts = [ + { + 'company': company, + 'account': tds_account + } + ] + + docs.extend([ + { + 'doctype': 'Tax Withholding Category', '__newname': 'TDS', + 'percent_of_tax_withheld': 10,'threshold': 150000, 'book_on_invoice': 1, + 'book_on_advance': 0, "withhold_cumulative_tax_amount": 0, + 'accounts': accounts + } + ]) + +def set_tds_account(docs, company): + docs.extend([ + { + 'doctype': 'Account', 'account_name': 'TDS', 'account_type': 'Payable', + 'parent_account': 'Accounts Payable', 'company': company + } + ]) \ No newline at end of file From 3b4a6be4d029be652903bfba57807789d0bff450 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 11 May 2018 14:02:25 +0530 Subject: [PATCH 76/79] Supplier Tax Withholding Config --- .../party_tax_withholding_config/__init__.py | 0 .../party_tax_withholding_config.json | 166 ++++++++++++++++++ .../party_tax_withholding_config.py | 10 ++ erpnext/buying/doctype/supplier/supplier.json | 65 ++++++- 4 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 erpnext/buying/doctype/party_tax_withholding_config/__init__.py create mode 100644 erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json create mode 100644 erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py diff --git a/erpnext/buying/doctype/party_tax_withholding_config/__init__.py b/erpnext/buying/doctype/party_tax_withholding_config/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json new file mode 100644 index 0000000000..320485b318 --- /dev/null +++ b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.json @@ -0,0 +1,166 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2018-05-11 13:32:33.825307", + "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, + "fieldname": "tax_withholding_category", + "fieldtype": "Link", + "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": "Tax Withholding Category", + "length": 0, + "no_copy": 0, + "options": "Tax Withholding Category", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "valid_till", + "fieldtype": "Date", + "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": "Valid Till", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "applicable_percent", + "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": "Applicable Percent", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "certificate_received", + "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": "Certificate Received", + "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 + } + ], + "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": "2018-05-11 13:35:44.424855", + "modified_by": "Administrator", + "module": "Buying", + "name": "Party Tax Withholding Config", + "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": "DESC", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py new file mode 100644 index 0000000000..bec7e83f23 --- /dev/null +++ b/erpnext/buying/doctype/party_tax_withholding_config/party_tax_withholding_config.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class PartyTaxWithholdingConfig(Document): + pass diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index d342e115b7..508389f39d 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -960,6 +960,69 @@ "translatable": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "default_tax_withholding_config", + "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": "Default Tax Withholding Config", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "tax_withholding_account", + "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": "Tax Withholding Account", + "length": 0, + "no_copy": 0, + "options": "Party Tax Withholding Config", + "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_on_submit": 0, @@ -1163,7 +1226,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-11 12:19:52.519026", + "modified": "2018-05-11 14:00:36.204532", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", From b9d3385fec886168cd9a34143a06749a1509b21b Mon Sep 17 00:00:00 2001 From: Saurabh Date: Sat, 12 May 2018 17:42:20 +0530 Subject: [PATCH 77/79] calculate TDS on Sales Invoice Amount --- .../in_standard_chart_of_accounts.json | 8 +-- .../purchase_invoice/purchase_invoice.py | 21 +++++- .../tax_withholding_category.json | 64 ++++++++++++++++++- .../tax_withholding_category.py | 5 +- erpnext/accounts/party.py | 61 +++++++++++++++++- erpnext/buying/doctype/supplier/supplier.json | 38 +---------- erpnext/regional/india/setup.py | 4 +- 7 files changed, 153 insertions(+), 48 deletions(-) diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json index f2c767f7cd..2ec0b7f70c 100644 --- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json +++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/in_standard_chart_of_accounts.json @@ -139,9 +139,6 @@ "Creditors": { "account_type": "Payable" }, - "TDS": { - "account_type": "Payable" - }, "Payroll Payable": {} }, "Stock Liabilities": { @@ -150,8 +147,9 @@ } }, "Duties and Taxes": { - "account_type": "Tax", - "is_group": 1 + "TDS": { + "account_type": "Tax" + } }, "Loans (Liabilities)": { "Secured Loans": {}, diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 3e375e5051..0b544b13a3 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -8,7 +8,7 @@ from frappe import _, throw import frappe.defaults from erpnext.controllers.buying_controller import BuyingController -from erpnext.accounts.party import get_party_account, get_due_date +from erpnext.accounts.party import get_party_account, get_due_date, get_patry_tax_withholding_details from erpnext.accounts.utils import get_account_currency, get_fiscal_year from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billed_amount_based_on_po from erpnext.stock import get_warehouse_account_map @@ -46,6 +46,7 @@ class PurchaseInvoice(BuyingController): self.is_opening = 'No' self.validate_posting_time() + self.set_tax_withholding() super(PurchaseInvoice, self).validate() if not self.is_return: @@ -53,7 +54,6 @@ class PurchaseInvoice(BuyingController): self.pr_required() self.validate_supplier_invoice() - # validate cash purchase if (self.is_paid == 1): self.validate_cash() @@ -168,7 +168,6 @@ class PurchaseInvoice(BuyingController): super(PurchaseInvoice, self).validate_warehouse() - def validate_item_code(self): for d in self.get('items'): if not d.item_code: @@ -731,6 +730,22 @@ class PurchaseInvoice(BuyingController): def on_recurring(self, reference_doc, subscription_doc): self.due_date = None + def set_tax_withholding(self): + """ + 1. Get TDS Configurations against Supplier or Pull Default One. + 2. Form Purchase Order, identify partial payments + 3. If sum of all invoices grand total is greater than threshold and If TDS not deducted in previos Invoices + then deduct TDS for sum amount else deduct TDS for current Invoice + """ + if not self.get("__islocal"): + return + + tax_withholding_details = get_patry_tax_withholding_details(self) + + if tax_withholding_details and\ + flt(self.get("rounded_total") or self.grand_total) >= flt(tax_withholding_details['threshold']): + self.append('taxes', tax_withholding_details['taxes']) + @frappe.whitelist() def make_debit_note(source_name, target_doc=None): from erpnext.controllers.sales_and_purchase_return import make_return_doc diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json index f02a52043e..a590776e68 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json @@ -13,6 +13,68 @@ "editable_grid": 1, "engine": "InnoDB", "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "is_default", + "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": "Is Default", + "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_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "enabled", + "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": "Enabled", + "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_on_submit": 0, @@ -271,7 +333,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-04-13 19:17:12.494050", + "modified": "2018-05-11 14:25:07.474461", "modified_by": "Administrator", "module": "Accounts", "name": "Tax Withholding Category", diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py index 4940c4f3fe..61f4b60c8b 100644 --- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py +++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py @@ -7,4 +7,7 @@ import frappe from frappe.model.document import Document class TaxWithholdingCategory(Document): - pass + def validate(self): + if not frappe.db.get_value("Tax Withholding Category", + {"is_default": 1, "name": ("!=", self.name)}, "name"): + self.is_default = 1 \ No newline at end of file diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 652272dbe1..6c778f99c8 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -448,7 +448,6 @@ def get_dashboard_info(party_type, party): return info - def get_party_shipping_address(doctype, name): """ Returns an Address name (best guess) for the given doctype and name for which `address_type == 'Shipping'` is true. @@ -476,3 +475,63 @@ def get_party_shipping_address(doctype, name): return out[0][0] else: return '' + +def get_patry_tax_withholding_details(ref_doc): + supplier = frappe.get_doc("Supplier", ref_doc.supplier) + tax_withholding_details = {} + + for tax in supplier.tax_withholding_config: + tax_mapper = get_tax_mapper() + + set_tax_withholding_details(tax_mapper, ref_doc, tax_withholding_category=tax.tax_withholding_category) + + if tax.valid_till and date_diff(tax.valid_till, ref_doc.posting_date) > 0: + tax_mapper.update({ + "rate": tax.applicable_percentage + }) + + prepare_tax_withholding_details(tax_mapper, tax_withholding_details) + + if not tax_withholding_details: + tax_mapper = get_tax_mapper() + set_tax_withholding_details(tax_mapper, ref_doc, use_default=1) + prepare_tax_withholding_details(tax_mapper, tax_withholding_details) + + return tax_withholding_details + +def prepare_tax_withholding_details(tax_mapper, tax_withholding_details): + if tax_mapper.get('account_head'): + tax_withholding_details.update({ + "threshold": tax_mapper['threshold'], + "taxes": tax_mapper + }) + del tax_mapper['threshold'] + +def set_tax_withholding_details(tax_mapper, ref_doc, tax_withholding_category=None, use_default=0): + if tax_withholding_category: + tax_withholding = frappe.get_doc("Tax Withholding Category", tax_withholding_category) + else: + tax_withholding = frappe.get_doc("Tax Withholding Category", {'is_default': 1, 'enabled': 1}) + + if tax_withholding.book_on_invoice and ref_doc.doctype=='Purchase Invoice' \ + or tax_withholding.book_on_advance and ref_doc.doctype in ('Payment Entry', 'Journal Entry'): + + for account_detail in tax_withholding.accounts: + if ref_doc.company == account_detail.company: + tax_mapper.update({ + "account_head": account_detail.account, + "rate": tax_withholding.percent_of_tax_withheld, + "threshold": tax_withholding.threshold, + "description": tax_withholding.name + }) + +def get_tax_mapper(): + return { + "category": "Total", + "add_deduct_tax": "Deduct", + "charge_type": "On Net Total", + "rate": 0, + "description": '', + "account_head": '', + "threshold": 0.0 + } \ No newline at end of file diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json index 508389f39d..eedbac1dff 100644 --- a/erpnext/buying/doctype/supplier/supplier.json +++ b/erpnext/buying/doctype/supplier/supplier.json @@ -233,7 +233,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "translatable": 0, @@ -997,7 +997,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "tax_withholding_account", + "fieldname": "tax_withholding_config", "fieldtype": "Table", "hidden": 0, "ignore_user_permissions": 0, @@ -1180,38 +1180,6 @@ "set_only_once": 0, "translatable": 0, "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "tax_withholding_category", - "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": "Tax Withholding Category", - "length": 0, - "no_copy": 0, - "options": "Tax Withholding Category", - "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, @@ -1226,7 +1194,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2018-05-11 14:00:36.204532", + "modified": "2018-05-11 15:15:19.912308", "modified_by": "Administrator", "module": "Buying", "name": "Supplier", diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index f035249aa4..70960d714b 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -240,7 +240,7 @@ def set_tax_withholding_category(docs, company): def set_tds_account(docs, company): docs.extend([ { - 'doctype': 'Account', 'account_name': 'TDS', 'account_type': 'Payable', - 'parent_account': 'Accounts Payable', 'company': company + 'doctype': 'Account', 'account_name': 'TDS', 'account_type': 'Tax', + 'parent_account': 'Duties and Taxes', 'company': company } ]) \ No newline at end of file From f3f438ad183e13ba9ea56fe96dd11941fc6d203f Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 14 May 2018 20:19:39 +0530 Subject: [PATCH 78/79] tds fixed for PI --- .../purchase_invoice/purchase_invoice.py | 18 ++++++++---------- erpnext/accounts/party.py | 17 +++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py index 0b544b13a3..c9cf47d114 100644 --- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py +++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py @@ -732,19 +732,17 @@ class PurchaseInvoice(BuyingController): def set_tax_withholding(self): """ - 1. Get TDS Configurations against Supplier or Pull Default One. - 2. Form Purchase Order, identify partial payments - 3. If sum of all invoices grand total is greater than threshold and If TDS not deducted in previos Invoices - then deduct TDS for sum amount else deduct TDS for current Invoice + 1. Get TDS Configurations against Supplier """ - if not self.get("__islocal"): - return tax_withholding_details = get_patry_tax_withholding_details(self) - - if tax_withholding_details and\ - flt(self.get("rounded_total") or self.grand_total) >= flt(tax_withholding_details['threshold']): - self.append('taxes', tax_withholding_details['taxes']) + for tax_details in tax_withholding_details: + if flt(self.get("rounded_total") or self.grand_total) >= flt(tax_details['threshold']): + if self.taxes: + if tax_details['tax']['description'] not in [tax.description for tax in self.taxes]: + self.append('taxes', tax_details['tax']) + else: + self.append('taxes', tax_details['tax']) @frappe.whitelist() def make_debit_note(source_name, target_doc=None): diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 6c778f99c8..75089b2ff4 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -478,8 +478,8 @@ def get_party_shipping_address(doctype, name): def get_patry_tax_withholding_details(ref_doc): supplier = frappe.get_doc("Supplier", ref_doc.supplier) - tax_withholding_details = {} - + tax_withholding_details = [] + print(supplier) for tax in supplier.tax_withholding_config: tax_mapper = get_tax_mapper() @@ -492,19 +492,16 @@ def get_patry_tax_withholding_details(ref_doc): prepare_tax_withholding_details(tax_mapper, tax_withholding_details) - if not tax_withholding_details: - tax_mapper = get_tax_mapper() - set_tax_withholding_details(tax_mapper, ref_doc, use_default=1) - prepare_tax_withholding_details(tax_mapper, tax_withholding_details) - return tax_withholding_details def prepare_tax_withholding_details(tax_mapper, tax_withholding_details): if tax_mapper.get('account_head'): - tax_withholding_details.update({ + + tax_withholding_details.append({ "threshold": tax_mapper['threshold'], - "taxes": tax_mapper + "tax": tax_mapper }) + del tax_mapper['threshold'] def set_tax_withholding_details(tax_mapper, ref_doc, tax_withholding_category=None, use_default=0): @@ -534,4 +531,4 @@ def get_tax_mapper(): "description": '', "account_head": '', "threshold": 0.0 - } \ No newline at end of file + } From 4ae089d6a12dd7a096d92b200f529d087c7e8685 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 14 May 2018 20:20:12 +0530 Subject: [PATCH 79/79] Provision to deduct TDS on Advance --- .../doctype/payment_entry/payment_entry.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py index f983868a0f..0a22ae0a47 100644 --- a/erpnext/accounts/doctype/payment_entry/payment_entry.py +++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py @@ -7,7 +7,7 @@ import frappe, erpnext, json from frappe import _, scrub, ValidationError from frappe.utils import flt, comma_or, nowdate from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on -from erpnext.accounts.party import get_party_account +from erpnext.accounts.party import get_party_account, get_patry_tax_withholding_details from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account from erpnext.setup.utils import get_exchange_rate from erpnext.accounts.general_ledger import make_gl_entries @@ -43,6 +43,7 @@ class PaymentEntry(AccountsController): def validate(self): self.setup_party_account_field() + self.set_tax_withholding() self.set_missing_values() self.validate_payment_type() self.validate_party_details() @@ -510,6 +511,27 @@ class PaymentEntry(AccountsController): def on_recurring(self, reference_doc, subscription_doc): self.reference_no = reference_doc.name self.reference_date = nowdate() + + def set_tax_withholding(self): + if self.party_type != 'Supplier': + return + + self.supplier = self.party + tax_withholding_details = get_patry_tax_withholding_details(self) + + for tax_details in tax_withholding_details: + if self.deductions: + if tax_details['tax']['account_head'] not in [deduction.account for deduction in self.deductions]: + self.append('deductions', self.calculate_deductions(tax_details)) + else: + self.append('deductions', self.calculate_deductions(tax_details)) + + def calculate_deductions(self, tax_details): + return { + "account": tax_details['tax']['account_head'], + "cost_center": frappe.db.get_value("Company", self.company, "cost_center"), + "amount": self.total_allocated_amount * (tax_details['tax']['rate'] / 100) + } @frappe.whitelist() def get_outstanding_reference_documents(args):