Skip to content

Commit e169f15

Browse files
authored
Merge pull request #2322 from frappe/version-15-hotfix
2 parents 281c25b + d2a693f commit e169f15

File tree

17 files changed

+400
-152
lines changed

17 files changed

+400
-152
lines changed

frontend/src/views/Login.vue

+21-1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@
3232
Login
3333
</Button>
3434
</form>
35+
36+
<template v-if="authProviders.data?.length">
37+
<div class="text-center text-sm text-gray-600 my-4">or</div>
38+
<div class="space-y-4">
39+
<a
40+
v-for="provider in authProviders.data"
41+
:key="provider.name"
42+
class="flex items-center justify-center gap-2 transition-colors focus:outline-none text-gray-800 bg-gray-100 hover:bg-gray-200 active:bg-gray-300 focus-visible:ring focus-visible:ring-gray-400 h-7 text-base p-2 rounded"
43+
:href="provider.auth_url"
44+
>
45+
<img class="h-4 w-4" :src="provider.icon" :alt="provider.provider_name" />
46+
<span>Login with {{ provider.provider_name }}</span>
47+
</a>
48+
</div>
49+
</template>
3550
</div>
3651
</div>
3752

@@ -88,7 +103,7 @@
88103
<script setup>
89104
import { IonPage, IonContent } from "@ionic/vue"
90105
import { inject, reactive, ref } from "vue"
91-
import { Input, Button, ErrorMessage, Dialog } from "frappe-ui"
106+
import { Input, Button, ErrorMessage, Dialog, createResource } from "frappe-ui"
92107
93108
import FrappeHRLogo from "@/components/icons/FrappeHRLogo.vue"
94109
@@ -141,4 +156,9 @@ async function submit(e) {
141156
errorMessage.value = error.messages.join("\n")
142157
}
143158
}
159+
160+
const authProviders = createResource({
161+
url: "hrms.api.oauth.oauth_providers",
162+
auto: true,
163+
})
144164
</script>

hrms/api/oauth.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import frappe
2+
3+
4+
@frappe.whitelist(allow_guest=True)
5+
def oauth_providers():
6+
from frappe.utils.html_utils import get_icon_html
7+
from frappe.utils.oauth import get_oauth2_authorize_url, get_oauth_keys
8+
from frappe.utils.password import get_decrypted_password
9+
10+
out = []
11+
providers = frappe.get_all(
12+
"Social Login Key",
13+
filters={"enable_social_login": 1},
14+
fields=["name", "client_id", "base_url", "provider_name", "icon"],
15+
order_by="name",
16+
)
17+
18+
for provider in providers:
19+
client_secret = get_decrypted_password("Social Login Key", provider.name, "client_secret")
20+
if not client_secret:
21+
continue
22+
23+
if provider.client_id and provider.base_url and get_oauth_keys(provider.name):
24+
out.append(
25+
{
26+
"name": provider.name,
27+
"provider_name": provider.provider_name,
28+
"auth_url": get_oauth2_authorize_url(provider.name, "/hrms"),
29+
"icon": provider.icon,
30+
}
31+
)
32+
33+
return out

hrms/hr/doctype/employee_advance/employee_advance.py

+11
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def validate(self):
3333

3434
def on_cancel(self):
3535
self.ignore_linked_doctypes = "GL Entry"
36+
self.check_linked_payment_entry()
3637
self.set_status(update=True)
3738

3839
def on_update(self):
@@ -175,6 +176,16 @@ def set_pending_amount(self):
175176
)
176177
).run()[0][0] or 0.0
177178

179+
def check_linked_payment_entry(self):
180+
from erpnext.accounts.utils import (
181+
remove_ref_doc_link_from_pe,
182+
update_accounting_ledgers_after_reference_removal,
183+
)
184+
185+
if frappe.db.get_single_value("HR Settings", "unlink_payment_on_cancellation_of_employee_advance"):
186+
remove_ref_doc_link_from_pe(self.doctype, self.name)
187+
update_accounting_ledgers_after_reference_removal(self.doctype, self.name)
188+
178189

179190
@frappe.whitelist()
180191
def make_bank_entry(dt, dn):

hrms/hr/doctype/employee_advance/test_employee_advance.py

+41-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# See license.txt
33

44
import frappe
5-
from frappe.tests.utils import FrappeTestCase
5+
from frappe.tests.utils import FrappeTestCase, change_settings
66
from frappe.utils import flt, nowdate
77

88
import erpnext
@@ -26,9 +26,10 @@
2626
class TestEmployeeAdvance(FrappeTestCase):
2727
def setUp(self):
2828
frappe.db.delete("Employee Advance")
29+
self.update_company_in_fiscal_year()
2930

3031
def test_paid_amount_and_status(self):
31-
employee_name = make_employee("_T@employe.advance")
32+
employee_name = make_employee("_T@employee.advance", "_Test Company")
3233
advance = make_employee_advance(employee_name)
3334

3435
journal_entry = make_journal_entry_for_advance(advance)
@@ -44,7 +45,7 @@ def test_paid_amount_and_status(self):
4445
self.assertRaises(EmployeeAdvanceOverPayment, journal_entry1.submit)
4546

4647
def test_paid_amount_on_pe_cancellation(self):
47-
employee_name = make_employee("_T@employe.advance")
48+
employee_name = make_employee("_T@employee.advance", "_Test Company")
4849
advance = make_employee_advance(employee_name)
4950

5051
journal_entry = make_journal_entry_for_advance(advance)
@@ -159,14 +160,19 @@ def test_partly_claimed_and_returned_status(self):
159160
self.assertTrue(advance.name in advances)
160161

161162
def test_repay_unclaimed_amount_from_salary(self):
162-
employee_name = make_employee("_T@employe.advance")
163+
employee_name = make_employee("_T@employee.advance", "_Test Company")
163164
advance = make_employee_advance(employee_name, {"repay_unclaimed_amount_from_salary": 1})
164165
journal_entry = make_journal_entry_for_advance(advance)
165166
journal_entry.submit()
166167

167168
args = {"type": "Deduction"}
168169
create_salary_component("Advance Salary - Deduction", **args)
169-
make_salary_structure("Test Additional Salary for Advance Return", "Monthly", employee=employee_name)
170+
make_salary_structure(
171+
"Test Additional Salary for Advance Return",
172+
"Monthly",
173+
employee=employee_name,
174+
company="_Test Company",
175+
)
170176

171177
# additional salary for 700 first
172178
advance.reload()
@@ -199,7 +205,7 @@ def test_repay_unclaimed_amount_from_salary(self):
199205
self.assertEqual(advance.status, "Paid")
200206

201207
def test_payment_entry_against_advance(self):
202-
employee_name = make_employee("[email protected]")
208+
employee_name = make_employee("[email protected]", "_Test Company")
203209
advance = make_employee_advance(employee_name)
204210

205211
pe = make_payment_entry(advance, 700)
@@ -218,7 +224,7 @@ def test_payment_entry_against_advance(self):
218224
self.assertEqual(advance.paid_amount, 700)
219225

220226
def test_precision(self):
221-
employee_name = make_employee("[email protected]")
227+
employee_name = make_employee("[email protected]", "_Test Company")
222228
advance = make_employee_advance(employee_name)
223229
journal_entry = make_journal_entry_for_advance(advance)
224230
journal_entry.submit()
@@ -257,7 +263,7 @@ def test_precision(self):
257263
self.assertEqual(advance.status, "Partly Claimed and Returned")
258264

259265
def test_pending_amount(self):
260-
employee_name = make_employee("[email protected]")
266+
employee_name = make_employee("[email protected]", "_Test Company")
261267

262268
advance1 = make_employee_advance(employee_name)
263269
make_payment_entry(advance1, 500)
@@ -271,6 +277,33 @@ def test_pending_amount(self):
271277
# (1000 - 500) + (1000 - 700)
272278
self.assertEqual(advance3.pending_amount, 800)
273279

280+
@change_settings("HR Settings", {"unlink_payment_on_cancellation_of_employee_advance": True})
281+
def test_unlink_payment_entries(self):
282+
employee_name = make_employee("[email protected]", "_Test Company")
283+
self.assertTrue(frappe.db.exists("Employee", employee_name))
284+
285+
advance = make_employee_advance(employee_name)
286+
self.assertTrue(advance)
287+
288+
advance_payment = make_payment_entry(advance, 1000)
289+
self.assertTrue(advance_payment)
290+
self.assertEqual(advance_payment.total_allocated_amount, 1000)
291+
292+
advance.reload()
293+
advance.cancel()
294+
advance_payment.reload()
295+
self.assertEqual(advance_payment.unallocated_amount, 1000)
296+
self.assertEqual(advance_payment.references, [])
297+
298+
def update_company_in_fiscal_year(self):
299+
fy_entries = frappe.get_all("Fiscal Year")
300+
for fy_entry in fy_entries:
301+
fiscal_year = frappe.get_doc("Fiscal Year", fy_entry.name)
302+
company_list = [fy_c.company for fy_c in fiscal_year.companies if fy_c.company]
303+
if "_Test Company" not in company_list:
304+
fiscal_year.append("companies", {"company": "_Test Company"})
305+
fiscal_year.save()
306+
274307

275308
def make_journal_entry_for_advance(advance):
276309
journal_entry = frappe.get_doc(make_bank_entry("Employee Advance", advance.name))

hrms/hr/doctype/hr_settings/hr_settings.json

+15-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@
4949
"exit_questionnaire_notification_template",
5050
"attendance_settings_section",
5151
"allow_employee_checkin_from_mobile_app",
52-
"allow_geolocation_tracking"
52+
"allow_geolocation_tracking",
53+
"unlink_payment_section",
54+
"unlink_payment_on_cancellation_of_employee_advance"
5355
],
5456
"fields": [
5557
{
@@ -316,13 +318,24 @@
316318
"fieldname": "attendance_settings_section",
317319
"fieldtype": "Section Break",
318320
"label": "Attendance Settings"
321+
},
322+
{
323+
"fieldname": "unlink_payment_section",
324+
"fieldtype": "Section Break",
325+
"label": "Unlink Payment"
326+
},
327+
{
328+
"default": "0",
329+
"fieldname": "unlink_payment_on_cancellation_of_employee_advance",
330+
"fieldtype": "Check",
331+
"label": " Unlink Payment on Cancellation of Employee Advance"
319332
}
320333
],
321334
"icon": "fa fa-cog",
322335
"idx": 1,
323336
"issingle": 1,
324337
"links": [],
325-
"modified": "2024-06-26 15:20:17.802079",
338+
"modified": "2024-09-29 12:49:16.175079",
326339
"modified_by": "Administrator",
327340
"module": "HR",
328341
"name": "HR Settings",

hrms/hr/doctype/leave_policy_assignment/leave_policy_assignment.py

+20-11
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,11 @@ def _get_months_passed(current_date, from_date, consider_current_month):
182182
months_passed += 1
183183

184184
elif current_date.year > from_date.year:
185-
months_passed = (12 - from_date.month) + current_date.month
185+
months_passed = (
186+
(12 - from_date.month)
187+
+ (current_date.year - from_date.year - 1) * 12
188+
+ current_date.month
189+
)
186190
if consider_current_month:
187191
months_passed += 1
188192

@@ -286,16 +290,7 @@ def create_assignment_for_multiple_employees(employees, data):
286290
failed = []
287291

288292
for employee in employees:
289-
assignment = frappe.new_doc("Leave Policy Assignment")
290-
assignment.employee = employee
291-
assignment.assignment_based_on = data.assignment_based_on or None
292-
assignment.leave_policy = data.leave_policy
293-
assignment.effective_from = getdate(data.effective_from) or None
294-
assignment.effective_to = getdate(data.effective_to) or None
295-
assignment.leave_period = data.leave_period or None
296-
assignment.carry_forward = data.carry_forward
297-
assignment.save()
298-
293+
assignment = create_assignment(employee, data)
299294
savepoint = "before_assignment_submission"
300295
try:
301296
frappe.db.savepoint(savepoint)
@@ -313,6 +308,20 @@ def create_assignment_for_multiple_employees(employees, data):
313308
return docs_name
314309

315310

311+
@frappe.whitelist()
312+
def create_assignment(employee, data):
313+
assignment = frappe.new_doc("Leave Policy Assignment")
314+
assignment.employee = employee
315+
assignment.assignment_based_on = data.assignment_based_on or None
316+
assignment.leave_policy = data.leave_policy
317+
assignment.effective_from = getdate(data.effective_from) or None
318+
assignment.effective_to = getdate(data.effective_to) or None
319+
assignment.leave_period = data.leave_period or None
320+
assignment.carry_forward = data.carry_forward
321+
assignment.save()
322+
return assignment
323+
324+
316325
def show_assignment_submission_status(failed):
317326
frappe.clear_messages()
318327
assignment_list = [get_link_to_form("Leave Policy Assignment", entry) for entry in failed]

hrms/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py

+47
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
from frappe.utils import add_months, get_first_day, get_year_ending, getdate
77

88
from hrms.hr.doctype.leave_application.test_leave_application import get_employee, get_leave_period
9+
from hrms.hr.doctype.leave_period.test_leave_period import create_leave_period
910
from hrms.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
1011
from hrms.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
12+
create_assignment,
1113
create_assignment_for_multiple_employees,
1214
)
15+
from hrms.hr.doctype.leave_type.test_leave_type import create_leave_type
1316

1417
test_dependencies = ["Employee"]
1518

@@ -20,6 +23,7 @@ def setUp(self):
2023
"Leave Period",
2124
"Leave Application",
2225
"Leave Allocation",
26+
"Leave Policy",
2327
"Leave Policy Assignment",
2428
"Leave Ledger Entry",
2529
]:
@@ -117,6 +121,49 @@ def test_pro_rated_leave_allocation(self):
117121
# pro-rated leave allocation for 9 months
118122
self.assertEqual(allocation, 9)
119123

124+
# tests no of leaves for passed months if assignment is based on Leave Period / Joining Date
125+
def test_get_leaves_for_passed_months(self):
126+
first_day = get_first_day(getdate())
127+
annual_allocation = 10
128+
leave_type = create_leave_type(
129+
leave_type_name="_Test Earned Leave", is_earned_leave=True, allocate_on_day="First Day"
130+
)
131+
leave_policy = create_leave_policy(leave_type=leave_type, annual_allocation=annual_allocation)
132+
leave_policy.submit()
133+
134+
data = {
135+
"assignment_based_on": "Joining Date",
136+
"leave_policy": leave_policy.name,
137+
}
138+
139+
self.employee.date_of_joining = add_months(first_day, -5)
140+
self.employee.save()
141+
assignment = create_assignment(self.employee.name, frappe._dict(data))
142+
new_leaves_allocated = assignment.get_leaves_for_passed_months(
143+
annual_allocation, leave_type, self.employee.date_of_joining
144+
)
145+
self.assertEqual(new_leaves_allocated, 5)
146+
147+
self.employee.date_of_joining = add_months(first_day, -35)
148+
self.employee.save()
149+
assignment = create_assignment(self.employee.name, frappe._dict(data))
150+
new_leaves_allocated = assignment.get_leaves_for_passed_months(
151+
annual_allocation, leave_type, self.employee.date_of_joining
152+
)
153+
self.assertEqual(new_leaves_allocated, 30)
154+
155+
leave_period = create_leave_period(add_months(first_day, -23), first_day)
156+
data = {
157+
"assignment_based_on": "Leave Period",
158+
"leave_policy": leave_policy.name,
159+
"leave_period": leave_period.name,
160+
}
161+
assignment = create_assignment(self.employee.name, frappe._dict(data))
162+
new_leaves_allocated = assignment.get_leaves_for_passed_months(
163+
annual_allocation, leave_type, self.employee.date_of_joining
164+
)
165+
self.assertEqual(new_leaves_allocated, 20)
166+
120167
def test_pro_rated_leave_allocation_for_custom_date_range(self):
121168
leave_type = frappe.get_doc(
122169
{

hrms/hr/doctype/leave_type/test_leave_type.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88

99
def create_leave_type(**args):
1010
args = frappe._dict(args)
11-
if frappe.db.exists("Leave Type", args.leave_type_name):
12-
frappe.delete_doc("Leave Type", args.leave_type_name, force=True)
11+
leave_type_name = args.leave_type_name or "_Test Leave Type"
12+
if frappe.db.exists("Leave Type", leave_type_name):
13+
frappe.delete_doc("Leave Type", leave_type_name, force=True)
1314

1415
leave_type = frappe.get_doc(
1516
{
1617
"doctype": "Leave Type",
17-
"leave_type_name": args.leave_type_name or "_Test Leave Type",
18+
"leave_type_name": leave_type_name,
1819
"include_holiday": args.include_holidays or 1,
1920
"allow_encashment": args.allow_encashment or 0,
2021
"is_earned_leave": args.is_earned_leave or 0,
@@ -26,6 +27,7 @@ def create_leave_type(**args):
2627
"earning_component": "Leave Encashment",
2728
"max_leaves_allowed": args.max_leaves_allowed,
2829
"maximum_carry_forwarded_leaves": args.maximum_carry_forwarded_leaves,
30+
"allocate_on_day": args.allocate_on_day or "Last Day",
2931
}
3032
)
3133

0 commit comments

Comments
 (0)