@@ -6,11 +6,12 @@ import type { IScopes } from './types';
66const { EventReportPilot : TrainingReport , Grant, sequelize } = db ;
77
88interface ITrainingReportForSessionCount {
9- sessionReports : { id : number } [ ] ;
9+ sessionReports : { id : number ; data : { duration ?: number | string } } [ ] ;
1010}
1111
1212/**
13- * Widget: count of approved (COMPLETE) Training Report sessions for a given recipient.
13+ * Widget: count of approved (COMPLETE) Training Report sessions for a given recipient,
14+ * plus the total hours of TTA delivered on those sessions.
1415 * Used on the RTR TTA History tab.
1516 *
1617 * The recipient filter flows in via scopes.grant.where (from the recipientId.ctn
@@ -19,10 +20,12 @@ interface ITrainingReportForSessionCount {
1920 *
2021 * NOTE: The `numSessions` key returned here is per-recipient and is distinct from the
2122 * `numSessions` returned by `trOverview`, which is a global count across visible TRs.
23+ * `sumDuration` is returned as a raw number so that it can be summed with the AR
24+ * duration in `ttaHistoryOverview`; formatting happens in the caller.
2225 */
2326export default async function trSessionsForRecipient (
2427 scopes : IScopes
25- ) : Promise < { numSessions : string } > {
28+ ) : Promise < { numSessions : string ; sumDuration : number } > {
2629 // Find all grants visible to this user/recipient via the standard grant scopes.
2730 const grants = ( await Grant . findAll ( {
2831 attributes : [ 'id' ] ,
@@ -40,7 +43,7 @@ export default async function trSessionsForRecipient(
4043 . filter ( ( id ) => Number . isInteger ( id ) && id > 0 ) ;
4144
4245 if ( grantIdList . length === 0 ) {
43- return { numSessions : '0' } ;
46+ return { numSessions : '0' , sumDuration : 0 } ;
4447 }
4548
4649 // Build a SQL literal that restricts sessions to only those containing at least
@@ -68,7 +71,7 @@ export default async function trSessionsForRecipient(
6871 ...baseScopes ,
6972 include : {
7073 ...baseScopes . include ,
71- attributes : [ 'id' ] ,
74+ attributes : [ 'id' , 'data' ] ,
7275 where : {
7376 ...baseScopes . include . where ,
7477 [ Op . and ] : [ recipientGrantFilter ] ,
@@ -79,5 +82,16 @@ export default async function trSessionsForRecipient(
7982 // Each session in the result already matches a recipient grant, so count them all.
8083 const numSessions = reports . reduce ( ( sum , r ) => sum + r . sessionReports . length , 0 ) ;
8184
82- return { numSessions : formatNumber ( numSessions ) } ;
85+ // Sum the duration across every matching session. Sessions store duration as a
86+ // number in JSONB, but we parseFloat defensively to match the activity report
87+ // overview's handling of legacy/string-typed durations.
88+ const sumDuration = reports . reduce (
89+ ( sum , r ) => sum + r . sessionReports . reduce (
90+ ( sessionSum , s ) => sessionSum + ( parseFloat ( s . data ?. duration as string ) || 0 ) ,
91+ 0 ,
92+ ) ,
93+ 0 ,
94+ ) ;
95+
96+ return { numSessions : formatNumber ( numSessions ) , sumDuration } ;
8397}
0 commit comments