Skip to content

Commit

Permalink
Merge pull request #1401 from frappe/version-15-hotfix
Browse files Browse the repository at this point in the history
chore: release v15
  • Loading branch information
ruchamahabal authored Feb 6, 2024
2 parents 2343741 + 7948d3c commit ecbb8ad
Show file tree
Hide file tree
Showing 20 changed files with 351 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
tests:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
NODE_ENV: "production"

strategy:
fail-fast: false
Expand Down
14 changes: 6 additions & 8 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,19 @@
"dependencies": {
"@ionic/vue": "^7.4.3",
"@ionic/vue-router": "^7.4.3",
"@vitejs/plugin-vue": "^4.4.0",
"dayjs": "^1.11.7",
"feather-icons": "^4.28.0",
"frappe-ui": "^0.1.18",
"vue": "^3.2.25",
"vue-router": "^4.0.12"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.4.0",
"vue-router": "^4.0.12",
"vite": "^4.5.0",
"vite-plugin-pwa": "^0.16.6",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"eslint": "^8.39.0",
"eslint-plugin-vue": "^9.11.0",
"postcss": "^8.4.5",
"prettier": "^2.8.8",
"tailwindcss": "^3.0.15",
"vite": "^4.5.0",
"vite-plugin-pwa": "^0.16.6"
"tailwindcss": "^3.0.15"
}
}
6 changes: 3 additions & 3 deletions hrms/hr/doctype/leave_application/leave_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,15 +164,15 @@ def validate_dates(self):
if not allowed_role:
frappe.throw(
_("Backdated Leave Application is restricted. Please set the {} in {}").format(
frappe.bold("Role Allowed to Create Backdated Leave Application"),
get_link_to_form("HR Settings", "HR Settings"),
frappe.bold(_("Role Allowed to Create Backdated Leave Application")),
get_link_to_form("HR Settings", "HR Settings", _("HR Settings")),
)
)

if allowed_role and allowed_role not in user_roles:
frappe.throw(
_("Only users with the {0} role can create backdated leave applications").format(
allowed_role
_(allowed_role)
)
)

Expand Down
7 changes: 6 additions & 1 deletion hrms/hr/doctype/leave_type/leave_type.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ frappe.tour["Leave Type"] = [
{
fieldname: "is_compensatory",
title: "Is Compensatory Leave",
description: __("Leaves you can avail against a holiday you worked on. You can claim Compensatory Off Leave using Compensatory Leave request. Click") + " <a href='https://docs.erpnext.com/docs/v13/user/manual/en/human-resources/compensatory-leave-request' target='_blank'>here</a> " + __('to know more')
description: __(
"Leaves you can avail against a holiday you worked on. You can claim Compensatory Off Leave using Compensatory Leave Request. Click {0} to know more",
[
`<a href='https://frappehr.com/docs/v14/en/compensatory-leave-request' target='_blank'>${__("here")}</a>`
]
)
},
{
fieldname: "allow_encashment",
Expand Down
4 changes: 2 additions & 2 deletions hrms/hr/report/shift_attendance/shift_attendance.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,13 +288,13 @@ def format_in_out_time(in_time, out_time, attendance_date):
in_time = in_time.time()
elif out_time and not in_time and out_time.date() == attendance_date:
out_time = out_time.time()
elif in_time and out_time:
else:
in_time, out_time = convert_datetime_to_time_for_same_date(in_time, out_time)
return in_time, out_time


def convert_datetime_to_time_for_same_date(start, end):
if start.date() == end.date():
if start and end and start.date() == end.date():
start = start.time()
end = end.time()
else:
Expand Down
4 changes: 2 additions & 2 deletions hrms/hr/workspace/expense_claims/expense_claims.json
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@
},
{
"hidden": 0,
"is_query_report": 0,
"is_query_report": 1,
"label": "Vehicle Expenses",
"link_count": 0,
"link_to": "Vehicle Expenses",
Expand All @@ -256,7 +256,7 @@
"type": "Link"
}
],
"modified": "2023-08-02 10:19:14.376183",
"modified": "2024-02-05 09:10:45.636550",
"modified_by": "Administrator",
"module": "HR",
"name": "Expense Claims",
Expand Down
3 changes: 2 additions & 1 deletion hrms/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ hrms.patches.v14_0.create_custom_field_in_loan
hrms.patches.v14_0.update_loan_repayment_repay_from_salary
hrms.patches.v15_0.migrate_loan_type_to_loan_product
hrms.patches.v15_0.rename_and_update_leave_encashment_fields
hrms.patches.v14_0.update_title_in_employee_onboarding_and_separation_templates
hrms.patches.v14_0.update_title_in_employee_onboarding_and_separation_templates
hrms.patches.v15_0.make_hr_settings_tab_in_company_master
22 changes: 22 additions & 0 deletions hrms/patches/v15_0/make_hr_settings_tab_in_company_master.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields


def execute():
custom_fields = {
"Company": [
{
"fieldname": "hr_and_payroll_tab",
"fieldtype": "Tab Break",
"label": "HR & Payroll",
"insert_after": "credit_limit",
},
{
"fieldname": "hr_settings_section",
"fieldtype": "Section Break",
"label": "HR & Payroll Settings",
"insert_after": "hr_and_payroll_tab",
},
],
}

create_custom_fields(custom_fields)
78 changes: 75 additions & 3 deletions hrms/payroll/doctype/salary_component/salary_component.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,17 @@ frappe.ui.form.on("Salary Component", {
},

refresh: function (frm) {
hrms.payroll_common.set_autocompletions_for_condition_and_formula(frm);

if (!frm.doc.__islocal) {
frm.add_custom_button(__("Salary Structure"), () => {
frm.trigger("create_salary_structure");
}, __("Create"));
frm.trigger("add_update_structure_button");
frm.add_custom_button(
__("Salary Structure"),
() => {
frm.trigger("create_salary_structure");
},
__("Create")
);
}
},

Expand Down Expand Up @@ -70,6 +77,71 @@ frappe.ui.form.on("Salary Component", {
}
},

add_update_structure_button: function (frm) {
for (const df of ["Condition", "Formula"]) {
frm.add_custom_button(
__("Sync {0}", [df]),
function () {
frappe
.call({
method: "get_structures_to_be_updated",
doc: frm.doc,
})
.then((r) => {
if (r.message.length)
frm.events.update_salary_structures(frm, df, r.message);
else
frappe.msgprint({
message: __(
"Salary Component {0} is currently not used in any Salary Structure.",
[frm.doc.name.bold()]
),
title: __("No Salary Structures"),
indicator: "orange",
});
});
},
__("Update Salary Structures")
);
}
},

update_salary_structures: function (frm, df, structures) {
let msg = __(
"{0} will be updated for the following Salary Structures: {1}.",
[
df,
frappe.utils.comma_and(
structures.map((d) =>
frappe.utils.get_form_link("Salary Structure", d, true).bold()
)
),
]
);
msg += "<br>";
msg += __("Are you sure you want to proceed?");
frappe.confirm(msg, () => {
frappe
.call({
method: "update_salary_structures",
doc: frm.doc,
args: {
structures: structures,
field: df.toLowerCase(),
value: frm.get_field(df.toLowerCase()).value,
},
})
.then((r) => {
if (!r.exc) {
frappe.show_alert({
message: __("Salary Structures updated successfully"),
indicator: "green",
});
}
});
});
},

create_salary_structure: function (frm) {
frappe.model.with_doctype("Salary Structure", () => {
const salary_structure = frappe.model.get_new_doc("Salary Structure");
Expand Down
8 changes: 5 additions & 3 deletions hrms/payroll/doctype/salary_component/salary_component.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@
{
"fieldname": "condition",
"fieldtype": "Code",
"label": "Condition"
"label": "Condition",
"options": "PythonExpression"
},
{
"default": "0",
Expand All @@ -198,7 +199,8 @@
"depends_on": "amount_based_on_formula",
"fieldname": "formula",
"fieldtype": "Code",
"label": "Formula"
"label": "Formula",
"options": "PythonExpression"
},
{
"depends_on": "eval:doc.amount_based_on_formula!==1",
Expand Down Expand Up @@ -266,7 +268,7 @@
"icon": "fa fa-flag",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2023-08-25 13:35:37.413696",
"modified": "2024-02-02 13:55:55.989527",
"modified_by": "Administrator",
"module": "Payroll",
"name": "Salary Component",
Expand Down
63 changes: 63 additions & 0 deletions hrms/payroll/doctype/salary_component/salary_component.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

import copy

import frappe
from frappe import _
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists

from hrms.payroll.utils import sanitize_expression


class SalaryComponent(Document):
def before_validate(self):
self._condition, self.condition = self.condition, sanitize_expression(self.condition)
self._formula, self.formula = self.formula, sanitize_expression(self.formula)

def validate(self):
self.validate_abbr()
self.validate_accounts()

def on_update(self):
# set old values (allowing multiline strings for better readability in the doctype form)
if self._condition != self.condition:
self.db_set("condition", self._condition)
if self._formula != self.formula:
self.db_set("formula", self._formula)

def clear_cache(self):
from hrms.payroll.doctype.salary_slip.salary_slip import (
Expand All @@ -32,3 +49,49 @@ def validate_abbr(self):
separator="_",
filters={"name": ["!=", self.name]},
)

def validate_accounts(self):
if not (self.statistical_component or (self.accounts and all(d.account for d in self.accounts))):
frappe.msgprint(
title=_("Warning"),
msg=_("Accounts not set for Salary Component {0}").format(self.name),
indicator="orange",
)

@frappe.whitelist()
def get_structures_to_be_updated(self):
SalaryStructure = frappe.qb.DocType("Salary Structure")
SalaryDetail = frappe.qb.DocType("Salary Detail")
return (
frappe.qb.from_(SalaryStructure)
.inner_join(SalaryDetail)
.on(SalaryStructure.name == SalaryDetail.parent)
.select(SalaryStructure.name)
.where((SalaryDetail.salary_component == self.name) & (SalaryStructure.docstatus != 2))
.run(pluck=True)
)

@frappe.whitelist()
def update_salary_structures(self, field, value, structures=None):
if not structures:
structures = self.get_structures_to_be_updated()

for structure in structures:
salary_structure = frappe.get_doc("Salary Structure", structure)
# this is only used for versioning and we do not want
# to make separate db calls by using load_doc_before_save
# which proves to be expensive while doing bulk replace
salary_structure._doc_before_save = copy.deepcopy(salary_structure)

salary_detail_row = next(
(d for d in salary_structure.get(f"{self.type.lower()}s") if d.salary_component == self.name),
None,
)
salary_detail_row.set(field, value)
salary_structure.db_update_all()
salary_structure.flags.updater_reference = {
"doctype": self.doctype,
"docname": self.name,
"label": _("via Salary Component sync"),
}
salary_structure.save_version()
47 changes: 46 additions & 1 deletion hrms/payroll/doctype/salary_component/test_salary_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,54 @@
import frappe
from frappe.tests.utils import FrappeTestCase

from hrms.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure


class TestSalaryComponent(FrappeTestCase):
pass
def test_update_salary_structures(self):
salary_component = create_salary_component("Special Allowance")
salary_component.condition = "H < 10000"
salary_component.formula = "BS*.5"
salary_component.save()

salary_structure1 = make_salary_structure("Salary Structure 1", "Monthly")
salary_structure2 = make_salary_structure("Salary Structure 2", "Monthly")
salary_structure3 = make_salary_structure("Salary Structure 3", "Monthly")
salary_structure3.cancel() # Details should not update for cancelled Salary Structures

ss1_detail = [
d for d in salary_structure1.earnings if d.salary_component == "Special Allowance"
][0]
self.assertEqual(ss1_detail.condition, "H < 10000")
self.assertEqual(ss1_detail.formula, "BS*.5")

ss2_detail = [
d for d in salary_structure2.earnings if d.salary_component == "Special Allowance"
][0]
self.assertEqual(ss2_detail.condition, "H < 10000")
self.assertEqual(ss2_detail.formula, "BS*.5")

ss3_detail = [
d for d in salary_structure3.earnings if d.salary_component == "Special Allowance"
][0]
self.assertEqual(ss3_detail.condition, "H < 10000")
self.assertEqual(ss3_detail.formula, "BS*.5")

salary_component.update_salary_structures("condition", "H < 8000")
ss1_detail.reload()
self.assertEqual(ss1_detail.condition, "H < 8000")
ss2_detail.reload()
self.assertEqual(ss2_detail.condition, "H < 8000")
ss3_detail.reload()
self.assertEqual(ss3_detail.condition, "H < 10000")

salary_component.update_salary_structures("formula", "BS*.3")
ss1_detail.reload()
self.assertEqual(ss1_detail.formula, "BS*.3")
ss2_detail.reload()
self.assertEqual(ss2_detail.formula, "BS*.3")
ss3_detail.reload()
self.assertEqual(ss3_detail.formula, "BS*.5")


def create_salary_component(component_name, **args):
Expand Down
Loading

0 comments on commit ecbb8ad

Please sign in to comment.