Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
H
hrsystem
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Administrator
hrsystem
Commits
ef9a81cb
Commit
ef9a81cb
authored
Apr 01, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 27 files via Son of Anton
parent
8494e6c4
Changes
27
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
2289 additions
and
0 deletions
+2289
-0
app.module.ts
backend/src/app.module.ts
+11
-0
create-holiday.dto.ts
backend/src/modules/holidays/dto/create-holiday.dto.ts
+22
-0
holiday-filter.dto.ts
backend/src/modules/holidays/dto/holiday-filter.dto.ts
+22
-0
update-holiday.dto.ts
backend/src/modules/holidays/dto/update-holiday.dto.ts
+25
-0
holidays.controller.ts
backend/src/modules/holidays/holidays.controller.ts
+62
-0
holidays.module.ts
backend/src/modules/holidays/holidays.module.ts
+10
-0
holidays.service.ts
backend/src/modules/holidays/holidays.service.ts
+232
-0
create-meeting.dto.ts
backend/src/modules/meetings/dto/create-meeting.dto.ts
+41
-0
meeting-filter.dto.ts
backend/src/modules/meetings/dto/meeting-filter.dto.ts
+24
-0
meeting-notes.dto.ts
backend/src/modules/meetings/dto/meeting-notes.dto.ts
+15
-0
update-meeting.dto.ts
backend/src/modules/meetings/dto/update-meeting.dto.ts
+33
-0
meetings.controller.ts
backend/src/modules/meetings/meetings.controller.ts
+83
-0
meetings.module.ts
backend/src/modules/meetings/meetings.module.ts
+12
-0
meetings.service.ts
backend/src/modules/meetings/meetings.service.ts
+490
-0
review-schedule-change.dto.ts
...d/src/modules/schedules/dto/review-schedule-change.dto.ts
+11
-0
schedule-change-request.dto.ts
.../src/modules/schedules/dto/schedule-change-request.dto.ts
+14
-0
schedules.controller.ts
backend/src/modules/schedules/schedules.controller.ts
+69
-0
schedules.module.ts
backend/src/modules/schedules/schedules.module.ts
+13
-0
schedules.service.ts
backend/src/modules/schedules/schedules.service.ts
+387
-0
create-unavailability.dto.ts
...c/modules/unavailability/dto/create-unavailability.dto.ts
+20
-0
unavailability-filter.dto.ts
...c/modules/unavailability/dto/unavailability-filter.dto.ts
+20
-0
update-unavailability.dto.ts
...c/modules/unavailability/dto/update-unavailability.dto.ts
+19
-0
unavailability.controller.ts
...d/src/modules/unavailability/unavailability.controller.ts
+80
-0
unavailability.module.ts
backend/src/modules/unavailability/unavailability.module.ts
+12
-0
unavailability.service.ts
backend/src/modules/unavailability/unavailability.service.ts
+412
-0
schema-scheduling.prisma
prisma/schema-scheduling.prisma
+118
-0
scheduling.enum.ts
shared/src/enums/scheduling.enum.ts
+32
-0
No files found.
backend/src/app.module.ts
View file @
ef9a81cb
...
...
@@ -46,6 +46,12 @@ import { EvaluationsModule } from './modules/evaluations/evaluations.module';
import
{
PIPModule
}
from
'./modules/pip/pip.module'
;
import
{
LearningModule
}
from
'./modules/learning/learning.module'
;
// ─── Phase 2C: Time & Scheduling ────────────────────────────
import
{
HolidaysModule
}
from
'./modules/holidays/holidays.module'
;
import
{
UnavailabilityModule
}
from
'./modules/unavailability/unavailability.module'
;
import
{
SchedulesModule
}
from
'./modules/schedules/schedules.module'
;
import
{
MeetingsModule
}
from
'./modules/meetings/meetings.module'
;
import
{
JwtAuthGuard
}
from
'./common/guards/jwt-auth.guard'
;
import
{
RolesGuard
}
from
'./common/guards/roles.guard'
;
import
{
TransformInterceptor
}
from
'./common/interceptors/transform.interceptor'
;
...
...
@@ -92,6 +98,11 @@ import { RateLimitMiddleware } from './common/middleware/rate-limit.middleware';
EvaluationsModule
,
PIPModule
,
LearningModule
,
// Phase 2C
HolidaysModule
,
UnavailabilityModule
,
SchedulesModule
,
MeetingsModule
,
],
providers
:
[
{
provide
:
APP_GUARD
,
useClass
:
JwtAuthGuard
},
...
...
backend/src/modules/holidays/dto/create-holiday.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsBoolean
,
IsOptional
,
IsDateString
,
MinLength
,
MaxLength
}
from
'class-validator'
;
export
class
CreateHolidayDto
{
@
IsString
()
@
MinLength
(
2
)
@
MaxLength
(
200
)
name
:
string
;
@
IsDateString
()
startDate
:
string
;
@
IsDateString
()
endDate
:
string
;
@
IsOptional
()
@
IsBoolean
()
isRecurring
?:
boolean
;
@
IsOptional
()
@
IsString
()
notes
?:
string
;
}
\ No newline at end of file
backend/src/modules/holidays/dto/holiday-filter.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsOptional
,
IsString
,
IsDateString
,
IsBoolean
}
from
'class-validator'
;
import
{
Type
}
from
'class-transformer'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
HolidayFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsDateString
()
dateFrom
?:
string
;
@
IsOptional
()
@
IsDateString
()
dateTo
?:
string
;
@
IsOptional
()
@
Type
(()
=>
Boolean
)
@
IsBoolean
()
isRecurring
?:
boolean
;
@
IsOptional
()
@
IsString
()
year
?:
string
;
}
\ No newline at end of file
backend/src/modules/holidays/dto/update-holiday.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsBoolean
,
IsOptional
,
IsDateString
,
MinLength
,
MaxLength
}
from
'class-validator'
;
export
class
UpdateHolidayDto
{
@
IsOptional
()
@
IsString
()
@
MinLength
(
2
)
@
MaxLength
(
200
)
name
?:
string
;
@
IsOptional
()
@
IsDateString
()
startDate
?:
string
;
@
IsOptional
()
@
IsDateString
()
endDate
?:
string
;
@
IsOptional
()
@
IsBoolean
()
isRecurring
?:
boolean
;
@
IsOptional
()
@
IsString
()
notes
?:
string
;
}
\ No newline at end of file
backend/src/modules/holidays/holidays.controller.ts
0 → 100644
View file @
ef9a81cb
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
HolidaysService
}
from
'./holidays.service'
;
import
{
CreateHolidayDto
}
from
'./dto/create-holiday.dto'
;
import
{
UpdateHolidayDto
}
from
'./dto/update-holiday.dto'
;
import
{
HolidayFilterDto
}
from
'./dto/holiday-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'holidays'
)
export
class
HolidaysController
{
constructor
(
private
readonly
holidaysService
:
HolidaysService
)
{}
@
Post
()
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
create
(@
Body
()
dto
:
CreateHolidayDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
holidaysService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
HolidayFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
holidaysService
.
findAll
(
filter
,
user
);
}
@
Get
(
'upcoming'
)
async
getUpcoming
(@
Query
(
'days'
)
days
?:
string
)
{
return
this
.
holidaysService
.
getUpcoming
(
days
?
parseInt
(
days
,
10
)
:
90
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
)
{
return
this
.
holidaysService
.
findById
(
id
);
}
@
Put
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
update
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateHolidayDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
holidaysService
.
update
(
id
,
dto
,
user
);
}
@
Delete
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
holidaysService
.
delete
(
id
,
user
);
return
{
message
:
'Holiday deleted'
};
}
}
\ No newline at end of file
backend/src/modules/holidays/holidays.module.ts
0 → 100644
View file @
ef9a81cb
import
{
Module
}
from
'@nestjs/common'
;
import
{
HolidaysController
}
from
'./holidays.controller'
;
import
{
HolidaysService
}
from
'./holidays.service'
;
@
Module
({
controllers
:
[
HolidaysController
],
providers
:
[
HolidaysService
],
exports
:
[
HolidaysService
],
})
export
class
HolidaysModule
{}
\ No newline at end of file
backend/src/modules/holidays/holidays.service.ts
0 → 100644
View file @
ef9a81cb
import
{
Injectable
,
NotFoundException
,
ForbiddenException
,
BadRequestException
,
Logger
,
}
from
'@nestjs/common'
;
import
{
PrismaService
}
from
'../../prisma/prisma.service'
;
import
{
CreateHolidayDto
}
from
'./dto/create-holiday.dto'
;
import
{
UpdateHolidayDto
}
from
'./dto/update-holiday.dto'
;
import
{
HolidayFilterDto
}
from
'./dto/holiday-filter.dto'
;
import
{
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
getSkip
,
buildPaginatedResponse
,
PaginatedResult
}
from
'../../common/utils/pagination.util'
;
@
Injectable
()
export
class
HolidaysService
{
private
readonly
logger
=
new
Logger
(
HolidaysService
.
name
);
constructor
(
private
readonly
prisma
:
PrismaService
)
{}
async
create
(
dto
:
CreateHolidayDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
&&
currentUser
.
role
!==
'ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin and Admin can manage holidays'
);
}
const
startDate
=
new
Date
(
dto
.
startDate
);
const
endDate
=
new
Date
(
dto
.
endDate
);
if
(
endDate
<
startDate
)
{
throw
new
BadRequestException
(
'End date cannot be before start date'
);
}
// Check for overlapping holidays
const
overlap
=
await
this
.
prisma
.
holiday
.
findFirst
({
where
:
{
OR
:
[
{
startDate
:
{
lte
:
endDate
},
endDate
:
{
gte
:
startDate
}
},
],
},
});
if
(
overlap
)
{
throw
new
BadRequestException
(
`This holiday overlaps with "
${
overlap
.
name
}
" (
${
overlap
.
startDate
.
toISOString
().
split
(
'T'
)[
0
]}
-
${
overlap
.
endDate
.
toISOString
().
split
(
'T'
)[
0
]}
)`
,
);
}
const
holiday
=
await
this
.
prisma
.
holiday
.
create
({
data
:
{
name
:
dto
.
name
,
startDate
,
endDate
,
isRecurring
:
dto
.
isRecurring
??
false
,
notes
:
dto
.
notes
||
null
,
createdById
:
currentUser
.
id
,
},
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
});
this
.
logger
.
log
(
`Holiday "
${
dto
.
name
}
" created by
${
currentUser
.
email
}
`
);
return
holiday
;
}
async
findAll
(
filter
:
HolidayFilterDto
,
currentUser
:
RequestUser
):
Promise
<
PaginatedResult
<
any
>>
{
const
page
=
filter
.
page
||
1
;
const
limit
=
filter
.
limit
||
50
;
const
where
:
any
=
{};
if
(
filter
.
dateFrom
||
filter
.
dateTo
)
{
if
(
filter
.
dateFrom
)
{
where
.
endDate
=
{
...(
where
.
endDate
||
{}),
gte
:
new
Date
(
filter
.
dateFrom
)
};
}
if
(
filter
.
dateTo
)
{
where
.
startDate
=
{
...(
where
.
startDate
||
{}),
lte
:
new
Date
(
filter
.
dateTo
)
};
}
}
if
(
filter
.
year
)
{
const
year
=
parseInt
(
filter
.
year
,
10
);
const
yearStart
=
new
Date
(
year
,
0
,
1
);
const
yearEnd
=
new
Date
(
year
,
11
,
31
,
23
,
59
,
59
,
999
);
where
.
startDate
=
{
...(
where
.
startDate
||
{}),
lte
:
yearEnd
};
where
.
endDate
=
{
...(
where
.
endDate
||
{}),
gte
:
yearStart
};
}
if
(
filter
.
isRecurring
!==
undefined
)
{
where
.
isRecurring
=
filter
.
isRecurring
;
}
const
[
data
,
total
]
=
await
Promise
.
all
([
this
.
prisma
.
holiday
.
findMany
({
where
,
skip
:
getSkip
(
page
,
limit
),
take
:
limit
,
orderBy
:
{
startDate
:
'asc'
},
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
}),
this
.
prisma
.
holiday
.
count
({
where
}),
]);
return
buildPaginatedResponse
(
data
,
total
,
{
page
,
limit
,
sortOrder
:
'asc'
});
}
async
findById
(
id
:
string
):
Promise
<
any
>
{
const
holiday
=
await
this
.
prisma
.
holiday
.
findUnique
({
where
:
{
id
},
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
});
if
(
!
holiday
)
throw
new
NotFoundException
(
'Holiday not found'
);
return
holiday
;
}
async
update
(
id
:
string
,
dto
:
UpdateHolidayDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
&&
currentUser
.
role
!==
'ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin and Admin can manage holidays'
);
}
const
holiday
=
await
this
.
prisma
.
holiday
.
findUnique
({
where
:
{
id
}
});
if
(
!
holiday
)
throw
new
NotFoundException
(
'Holiday not found'
);
const
startDate
=
dto
.
startDate
?
new
Date
(
dto
.
startDate
)
:
holiday
.
startDate
;
const
endDate
=
dto
.
endDate
?
new
Date
(
dto
.
endDate
)
:
holiday
.
endDate
;
if
(
endDate
<
startDate
)
{
throw
new
BadRequestException
(
'End date cannot be before start date'
);
}
// Check overlaps excluding self
const
overlap
=
await
this
.
prisma
.
holiday
.
findFirst
({
where
:
{
id
:
{
not
:
id
},
OR
:
[
{
startDate
:
{
lte
:
endDate
},
endDate
:
{
gte
:
startDate
}
},
],
},
});
if
(
overlap
)
{
throw
new
BadRequestException
(
`This holiday would overlap with "
${
overlap
.
name
}
"`
,
);
}
const
updateData
:
any
=
{};
if
(
dto
.
name
!==
undefined
)
updateData
.
name
=
dto
.
name
;
if
(
dto
.
startDate
!==
undefined
)
updateData
.
startDate
=
new
Date
(
dto
.
startDate
);
if
(
dto
.
endDate
!==
undefined
)
updateData
.
endDate
=
new
Date
(
dto
.
endDate
);
if
(
dto
.
isRecurring
!==
undefined
)
updateData
.
isRecurring
=
dto
.
isRecurring
;
if
(
dto
.
notes
!==
undefined
)
updateData
.
notes
=
dto
.
notes
;
const
updated
=
await
this
.
prisma
.
holiday
.
update
({
where
:
{
id
},
data
:
updateData
,
include
:
{
createdBy
:
{
select
:
{
id
:
true
,
firstName
:
true
,
lastName
:
true
}
},
},
});
this
.
logger
.
log
(
`Holiday "
${
updated
.
name
}
" updated by
${
currentUser
.
email
}
`
);
return
updated
;
}
async
delete
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
void
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
&&
currentUser
.
role
!==
'ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin and Admin can manage holidays'
);
}
const
holiday
=
await
this
.
prisma
.
holiday
.
findUnique
({
where
:
{
id
}
});
if
(
!
holiday
)
throw
new
NotFoundException
(
'Holiday not found'
);
await
this
.
prisma
.
holiday
.
delete
({
where
:
{
id
}
});
this
.
logger
.
log
(
`Holiday "
${
holiday
.
name
}
" deleted by
${
currentUser
.
email
}
`
);
}
async
getUpcoming
(
days
:
number
=
90
):
Promise
<
any
[]
>
{
const
now
=
new
Date
();
const
future
=
new
Date
(
now
.
getTime
()
+
days
*
24
*
60
*
60
*
1000
);
return
this
.
prisma
.
holiday
.
findMany
({
where
:
{
startDate
:
{
gte
:
now
,
lte
:
future
},
},
orderBy
:
{
startDate
:
'asc'
},
});
}
async
isHoliday
(
date
:
Date
):
Promise
<
boolean
>
{
const
dayStart
=
new
Date
(
date
);
dayStart
.
setHours
(
0
,
0
,
0
,
0
);
const
dayEnd
=
new
Date
(
date
);
dayEnd
.
setHours
(
23
,
59
,
59
,
999
);
const
holiday
=
await
this
.
prisma
.
holiday
.
findFirst
({
where
:
{
startDate
:
{
lte
:
dayEnd
},
endDate
:
{
gte
:
dayStart
},
},
});
return
!!
holiday
;
}
async
getHolidaysInRange
(
startDate
:
Date
,
endDate
:
Date
):
Promise
<
Date
[]
>
{
const
holidays
=
await
this
.
prisma
.
holiday
.
findMany
({
where
:
{
startDate
:
{
lte
:
endDate
},
endDate
:
{
gte
:
startDate
},
},
});
const
holidayDates
:
Date
[]
=
[];
for
(
const
h
of
holidays
)
{
const
current
=
new
Date
(
Math
.
max
(
h
.
startDate
.
getTime
(),
startDate
.
getTime
()));
const
end
=
new
Date
(
Math
.
min
(
h
.
endDate
.
getTime
(),
endDate
.
getTime
()));
while
(
current
<=
end
)
{
holidayDates
.
push
(
new
Date
(
current
));
current
.
setDate
(
current
.
getDate
()
+
1
);
}
}
return
holidayDates
;
}
}
\ No newline at end of file
backend/src/modules/meetings/dto/create-meeting.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
IsDateString
,
IsArray
,
MinLength
,
MaxLength
}
from
'class-validator'
;
export
class
CreateMeetingDto
{
@
IsString
()
@
MinLength
(
3
)
@
MaxLength
(
200
)
title
:
string
;
@
IsOptional
()
@
IsString
()
description
?:
string
;
@
IsDateString
()
meetingDate
:
string
;
@
IsDateString
()
startTime
:
string
;
@
IsDateString
()
endTime
:
string
;
@
IsOptional
()
@
IsString
()
location
?:
string
;
@
IsOptional
()
@
IsString
()
recurrence
?:
string
;
// NONE, WEEKLY, BIWEEKLY, MONTHLY
@
IsOptional
()
@
IsString
()
relatedEntityType
?:
string
;
// PIP, EVALUATION, CARD
@
IsOptional
()
@
IsString
()
relatedEntityId
?:
string
;
@
IsArray
()
@
IsString
({
each
:
true
})
inviteeIds
:
string
[];
}
\ No newline at end of file
backend/src/modules/meetings/dto/meeting-filter.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsOptional
,
IsString
,
IsDateString
}
from
'class-validator'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
MeetingFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsString
()
status
?:
string
;
@
IsOptional
()
@
IsDateString
()
dateFrom
?:
string
;
@
IsOptional
()
@
IsDateString
()
dateTo
?:
string
;
@
IsOptional
()
@
IsString
()
relatedEntityType
?:
string
;
@
IsOptional
()
@
IsString
()
relatedEntityId
?:
string
;
}
\ No newline at end of file
backend/src/modules/meetings/dto/meeting-notes.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
IsArray
,
MinLength
}
from
'class-validator'
;
export
class
CreateMeetingNotesDto
{
@
IsOptional
()
@
IsArray
()
@
IsString
({
each
:
true
})
attendeeIds
?:
string
[];
@
IsString
()
@
MinLength
(
20
)
summary
:
string
;
@
IsOptional
()
actionItems
?:
any
;
// Array of { description: string, assigneeId?: string }
}
\ No newline at end of file
backend/src/modules/meetings/dto/update-meeting.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
IsDateString
,
IsArray
,
MaxLength
}
from
'class-validator'
;
export
class
UpdateMeetingDto
{
@
IsOptional
()
@
IsString
()
@
MaxLength
(
200
)
title
?:
string
;
@
IsOptional
()
@
IsString
()
description
?:
string
;
@
IsOptional
()
@
IsDateString
()
meetingDate
?:
string
;
@
IsOptional
()
@
IsDateString
()
startTime
?:
string
;
@
IsOptional
()
@
IsDateString
()
endTime
?:
string
;
@
IsOptional
()
@
IsString
()
location
?:
string
;
@
IsOptional
()
@
IsArray
()
@
IsString
({
each
:
true
})
inviteeIds
?:
string
[];
}
\ No newline at end of file
backend/src/modules/meetings/meetings.controller.ts
0 → 100644
View file @
ef9a81cb
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
MeetingsService
}
from
'./meetings.service'
;
import
{
CreateMeetingDto
}
from
'./dto/create-meeting.dto'
;
import
{
UpdateMeetingDto
}
from
'./dto/update-meeting.dto'
;
import
{
CreateMeetingNotesDto
}
from
'./dto/meeting-notes.dto'
;
import
{
MeetingFilterDto
}
from
'./dto/meeting-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'meetings'
)
export
class
MeetingsController
{
constructor
(
private
readonly
meetingsService
:
MeetingsService
)
{}
@
Post
()
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
,
'TEAM_LEAD'
)
async
create
(@
Body
()
dto
:
CreateMeetingDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
meetingsService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
MeetingFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
meetingsService
.
findAll
(
filter
,
user
);
}
@
Get
(
'upcoming'
)
async
getUpcoming
(
@
CurrentUser
()
user
:
RequestUser
,
@
Query
(
'days'
)
days
?:
string
,
)
{
return
this
.
meetingsService
.
getUpcoming
(
user
,
days
?
parseInt
(
days
,
10
)
:
7
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
meetingsService
.
findById
(
id
,
user
);
}
@
Put
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
,
'TEAM_LEAD'
)
async
update
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateMeetingDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
meetingsService
.
update
(
id
,
dto
,
user
);
}
@
Post
(
':id/cancel'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
,
'TEAM_LEAD'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
cancel
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
meetingsService
.
cancel
(
id
,
user
);
}
@
Post
(
':id/notes'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
,
'TEAM_LEAD'
)
async
addNotes
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
CreateMeetingNotesDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
meetingsService
.
addNotes
(
id
,
dto
,
user
);
}
@
Delete
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
meetingsService
.
delete
(
id
,
user
);
return
{
message
:
'Meeting deleted'
};
}
}
\ No newline at end of file
backend/src/modules/meetings/meetings.module.ts
0 → 100644
View file @
ef9a81cb
import
{
Module
}
from
'@nestjs/common'
;
import
{
MeetingsController
}
from
'./meetings.controller'
;
import
{
MeetingsService
}
from
'./meetings.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
@
Module
({
imports
:
[
NotificationsModule
],
controllers
:
[
MeetingsController
],
providers
:
[
MeetingsService
],
exports
:
[
MeetingsService
],
})
export
class
MeetingsModule
{}
\ No newline at end of file
backend/src/modules/meetings/meetings.service.ts
0 → 100644
View file @
ef9a81cb
This diff is collapsed.
Click to expand it.
backend/src/modules/schedules/dto/review-schedule-change.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
MinLength
}
from
'class-validator'
;
export
class
ReviewScheduleChangeDto
{
@
IsString
()
decision
:
string
;
// APPROVED, REJECTED
@
IsOptional
()
@
IsString
()
@
MinLength
(
10
)
rejectionReason
?:
string
;
}
\ No newline at end of file
backend/src/modules/schedules/dto/schedule-change-request.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsDateString
,
IsObject
,
MinLength
}
from
'class-validator'
;
export
class
CreateScheduleChangeRequestDto
{
@
IsObject
()
proposedSchedule
:
Record
<
string
,
string
>
;
// { sunday: 'IN_OFFICE', monday: 'REMOTE', tuesday: 'OFF', ... }
@
IsDateString
()
effectiveDate
:
string
;
@
IsString
()
@
MinLength
(
50
,
{
message
:
'Reason must be at least 50 characters'
})
reason
:
string
;
}
\ No newline at end of file
backend/src/modules/schedules/schedules.controller.ts
0 → 100644
View file @
ef9a81cb
import
{
Controller
,
Get
,
Post
,
Put
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
SchedulesService
}
from
'./schedules.service'
;
import
{
CreateScheduleChangeRequestDto
}
from
'./dto/schedule-change-request.dto'
;
import
{
ReviewScheduleChangeDto
}
from
'./dto/review-schedule-change.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'schedules'
)
export
class
SchedulesController
{
constructor
(
private
readonly
schedulesService
:
SchedulesService
)
{}
@
Post
(
'change-request'
)
async
createRequest
(
@
Body
()
dto
:
CreateScheduleChangeRequestDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
schedulesService
.
createRequest
(
dto
,
user
);
}
@
Get
(
'change-requests'
)
async
findAll
(
@
CurrentUser
()
user
:
RequestUser
,
@
Query
(
'page'
)
page
?:
string
,
@
Query
(
'limit'
)
limit
?:
string
,
)
{
return
this
.
schedulesService
.
findAll
(
user
,
page
?
parseInt
(
page
,
10
)
:
1
,
limit
?
parseInt
(
limit
,
10
)
:
20
,
);
}
@
Get
(
'change-requests/:id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
schedulesService
.
findById
(
id
,
user
);
}
@
Put
(
'change-requests/:id/review'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
review
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
ReviewScheduleChangeDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
schedulesService
.
review
(
id
,
dto
,
user
);
}
@
Put
(
'direct-edit/:userId'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
directEdit
(
@
Param
(
'userId'
)
userId
:
string
,
@
Body
(
'schedule'
)
schedule
:
Record
<
string
,
string
>
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
schedulesService
.
directEdit
(
userId
,
schedule
,
user
);
}
}
\ No newline at end of file
backend/src/modules/schedules/schedules.module.ts
0 → 100644
View file @
ef9a81cb
import
{
Module
}
from
'@nestjs/common'
;
import
{
SchedulesController
}
from
'./schedules.controller'
;
import
{
SchedulesService
}
from
'./schedules.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
import
{
HudModule
}
from
'../hud/hud.module'
;
@
Module
({
imports
:
[
NotificationsModule
,
HudModule
],
controllers
:
[
SchedulesController
],
providers
:
[
SchedulesService
],
exports
:
[
SchedulesService
],
})
export
class
SchedulesModule
{}
\ No newline at end of file
backend/src/modules/schedules/schedules.service.ts
0 → 100644
View file @
ef9a81cb
This diff is collapsed.
Click to expand it.
backend/src/modules/unavailability/dto/create-unavailability.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
IsDateString
,
MinLength
}
from
'class-validator'
;
export
class
CreateUnavailabilityDto
{
@
IsOptional
()
@
IsString
()
userId
?:
string
;
// If not provided, uses current user
@
IsDateString
()
startDate
:
string
;
@
IsDateString
()
endDate
:
string
;
@
IsString
()
reasonCategory
:
string
;
// PERSONAL, MEDICAL, RELIGIOUS, EMERGENCY, OTHER
@
IsOptional
()
@
IsString
()
notes
?:
string
;
}
\ No newline at end of file
backend/src/modules/unavailability/dto/unavailability-filter.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsOptional
,
IsString
,
IsDateString
}
from
'class-validator'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
UnavailabilityFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsString
()
userId
?:
string
;
@
IsOptional
()
@
IsString
()
reasonCategory
?:
string
;
@
IsOptional
()
@
IsDateString
()
dateFrom
?:
string
;
@
IsOptional
()
@
IsDateString
()
dateTo
?:
string
;
}
\ No newline at end of file
backend/src/modules/unavailability/dto/update-unavailability.dto.ts
0 → 100644
View file @
ef9a81cb
import
{
IsString
,
IsOptional
,
IsDateString
}
from
'class-validator'
;
export
class
UpdateUnavailabilityDto
{
@
IsOptional
()
@
IsDateString
()
startDate
?:
string
;
@
IsOptional
()
@
IsDateString
()
endDate
?:
string
;
@
IsOptional
()
@
IsString
()
reasonCategory
?:
string
;
@
IsOptional
()
@
IsString
()
notes
?:
string
;
}
\ No newline at end of file
backend/src/modules/unavailability/unavailability.controller.ts
0 → 100644
View file @
ef9a81cb
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
UnavailabilityService
}
from
'./unavailability.service'
;
import
{
CreateUnavailabilityDto
}
from
'./dto/create-unavailability.dto'
;
import
{
UpdateUnavailabilityDto
}
from
'./dto/update-unavailability.dto'
;
import
{
UnavailabilityFilterDto
}
from
'./dto/unavailability-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'unavailability'
)
export
class
UnavailabilityController
{
constructor
(
private
readonly
unavailabilityService
:
UnavailabilityService
)
{}
@
Post
()
async
create
(@
Body
()
dto
:
CreateUnavailabilityDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
unavailabilityService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
UnavailabilityFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
unavailabilityService
.
findAll
(
filter
,
user
);
}
@
Get
(
'team-availability'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
,
'TEAM_LEAD'
)
async
getTeamAvailability
(
@
Query
(
'startDate'
)
startDate
:
string
,
@
Query
(
'endDate'
)
endDate
:
string
,
@
Query
(
'boardId'
)
boardId
:
string
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
unavailabilityService
.
getTeamAvailability
(
startDate
,
endDate
,
user
,
boardId
);
}
@
Get
(
'stats/:userId'
)
async
getStats
(
@
Param
(
'userId'
)
userId
:
string
,
@
Query
(
'year'
)
year
:
string
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
if
(
user
.
role
===
'CONTRACTOR'
&&
userId
!==
user
.
id
)
{
return
{
totalDays
:
0
,
byCategory
:
{},
recordCount
:
0
};
}
return
this
.
unavailabilityService
.
getUserUnavailabilityStats
(
userId
,
year
?
parseInt
(
year
,
10
)
:
new
Date
().
getFullYear
(),
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
unavailabilityService
.
findById
(
id
,
user
);
}
@
Put
(
':id'
)
async
update
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateUnavailabilityDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
unavailabilityService
.
update
(
id
,
dto
,
user
);
}
@
Delete
(
':id'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
unavailabilityService
.
delete
(
id
,
user
);
return
{
message
:
'Unavailability record deleted'
};
}
}
\ No newline at end of file
backend/src/modules/unavailability/unavailability.module.ts
0 → 100644
View file @
ef9a81cb
import
{
Module
}
from
'@nestjs/common'
;
import
{
UnavailabilityController
}
from
'./unavailability.controller'
;
import
{
UnavailabilityService
}
from
'./unavailability.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
@
Module
({
imports
:
[
NotificationsModule
],
controllers
:
[
UnavailabilityController
],
providers
:
[
UnavailabilityService
],
exports
:
[
UnavailabilityService
],
})
export
class
UnavailabilityModule
{}
\ No newline at end of file
backend/src/modules/unavailability/unavailability.service.ts
0 → 100644
View file @
ef9a81cb
This diff is collapsed.
Click to expand it.
prisma/schema-scheduling.prisma
0 → 100644
View file @
ef9a81cb
//
───
TIME
&
SCHEDULING
MODELS
──────────────────────────────────
//
Phase
2
C
:
Unavailability
,
Holidays
,
Schedule
Changes
,
Meetings
model
Holiday
{
id
String
@
id
@
default
(
uuid
())
name
String
startDate
DateTime
endDate
DateTime
isRecurring
Boolean
@
default
(
false
)
notes
String
?
createdById
String
?
createdBy
User
?
@
relation
(
"HolidayCreator"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
SetNull
)
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
startDate
])
@@
index
([
endDate
])
}
model
Unavailability
{
id
String
@
id
@
default
(
uuid
())
userId
String
user
User
@
relation
(
"UserUnavailability"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
startDate
DateTime
endDate
DateTime
reasonCategory
String
//
PERSONAL
,
MEDICAL
,
RELIGIOUS
,
EMERGENCY
,
OTHER
notes
String
?
createdById
String
?
//
null
means
self
-
logged
createdBy
User
?
@
relation
(
"UnavailabilityCreator"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
SetNull
)
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
userId
])
@@
index
([
startDate
])
@@
index
([
endDate
])
@@
index
([
userId
,
startDate
,
endDate
])
}
model
ScheduleChangeRequest
{
id
String
@
id
@
default
(
uuid
())
userId
String
user
User
@
relation
(
"ScheduleChangeRequests"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
currentSchedule
Json
//
snapshot
of
current
weeklySchedule
proposedSchedule
Json
//
proposed
new
weeklySchedule
currentBaseSalaryPiasters
Int
proposedBaseSalaryPiasters
Int
effectiveDate
DateTime
reason
String
//
min
50
chars
status
String
@
default
(
"PENDING"
)
//
PENDING
,
APPROVED
,
REJECTED
reviewedById
String
?
reviewedBy
User
?
@
relation
(
"ScheduleChangeReviewer"
,
fields
:
[
reviewedById
],
references
:
[
id
],
onDelete
:
SetNull
)
reviewedAt
DateTime
?
rejectionReason
String
?
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
userId
])
@@
index
([
status
])
@@
index
([
userId
,
status
])
}
model
Meeting
{
id
String
@
id
@
default
(
uuid
())
title
String
description
String
?
meetingDate
DateTime
startTime
DateTime
endTime
DateTime
location
String
?
//
physical
location
or
video
link
recurrence
String
?
//
NONE
,
WEEKLY
,
BIWEEKLY
,
MONTHLY
relatedEntityType
String
?
//
PIP
,
EVALUATION
,
CARD
relatedEntityId
String
?
status
String
@
default
(
"SCHEDULED"
)
//
SCHEDULED
,
COMPLETED
,
CANCELLED
reminderSent1h
Boolean
@
default
(
false
)
reminderSent24h
Boolean
@
default
(
false
)
createdById
String
createdBy
User
@
relation
(
"MeetingCreator"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
Cascade
)
cancelledAt
DateTime
?
cancelledById
String
?
cancelledBy
User
?
@
relation
(
"MeetingCanceller"
,
fields
:
[
cancelledById
],
references
:
[
id
],
onDelete
:
SetNull
)
invitees
MeetingInvitee
[]
notes
MeetingNotes
[]
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
meetingDate
])
@@
index
([
createdById
])
@@
index
([
status
])
@@
index
([
startTime
])
}
model
MeetingInvitee
{
id
String
@
id
@
default
(
uuid
())
meetingId
String
meeting
Meeting
@
relation
(
fields
:
[
meetingId
],
references
:
[
id
],
onDelete
:
Cascade
)
userId
String
user
User
@
relation
(
"MeetingInvitations"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
attended
Boolean
?
//
null
=
not
yet
recorded
,
true
=
attended
,
false
=
absent
createdAt
DateTime
@
default
(
now
())
@@
unique
([
meetingId
,
userId
])
@@
index
([
userId
])
}
model
MeetingNotes
{
id
String
@
id
@
default
(
uuid
())
meetingId
String
meeting
Meeting
@
relation
(
fields
:
[
meetingId
],
references
:
[
id
],
onDelete
:
Cascade
)
authorId
String
author
User
@
relation
(
"MeetingNotesAuthor"
,
fields
:
[
authorId
],
references
:
[
id
],
onDelete
:
Cascade
)
attendees
Json
?
//
array
of
user
IDs
who
actually
attended
summary
String
actionItems
Json
?
//
array
of
{
description
,
assigneeId
?
}
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
meetingId
])
}
\ No newline at end of file
shared/src/enums/scheduling.enum.ts
0 → 100644
View file @
ef9a81cb
export
enum
UnavailabilityReason
{
PERSONAL
=
'PERSONAL'
,
MEDICAL
=
'MEDICAL'
,
RELIGIOUS
=
'RELIGIOUS'
,
EMERGENCY
=
'EMERGENCY'
,
OTHER
=
'OTHER'
,
}
export
enum
ScheduleChangeStatus
{
PENDING
=
'PENDING'
,
APPROVED
=
'APPROVED'
,
REJECTED
=
'REJECTED'
,
}
export
enum
MeetingStatus
{
SCHEDULED
=
'SCHEDULED'
,
COMPLETED
=
'COMPLETED'
,
CANCELLED
=
'CANCELLED'
,
}
export
enum
MeetingRecurrence
{
NONE
=
'NONE'
,
WEEKLY
=
'WEEKLY'
,
BIWEEKLY
=
'BIWEEKLY'
,
MONTHLY
=
'MONTHLY'
,
}
export
enum
DayType
{
IN_OFFICE
=
'IN_OFFICE'
,
REMOTE
=
'REMOTE'
,
OFF
=
'OFF'
,
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment