From e11ce57f3c4e789552bbbdd051649a59d2ee897e Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Wed, 24 Feb 2021 21:45:29 +0100 Subject: [PATCH 01/14] fix: add item taxes at the same times as sales and purchase taxes --- erpnext/setup/doctype/company/company.js | 2 +- .../setup_wizard/operations/taxes_setup.py | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js index 36033d9dae..c041d269a7 100644 --- a/erpnext/setup/doctype/company/company.js +++ b/erpnext/setup/doctype/company/company.js @@ -140,7 +140,7 @@ frappe.ui.form.on("Company", { doc: frm.doc, freeze: true, callback: function() { - frappe.msgprint(__("Default tax templates for sales and purchase are created.")); + frappe.msgprint(__("Default tax templates for sales, purchase and items are created.")); } }) }, diff --git a/erpnext/setup/setup_wizard/operations/taxes_setup.py b/erpnext/setup/setup_wizard/operations/taxes_setup.py index e66fa76f93..c3c1593c04 100644 --- a/erpnext/setup/setup_wizard/operations/taxes_setup.py +++ b/erpnext/setup/setup_wizard/operations/taxes_setup.py @@ -29,6 +29,7 @@ def make_tax_account_and_template(company, account_name, tax_rate, template_name try: if accounts: make_sales_and_purchase_tax_templates(accounts, template_name) + make_item_tax_templates(accounts, template_name) except frappe.NameError: if frappe.message_log: frappe.message_log.pop() except RootNotEditable: @@ -84,6 +85,27 @@ def make_sales_and_purchase_tax_templates(accounts, template_name=None): doc = frappe.get_doc(purchase_tax_template) doc.insert(ignore_permissions=True) +def make_item_tax_templates(accounts, template_name=None): + if not template_name: + template_name = accounts[0].name + + item_tax_template = { + "doctype": "Item Tax Template", + "title": template_name, + "company": accounts[0].company, + 'taxes': [] + } + + + for account in accounts: + item_tax_template['taxes'].append({ + "tax_type": account.name, + "tax_rate": account.tax_rate + }) + + # Items + frappe.get_doc(copy.deepcopy(item_tax_template)).insert(ignore_permissions=True) + def get_tax_account_group(company): tax_group = frappe.db.get_value("Account", {"account_name": "Duties and Taxes", "is_group": 1, "company": company}) From 027db0b41e74685be3e6ecb5703757f81e973d6f Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Thu, 25 Feb 2021 17:48:33 +0530 Subject: [PATCH 02/14] fix(HR): hide "more" button from team updates --- erpnext/hr/page/team_updates/team_updates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/page/team_updates/team_updates.js b/erpnext/hr/page/team_updates/team_updates.js index 13d0074660..991b316e1e 100644 --- a/erpnext/hr/page/team_updates/team_updates.js +++ b/erpnext/hr/page/team_updates/team_updates.js @@ -36,7 +36,7 @@ frappe.team_updates = { start: me.start }, callback: function(r) { - if(r.message) { + if (r.message && r.message.length > 0) { r.message.forEach(function(d) { me.add_row(d); }); @@ -77,4 +77,4 @@ frappe.team_updates = { $(frappe.render_template('team_update_row', data)).appendTo(me.body) } -} \ No newline at end of file +} From 980383543691fe1f97f45d360389bca179d392ba Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 00:33:48 -0600 Subject: [PATCH 03/14] Add "description" field to Skill doctype Lets us describe a skill past just the title. Closes #23592 --- erpnext/hr/doctype/skill/skill.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/erpnext/hr/doctype/skill/skill.json b/erpnext/hr/doctype/skill/skill.json index a10381fac1..67f3d372eb 100644 --- a/erpnext/hr/doctype/skill/skill.json +++ b/erpnext/hr/doctype/skill/skill.json @@ -46,6 +46,11 @@ "set_only_once": 0, "translatable": 0, "unique": 1 + }, + { + "fieldname": "description", + "fieldtype": "Text", + "label": "Description" } ], "has_web_view": 0, @@ -56,7 +61,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2021-02-24 09:55:00.536328", + "modified": "2021-02-26 09:55:00.536328", "modified_by": "Administrator", "module": "HR", "name": "Skill", From aa09628358974a9938277df5c2ace6dff18d47ee Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Fri, 26 Feb 2021 15:24:34 -0600 Subject: [PATCH 04/14] Skill fields: enable allow_in_quick_entry --- erpnext/hr/doctype/skill/skill.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/erpnext/hr/doctype/skill/skill.json b/erpnext/hr/doctype/skill/skill.json index 67f3d372eb..4c8a8c92c1 100644 --- a/erpnext/hr/doctype/skill/skill.json +++ b/erpnext/hr/doctype/skill/skill.json @@ -16,7 +16,7 @@ "fields": [ { "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, + "allow_in_quick_entry": 1, "allow_on_submit": 0, "bold": 0, "collapsible": 0, @@ -48,6 +48,7 @@ "unique": 1 }, { + "allow_in_quick_entry": 1, "fieldname": "description", "fieldtype": "Text", "label": "Description" @@ -61,7 +62,7 @@ "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2021-02-26 09:55:00.536328", + "modified": "2021-02-26 10:55:00.536328", "modified_by": "Administrator", "module": "HR", "name": "Skill", From eb6b3cfe6da8ad800bd78748fda2042ddb249082 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Mon, 1 Mar 2021 11:32:39 +0530 Subject: [PATCH 05/14] fix: allow to select item code in batch naming --- erpnext/stock/doctype/batch/batch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py index c8424f13e1..8fdda565d2 100644 --- a/erpnext/stock/doctype/batch/batch.py +++ b/erpnext/stock/doctype/batch/batch.py @@ -93,7 +93,7 @@ class Batch(Document): if create_new_batch: if batch_number_series: - self.batch_id = make_autoname(batch_number_series) + self.batch_id = make_autoname(batch_number_series, doc=self) elif batch_uses_naming_series(): self.batch_id = self.get_name_from_naming_series() else: From 10e4b9d4e8b7bcc594de32dff10effeed6b970c0 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Mon, 1 Mar 2021 12:50:07 +0400 Subject: [PATCH 06/14] fix: GL Entries for AP/AR Summary SQL query modified to fetch only those GL Entries for Accounts Payable Summary and Accounts Receivable Summary reports where the corresponding payment entry is in submitted state. --- erpnext/accounts/party.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 38b228477f..7d53db2dc3 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -606,18 +606,20 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur cond = "1=1" if posting_date: if future_payment: - cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) + cond = "gle.posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) else: - cond = "posting_date <= '{0}'".format(posting_date) + cond = "gle.posting_date <= '{0}'".format(posting_date) if company: - cond += "and company = {0}".format(frappe.db.escape(company)) + cond += "and gle.company = {0}".format(frappe.db.escape(company)) - data = frappe.db.sql(""" SELECT party, sum({0}) as amount - FROM `tabGL Entry` + data = frappe.db.sql(""" SELECT gle.party, sum(gle.{0}) as amount + FROM `tabGL Entry` gle + INNER JOIN `tabPayment Entry` pe ON pe.name = gle.voucher_no WHERE - party_type = %s and against_voucher is null - and {1} GROUP BY party""" + gle.party_type = %s and gle.against_voucher is null + and pe.docstatus = 1 + and {1} GROUP BY gle.party""" .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) if data: From 2efdfa26b4e80b77628713e6022382d6e69cafec Mon Sep 17 00:00:00 2001 From: Daniel Chalmers Date: Mon, 1 Mar 2021 04:14:16 -0600 Subject: [PATCH 07/14] fix: Change FieldType from Text to Text Editor in Non-Conformance DocType (#24760) * Enable Text Editors Enable rich Text Editors for Corrective Action and Preventive Action fields. Closes #24759 * Update non_conformance.json --- .../doctype/non_conformance/non_conformance.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/erpnext/quality_management/doctype/non_conformance/non_conformance.json b/erpnext/quality_management/doctype/non_conformance/non_conformance.json index bfeb96bcaf..8dfe2d6859 100644 --- a/erpnext/quality_management/doctype/non_conformance/non_conformance.json +++ b/erpnext/quality_management/doctype/non_conformance/non_conformance.json @@ -70,18 +70,18 @@ }, { "fieldname": "corrective_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Corrective Action" }, { "fieldname": "preventive_action", - "fieldtype": "Text", + "fieldtype": "Text Editor", "label": "Preventive Action" } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-26 15:27:47.247814", + "modified": "2021-02-26 15:27:47.247814", "modified_by": "Administrator", "module": "Quality Management", "name": "Non Conformance", @@ -115,4 +115,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 810a36105ca10d555e7f92c3870199cf5af98710 Mon Sep 17 00:00:00 2001 From: UrvashiKishnani <41088003+UrvashiKishnani@users.noreply.github.com> Date: Tue, 2 Mar 2021 08:20:03 +0400 Subject: [PATCH 08/14] fix: GL Entries for AP/AR Summary without SQL join SQL query modified to fetch only those GL Entries for Accounts Payable Summary and Accounts Receivable Summary reports where the corresponding payment entry is not in cancelled state. --- erpnext/accounts/party.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 7d53db2dc3..e01cb6e151 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -606,20 +606,19 @@ def get_partywise_advanced_payment_amount(party_type, posting_date = None, futur cond = "1=1" if posting_date: if future_payment: - cond = "gle.posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) + cond = "posting_date <= '{0}' OR DATE(creation) <= '{0}' """.format(posting_date) else: - cond = "gle.posting_date <= '{0}'".format(posting_date) + cond = "posting_date <= '{0}'".format(posting_date) if company: - cond += "and gle.company = {0}".format(frappe.db.escape(company)) + cond += "and company = {0}".format(frappe.db.escape(company)) - data = frappe.db.sql(""" SELECT gle.party, sum(gle.{0}) as amount - FROM `tabGL Entry` gle - INNER JOIN `tabPayment Entry` pe ON pe.name = gle.voucher_no + data = frappe.db.sql(""" SELECT party, sum({0}) as amount + FROM `tabGL Entry` WHERE - gle.party_type = %s and gle.against_voucher is null - and pe.docstatus = 1 - and {1} GROUP BY gle.party""" + party_type = %s and against_voucher is null + and is_cancelled = 0 + and {1} GROUP BY party""" .format(("credit") if party_type == "Customer" else "debit", cond) , party_type) if data: From 47ce85484b56ea2aad159dadaeed53f4fe87174a Mon Sep 17 00:00:00 2001 From: Anupam Date: Sun, 28 Feb 2021 16:09:26 +0530 Subject: [PATCH 09/14] feat: provistion to pull timesheet in sales invoice --- .../doctype/sales_invoice/sales_invoice.js | 60 +++++++++++++++++++ .../projects/doctype/timesheet/timesheet.py | 10 ++-- 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index d3e8a4474d..b361c0c345 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -695,6 +695,7 @@ frappe.ui.form.on('Sales Invoice', { refresh_field(['timesheets']) } }) + frm.refresh(); }, onload: function(frm) { @@ -810,6 +811,65 @@ frappe.ui.form.on('Sales Invoice', { }, refresh: function(frm) { + if (frm.doc.project) { + frm.add_custom_button(__('Fetch Timesheet'), function() { + let d = new frappe.ui.Dialog({ + title: __('Fetch Timesheet'), + fields: [ + { + "label" : "From", + "fieldname": "from_time", + "fieldtype": "Date", + "reqd": 1, + }, + { + fieldtype: 'Column Break', + fieldname: 'col_break_1', + }, + { + "label" : "To", + "fieldname": "to_time", + "fieldtype": "Date", + "reqd": 1, + } + ], + primary_action: function() { + let data = d.get_values(); + frappe.call({ + method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data", + args: { + from_time: data.from_time, + to_time: data.to_time, + project: frm.doc.project + }, + callback: function(r) { + if(!r.exc) { + if(r.message.length > 0) { + frm.clear_table('timesheets') + r.message.forEach((d) => { + frm.add_child('timesheets',{ + 'time_sheet': d.parent, + 'billing_hours': d.billing_hours, + 'billing_amount': d.billing_amt, + 'timesheet_detail': d.name + }); + }); + frm.refresh_field('timesheets') + } + else { + frappe.msgprint(__('No Timesheet Found.')) + } + d.hide(); + } + } + }); + }, + primary_action_label: __('Get Timesheets') + }); + d.show(); + }) + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py index ea81b3eb64..ed02f79c2d 100644 --- a/erpnext/projects/doctype/timesheet/timesheet.py +++ b/erpnext/projects/doctype/timesheet/timesheet.py @@ -204,14 +204,16 @@ class Timesheet(Document): ts_detail.billing_rate = 0.0 @frappe.whitelist() -def get_projectwise_timesheet_data(project, parent=None): - cond = '' +def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None): + condition = '' if parent: - cond = "and parent = %(parent)s" + condition = "AND parent = %(parent)s" + if from_time and to_time: + condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s" return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1 - and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1) + and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1) @frappe.whitelist() @frappe.validate_and_sanitize_search_inputs From bd10d7c02841cd8d0ab0fcd862a2a60ebe8b9aa3 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Mar 2021 13:37:45 +0530 Subject: [PATCH 10/14] fix: reposting patch fixes (#24775) --- .../item_reposting_for_incorrect_sl_and_gl.py | 17 +++++++++++++---- .../purchase_receipt/purchase_receipt.py | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py index 3200363e01..d968e1fb76 100644 --- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py +++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py @@ -1,13 +1,24 @@ import frappe from frappe import _ -from frappe.utils import getdate, get_time +from frappe.utils import getdate, get_time, today from erpnext.stock.stock_ledger import update_entries_after from erpnext.accounts.utils import update_gl_entries_after def execute(): - frappe.reload_doc('stock', 'doctype', 'repost_item_valuation') + for doctype in ('repost_item_valuation', 'stock_entry_detail', 'purchase_receipt_item', + 'purchase_invoice_item', 'delivery_note_item', 'sales_invoice_item', 'packed_item'): + frappe.reload_doc('stock', 'doctype', doctype) + frappe.reload_doc('buying', 'doctype', 'purchase_receipt_item_supplied') reposting_project_deployed_on = get_creation_time() + posting_date = getdate(reposting_project_deployed_on) + posting_time = get_time(reposting_project_deployed_on) + + if posting_date == today(): + return + + frappe.clear_cache() + frappe.flags.warehouse_account_map = {} data = frappe.db.sql(''' SELECT @@ -41,8 +52,6 @@ def execute(): print("Reposting General Ledger Entries...") - posting_date = getdate(reposting_project_deployed_on) - posting_time = get_time(reposting_project_deployed_on) for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}): update_gl_entries_after(posting_date, posting_time, company=row.name) diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py index d72101412e..70687bdac2 100644 --- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py +++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py @@ -324,10 +324,12 @@ class PurchaseReceipt(BuyingController): else: loss_account = self.get_company_default("default_expense_account") + cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center") + gl_entries.append(self.get_gl_dict({ "account": loss_account, "against": warehouse_account[d.warehouse]["account"], - "cost_center": d.cost_center, + "cost_center": cost_center, "remarks": self.get("remarks") or _("Accounting Entry for Stock"), "debit": divisional_loss, "project": d.project From 190106a8b8eeb1be9e35e4fb5c062fbe4e0f3e50 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 2 Mar 2021 13:38:14 +0530 Subject: [PATCH 11/14] fix: rounding of earned leave is optional (#24782) --- .../leave_allocation/leave_allocation.py | 6 --- .../leave_policy_assignment.json | 6 ++- .../leave_policy_assignment.py | 48 ++++++++++++++++--- erpnext/hr/doctype/leave_type/leave_type.json | 5 +- erpnext/hr/utils.py | 23 ++++++--- 5 files changed, 65 insertions(+), 23 deletions(-) diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py index 5e3822e2da..69d605d063 100755 --- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py +++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py @@ -18,7 +18,6 @@ class ValueMultiplierError(frappe.ValidationError): pass class LeaveAllocation(Document): def validate(self): self.validate_period() - self.validate_new_leaves_allocated_value() self.validate_allocation_overlap() self.validate_back_dated_allocation() self.set_total_leaves_allocated() @@ -72,11 +71,6 @@ class LeaveAllocation(Document): if frappe.db.get_value("Leave Type", self.leave_type, "is_lwp"): frappe.throw(_("Leave Type {0} cannot be allocated since it is leave without pay").format(self.leave_type)) - def validate_new_leaves_allocated_value(self): - """validate that leave allocation is in multiples of 0.5""" - if flt(self.new_leaves_allocated) % 0.5: - frappe.throw(_("Leaves must be allocated in multiples of 0.5"), ValueMultiplierError) - def validate_allocation_overlap(self): leave_allocation = frappe.db.sql(""" SELECT diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json index a0327bdaa0..3373350e73 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.json @@ -106,12 +106,14 @@ "fieldname": "leaves_allocated", "fieldtype": "Check", "hidden": 1, - "label": "Leaves Allocated" + "label": "Leaves Allocated", + "no_copy": 1, + "print_hide": 1 } ], "is_submittable": 1, "links": [], - "modified": "2020-12-31 16:43:30.695206", + "modified": "2021-03-01 17:54:01.014509", "modified_by": "Administrator", "module": "HR", "name": "Leave Policy Assignment", diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py index a5068bc26d..4064c56e44 100644 --- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py +++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py @@ -6,7 +6,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe import _, bold -from frappe.utils import getdate, date_diff, comma_and, formatdate +from frappe.utils import getdate, date_diff, comma_and, formatdate, get_datetime, flt from math import ceil import json from six import string_types @@ -84,17 +84,52 @@ class LeavePolicyAssignment(Document): return allocation.name, new_leaves_allocated def get_new_leaves(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from frappe.model.meta import get_field_precision + precision = get_field_precision(frappe.get_meta("Leave Allocation").get_field("new_leaves_allocated")) + + # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 + if leave_type_details.get(leave_type).is_compensatory == 1: + new_leaves_allocated = 0 + + elif leave_type_details.get(leave_type).is_earned_leave == 1: + if self.assignment_based_on == "Leave Period": + new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining) + else: + new_leaves_allocated = 0 # Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period - if getdate(date_of_joining) > getdate(self.effective_from): + elif getdate(date_of_joining) > getdate(self.effective_from): remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1)) new_leaves_allocated = ceil(new_leaves_allocated * remaining_period) - # Earned Leaves and Compensatory Leaves are allocated by scheduler, initially allocate 0 - if leave_type_details.get(leave_type).is_earned_leave == 1 or leave_type_details.get(leave_type).is_compensatory == 1: - new_leaves_allocated = 0 + return flt(new_leaves_allocated, precision) + + def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining): + from erpnext.hr.utils import get_monthly_earned_leave + + current_month = get_datetime().month + current_year = get_datetime().year + + from_date = frappe.db.get_value("Leave Period", self.leave_period, "from_date") + if getdate(date_of_joining) > getdate(from_date): + from_date = date_of_joining + + from_date_month = get_datetime(from_date).month + from_date_year = get_datetime(from_date).year + + months_passed = 0 + if current_year == from_date_year and current_month > from_date_month: + months_passed = current_month - from_date_month + elif current_year > from_date_year: + months_passed = (12 - from_date_month) + current_month + + if months_passed > 0: + monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated, + leave_type_details.get(leave_type).earned_leave_frequency, leave_type_details.get(leave_type).rounding) + new_leaves_allocated = monthly_earned_leave * months_passed return new_leaves_allocated + @frappe.whitelist() def grant_leave_for_multiple_employees(leave_policy_assignments): leave_policy_assignments = json.loads(leave_policy_assignments) @@ -156,7 +191,8 @@ def automatically_allocate_leaves_based_on_leave_policy(): def get_leave_type_details(): leave_type_details = frappe._dict() leave_types = frappe.get_all("Leave Type", - fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "is_carry_forward", "expire_carry_forwarded_leaves_after_days"]) + fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", + "is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"]) for d in leave_types: leave_type_details.setdefault(d.name, d) return leave_type_details diff --git a/erpnext/hr/doctype/leave_type/leave_type.json b/erpnext/hr/doctype/leave_type/leave_type.json index a2092919f8..fc577ef1d3 100644 --- a/erpnext/hr/doctype/leave_type/leave_type.json +++ b/erpnext/hr/doctype/leave_type/leave_type.json @@ -172,7 +172,7 @@ "fieldname": "rounding", "fieldtype": "Select", "label": "Rounding", - "options": "0.5\n1.0" + "options": "\n0.25\n0.5\n1.0" }, { "depends_on": "is_carry_forward", @@ -197,6 +197,7 @@ "label": "Based On Date Of Joining" }, { + "default": "0", "depends_on": "eval:doc.is_lwp == 0", "fieldname": "is_ppl", "fieldtype": "Check", @@ -213,7 +214,7 @@ "icon": "fa fa-flag", "idx": 1, "links": [], - "modified": "2020-10-15 15:49:47.555105", + "modified": "2021-03-02 11:22:33.776320", "modified_by": "Administrator", "module": "HR", "name": "Leave Type", diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py index e2aa7a4e72..d57ef5955d 100644 --- a/erpnext/hr/utils.py +++ b/erpnext/hr/utils.py @@ -316,13 +316,7 @@ def allocate_earned_leaves(): update_previous_leave_allocation(allocation, annual_allocation, e_leave_type) def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type): - divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} - if annual_allocation: - earned_leaves = flt(annual_allocation) / divide_by_frequency[e_leave_type.earned_leave_frequency] - if e_leave_type.rounding == "0.5": - earned_leaves = round(earned_leaves * 2) / 2 - else: - earned_leaves = round(earned_leaves) + earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding) allocation = frappe.get_doc('Leave Allocation', allocation.name) new_allocation = flt(allocation.total_leaves_allocated) + flt(earned_leaves) @@ -335,6 +329,21 @@ def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type today_date = today() create_additional_leave_ledger_entry(allocation, earned_leaves, today_date) +def get_monthly_earned_leave(annual_leaves, frequency, rounding): + earned_leaves = 0.0 + divide_by_frequency = {"Yearly": 1, "Half-Yearly": 6, "Quarterly": 4, "Monthly": 12} + if annual_leaves: + earned_leaves = flt(annual_leaves) / divide_by_frequency[frequency] + if rounding: + if rounding == "0.25": + earned_leaves = round(earned_leaves * 4) / 4 + elif rounding == "0.5": + earned_leaves = round(earned_leaves * 2) / 2 + else: + earned_leaves = round(earned_leaves) + + return earned_leaves + def get_leave_allocations(date, leave_type): return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy From b27d4f6095686b36e0c15e325bec0719d0265b4d Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Tue, 2 Mar 2021 17:44:42 +0530 Subject: [PATCH 12/14] fix: salary slip working hours increment (#24784) --- erpnext/payroll/doctype/salary_slip/salary_slip.py | 1 + 1 file changed, 1 insertion(+) diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 60aff02b38..1f8bfea03c 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -1123,6 +1123,7 @@ class SalarySlip(TransactionBase): #calculate total working hours, earnings based on hourly wages and totals def calculate_total_for_salary_slip_based_on_timesheet(self): if self.timesheets: + self.total_working_hours = 0 for timesheet in self.timesheets: if timesheet.working_hours: self.total_working_hours += timesheet.working_hours From a5c4558f8b273752b786813ce3d78603c8fed207 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:36:48 +0530 Subject: [PATCH 13/14] feat: Additon of leave details in Salary Slip (#24674) * feat: Additon of leave details in Salary Slip * fix: Change leaves to leave --- .../payroll_settings/payroll_settings.json | 50 +++++------- .../doctype/salary_slip/salary_slip.json | 16 +++- .../doctype/salary_slip/salary_slip.py | 18 +++++ .../doctype/salary_slip_leave/__init__.py | 0 .../salary_slip_leave/salary_slip_leave.json | 78 +++++++++++++++++++ .../salary_slip_leave/salary_slip_leave.py | 10 +++ 6 files changed, 140 insertions(+), 32 deletions(-) create mode 100644 erpnext/payroll/doctype/salary_slip_leave/__init__.py create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json create mode 100644 erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py diff --git a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json index c47caa1227..680e518ca0 100644 --- a/erpnext/payroll/doctype/payroll_settings/payroll_settings.json +++ b/erpnext/payroll/doctype/payroll_settings/payroll_settings.json @@ -15,6 +15,7 @@ "daily_wages_fraction_for_half_day", "email_salary_slip_to_employee", "encrypt_salary_slips_in_emails", + "show_leave_balances_in_salary_slip", "password_policy" ], "fields": [ @@ -23,58 +24,44 @@ "fieldname": "payroll_based_on", "fieldtype": "Select", "label": "Calculate Payroll Working Days Based On", - "options": "Leave\nAttendance", - "show_days": 1, - "show_seconds": 1 + "options": "Leave\nAttendance" }, { "fieldname": "max_working_hours_against_timesheet", "fieldtype": "Float", - "label": "Max working hours against Timesheet", - "show_days": 1, - "show_seconds": 1 + "label": "Max working hours against Timesheet" }, { "default": "0", "description": "If checked, Total no. of Working Days will include holidays, and this will reduce the value of Salary Per Day", "fieldname": "include_holidays_in_total_working_days", "fieldtype": "Check", - "label": "Include holidays in Total no. of Working Days", - "show_days": 1, - "show_seconds": 1 + "label": "Include holidays in Total no. of Working Days" }, { "default": "0", "description": "If checked, hides and disables Rounded Total field in Salary Slips", "fieldname": "disable_rounded_total", "fieldtype": "Check", - "label": "Disable Rounded Total", - "show_days": 1, - "show_seconds": 1 + "label": "Disable Rounded Total" }, { "fieldname": "column_break_11", - "fieldtype": "Column Break", - "show_days": 1, - "show_seconds": 1 + "fieldtype": "Column Break" }, { "default": "0.5", "description": "The fraction of daily wages to be paid for half-day attendance", "fieldname": "daily_wages_fraction_for_half_day", "fieldtype": "Float", - "label": "Fraction of Daily Salary for Half Day", - "show_days": 1, - "show_seconds": 1 + "label": "Fraction of Daily Salary for Half Day" }, { "default": "1", "description": "Emails salary slip to employee based on preferred email selected in Employee", "fieldname": "email_salary_slip_to_employee", "fieldtype": "Check", - "label": "Email Salary Slip to Employee", - "show_days": 1, - "show_seconds": 1 + "label": "Email Salary Slip to Employee" }, { "default": "0", @@ -82,9 +69,7 @@ "description": "The salary slip emailed to the employee will be password protected, the password will be generated based on the password policy.", "fieldname": "encrypt_salary_slips_in_emails", "fieldtype": "Check", - "label": "Encrypt Salary Slips in Emails", - "show_days": 1, - "show_seconds": 1 + "label": "Encrypt Salary Slips in Emails" }, { "depends_on": "eval: doc.encrypt_salary_slips_in_emails == 1", @@ -92,24 +77,27 @@ "fieldname": "password_policy", "fieldtype": "Data", "in_list_view": 1, - "label": "Password Policy", - "show_days": 1, - "show_seconds": 1 + "label": "Password Policy" }, { "depends_on": "eval:doc.payroll_based_on == 'Attendance'", "fieldname": "consider_unmarked_attendance_as", "fieldtype": "Select", "label": "Consider Unmarked Attendance As", - "options": "Present\nAbsent", - "show_days": 1, - "show_seconds": 1 + "options": "Present\nAbsent" + }, + { + "default": "0", + "fieldname": "show_leave_balances_in_salary_slip", + "fieldtype": "Check", + "label": "Show Leave Balances in Salary Slip" } ], "icon": "fa fa-cog", + "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-06-22 17:00:58.408030", + "modified": "2021-02-19 11:07:55.873991", "modified_by": "Administrator", "module": "Payroll", "name": "Payroll Settings", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json index 9f9691b59d..6688368262 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.json +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json @@ -80,6 +80,8 @@ "total_in_words", "column_break_69", "base_total_in_words", + "leave_details_section", + "leave_details", "section_break_75", "amended_from" ], @@ -612,13 +614,25 @@ "label": "Month To Date(Company Currency)", "options": "Company:company:default_currency", "read_only": 1 + }, + { + "fieldname": "leave_details_section", + "fieldtype": "Section Break", + "label": "Leave Details" + }, + { + "fieldname": "leave_details", + "fieldtype": "Table", + "label": "Leave Details", + "options": "Salary Slip Leave", + "read_only": 1 } ], "icon": "fa fa-file-text", "idx": 9, "is_submittable": 1, "links": [], - "modified": "2021-01-14 13:37:38.180920", + "modified": "2021-02-19 11:48:05.383945", "modified_by": "Administrator", "module": "Payroll", "name": "Salary Slip", diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py index 1f8bfea03c..d9aadbf3aa 100644 --- a/erpnext/payroll/doctype/salary_slip/salary_slip.py +++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py @@ -19,6 +19,7 @@ from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_appli from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry from erpnext.accounts.utils import get_fiscal_year +from six import iteritems class SalarySlip(TransactionBase): def __init__(self, *args, **kwargs): @@ -53,6 +54,7 @@ class SalarySlip(TransactionBase): self.compute_year_to_date() self.compute_month_to_date() self.compute_component_wise_year_to_date() + self.add_leave_balances() if frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet"): max_working_hours = frappe.db.get_single_value("Payroll Settings", "max_working_hours_against_timesheet") @@ -1213,6 +1215,22 @@ class SalarySlip(TransactionBase): return period_start_date, period_end_date + def add_leave_balances(self): + self.set('leave_details', []) + + if frappe.db.get_single_value('Payroll Settings', 'show_leave_balances_in_salary_slip'): + from erpnext.hr.doctype.leave_application.leave_application import get_leave_details + leave_details = get_leave_details(self.employee, self.end_date) + + for leave_type, leave_values in iteritems(leave_details['leave_allocation']): + self.append('leave_details', { + 'leave_type': leave_type, + 'total_allocated_leaves': flt(leave_values.get('total_leaves')), + 'expired_leaves': flt(leave_values.get('expired_leaves')), + 'used_leaves': flt(leave_values.get('leaves_taken')), + 'pending_leaves': flt(leave_values.get('pending_leaves')), + 'available_leaves': flt(leave_values.get('remaining_leaves')) + }) def unlink_ref_doc_from_salary_slip(ref_no): linked_ss = frappe.db.sql_list("""select name from `tabSalary Slip` diff --git a/erpnext/payroll/doctype/salary_slip_leave/__init__.py b/erpnext/payroll/doctype/salary_slip_leave/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json new file mode 100644 index 0000000000..7ac453b3c3 --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.json @@ -0,0 +1,78 @@ +{ + "actions": [], + "creation": "2021-02-19 11:45:18.173417", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "leave_type", + "total_allocated_leaves", + "expired_leaves", + "used_leaves", + "pending_leaves", + "available_leaves" + ], + "fields": [ + { + "fieldname": "leave_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Leave Type", + "no_copy": 1, + "options": "Leave Type", + "read_only": 1 + }, + { + "fieldname": "total_allocated_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Allocated Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "expired_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Expired Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "used_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Used Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "pending_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Pending Leave", + "no_copy": 1, + "read_only": 1 + }, + { + "fieldname": "available_leaves", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Available Leave", + "no_copy": 1, + "read_only": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-02-19 10:47:48.546724", + "modified_by": "Administrator", + "module": "Payroll", + "name": "Salary Slip Leave", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py new file mode 100644 index 0000000000..7a92bf18f7 --- /dev/null +++ b/erpnext/payroll/doctype/salary_slip_leave/salary_slip_leave.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, 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 SalarySlipLeave(Document): + pass From ecde26409b9264e3b4185861cb35f4a5318c83fe Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Tue, 2 Mar 2021 18:54:50 +0530 Subject: [PATCH 14/14] Update team_updates.js --- erpnext/hr/page/team_updates/team_updates.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/erpnext/hr/page/team_updates/team_updates.js b/erpnext/hr/page/team_updates/team_updates.js index 991b316e1e..358329748e 100644 --- a/erpnext/hr/page/team_updates/team_updates.js +++ b/erpnext/hr/page/team_updates/team_updates.js @@ -75,6 +75,6 @@ frappe.team_updates = { } me.last_feed_date = date; - $(frappe.render_template('team_update_row', data)).appendTo(me.body) + $(frappe.render_template('team_update_row', data)).appendTo(me.body); } }