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
dbe7775f
Commit
dbe7775f
authored
Apr 01, 2026
by
Administrator
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update 28 files via Son of Anton
parent
0ff1fc23
Changes
28
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
2664 additions
and
130 deletions
+2664
-130
app.module.ts
backend/src/app.module.ts
+9
-0
auto-metrics.service.ts
backend/src/modules/evaluations/auto-metrics.service.ts
+251
-0
create-evaluation.dto.ts
backend/src/modules/evaluations/dto/create-evaluation.dto.ts
+15
-0
evaluation-filter.dto.ts
backend/src/modules/evaluations/dto/evaluation-filter.dto.ts
+27
-0
evaluation-response.dto.ts
...nd/src/modules/evaluations/dto/evaluation-response.dto.ts
+16
-0
professional-eval.dto.ts
backend/src/modules/evaluations/dto/professional-eval.dto.ts
+47
-0
technical-eval.dto.ts
backend/src/modules/evaluations/dto/technical-eval.dto.ts
+47
-0
evaluations.controller.ts
backend/src/modules/evaluations/evaluations.controller.ts
+101
-0
evaluations.module.ts
backend/src/modules/evaluations/evaluations.module.ts
+13
-0
evaluations.service.ts
backend/src/modules/evaluations/evaluations.service.ts
+567
-0
competency.service.ts
backend/src/modules/learning/competency.service.ts
+145
-0
assess-learning-goal.dto.ts
backend/src/modules/learning/dto/assess-learning-goal.dto.ts
+10
-0
competency-area.dto.ts
backend/src/modules/learning/dto/competency-area.dto.ts
+38
-0
create-learning-goal.dto.ts
backend/src/modules/learning/dto/create-learning-goal.dto.ts
+32
-0
learning-goal-filter.dto.ts
backend/src/modules/learning/dto/learning-goal-filter.dto.ts
+20
-0
learning.controller.ts
backend/src/modules/learning/learning.controller.ts
+129
-0
learning.module.ts
backend/src/modules/learning/learning.module.ts
+13
-0
learning.service.ts
backend/src/modules/learning/learning.service.ts
+378
-0
create-pip.dto.ts
backend/src/modules/pip/dto/create-pip.dto.ts
+69
-0
pip-checkin.dto.ts
backend/src/modules/pip/dto/pip-checkin.dto.ts
+19
-0
pip-filter.dto.ts
backend/src/modules/pip/dto/pip-filter.dto.ts
+12
-0
pip-result.dto.ts
backend/src/modules/pip/dto/pip-result.dto.ts
+10
-0
update-pip.dto.ts
backend/src/modules/pip/dto/update-pip.dto.ts
+37
-0
pip.controller.ts
backend/src/modules/pip/pip.controller.ts
+79
-0
pip.module.ts
backend/src/modules/pip/pip.module.ts
+12
-0
pip.service.ts
backend/src/modules/pip/pip.service.ts
+374
-0
schema-additions.prisma
prisma/schema-additions.prisma
+21
-130
schema-evaluations.prisma
prisma/schema-evaluations.prisma
+173
-0
No files found.
backend/src/app.module.ts
View file @
dbe7775f
...
@@ -41,6 +41,11 @@ import { NoticesModule } from './modules/notices/notices.module';
...
@@ -41,6 +41,11 @@ import { NoticesModule } from './modules/notices/notices.module';
// ─── Phase 1F: Background Jobs ──────────────────────────────
// ─── Phase 1F: Background Jobs ──────────────────────────────
import
{
JobsModule
}
from
'./jobs/jobs.module'
;
import
{
JobsModule
}
from
'./jobs/jobs.module'
;
// ─── Phase 2A: Evaluation & Performance ─────────────────────
import
{
EvaluationsModule
}
from
'./modules/evaluations/evaluations.module'
;
import
{
PIPModule
}
from
'./modules/pip/pip.module'
;
import
{
LearningModule
}
from
'./modules/learning/learning.module'
;
import
{
JwtAuthGuard
}
from
'./common/guards/jwt-auth.guard'
;
import
{
JwtAuthGuard
}
from
'./common/guards/jwt-auth.guard'
;
import
{
RolesGuard
}
from
'./common/guards/roles.guard'
;
import
{
RolesGuard
}
from
'./common/guards/roles.guard'
;
import
{
TransformInterceptor
}
from
'./common/interceptors/transform.interceptor'
;
import
{
TransformInterceptor
}
from
'./common/interceptors/transform.interceptor'
;
...
@@ -83,6 +88,10 @@ import { RateLimitMiddleware } from './common/middleware/rate-limit.middleware';
...
@@ -83,6 +88,10 @@ import { RateLimitMiddleware } from './common/middleware/rate-limit.middleware';
NoticesModule
,
NoticesModule
,
// Phase 1F
// Phase 1F
JobsModule
,
JobsModule
,
// Phase 2A
EvaluationsModule
,
PIPModule
,
LearningModule
,
],
],
providers
:
[
providers
:
[
{
provide
:
APP_GUARD
,
useClass
:
JwtAuthGuard
},
{
provide
:
APP_GUARD
,
useClass
:
JwtAuthGuard
},
...
...
backend/src/modules/evaluations/auto-metrics.service.ts
0 → 100644
View file @
dbe7775f
import
{
Injectable
,
Logger
}
from
'@nestjs/common'
;
import
{
PrismaService
}
from
'../../prisma/prisma.service'
;
export
interface
SystemMetrics
{
daysReported
:
number
;
expectedDays
:
number
;
onTimeRate
:
number
;
tasksCompleted
:
number
;
tasksAssigned
:
number
;
deadlineHitRate
:
number
;
totalDeductionCount
:
number
;
totalDeductionAmountPiasters
:
number
;
totalBountyCount
:
number
;
totalBountyAmountPiasters
:
number
;
avgDailyHoursReported
:
number
;
currentStreak
:
number
;
bestStreak
:
number
;
messagesSent
:
number
;
}
@
Injectable
()
export
class
AutoMetricsService
{
private
readonly
logger
=
new
Logger
(
AutoMetricsService
.
name
);
constructor
(
private
readonly
prisma
:
PrismaService
)
{}
async
calculate
(
userId
:
string
,
month
:
number
,
year
:
number
):
Promise
<
SystemMetrics
>
{
const
startDate
=
new
Date
(
year
,
month
-
1
,
1
);
const
endDate
=
new
Date
(
year
,
month
,
0
,
23
,
59
,
59
,
999
);
// ─── Tasks Completed & Assigned ─────────────────────────
let
tasksCompleted
=
0
;
let
tasksAssigned
=
0
;
let
deadlinedTasksCompleted
=
0
;
let
deadlinedTasksOnTime
=
0
;
try
{
// Cards assigned to user during this month
tasksAssigned
=
await
this
.
prisma
.
card
.
count
({
where
:
{
assignees
:
{
some
:
{
id
:
userId
}
},
createdAt
:
{
lte
:
endDate
},
deletedAt
:
null
,
},
});
// Cards completed (moved to Done) during this month
tasksCompleted
=
await
this
.
prisma
.
card
.
count
({
where
:
{
assignees
:
{
some
:
{
id
:
userId
}
},
completedAt
:
{
gte
:
startDate
,
lte
:
endDate
},
deletedAt
:
null
,
},
});
// Deadline compliance: cards completed that had deadlines
const
completedWithDeadline
=
await
this
.
prisma
.
card
.
findMany
({
where
:
{
assignees
:
{
some
:
{
id
:
userId
}
},
completedAt
:
{
gte
:
startDate
,
lte
:
endDate
},
dueDate
:
{
not
:
null
},
deletedAt
:
null
,
},
select
:
{
completedAt
:
true
,
dueDate
:
true
},
});
deadlinedTasksCompleted
=
completedWithDeadline
.
length
;
deadlinedTasksOnTime
=
completedWithDeadline
.
filter
(
(
c
)
=>
c
.
completedAt
&&
c
.
dueDate
&&
new
Date
(
c
.
completedAt
)
<=
new
Date
(
c
.
dueDate
),
).
length
;
}
catch
(
err
)
{
this
.
logger
.
warn
(
`Failed to calculate task metrics for
${
userId
}
:
${
err
.
message
}
`
);
}
// ─── Reports ────────────────────────────────────────────
let
daysReported
=
0
;
let
expectedDays
=
0
;
let
onTimeReports
=
0
;
try
{
// Get user's schedule to calculate expected days
const
user
=
await
this
.
prisma
.
user
.
findUnique
({
where
:
{
id
:
userId
},
select
:
{
weeklySchedule
:
true
},
});
if
(
user
?.
weeklySchedule
)
{
const
schedule
=
user
.
weeklySchedule
as
Record
<
string
,
string
>
;
const
{
getScheduledDaysOfWeek
,
getWorkingDaysInMonth
}
=
await
import
(
'../../common/utils/date.util'
);
const
scheduledDays
=
getScheduledDaysOfWeek
(
schedule
);
expectedDays
=
getWorkingDaysInMonth
(
year
,
month
,
scheduledDays
);
}
// Count reports using DailyReport model (Phase 2D)
// For now, this will be 0 until Phase 2D is built
try
{
const
dailyReportModel
=
(
this
.
prisma
as
any
).
dailyReport
;
if
(
dailyReportModel
&&
typeof
dailyReportModel
.
count
===
'function'
)
{
daysReported
=
await
dailyReportModel
.
count
({
where
:
{
userId
,
reportDate
:
{
gte
:
startDate
,
lte
:
endDate
},
status
:
{
not
:
'DRAFT'
},
},
});
onTimeReports
=
await
dailyReportModel
.
count
({
where
:
{
userId
,
reportDate
:
{
gte
:
startDate
,
lte
:
endDate
},
status
:
{
in
:
[
'SUBMITTED'
,
'APPROVED'
,
'AUTO_APPROVED'
]
},
},
});
}
}
catch
{
// DailyReport model doesn't exist yet (Phase 2D)
}
}
catch
(
err
)
{
this
.
logger
.
warn
(
`Failed to calculate report metrics for
${
userId
}
:
${
err
.
message
}
`
);
}
// ─── Deductions ─────────────────────────────────────────
let
totalDeductionCount
=
0
;
let
totalDeductionAmountPiasters
=
0
;
try
{
const
deductions
=
await
this
.
prisma
.
deduction
.
findMany
({
where
:
{
userId
,
payrollMonth
:
month
,
payrollYear
:
year
,
status
:
{
in
:
[
'UPHELD'
,
'REDUCED'
,
'AUTO_APPLIED'
]
},
appliedAmountPiasters
:
{
not
:
null
},
},
select
:
{
appliedAmountPiasters
:
true
},
});
totalDeductionCount
=
deductions
.
length
;
totalDeductionAmountPiasters
=
deductions
.
reduce
(
(
sum
,
d
)
=>
sum
+
(
d
.
appliedAmountPiasters
||
0
),
0
,
);
}
catch
(
err
)
{
this
.
logger
.
warn
(
`Failed to calculate deduction metrics:
${
err
.
message
}
`
);
}
// ─── Bounties ───────────────────────────────────────────
let
totalBountyCount
=
0
;
let
totalBountyAmountPiasters
=
0
;
try
{
const
bounties
=
await
this
.
prisma
.
bountyPayout
.
findMany
({
where
:
{
userId
,
payrollMonth
:
month
,
payrollYear
:
year
,
revokedAt
:
null
,
},
select
:
{
amountPiasters
:
true
},
});
totalBountyCount
=
bounties
.
length
;
totalBountyAmountPiasters
=
bounties
.
reduce
((
sum
,
b
)
=>
sum
+
b
.
amountPiasters
,
0
);
}
catch
(
err
)
{
this
.
logger
.
warn
(
`Failed to calculate bounty metrics:
${
err
.
message
}
`
);
}
// ─── Messages Sent ──────────────────────────────────────
let
messagesSent
=
0
;
try
{
messagesSent
=
await
this
.
prisma
.
message
.
count
({
where
:
{
senderId
:
userId
,
createdAt
:
{
gte
:
startDate
,
lte
:
endDate
},
deletedAt
:
null
,
},
});
}
catch
(
err
)
{
this
.
logger
.
warn
(
`Failed to count messages:
${
err
.
message
}
`
);
}
// ─── Compile Results ────────────────────────────────────
const
onTimeRate
=
daysReported
>
0
?
Math
.
round
((
onTimeReports
/
daysReported
)
*
100
)
:
0
;
const
deadlineHitRate
=
deadlinedTasksCompleted
>
0
?
Math
.
round
((
deadlinedTasksOnTime
/
deadlinedTasksCompleted
)
*
100
)
:
100
;
// No deadlined tasks = 100% compliance
return
{
daysReported
,
expectedDays
,
onTimeRate
,
tasksCompleted
,
tasksAssigned
,
deadlineHitRate
,
totalDeductionCount
,
totalDeductionAmountPiasters
,
totalBountyCount
,
totalBountyAmountPiasters
,
avgDailyHoursReported
:
0
,
// Phase 2D
currentStreak
:
0
,
// Requires Reports module
bestStreak
:
0
,
// Requires Reports module
messagesSent
,
};
}
/**
* Auto-calculate the Technical evaluation scores:
* - Task Completion Rate: (Cards Done / Cards Assigned) × 5
* - Deadline Compliance: (Cards on time / Cards with deadline completed) × 5
*/
calculateAutoTechnicalScores
(
metrics
:
SystemMetrics
):
{
taskCompletion
:
number
;
deadlineCompliance
:
number
;
}
{
const
taskCompletion
=
metrics
.
tasksAssigned
>
0
?
Math
.
round
((
metrics
.
tasksCompleted
/
metrics
.
tasksAssigned
)
*
5
*
10
)
/
10
:
5.0
;
const
deadlineCompliance
=
Math
.
round
((
metrics
.
deadlineHitRate
/
100
)
*
5
*
10
)
/
10
;
return
{
taskCompletion
:
Math
.
min
(
5
,
Math
.
max
(
1
,
taskCompletion
)),
deadlineCompliance
:
Math
.
min
(
5
,
Math
.
max
(
1
,
deadlineCompliance
)),
};
}
/**
* Auto-calculate the Professional evaluation scores:
* - Reporting Compliance: (On-time reports / Expected) × 5
* - Policy Compliance: 5 - (violations × 0.5), min 1
*/
calculateAutoProfessionalScores
(
metrics
:
SystemMetrics
):
{
reportingCompliance
:
number
;
policyCompliance
:
number
;
}
{
const
reportingCompliance
=
metrics
.
expectedDays
>
0
?
Math
.
round
((
metrics
.
daysReported
/
metrics
.
expectedDays
)
*
5
*
10
)
/
10
:
5.0
;
const
policyViolations
=
metrics
.
totalDeductionCount
;
// Each deduction counts as a violation
const
policyCompliance
=
Math
.
max
(
1
,
5
-
policyViolations
*
0.5
);
return
{
reportingCompliance
:
Math
.
min
(
5
,
Math
.
max
(
1
,
reportingCompliance
)),
policyCompliance
:
Math
.
round
(
policyCompliance
*
10
)
/
10
,
};
}
}
\ No newline at end of file
backend/src/modules/evaluations/dto/create-evaluation.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsInt
,
IsOptional
,
Min
,
Max
}
from
'class-validator'
;
export
class
CreateEvaluationDto
{
@
IsString
()
userId
:
string
;
@
IsInt
()
@
Min
(
1
)
@
Max
(
12
)
month
:
number
;
@
IsInt
()
@
Min
(
2020
)
year
:
number
;
}
\ No newline at end of file
backend/src/modules/evaluations/dto/evaluation-filter.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsOptional
,
IsString
,
IsInt
}
from
'class-validator'
;
import
{
Type
}
from
'class-transformer'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
EvaluationFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsString
()
userId
?:
string
;
@
IsOptional
()
@
Type
(()
=>
Number
)
@
IsInt
()
month
?:
number
;
@
IsOptional
()
@
Type
(()
=>
Number
)
@
IsInt
()
year
?:
number
;
@
IsOptional
()
@
IsString
()
status
?:
string
;
@
IsOptional
()
@
IsString
()
rating
?:
string
;
}
\ No newline at end of file
backend/src/modules/evaluations/dto/evaluation-response.dto.ts
0 → 100644
View file @
dbe7775f
export
class
EvaluationResponseDto
{
id
:
string
;
userId
:
string
;
month
:
number
;
year
:
number
;
technicalScore
:
number
|
null
;
professionalScore
:
number
|
null
;
overallScore
:
number
|
null
;
rating
:
string
|
null
;
status
:
string
;
systemMetrics
:
any
;
acknowledgedAt
:
string
|
null
;
responseText
:
string
|
null
;
compiledAt
:
string
|
null
;
createdAt
:
string
;
}
\ No newline at end of file
backend/src/modules/evaluations/dto/professional-eval.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsNumber
,
IsString
,
IsOptional
,
Min
,
Max
,
MinLength
}
from
'class-validator'
;
export
class
SubmitProfessionalEvalDto
{
@
IsOptional
()
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
profReportingCompliance
?:
number
;
// Override auto-calculated
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
profCommunication
:
number
;
@
IsString
()
@
MinLength
(
20
)
profCommunicationNotes
:
string
;
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
profCollaboration
:
number
;
@
IsString
()
@
MinLength
(
20
)
profCollaborationNotes
:
string
;
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
profReliability
:
number
;
@
IsString
()
@
MinLength
(
20
)
profReliabilityNotes
:
string
;
@
IsOptional
()
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
profPolicyCompliance
?:
number
;
// Override auto-calculated
@
IsOptional
()
@
IsString
()
@
MinLength
(
30
)
profOverrideJustification
?:
string
;
}
\ No newline at end of file
backend/src/modules/evaluations/dto/technical-eval.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsNumber
,
IsString
,
IsOptional
,
Min
,
Max
,
MinLength
}
from
'class-validator'
;
export
class
SubmitTechnicalEvalDto
{
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
techCodeQuality
:
number
;
@
IsString
()
@
MinLength
(
20
)
techCodeQualityNotes
:
string
;
@
IsOptional
()
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
techTaskCompletion
?:
number
;
// Override auto-calculated
@
IsOptional
()
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
techDeadlineCompliance
?:
number
;
// Override auto-calculated
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
techGrowth
:
number
;
@
IsString
()
@
MinLength
(
20
)
techGrowthNotes
:
string
;
@
IsNumber
()
@
Min
(
1
)
@
Max
(
5
)
techProblemSolving
:
number
;
@
IsString
()
@
MinLength
(
20
)
techProblemSolvingNotes
:
string
;
@
IsOptional
()
@
IsString
()
@
MinLength
(
30
)
techOverrideJustification
?:
string
;
// Required if overriding auto values
}
\ No newline at end of file
backend/src/modules/evaluations/evaluations.controller.ts
0 → 100644
View file @
dbe7775f
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
EvaluationsService
}
from
'./evaluations.service'
;
import
{
CreateEvaluationDto
}
from
'./dto/create-evaluation.dto'
;
import
{
SubmitTechnicalEvalDto
}
from
'./dto/technical-eval.dto'
;
import
{
SubmitProfessionalEvalDto
}
from
'./dto/professional-eval.dto'
;
import
{
EvaluationFilterDto
}
from
'./dto/evaluation-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'evaluations'
)
export
class
EvaluationsController
{
constructor
(
private
readonly
evaluationsService
:
EvaluationsService
)
{}
@
Post
()
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
create
(@
Body
()
dto
:
CreateEvaluationDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
evaluationsService
.
create
(
dto
,
user
);
}
@
Post
(
'bulk'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
createBulk
(
@
Body
()
body
:
{
month
:
number
;
year
:
number
},
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
evaluationsService
.
createBulkForMonth
(
body
.
month
,
body
.
year
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
EvaluationFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
evaluationsService
.
findAll
(
filter
,
user
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
evaluationsService
.
findById
(
id
,
user
);
}
@
Post
(
':id/technical'
)
@
Roles
(
'SUPER_ADMIN'
,
'TEAM_LEAD'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
submitTechnical
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
SubmitTechnicalEvalDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
evaluationsService
.
submitTechnical
(
id
,
dto
,
user
);
}
@
Post
(
':id/professional'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
submitProfessional
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
SubmitProfessionalEvalDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
evaluationsService
.
submitProfessional
(
id
,
dto
,
user
);
}
@
Post
(
':id/acknowledge'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
acknowledge
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
evaluationsService
.
acknowledge
(
id
,
user
);
}
@
Post
(
':id/respond'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
respond
(
@
Param
(
'id'
)
id
:
string
,
@
Body
(
'responseText'
)
responseText
:
string
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
evaluationsService
.
respond
(
id
,
responseText
,
user
);
}
@
Put
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
)
async
update
(@
Param
(
'id'
)
id
:
string
,
@
Body
()
data
:
any
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
evaluationsService
.
update
(
id
,
data
,
user
);
}
@
Delete
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
evaluationsService
.
delete
(
id
,
user
);
return
{
message
:
'Evaluation deleted'
};
}
}
\ No newline at end of file
backend/src/modules/evaluations/evaluations.module.ts
0 → 100644
View file @
dbe7775f
import
{
Module
}
from
'@nestjs/common'
;
import
{
EvaluationsController
}
from
'./evaluations.controller'
;
import
{
EvaluationsService
}
from
'./evaluations.service'
;
import
{
AutoMetricsService
}
from
'./auto-metrics.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
@
Module
({
imports
:
[
NotificationsModule
],
controllers
:
[
EvaluationsController
],
providers
:
[
EvaluationsService
,
AutoMetricsService
],
exports
:
[
EvaluationsService
,
AutoMetricsService
],
})
export
class
EvaluationsModule
{}
\ No newline at end of file
backend/src/modules/evaluations/evaluations.service.ts
0 → 100644
View file @
dbe7775f
This diff is collapsed.
Click to expand it.
backend/src/modules/learning/competency.service.ts
0 → 100644
View file @
dbe7775f
import
{
Injectable
,
NotFoundException
,
ForbiddenException
,
ConflictException
,
Logger
,
}
from
'@nestjs/common'
;
import
{
PrismaService
}
from
'../../prisma/prisma.service'
;
import
{
CreateCompetencyAreaDto
,
UpdateCompetencyAreaDto
}
from
'./dto/competency-area.dto'
;
import
{
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
@
Injectable
()
export
class
CompetencyService
{
private
readonly
logger
=
new
Logger
(
CompetencyService
.
name
);
constructor
(
private
readonly
prisma
:
PrismaService
)
{}
async
findAllAreas
():
Promise
<
any
[]
>
{
return
this
.
prisma
.
competencyArea
.
findMany
({
where
:
{
isActive
:
true
},
orderBy
:
{
order
:
'asc'
},
});
}
async
findAllAreasAdmin
():
Promise
<
any
[]
>
{
return
this
.
prisma
.
competencyArea
.
findMany
({
orderBy
:
{
order
:
'asc'
},
});
}
async
createArea
(
dto
:
CreateCompetencyAreaDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage competency areas'
);
}
const
existing
=
await
this
.
prisma
.
competencyArea
.
findUnique
({
where
:
{
name
:
dto
.
name
}
});
if
(
existing
)
throw
new
ConflictException
(
`Competency area "
${
dto
.
name
}
" already exists`
);
const
maxOrder
=
await
this
.
prisma
.
competencyArea
.
aggregate
({
_max
:
{
order
:
true
}
});
return
this
.
prisma
.
competencyArea
.
create
({
data
:
{
name
:
dto
.
name
,
description
:
dto
.
description
||
null
,
order
:
dto
.
order
??
((
maxOrder
.
_max
?.
order
||
0
)
+
1
),
},
});
}
async
updateArea
(
id
:
string
,
dto
:
UpdateCompetencyAreaDto
,
currentUser
:
RequestUser
):
Promise
<
any
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage competency areas'
);
}
const
area
=
await
this
.
prisma
.
competencyArea
.
findUnique
({
where
:
{
id
}
});
if
(
!
area
)
throw
new
NotFoundException
(
'Competency area not found'
);
const
updateData
:
any
=
{};
if
(
dto
.
name
!==
undefined
)
updateData
.
name
=
dto
.
name
;
if
(
dto
.
description
!==
undefined
)
updateData
.
description
=
dto
.
description
;
if
(
dto
.
order
!==
undefined
)
updateData
.
order
=
dto
.
order
;
if
(
dto
.
isActive
!==
undefined
)
updateData
.
isActive
=
dto
.
isActive
;
return
this
.
prisma
.
competencyArea
.
update
({
where
:
{
id
},
data
:
updateData
});
}
async
deleteArea
(
id
:
string
,
currentUser
:
RequestUser
):
Promise
<
void
>
{
if
(
currentUser
.
role
!==
'SUPER_ADMIN'
)
{
throw
new
ForbiddenException
(
'Only Super Admin can manage competency areas'
);
}
const
area
=
await
this
.
prisma
.
competencyArea
.
findUnique
({
where
:
{
id
}
});
if
(
!
area
)
throw
new
NotFoundException
(
'Competency area not found'
);
// Soft-deactivate instead of deleting (ratings reference this)
await
this
.
prisma
.
competencyArea
.
update
({
where
:
{
id
},
data
:
{
isActive
:
false
},
});
}
async
getRadarChartData
(
userId
:
string
):
Promise
<
any
>
{
const
areas
=
await
this
.
prisma
.
competencyArea
.
findMany
({
where
:
{
isActive
:
true
},
orderBy
:
{
order
:
'asc'
},
});
const
ratings
=
await
this
.
prisma
.
competencyRating
.
findMany
({
where
:
{
userId
},
include
:
{
competencyArea
:
{
select
:
{
name
:
true
}
}
},
});
const
selfRatings
:
Record
<
string
,
number
>
=
{};
const
plRatings
:
Record
<
string
,
number
>
=
{};
for
(
const
rating
of
ratings
)
{
if
(
rating
.
type
===
'SELF'
)
{
selfRatings
[
rating
.
competencyAreaId
]
=
rating
.
level
;
}
else
if
(
rating
.
type
===
'PL_ASSESSMENT'
)
{
plRatings
[
rating
.
competencyAreaId
]
=
rating
.
level
;
}
}
return
{
areas
:
areas
.
map
((
area
)
=>
({
id
:
area
.
id
,
name
:
area
.
name
,
selfLevel
:
selfRatings
[
area
.
id
]
??
null
,
plLevel
:
plRatings
[
area
.
id
]
??
null
,
})),
lastSelfAssessment
:
ratings
.
find
((
r
)
=>
r
.
type
===
'SELF'
)?.
assessedAt
||
null
,
lastPLAssessment
:
ratings
.
find
((
r
)
=>
r
.
type
===
'PL_ASSESSMENT'
)?.
assessedAt
||
null
,
};
}
async
updateRating
(
userId
:
string
,
competencyAreaId
:
string
,
type
:
string
,
level
:
number
,
assessedById
:
string
,
notes
?:
string
,
):
Promise
<
any
>
{
return
this
.
prisma
.
competencyRating
.
upsert
({
where
:
{
userId_competencyAreaId_type
:
{
userId
,
competencyAreaId
,
type
},
},
update
:
{
level
,
assessedById
,
assessedAt
:
new
Date
(),
notes
:
notes
||
null
,
},
create
:
{
userId
,
competencyAreaId
,
type
,
level
,
assessedById
,
assessedAt
:
new
Date
(),
notes
:
notes
||
null
,
},
});
}
}
\ No newline at end of file
backend/src/modules/learning/dto/assess-learning-goal.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
MinLength
}
from
'class-validator'
;
export
class
AssessLearningGoalDto
{
@
IsString
()
result
:
string
;
// PASSED, FAILED
@
IsString
()
@
MinLength
(
20
)
assessmentNotes
:
string
;
}
\ No newline at end of file
backend/src/modules/learning/dto/competency-area.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsOptional
,
IsInt
,
IsBoolean
,
MinLength
,
MaxLength
,
Min
}
from
'class-validator'
;
export
class
CreateCompetencyAreaDto
{
@
IsString
()
@
MinLength
(
3
)
@
MaxLength
(
200
)
name
:
string
;
@
IsOptional
()
@
IsString
()
description
?:
string
;
@
IsOptional
()
@
IsInt
()
@
Min
(
0
)
order
?:
number
;
}
export
class
UpdateCompetencyAreaDto
{
@
IsOptional
()
@
IsString
()
@
MinLength
(
3
)
@
MaxLength
(
200
)
name
?:
string
;
@
IsOptional
()
@
IsString
()
description
?:
string
;
@
IsOptional
()
@
IsInt
()
@
Min
(
0
)
order
?:
number
;
@
IsOptional
()
@
IsBoolean
()
isActive
?:
boolean
;
}
\ No newline at end of file
backend/src/modules/learning/dto/create-learning-goal.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsDateString
,
IsOptional
,
MinLength
,
MaxLength
}
from
'class-validator'
;
export
class
CreateLearningGoalDto
{
@
IsString
()
userId
:
string
;
@
IsString
()
@
MinLength
(
1
)
@
MaxLength
(
100
)
title
:
string
;
@
IsString
()
@
MinLength
(
50
)
description
:
string
;
@
IsString
()
competencyAreaId
:
string
;
@
IsDateString
()
deadline
:
string
;
@
IsString
()
assessmentMethod
:
string
;
// PL_ASSESSMENT, LIVE_DEMONSTRATION, DELIVERABLE_REVIEW, QUIZ_TEST
@
IsString
()
@
MinLength
(
50
)
passFailCriteria
:
string
;
@
IsOptional
()
@
IsString
()
source
?:
string
;
// MANUAL, SELF_ASSESSMENT_GAP, PIP, EVALUATION
}
\ No newline at end of file
backend/src/modules/learning/dto/learning-goal-filter.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsOptional
,
IsString
}
from
'class-validator'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
LearningGoalFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsString
()
userId
?:
string
;
@
IsOptional
()
@
IsString
()
competencyAreaId
?:
string
;
@
IsOptional
()
@
IsString
()
status
?:
string
;
@
IsOptional
()
@
IsString
()
source
?:
string
;
}
\ No newline at end of file
backend/src/modules/learning/learning.controller.ts
0 → 100644
View file @
dbe7775f
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
LearningService
}
from
'./learning.service'
;
import
{
CompetencyService
}
from
'./competency.service'
;
import
{
CreateLearningGoalDto
}
from
'./dto/create-learning-goal.dto'
;
import
{
AssessLearningGoalDto
}
from
'./dto/assess-learning-goal.dto'
;
import
{
LearningGoalFilterDto
}
from
'./dto/learning-goal-filter.dto'
;
import
{
CreateCompetencyAreaDto
,
UpdateCompetencyAreaDto
}
from
'./dto/competency-area.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'learning'
)
export
class
LearningController
{
constructor
(
private
readonly
learningService
:
LearningService
,
private
readonly
competencyService
:
CompetencyService
,
)
{}
// ─── LEARNING GOALS ──────────────────────────────────
@
Post
(
'goals'
)
async
createGoal
(@
Body
()
dto
:
CreateLearningGoalDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
learningService
.
create
(
dto
,
user
);
}
@
Get
(
'goals'
)
async
findAllGoals
(@
Query
()
filter
:
LearningGoalFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
learningService
.
findAll
(
filter
,
user
);
}
@
Get
(
'goals/:id'
)
async
findGoalById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
learningService
.
findById
(
id
,
user
);
}
@
Put
(
'goals/:id'
)
async
updateGoal
(@
Param
(
'id'
)
id
:
string
,
@
Body
()
data
:
any
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
learningService
.
update
(
id
,
data
,
user
);
}
@
Post
(
'goals/:id/assess'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
assessGoal
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
AssessLearningGoalDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
learningService
.
assess
(
id
,
dto
,
user
);
}
@
Post
(
'goals/:id/extend'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
extendGoal
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
body
:
{
deadline
:
string
;
reason
:
string
},
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
learningService
.
extend
(
id
,
body
.
deadline
,
body
.
reason
,
user
);
}
@
Delete
(
'goals/:id'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
deleteGoal
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
learningService
.
delete
(
id
,
user
);
return
{
message
:
'Learning goal deleted'
};
}
// ─── COMPETENCY AREAS ────────────────────────────────
@
Get
(
'competency-areas'
)
async
findAllAreas
(@
CurrentUser
()
user
:
RequestUser
)
{
if
(
user
.
role
===
'SUPER_ADMIN'
)
{
return
this
.
competencyService
.
findAllAreasAdmin
();
}
return
this
.
competencyService
.
findAllAreas
();
}
@
Post
(
'competency-areas'
)
@
Roles
(
'SUPER_ADMIN'
)
async
createArea
(@
Body
()
dto
:
CreateCompetencyAreaDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
competencyService
.
createArea
(
dto
,
user
);
}
@
Put
(
'competency-areas/:id'
)
@
Roles
(
'SUPER_ADMIN'
)
async
updateArea
(
@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdateCompetencyAreaDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
competencyService
.
updateArea
(
id
,
dto
,
user
);
}
@
Delete
(
'competency-areas/:id'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
deleteArea
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
competencyService
.
deleteArea
(
id
,
user
);
return
{
message
:
'Competency area deactivated'
};
}
// ─── COMPETENCY PROFILE (RADAR CHART) ────────────────
@
Get
(
'profile/:userId'
)
async
getRadarChart
(@
Param
(
'userId'
)
userId
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
// Contractors can only see their own
if
(
user
.
role
===
'CONTRACTOR'
&&
userId
!==
user
.
id
)
{
return
{
areas
:
[]
};
}
return
this
.
competencyService
.
getRadarChartData
(
userId
);
}
@
Get
(
'profile'
)
async
getMyRadarChart
(@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
competencyService
.
getRadarChartData
(
user
.
id
);
}
}
\ No newline at end of file
backend/src/modules/learning/learning.module.ts
0 → 100644
View file @
dbe7775f
import
{
Module
}
from
'@nestjs/common'
;
import
{
LearningController
}
from
'./learning.controller'
;
import
{
LearningService
}
from
'./learning.service'
;
import
{
CompetencyService
}
from
'./competency.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
@
Module
({
imports
:
[
NotificationsModule
],
controllers
:
[
LearningController
],
providers
:
[
LearningService
,
CompetencyService
],
exports
:
[
LearningService
,
CompetencyService
],
})
export
class
LearningModule
{}
\ No newline at end of file
backend/src/modules/learning/learning.service.ts
0 → 100644
View file @
dbe7775f
This diff is collapsed.
Click to expand it.
backend/src/modules/pip/dto/create-pip.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsInt
,
IsDateString
,
IsArray
,
IsOptional
,
Min
,
Max
,
MinLength
,
ValidateNested
}
from
'class-validator'
;
import
{
Type
}
from
'class-transformer'
;
export
class
PIPIssueDto
{
@
IsInt
()
number
:
number
;
@
IsString
()
@
MinLength
(
50
)
text
:
string
;
}
export
class
PIPTargetDto
{
@
IsInt
()
number
:
number
;
@
IsString
()
@
MinLength
(
30
)
text
:
string
;
@
IsString
()
@
MinLength
(
10
)
measurableTarget
:
string
;
}
export
class
CreatePIPDto
{
@
IsString
()
userId
:
string
;
@
IsInt
()
@
Min
(
30
)
@
Max
(
60
)
duration
:
number
;
// 30, 45, or 60
@
IsDateString
()
startDate
:
string
;
@
IsArray
()
@
ValidateNested
({
each
:
true
})
@
Type
(()
=>
PIPIssueDto
)
specificIssues
:
PIPIssueDto
[];
// min 3
@
IsArray
()
@
ValidateNested
({
each
:
true
})
@
Type
(()
=>
PIPTargetDto
)
improvementTargets
:
PIPTargetDto
[];
// min 3
@
IsString
()
checkInSchedule
:
string
;
// WEEKLY, BIWEEKLY
@
IsString
()
checkInDay
:
string
;
// SUNDAY, MONDAY, etc.
@
IsString
()
@
MinLength
(
100
)
successCriteria
:
string
;
@
IsOptional
()
@
IsString
()
consequenceOfFailure
?:
string
;
@
IsOptional
()
@
IsString
()
triggerType
?:
string
;
@
IsOptional
()
@
IsString
()
triggerEntityId
?:
string
;
}
\ No newline at end of file
backend/src/modules/pip/dto/pip-checkin.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsArray
,
IsOptional
,
IsDateString
,
MinLength
}
from
'class-validator'
;
export
class
PIPCheckInDto
{
@
IsOptional
()
@
IsArray
()
attendees
?:
string
[];
// User IDs
@
IsString
()
@
MinLength
(
30
)
summary
:
string
;
@
IsOptional
()
@
IsString
()
progressNotes
?:
string
;
@
IsOptional
()
@
IsArray
()
actionItems
?:
Array
<
{
text
:
string
;
assignee
?:
string
;
dueDate
?:
string
}
>
;
}
\ No newline at end of file
backend/src/modules/pip/dto/pip-filter.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsOptional
,
IsString
}
from
'class-validator'
;
import
{
PaginationDto
}
from
'../../../common/dto/pagination.dto'
;
export
class
PIPFilterDto
extends
PaginationDto
{
@
IsOptional
()
@
IsString
()
userId
?:
string
;
@
IsOptional
()
@
IsString
()
status
?:
string
;
}
\ No newline at end of file
backend/src/modules/pip/dto/pip-result.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
MinLength
}
from
'class-validator'
;
export
class
PIPResultDto
{
@
IsString
()
result
:
string
;
// PASSED, FAILED
@
IsString
()
@
MinLength
(
50
)
resultNotes
:
string
;
}
\ No newline at end of file
backend/src/modules/pip/dto/update-pip.dto.ts
0 → 100644
View file @
dbe7775f
import
{
IsString
,
IsInt
,
IsOptional
,
IsDateString
,
IsArray
,
Min
,
Max
,
MinLength
}
from
'class-validator'
;
export
class
UpdatePIPDto
{
@
IsOptional
()
@
IsInt
()
@
Min
(
30
)
@
Max
(
60
)
duration
?:
number
;
@
IsOptional
()
@
IsDateString
()
endDate
?:
string
;
@
IsOptional
()
@
IsArray
()
specificIssues
?:
any
[];
@
IsOptional
()
@
IsArray
()
improvementTargets
?:
any
[];
@
IsOptional
()
@
IsString
()
successCriteria
?:
string
;
@
IsOptional
()
@
IsString
()
consequenceOfFailure
?:
string
;
@
IsOptional
()
@
IsString
()
checkInSchedule
?:
string
;
@
IsOptional
()
@
IsString
()
checkInDay
?:
string
;
}
\ No newline at end of file
backend/src/modules/pip/pip.controller.ts
0 → 100644
View file @
dbe7775f
import
{
Controller
,
Get
,
Post
,
Put
,
Delete
,
Body
,
Param
,
Query
,
HttpCode
,
HttpStatus
,
}
from
'@nestjs/common'
;
import
{
PIPService
}
from
'./pip.service'
;
import
{
CreatePIPDto
}
from
'./dto/create-pip.dto'
;
import
{
UpdatePIPDto
}
from
'./dto/update-pip.dto'
;
import
{
PIPCheckInDto
}
from
'./dto/pip-checkin.dto'
;
import
{
PIPResultDto
}
from
'./dto/pip-result.dto'
;
import
{
PIPFilterDto
}
from
'./dto/pip-filter.dto'
;
import
{
CurrentUser
,
RequestUser
}
from
'../../common/decorators/current-user.decorator'
;
import
{
Roles
}
from
'../../common/decorators/roles.decorator'
;
@
Controller
(
'pips'
)
export
class
PIPController
{
constructor
(
private
readonly
pipService
:
PIPService
)
{}
@
Post
()
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
create
(@
Body
()
dto
:
CreatePIPDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
create
(
dto
,
user
);
}
@
Get
()
async
findAll
(@
Query
()
filter
:
PIPFilterDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
findAll
(
filter
,
user
);
}
@
Get
(
':id'
)
async
findById
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
findById
(
id
,
user
);
}
@
Put
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
async
update
(@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
UpdatePIPDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
update
(
id
,
dto
,
user
);
}
@
Post
(
':id/acknowledge'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
acknowledge
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
acknowledge
(
id
,
user
);
}
@
Post
(
':id/checkins/:checkInId'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
logCheckIn
(
@
Param
(
'id'
)
id
:
string
,
@
Param
(
'checkInId'
)
checkInId
:
string
,
@
Body
()
dto
:
PIPCheckInDto
,
@
CurrentUser
()
user
:
RequestUser
,
)
{
return
this
.
pipService
.
logCheckIn
(
id
,
checkInId
,
dto
,
user
);
}
@
Post
(
':id/result'
)
@
Roles
(
'SUPER_ADMIN'
,
'ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
recordResult
(@
Param
(
'id'
)
id
:
string
,
@
Body
()
dto
:
PIPResultDto
,
@
CurrentUser
()
user
:
RequestUser
)
{
return
this
.
pipService
.
recordResult
(
id
,
dto
,
user
);
}
@
Delete
(
':id'
)
@
Roles
(
'SUPER_ADMIN'
)
@
HttpCode
(
HttpStatus
.
OK
)
async
delete
(@
Param
(
'id'
)
id
:
string
,
@
CurrentUser
()
user
:
RequestUser
)
{
await
this
.
pipService
.
delete
(
id
,
user
);
return
{
message
:
'PIP deleted'
};
}
}
\ No newline at end of file
backend/src/modules/pip/pip.module.ts
0 → 100644
View file @
dbe7775f
import
{
Module
}
from
'@nestjs/common'
;
import
{
PIPController
}
from
'./pip.controller'
;
import
{
PIPService
}
from
'./pip.service'
;
import
{
NotificationsModule
}
from
'../notifications/notifications.module'
;
@
Module
({
imports
:
[
NotificationsModule
],
controllers
:
[
PIPController
],
providers
:
[
PIPService
],
exports
:
[
PIPService
],
})
export
class
PIPModule
{}
\ No newline at end of file
backend/src/modules/pip/pip.service.ts
0 → 100644
View file @
dbe7775f
This diff is collapsed.
Click to expand it.
prisma/schema-additions.prisma
View file @
dbe7775f
//
───
ADD
TO
User
MODEL
──────────────────────────────────────────
//
───
COMPETENCY
&
PERFORMANCE
ADDITIONS
──────────────────────────
//
These
fields
must
exist
on
the
User
model
:
//
Models
that
extend
the
core
schema
for
performance
tracking
//
nameArabic
String
?
//
nationalId
String
?
@
unique
//
dateOfBirth
DateTime
?
//
phone
String
?
@
unique
//
phoneSecondary
String
?
//
address
String
?
//
emergencyContactName
String
?
//
emergencyContactPhone
String
?
//
emergencyContactRelationship
String
?
//
bankName
String
?
//
bankAccountNumber
String
?
//
bankAccountHolderName
String
?
//
taxRegistrationNumber
String
?
//
contractorType
String
?
//
department
String
?
//
title
String
?
//
bio
String
?
//
timezone
String
@
default
(
"Africa/Cairo"
)
//
weeklySchedule
Json
?
//
baseSalaryPiasters
Int
@
default
(
0
)
//
actualSalaryPiasters
Int
@
default
(
0
)
//
startDate
DateTime
?
//
contractStartDate
DateTime
?
//
contractEndDate
DateTime
?
//
assignedProjectLeaderId
String
?
//
onboardingChecklist
Json
?
//
deletedAt
DateTime
?
//
───
NEW
MODELS
─────────────────────────────────────────────────
model
CompetencyArea
{
model
Invite
{
id
String
@
id
@
default
(
uuid
())
code
String
@
unique
token
String
@
unique
contractorType
String
assignedProjectLeaderId
String
?
assignedBoardIds
String
[]
welcomeNote
String
?
expiresAt
DateTime
status
String
@
default
(
"ACTIVE"
)
//
ACTIVE
,
USED
,
EXPIRED
,
REVOKED
createdById
String
createdBy
User
@
relation
(
"InviteCreator"
,
fields
:
[
createdById
],
references
:
[
id
])
usedById
String
?
usedBy
User
?
@
relation
(
"InviteUsed"
,
fields
:
[
usedById
],
references
:
[
id
])
usedAt
DateTime
?
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
code
])
@@
index
([
token
])
@@
index
([
status
])
}
model
BoardMember
{
id
String
@
id
@
default
(
uuid
())
id
String
@
id
@
default
(
uuid
())
boardId
String
userId
String
role
String
@
default
(
"MEMBER"
)
//
OWNER
,
ADMIN
,
MEMBER
,
VIEWER
joinedAt
DateTime
@
default
(
now
())
createdAt
DateTime
@
default
(
now
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
updatedAt
DateTime
@
updatedAt
user
User
@
relation
(
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
name
String
@
unique
description
String
?
@@
unique
([
boardId
,
userId
])
order
Int
@
default
(
0
)
@@
index
([
boardId
])
isActive
Boolean
@
default
(
true
)
@@
index
([
userId
])
}
model
Contract
{
id
String
@
id
@
default
(
uuid
())
userId
String
user
User
@
relation
(
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
contractType
String
contractText
String
signedAt
DateTime
signedFullName
String
acknowledgedClauses
String
[]
signatureIpAddress
String
signatureUserAgent
String
baseSalaryAtSigning
Int
scheduleAtSigning
Json
?
startDate
DateTime
endDate
DateTime
?
status
String
@
default
(
"ACTIVE"
)
//
DRAFT
,
ACTIVE
,
EXPIRED
,
TERMINATED
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
index
([
userId
])
ratings
CompetencyRating
[]
@@
index
([
status
])
learningGoals
LearningGoal
[]
}
}
model
CompetencyRating
{
model
CompetencyRating
{
id
String
@
id
@
default
(
uuid
())
id
String
@
id
@
default
(
uuid
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
userId
String
userId
String
competencyAreaId
String
competencyAreaId
String
type
String
//
SELF
,
PL_ASSESSMENT
competencyArea
CompetencyArea
@
relation
(
fields
:
[
competencyAreaId
],
references
:
[
id
],
onDelete
:
Cascade
)
level
Int
//
0
-
5
assessedAt
DateTime
@
default
(
now
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
user
User
@
relation
(
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
type
String
//
SELF
,
PL_ASSESSMENT
competencyArea
CompetencyArea
@
relation
(
fields
:
[
competencyAreaId
],
references
:
[
id
])
level
Int
//
0
-
5
@@
unique
([
userId
,
competencyAreaId
,
type
])
assessedById
String
?
@@
index
([
userId
])
assessedAt
DateTime
?
}
notes
String
?
model
SalaryChangeLog
{
id
String
@
id
@
default
(
uuid
())
userId
String
oldSalaryPiasters
Int
newSalaryPiasters
Int
reason
String
changedById
String
createdAt
DateTime
@
default
(
now
())
@@
index
([
userId
])
}
model
StatusChangeLog
{
id
String
@
id
@
default
(
uuid
())
userId
String
fromStatus
String
toStatus
String
reason
String
?
changedById
String
createdAt
DateTime
@
default
(
now
())
@@
index
([
userId
])
}
model
PrivateNote
{
id
String
@
id
@
default
(
uuid
())
userId
String
content
String
authorId
String
author
User
@
relation
(
"NoteAuthor"
,
fields
:
[
authorId
],
references
:
[
id
])
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
@@
unique
([
userId_competencyAreaId_type
],
name
:
"userId_competencyAreaId_type"
)
@@
index
([
userId
])
@@
index
([
userId
])
@@
index
([
competencyAreaId
])
}
}
\ No newline at end of file
prisma/schema-evaluations.prisma
0 → 100644
View file @
dbe7775f
//
───
EVALUATION
&
PERFORMANCE
MODELS
─────────────────────────────
//
Phase
2
A
:
Monthly
evaluations
,
PIPs
,
learning
goals
model
Evaluation
{
id
String
@
id
@
default
(
uuid
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
userId
String
user
User
@
relation
(
"UserEvaluations"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
month
Int
//
1
-
12
year
Int
//
Technical
Evaluation
(
by
Project
Leader
)
technicalSubmittedById
String
?
technicalSubmittedAt
DateTime
?
techCodeQuality
Float
?
//
1
-
5
techCodeQualityNotes
String
?
techTaskCompletion
Float
?
//
1
-
5
(
auto
-
calculated
)
techTaskCompletionAuto
Float
?
//
The
auto
-
calculated
value
before
override
techDeadlineCompliance
Float
?
//
1
-
5
(
auto
-
calculated
)
techDeadlineComplianceAuto
Float
?
//
The
auto
-
calculated
value
before
override
techGrowth
Float
?
//
1
-
5
techGrowthNotes
String
?
techProblemSolving
Float
?
//
1
-
5
techProblemSolvingNotes
String
?
techOverrideJustification
String
?
//
Required
if
auto
-
values
are
overridden
technicalScore
Float
?
//
Weighted
average
//
Professional
Evaluation
(
by
Admin
)
professionalSubmittedById
String
?
professionalSubmittedAt
DateTime
?
profReportingCompliance
Float
?
//
1
-
5
(
auto
-
calculated
)
profReportingComplianceAuto
Float
?
profCommunication
Float
?
//
1
-
5
profCommunicationNotes
String
?
profCollaboration
Float
?
//
1
-
5
profCollaborationNotes
String
?
profReliability
Float
?
//
1
-
5
profReliabilityNotes
String
?
profPolicyCompliance
Float
?
//
1
-
5
(
auto
-
calculated
)
profPolicyComplianceAuto
Float
?
profOverrideJustification
String
?
professionalScore
Float
?
//
Weighted
average
//
Compiled
overallScore
Float
?
//
(
technical
*
0.5
)
+
(
professional
*
0.5
)
rating
String
?
//
EXCEPTIONAL
,
STRONG
,
ADEQUATE
,
BELOW_EXPECTATIONS
,
UNACCEPTABLE
compiledAt
DateTime
?
compiledById
String
?
//
Auto
-
calculated
system
metrics
(
supplementary
,
not
scored
)
systemMetrics
Json
?
//
{
daysReported
,
expectedDays
,
onTimeRate
,
tasksCompleted
,
tasksAssigned
,
deadlineHitRate
,
totalDeductions
,
totalBounties
,
avgDailyHours
,
currentStreak
,
bestStreak
,
messagesSent
}
//
Contractor
acknowledgment
acknowledgedAt
DateTime
?
responseText
String
?
respondedAt
DateTime
?
status
String
@
default
(
"PENDING_TECHNICAL"
)
//
PENDING_TECHNICAL
,
PENDING_PROFESSIONAL
,
COMPILED
,
ACKNOWLEDGED
,
RESPONDED
@@
unique
([
userId
,
month
,
year
])
@@
index
([
userId
])
@@
index
([
month
,
year
])
@@
index
([
status
])
}
model
PIP
{
id
String
@
id
@
default
(
uuid
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
userId
String
user
User
@
relation
(
"UserPIPs"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
createdById
String
createdBy
User
@
relation
(
"PIPCreatedBy"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
Restrict
)
duration
Int
//
30
,
45
,
or
60
calendar
days
startDate
DateTime
endDate
DateTime
specificIssues
Json
//
Array
of
{
number
,
text
}
—
min
3
items
improvementTargets
Json
//
Array
of
{
number
,
text
,
measurableTarget
}
—
min
3
items
checkInSchedule
String
//
WEEKLY
,
BIWEEKLY
checkInDay
String
//
SUNDAY
,
MONDAY
,
etc
.
successCriteria
String
//
Min
100
chars
consequenceOfFailure
String
@
default
(
"Termination of engagement."
)
//
Lifecycle
acknowledgedAt
DateTime
?
status
String
@
default
(
"ACTIVE"
)
//
ACTIVE
,
PASSED
,
FAILED
,
CANCELLED
result
String
?
//
PASSED
,
FAILED
resultNotes
String
?
resultDate
DateTime
?
resultById
String
?
//
Trigger
info
triggerType
String
?
//
DEDUCTION_THRESHOLD
,
LOW_EVALUATION
,
CONSECUTIVE_LOW_EVAL
,
DISAPPEARANCE
,
MANUAL
triggerEntityId
String
?
//
e
.
g
.,
evaluation
ID
or
deduction
ID
checkIns
PIPCheckIn
[]
@@
index
([
userId
])
@@
index
([
status
])
@@
index
([
startDate
,
endDate
])
}
model
PIPCheckIn
{
id
String
@
id
@
default
(
uuid
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
pipId
String
pip
PIP
@
relation
(
fields
:
[
pipId
],
references
:
[
id
],
onDelete
:
Cascade
)
scheduledDate
DateTime
completedAt
DateTime
?
loggedById
String
?
attendees
Json
?
//
Array
of
user
IDs
summary
String
?
progressNotes
String
?
actionItems
Json
?
//
Array
of
{
text
,
assignee
,
dueDate
}
isMissed
Boolean
@
default
(
false
)
@@
index
([
pipId
])
@@
index
([
scheduledDate
])
}
model
LearningGoal
{
id
String
@
id
@
default
(
uuid
())
createdAt
DateTime
@
default
(
now
())
updatedAt
DateTime
@
updatedAt
userId
String
user
User
@
relation
(
"UserLearningGoals"
,
fields
:
[
userId
],
references
:
[
id
],
onDelete
:
Cascade
)
createdById
String
createdBy
User
@
relation
(
"LearningGoalCreatedBy"
,
fields
:
[
createdById
],
references
:
[
id
],
onDelete
:
Restrict
)
title
String
description
String
competencyAreaId
String
competencyArea
CompetencyArea
@
relation
(
fields
:
[
competencyAreaId
],
references
:
[
id
],
onDelete
:
Restrict
)
deadline
DateTime
originalDeadline
DateTime
//
Preserved
even
if
extended
assessmentMethod
String
//
PL_ASSESSMENT
,
LIVE_DEMONSTRATION
,
DELIVERABLE_REVIEW
,
QUIZ_TEST
passFailCriteria
String
//
Min
50
chars
//
Assessment
assessedById
String
?
assessedAt
DateTime
?
assessmentResult
String
?
//
PASSED
,
FAILED
assessmentNotes
String
?
//
Extension
extendedAt
DateTime
?
extendedById
String
?
extensionReason
String
?
extensionCount
Int
@
default
(
0
)
//
Source
source
String
@
default
(
"MANUAL"
)
//
MANUAL
,
SELF_ASSESSMENT_GAP
,
PIP
,
EVALUATION
status
String
@
default
(
"ACTIVE"
)
//
ACTIVE
,
OVERDUE
,
PASSED
,
FAILED
,
EXTENDED
@@
index
([
userId
])
@@
index
([
competencyAreaId
])
@@
index
([
status
])
@@
index
([
deadline
])
}
\ 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