@@ -93,7 +93,15 @@ export class ControlsService {
9393 } ;
9494 }
9595
96- async findOne ( controlId : string , organizationId : string ) {
96+ async findOne (
97+ controlId : string ,
98+ organizationId : string ,
99+ frameworkInstanceId ?: string ,
100+ ) {
101+ if ( frameworkInstanceId ) {
102+ return this . findOneForFramework ( controlId , organizationId , frameworkInstanceId ) ;
103+ }
104+
97105 const control = await db . control . findUnique ( {
98106 where : { id : controlId , organizationId } ,
99107 include : {
@@ -117,7 +125,11 @@ export class ControlsService {
117125 throw new NotFoundException ( 'Control not found' ) ;
118126 }
119127
120- const formTypes = ( control . controlDocumentTypes ?? [ ] ) . map (
128+ const policies = control . policies || [ ] ;
129+ const tasks = control . tasks || [ ] ;
130+ const controlDocumentTypes = control . controlDocumentTypes || [ ] ;
131+
132+ const formTypes = controlDocumentTypes . map (
121133 ( d ) => d . formType ,
122134 ) ;
123135 const notRelevantSettings =
@@ -150,8 +162,6 @@ export class ControlsService {
150162 }
151163
152164 // Compute progress
153- const policies = control . policies || [ ] ;
154- const tasks = control . tasks || [ ] ;
155165 const totalItems = policies . length + tasks . length ;
156166
157167 let policyCompleted = 0 ;
@@ -168,7 +178,9 @@ export class ControlsService {
168178
169179 return {
170180 ...control ,
171- controlDocumentTypes : ( control . controlDocumentTypes ?? [ ] ) . map (
181+ policies,
182+ tasks,
183+ controlDocumentTypes : controlDocumentTypes . map (
172184 ( documentType ) => ( {
173185 ...documentType ,
174186 isNotRelevant : notRelevantFormTypes . has ( documentType . formType ) ,
@@ -188,6 +200,118 @@ export class ControlsService {
188200 } ;
189201 }
190202
203+ private async findOneForFramework (
204+ controlId : string ,
205+ organizationId : string ,
206+ frameworkInstanceId : string ,
207+ ) {
208+ await this . ensureFrameworkInstance ( frameworkInstanceId , organizationId ) ;
209+ const control = await db . control . findUnique ( {
210+ where : { id : controlId , organizationId } ,
211+ include : {
212+ frameworkPolicyLinks : {
213+ where : {
214+ frameworkInstanceId,
215+ policy : { archivedAt : null } ,
216+ } ,
217+ include : { policy : true } ,
218+ } ,
219+ frameworkTaskLinks : {
220+ where : {
221+ frameworkInstanceId,
222+ task : { archivedAt : null } ,
223+ } ,
224+ include : { task : true } ,
225+ } ,
226+ frameworkDocumentLinks : {
227+ where : { frameworkInstanceId } ,
228+ } ,
229+ requirementsMapped : {
230+ where : { archivedAt : null } ,
231+ include : {
232+ frameworkInstance : {
233+ include : { framework : true , customFramework : true } ,
234+ } ,
235+ requirement : true ,
236+ customRequirement : true ,
237+ } ,
238+ } ,
239+ } ,
240+ } ) ;
241+
242+ if ( ! control ) {
243+ throw new NotFoundException ( 'Control not found' ) ;
244+ }
245+
246+ const policies = control . frameworkPolicyLinks . map ( ( link ) => link . policy ) ;
247+ const tasks = control . frameworkTaskLinks . map ( ( link ) => link . task ) ;
248+ const controlDocumentTypes = control . frameworkDocumentLinks ;
249+ const formTypes = controlDocumentTypes . map ( ( d ) => d . formType ) ;
250+ const notRelevantSettings =
251+ formTypes . length > 0
252+ ? await db . evidenceFormSetting . findMany ( {
253+ where : {
254+ organizationId,
255+ formType : { in : formTypes } ,
256+ isNotRelevant : true ,
257+ } ,
258+ select : { formType : true } ,
259+ } )
260+ : [ ] ;
261+ const notRelevantFormTypes = new Set (
262+ notRelevantSettings . map ( ( setting ) => setting . formType ) ,
263+ ) ;
264+ const submissionCountsByFormType : Record < string , number > = { } ;
265+ if ( formTypes . length > 0 ) {
266+ const grouped = await db . evidenceSubmission . groupBy ( {
267+ by : [ 'formType' ] ,
268+ where : {
269+ organizationId,
270+ formType : { in : formTypes } ,
271+ } ,
272+ _count : { _all : true } ,
273+ } ) ;
274+ for ( const g of grouped ) {
275+ submissionCountsByFormType [ g . formType ] = g . _count . _all ;
276+ }
277+ }
278+
279+ const policyCompleted = policies . filter ( ( p ) => p . status === 'published' ) . length ;
280+ const taskCompleted = tasks . filter (
281+ ( t ) => t . status === 'done' || t . status === 'not_relevant' ,
282+ ) . length ;
283+ const completed = policyCompleted + taskCompleted ;
284+ const totalItems = policies . length + tasks . length ;
285+
286+ const {
287+ frameworkPolicyLinks,
288+ frameworkTaskLinks,
289+ frameworkDocumentLinks,
290+ ...controlData
291+ } = control ;
292+
293+ return {
294+ ...controlData ,
295+ policies,
296+ tasks,
297+ controlDocumentTypes : controlDocumentTypes . map ( ( documentType ) => ( {
298+ ...documentType ,
299+ isNotRelevant : notRelevantFormTypes . has ( documentType . formType ) ,
300+ } ) ) ,
301+ submissionCountsByFormType,
302+ progress : {
303+ total : totalItems ,
304+ completed,
305+ progress :
306+ totalItems > 0 ? Math . round ( ( completed / totalItems ) * 100 ) : 0 ,
307+ byType : {
308+ policy : { total : policies . length , completed : policyCompleted } ,
309+ task : { total : tasks . length , completed : taskCompleted } ,
310+ } ,
311+ } ,
312+ } ;
313+ }
314+
191315 async getOptions ( organizationId : string ) {
192316 const [ policies , tasks , frameworkInstances ] = await Promise . all ( [
193317 db . policy . findMany ( {
@@ -480,10 +604,25 @@ export class ControlsService {
480604 return control ;
481605 }
482606
607+ private async ensureFrameworkInstance (
608+ frameworkInstanceId : string ,
609+ organizationId : string ,
610+ ) {
611+ const frameworkInstance = await db . frameworkInstance . findUnique ( {
612+ where : { id : frameworkInstanceId , organizationId } ,
613+ select : { id : true } ,
614+ } ) ;
615+ if ( ! frameworkInstance ) {
616+ throw new NotFoundException ( 'Framework instance not found' ) ;
617+ }
618+ return frameworkInstance ;
619+ }
620+
483621 async linkPolicies (
484622 controlId : string ,
485623 organizationId : string ,
486624 policyIds : string [ ] ,
625+ frameworkInstanceId ?: string ,
487626 ) {
488627 await this . ensureControl ( controlId , organizationId ) ;
489628
@@ -495,10 +634,22 @@ export class ControlsService {
495634 throw new BadRequestException ( 'No valid policies to link' ) ;
496635 }
497636
498- await db . control . update ( {
499- where : { id : controlId } ,
500- data : { policies : { connect : policies . map ( ( p ) => ( { id : p . id } ) ) } } ,
501- } ) ;
637+ if ( frameworkInstanceId ) {
638+ await this . ensureFrameworkInstance ( frameworkInstanceId , organizationId ) ;
639+ await db . frameworkControlPolicyLink . createMany ( {
640+ data : policies . map ( ( policy ) => ( {
641+ frameworkInstanceId,
642+ controlId,
643+ policyId : policy . id ,
644+ } ) ) ,
645+ skipDuplicates : true ,
646+ } ) ;
647+ } else {
648+ await db . control . update ( {
649+ where : { id : controlId } ,
650+ data : { policies : { connect : policies . map ( ( p ) => ( { id : p . id } ) ) } } ,
651+ } ) ;
652+ }
502653
503654 return { count : policies . length } ;
504655 }
@@ -507,6 +658,7 @@ export class ControlsService {
507658 controlId : string ,
508659 organizationId : string ,
509660 taskIds : string [ ] ,
661+ frameworkInstanceId ?: string ,
510662 ) {
511663 await this . ensureControl ( controlId , organizationId ) ;
512664
@@ -518,10 +670,22 @@ export class ControlsService {
518670 throw new BadRequestException ( 'No valid tasks to link' ) ;
519671 }
520672
521- await db . control . update ( {
522- where : { id : controlId } ,
523- data : { tasks : { connect : tasks . map ( ( t ) => ( { id : t . id } ) ) } } ,
524- } ) ;
673+ if ( frameworkInstanceId ) {
674+ await this . ensureFrameworkInstance ( frameworkInstanceId , organizationId ) ;
675+ await db . frameworkControlTaskLink . createMany ( {
676+ data : tasks . map ( ( task ) => ( {
677+ frameworkInstanceId,
678+ controlId,
679+ taskId : task . id ,
680+ } ) ) ,
681+ skipDuplicates : true ,
682+ } ) ;
683+ } else {
684+ await db . control . update ( {
685+ where : { id : controlId } ,
686+ data : { tasks : { connect : tasks . map ( ( t ) => ( { id : t . id } ) ) } } ,
687+ } ) ;
688+ }
525689
526690 return { count : tasks . length } ;
527691 }
@@ -627,8 +791,21 @@ export class ControlsService {
627791 controlId : string ,
628792 organizationId : string ,
629793 formTypes : EvidenceFormType [ ] ,
794+ frameworkInstanceId ?: string ,
630795 ) {
631796 await this . ensureControl ( controlId , organizationId ) ;
797+ if ( frameworkInstanceId ) {
798+ await this . ensureFrameworkInstance ( frameworkInstanceId , organizationId ) ;
799+ const result = await db . frameworkControlDocumentTypeLink . createMany ( {
800+ data : formTypes . map ( ( formType ) => ( {
801+ frameworkInstanceId,
802+ controlId,
803+ formType,
804+ } ) ) ,
805+ skipDuplicates : true ,
806+ } ) ;
807+ return { count : result . count } ;
808+ }
632809 const result = await db . controlDocumentType . createMany ( {
633810 data : formTypes . map ( ( formType ) => ( { controlId, formType } ) ) ,
634811 skipDuplicates : true ,
@@ -640,8 +817,16 @@ export class ControlsService {
640817 controlId : string ,
641818 organizationId : string ,
642819 formType : EvidenceFormType ,
820+ frameworkInstanceId ?: string ,
643821 ) {
644822 await this . ensureControl ( controlId , organizationId ) ;
823+ if ( frameworkInstanceId ) {
824+ await this . ensureFrameworkInstance ( frameworkInstanceId , organizationId ) ;
825+ await db . frameworkControlDocumentTypeLink . deleteMany ( {
826+ where : { frameworkInstanceId, controlId, formType } ,
827+ } ) ;
828+ return { success : true } ;
829+ }
645830 await db . controlDocumentType . deleteMany ( {
646831 where : { controlId, formType } ,
647832 } ) ;
0 commit comments