Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: base Roster Management #1760

Open
wants to merge 125 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
e34afb6
feat: init roster app
krantheman Apr 26, 2024
e7626fb
fix: update server proxy, routing, package.json scripts, .gitignore, …
krantheman Apr 26, 2024
e71ee98
chore: enable typescript
krantheman Apr 29, 2024
66e16a3
feat: add month view header
krantheman Apr 30, 2024
25992b3
chore: format index.html
krantheman Apr 30, 2024
70ad9a8
feat: add month table header
krantheman Apr 30, 2024
fce5b42
chore: update tsconfig.json
krantheman May 2, 2024
c27c354
feat: get context
krantheman May 2, 2024
d22ab27
feat: fetch shifts
krantheman May 2, 2024
a4a42eb
feat: display shifts on calendar
krantheman May 3, 2024
2045d9a
fix: table border
krantheman May 3, 2024
4cff97d
feat: add employee pics and fix spacing and font sizes
krantheman May 6, 2024
7b24c4d
feat: add shift timings
krantheman May 6, 2024
a8e1c84
feat: add shift assignment dialog
krantheman May 7, 2024
4ea22e2
refactor: use createListresource and createDocumentResource
krantheman May 7, 2024
1497941
feat: add update shift assignment values
krantheman May 7, 2024
49858ad
feat: add plus button to create shift assignments
krantheman May 7, 2024
7e5f166
refactor: move ShiftAssignmentDialog to a new component
krantheman May 7, 2024
54f4613
feat: fetch company and department from employee
krantheman May 8, 2024
ca35978
feat: select date from selected cell
krantheman May 8, 2024
533837c
feat: add shift assignment creation
krantheman May 8, 2024
6ddf02f
fix: submit shift assignment on insert
krantheman May 8, 2024
f5a855d
fix: firstOfMonth time
krantheman May 8, 2024
a7ef63e
fix: fetch employee data only when dialog opens
krantheman May 9, 2024
0a974a4
feat: add shift assignment deletion
krantheman May 9, 2024
c2df51c
feat: make employee column sticky
krantheman May 10, 2024
b12dc2f
fix: spacing
krantheman May 10, 2024
ba9bace
fix: cell height fluctuation
krantheman May 10, 2024
f1b71a9
fix: dialog buttons
krantheman May 10, 2024
76d12a5
chore: add typings
krantheman May 13, 2024
4f81dc5
feat: add holidays
krantheman May 13, 2024
ed01303
feat: add employee search
krantheman May 14, 2024
ac8cbc3
feat: add makeshift navbar
krantheman May 14, 2024
3c741cd
feat: add FilterBar
krantheman May 14, 2024
3b465ef
chore: remove redundant code
krantheman May 14, 2024
7605a04
chore: improve readability
krantheman May 14, 2024
5f41880
feat: add filters
krantheman May 15, 2024
6f80ce8
chore: improve readability
krantheman May 15, 2024
4a3d7ec
refactor: move filters to month select header
krantheman May 16, 2024
f36c13c
feat: add button to toggle showing of filters
krantheman May 16, 2024
75e621c
feat: add transition for filter button
krantheman May 16, 2024
905d60e
feat: add clear filters button
krantheman May 16, 2024
cf3b371
feat: add colors for shift types
krantheman May 17, 2024
8a91e18
fix: font weights
krantheman May 17, 2024
a7e2bb3
refactor: holiday cell
krantheman May 21, 2024
a6c9155
fix: leave filter bar open by default
krantheman May 23, 2024
65a0c2a
fix: spacing
krantheman May 23, 2024
a6f4a76
feat: add designation filter
krantheman May 23, 2024
4c1d500
feat: add multiple selections for employee search
krantheman May 23, 2024
820f047
fix(ShiftAssignmentDialog): use FormControl instead of Autocomplete
krantheman May 23, 2024
207de0f
fix: spacing
krantheman May 23, 2024
f5bc95f
feat: get_leaves api
krantheman May 24, 2024
1a9ed78
feat: add leaves to roster
krantheman May 24, 2024
fe2fa79
fix(Filters): use FormControl instead of Autocomplete
krantheman May 24, 2024
5807236
feat: add Select Working Days ui
krantheman May 27, 2024
614baa6
feat: select default working day based on start date
krantheman May 27, 2024
6e4497c
fix: setting of default day if no start date
krantheman May 27, 2024
3c4d902
feat: add repeating shift assignments
krantheman May 27, 2024
1499176
refactor: clump consecutive shift assignments as one
krantheman May 28, 2024
16d799a
fix: clumping of shifts with gap >= 1 week
krantheman May 28, 2024
7a17a0a
feat: use background job for creating repeating shifts if date_diff >…
krantheman May 28, 2024
c737085
feat: add Shift Assignment Group
krantheman May 28, 2024
8181923
feat: add delete repeating shift assignments
krantheman May 28, 2024
f8ce856
fix: unselected day button text color
krantheman May 29, 2024
ef97bae
chore: improve readability
krantheman May 29, 2024
aeab1df
style: increase employee column width
krantheman May 29, 2024
a8e7449
feat: add toasts everywhere
krantheman May 29, 2024
5e015d5
refactor: move employees resource to MonthView
krantheman May 30, 2024
6b8a8e2
fix: fetch events on creating shift from top bar
krantheman May 30, 2024
ee15dcd
chore: rename file: Navbar.vue -> NavBar.vue
krantheman May 30, 2024
4262c1b
chore: remove vue router
krantheman May 30, 2024
460c913
fix: dependencies
krantheman May 30, 2024
3df285a
revert: removal of vue-router
krantheman May 30, 2024
b16cc91
style: center employee names when no designation in employee column
krantheman May 30, 2024
546194b
fix: import
krantheman May 30, 2024
43fb3ec
style: fix table height and make th row stick
krantheman May 30, 2024
4d9ea29
fix: table height
krantheman May 30, 2024
16d061f
fix: creation of repeating shift with frequency > every week
krantheman May 31, 2024
ce7acf2
refactor: change Shift Assignment Group to Shift Assignment Schedule
krantheman May 31, 2024
a5fe047
feat: add shift move
krantheman Jun 4, 2024
b0550a9
feat: add shift swapping
krantheman Jun 4, 2024
d6996c8
refactor: shift swapping logic
krantheman Jun 4, 2024
1b0f679
fix: error message translation
krantheman Jun 10, 2024
8da9ff7
fix: employee column z index
krantheman Jun 10, 2024
fe314c5
refactor: join consecutive shifts on moving
krantheman Jun 11, 2024
adde0e5
refactor: use insert shift for creating shifts
krantheman Jun 11, 2024
e149476
fix: drag cursor and dragging into blocked days
krantheman Jun 11, 2024
b18929b
feat: scale up shift that is being dragged upon
krantheman Jun 11, 2024
3825ae4
feat: add delete current shift
krantheman Jun 11, 2024
243ff05
chore: rename repeating shift assignment to shift assignment schedule
krantheman Jun 11, 2024
fd2f43a
feat: add bg color to dragover cell
krantheman Jun 12, 2024
3cf536f
feat: can't drop shift to cells with same shift type and status
krantheman Jun 12, 2024
98d8ebf
feat: no bg color for forbidden drop days
krantheman Jun 12, 2024
33a7aac
feat: hide original shift element while dragging
krantheman Jun 12, 2024
84c1396
feat: add loading state
krantheman Jun 12, 2024
4f0b43b
fix(Shift Assignment): check for overlap on update after submission a…
krantheman Jun 13, 2024
199332f
fix(Shift Assignment Schedule): form placement
krantheman Jun 13, 2024
a6ec1c8
revert(Shift Assignment Schedule): form placement
krantheman Jun 13, 2024
39ba79c
feat: add process auto shift creation for shifts without end date
krantheman Jun 13, 2024
1148113
chore: update frappe-ui to v0.1.62
krantheman Jun 17, 2024
7973670
feat: use DatePicker instead of FormControl date
krantheman Jun 17, 2024
6b60596
fix: add website route rule for roster
krantheman Jun 17, 2024
e6f9bba
refactor: use frappe.delete_doc instead of frappe.db.delete
krantheman Jun 17, 2024
48ceb27
refactor: remove Select Days checkbox and add Shift Schedule Settings…
krantheman Jun 17, 2024
587868b
fix: creation of neverending shifts
krantheman Jun 17, 2024
76a35dd
fix: Shift Assignment Schedule naming
krantheman Jun 18, 2024
ba59b6e
feat(Shift Assignment Schedule): add Employee Name field and List Vie…
krantheman Jun 18, 2024
25b46b5
feat(Shift Assignment Schedule): add field description and Shift Assi…
krantheman Jun 18, 2024
2932ba8
feat: show schedule for existing shift assignments
krantheman Jun 18, 2024
cf944e0
feat: redirect to login page when logged out and make navbar actions …
krantheman Jun 19, 2024
2005073
feat: add Confirmation Dialog for delete operations
krantheman Jun 19, 2024
b9d3c2a
fix: field name in process_auto_shift_creation
krantheman Jun 20, 2024
06a1ecc
revert: ShiftAssignmentDialog
krantheman Jun 20, 2024
e130dcc
refactor: DeleteDialog message formatting
krantheman Jun 20, 2024
9133e0f
chore: remove vue-tsc and types/vue
krantheman Jun 21, 2024
bd75905
fix: Shedule Setting & Repeat On Days label names
krantheman Jun 25, 2024
8f963d2
feat: add toast for moving/swapping shifts
krantheman Jun 25, 2024
2f68c1c
Merge branch 'develop' into init-roster
krantheman Jun 25, 2024
606e579
refactor: select only day of start_date for Repeat On Days by default
krantheman Jun 27, 2024
65782df
refactor(Shift Assignment Schedule): change Status to Enabled
krantheman Jun 27, 2024
2c60213
refactor(ShiftAssignmentDialog): change Delete button icon to label f…
krantheman Jun 27, 2024
8d19962
fix: typing
krantheman Jun 27, 2024
b7013b0
feat(Shift Assignment): add validations for checking linked employee …
krantheman Jun 27, 2024
b73956e
refactor: use get_holiday_list_for_employee for holidays
krantheman Jun 28, 2024
a7a4106
fix: insertion of an indefinite shift right after a definite one of t…
krantheman Jul 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ trim_trailing_whitespace = true
charset = utf-8

# js indentation settings
[{*.js,*.vue,*.css,*.scss,*.html}]
[{*.js,*.ts,*.vue,*.css,*.scss,*.html}]
indent_style = tab
indent_size = 4
max_line_length = 99
max_line_length = 99
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ node_modules
hrms/docs/current
hrms/public/frontend
hrms/www/hrms.html
hrms/public/roster
hrms/www/roster.html
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ repos:
rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, vue, css, scss]
# Ignore fronetend folder and any files that might contain jinja / bundles
types_or: [javascript, ts, vue, css, scss]
# Ignore frontend folder and any files that might contain jinja / bundles
exclude: |
(?x)^(
frontend/.*|
Expand All @@ -46,4 +46,4 @@ repos:
ci:
autoupdate_schedule: weekly
skip: []
submodules: false
submodules: false
258 changes: 258 additions & 0 deletions hrms/api/roster.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import frappe
from frappe import _
from frappe.utils import add_days, date_diff, random_string

from hrms.hr.doctype.shift_assignment.shift_assignment import ShiftAssignment
from hrms.hr.doctype.shift_assignment_tool.shift_assignment_tool import create_shift_assignment


@frappe.whitelist()
def get_values(doctype: str, name: str, fields: list) -> dict[str, str]:
return frappe.db.get_value(doctype, name, fields, as_dict=True)


@frappe.whitelist()
def get_events(
month_start: str, month_end: str, employee_filters: dict[str, str], shift_filters: dict[str, str]
) -> dict[str, list[dict]]:
holidays = get_holidays(month_start, month_end, employee_filters)
leaves = get_leaves(month_start, month_end, employee_filters)
shifts = get_shifts(month_start, month_end, employee_filters, shift_filters)

events = {}
for event in [holidays, leaves, shifts]:
event = group_by_employee(event)
for key, value in event.items():
if key in events:
events[key].extend(value)
else:
events[key] = value
return events


@frappe.whitelist()
def create_shift_assignment_schedule(
employee: str,
company: str,
shift_type: str,
status: str,
start_date: str,
end_date: str | None,
days: list[str],
frequency: str,
) -> None:
schedule = frappe.get_doc(
{
"doctype": "Shift Assignment Schedule",
"schedule": random_string(10),
"frequency": frequency,
"days": [{"day": day} for day in days],
"status": "Inactive" if end_date else "Active",
krantheman marked this conversation as resolved.
Show resolved Hide resolved
"employee": employee,
"company": company,
"shift_type": shift_type,
"shift_status": status,
}
).insert()

if not end_date or date_diff(end_date, start_date) <= 90:
return schedule.create_shifts(start_date, end_date)

frappe.enqueue(schedule.create_shifts, timeout=4500, start_date=start_date, end_date=end_date)


@frappe.whitelist()
def delete_shift_assignment_schedule(schedule: str) -> None:
frappe.db.delete("Shift Assignment", {"schedule": schedule})
krantheman marked this conversation as resolved.
Show resolved Hide resolved
frappe.delete_doc("Shift Assignment Schedule", schedule)


@frappe.whitelist()
def swap_shift(
krantheman marked this conversation as resolved.
Show resolved Hide resolved
src_shift: str, src_date: str, tgt_employee: str, tgt_date: str, tgt_shift: str | None
) -> None:
if src_shift == tgt_shift:
frappe.throw(_("Source and target shifts cannot be the same"))

if tgt_shift:
tgt_shift_doc = frappe.get_doc("Shift Assignment", tgt_shift)
tgt_company = tgt_shift_doc.company
break_shift(tgt_shift_doc, tgt_date)
else:
tgt_company = frappe.db.get_value("Employee", tgt_employee, "company")

src_shift_doc = frappe.get_doc("Shift Assignment", src_shift)
break_shift(src_shift_doc, src_date)
insert_shift(
tgt_employee, tgt_company, src_shift_doc.shift_type, tgt_date, tgt_date, src_shift_doc.status
)

if tgt_shift:
insert_shift(
src_shift_doc.employee,
src_shift_doc.company,
tgt_shift_doc.shift_type,
src_date,
src_date,
tgt_shift_doc.status,
)


@frappe.whitelist()
def break_shift(assignment: str | ShiftAssignment, date: str) -> None:
if isinstance(assignment, str):
assignment = frappe.get_doc("Shift Assignment", assignment)

if assignment.end_date and date_diff(assignment.end_date, date) < 0:
frappe.throw(_("Cannot break shift after end date"))
if date_diff(assignment.start_date, date) > 0:
frappe.throw(_("Cannot break shift before start date"))

employee = assignment.employee
company = assignment.company
shift_type = assignment.shift_type
status = assignment.status
end_date = assignment.end_date

if date_diff(date, assignment.start_date) == 0:
assignment.cancel()
assignment.delete()
else:
assignment.end_date = add_days(date, -1)
assignment.save()

if not end_date or date_diff(end_date, date) > 0:
create_shift_assignment(employee, company, shift_type, add_days(date, 1), end_date, status)


@frappe.whitelist()
def insert_shift(
employee: str, company: str, shift_type: str, start_date: str, end_date: str, status: str
) -> None:
filters = {
"doctype": "Shift Assignment",
"employee": employee,
"company": company,
"shift_type": shift_type,
"status": status,
}
prev_shift = frappe.db.exists(dict({"end_date": add_days(start_date, -1)}, **filters))
next_shift = frappe.db.exists(dict({"start_date": add_days(end_date, 1)}, **filters))

if prev_shift:
if next_shift:
end_date = frappe.db.get_value("Shift Assignment", next_shift, "end_date")
frappe.db.set_value("Shift Assignment", next_shift, "docstatus", 2)
frappe.delete_doc("Shift Assignment", next_shift)
frappe.db.set_value("Shift Assignment", prev_shift, "end_date", end_date)

elif next_shift:
frappe.db.set_value("Shift Assignment", next_shift, "start_date", start_date)

else:
create_shift_assignment(employee, company, shift_type, start_date, end_date, status)


def get_holidays(month_start: str, month_end: str, employee_filters: dict[str, str]) -> dict[str, list[dict]]:
Employee = frappe.qb.DocType("Employee")
HolidayList = frappe.qb.DocType("Holiday List")
Holiday = frappe.qb.DocType("Holiday")

query = (
krantheman marked this conversation as resolved.
Show resolved Hide resolved
frappe.qb.select(
Employee.employee,
Holiday.name.as_("holiday"),
Holiday.holiday_date,
Holiday.description,
Holiday.weekly_off,
)
.from_(Employee)
.join(HolidayList)
.on(Employee.holiday_list == HolidayList.name)
.join(Holiday)
.on(Holiday.parent == HolidayList.name)
.where(Holiday.holiday_date[month_start:month_end])
)

for filter in employee_filters:
query = query.where(Employee[filter] == employee_filters[filter])

return query.run(as_dict=True)


def get_leaves(month_start: str, month_end: str, employee_filters: dict[str, str]) -> dict[str, list[dict]]:
LeaveApplication = frappe.qb.DocType("Leave Application")
Employee = frappe.qb.DocType("Employee")

query = (
frappe.qb.select(
LeaveApplication.name.as_("leave"),
LeaveApplication.employee,
LeaveApplication.leave_type,
LeaveApplication.from_date,
LeaveApplication.to_date,
)
.from_(LeaveApplication)
.left_join(Employee)
.on(LeaveApplication.employee == Employee.name)
.where(
(LeaveApplication.docstatus == 1)
& (LeaveApplication.status == "Approved")
& (LeaveApplication.from_date <= month_end)
& (LeaveApplication.to_date >= month_start)
)
)

for filter in employee_filters:
query = query.where(Employee[filter] == employee_filters[filter])

return query.run(as_dict=True)


def get_shifts(
month_start: str, month_end: str, employee_filters: dict[str, str], shift_filters: dict[str, str]
) -> dict[str, list[dict]]:
ShiftAssignment = frappe.qb.DocType("Shift Assignment")
ShiftType = frappe.qb.DocType("Shift Type")
Employee = frappe.qb.DocType("Employee")

query = (
frappe.qb.select(
ShiftAssignment.name,
ShiftAssignment.employee,
ShiftAssignment.shift_type,
ShiftAssignment.start_date,
ShiftAssignment.end_date,
ShiftAssignment.status,
ShiftType.start_time,
ShiftType.end_time,
ShiftType.color,
)
.from_(ShiftAssignment)
.left_join(ShiftType)
.on(ShiftAssignment.shift_type == ShiftType.name)
.left_join(Employee)
.on(ShiftAssignment.employee == Employee.name)
.where(
(ShiftAssignment.docstatus == 1)
& (ShiftAssignment.start_date <= month_end)
& ((ShiftAssignment.end_date >= month_start) | (ShiftAssignment.end_date.isnull()))
)
)

for filter in employee_filters:
query = query.where(Employee[filter] == employee_filters[filter])

for filter in shift_filters:
query = query.where(ShiftAssignment[filter] == shift_filters[filter])

return query.run(as_dict=True)


def group_by_employee(events: list[dict]) -> dict[str, list[dict]]:
grouped_events = {}
for event in events:
grouped_events.setdefault(event["employee"], []).append(
{k: v for k, v in event.items() if k != "employee"}
)
return grouped_events
1 change: 1 addition & 0 deletions hrms/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@
],
"hourly_long": [
"hrms.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
"hrms.hr.doctype.shift_assignment_schedule.shift_assignment_schedule.process_auto_shift_creation",
],
krantheman marked this conversation as resolved.
Show resolved Hide resolved
"daily": [
"hrms.controllers.employee_reminders.send_birthday_reminders",
Expand Down
10 changes: 9 additions & 1 deletion hrms/hr/doctype/shift_assignment/shift_assignment.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"employee_name",
"shift_type",
"status",
"schedule",
"column_break_3",
"company",
"start_date",
Expand Down Expand Up @@ -101,11 +102,18 @@
"fieldtype": "Select",
"label": "Status",
"options": "Active\nInactive"
},
{
"fieldname": "schedule",
"fieldtype": "Link",
"label": "Schedule",
"options": "Shift Assignment Schedule",
"read_only": 1
}
],
"is_submittable": 1,
"links": [],
"modified": "2024-04-04 17:13:13.137431",
"modified": "2024-05-31 16:41:32.869130",
"modified_by": "Administrator",
"module": "HR",
"name": "Shift Assignment",
Expand Down
7 changes: 7 additions & 0 deletions hrms/hr/doctype/shift_assignment/shift_assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,19 @@ class MultipleShiftError(frappe.ValidationError):
class ShiftAssignment(Document):
def validate(self):
validate_active_employee(self.employee)
if self.end_date:
self.validate_from_to_dates("start_date", "end_date")
self.validate_overlapping_shifts()

def on_update_after_submit(self):
if self.end_date:
self.validate_from_to_dates("start_date", "end_date")
self.validate_overlapping_shifts()

def validate_overlapping_shifts(self):
if self.status == "Inactive":
return

overlapping_dates = self.get_overlapping_dates()
if len(overlapping_dates):
self.validate_same_date_multiple_shifts(overlapping_dates)
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt

// frappe.ui.form.on("Shift Assignment Schedule", {
// refresh(frm) {

// },
// });
Loading
Loading