@@ -12,8 +12,10 @@ import { difference, pickBy, upperFirst } from 'lodash';
12
12
import { DateTime } from 'luxon' ;
13
13
import { MergeExclusive } from 'type-fest' ;
14
14
import {
15
+ DuplicateException ,
15
16
generateId ,
16
17
ID ,
18
+ InputException ,
17
19
labelForView ,
18
20
NotFoundException ,
19
21
ObjectView ,
@@ -24,7 +26,7 @@ import {
24
26
viewOfChangeset ,
25
27
} from '~/common' ;
26
28
import { CommonRepository , OnIndex } from '~/core/database' ;
27
- import { ChangesOf , getChanges } from '~/core/database/changes' ;
29
+ import { getChanges } from '~/core/database/changes' ;
28
30
import {
29
31
ACTIVE ,
30
32
coalesce ,
@@ -46,11 +48,13 @@ import {
46
48
whereNotDeletedInChangeset ,
47
49
} from '~/core/database/query' ;
48
50
import { Privileges } from '../authorization' ;
51
+ import { FileService } from '../file' ;
49
52
import { FileId } from '../file/dto' ;
50
53
import {
51
54
languageFilters ,
52
55
languageSorters ,
53
56
} from '../language/language.repository' ;
57
+ import { Location } from '../location/dto' ;
54
58
import {
55
59
matchCurrentDue ,
56
60
progressReportSorters ,
@@ -59,6 +63,7 @@ import { ProjectType } from '../project/dto';
59
63
import { projectFilters } from '../project/project-filters.query' ;
60
64
import { projectSorters } from '../project/project.repository' ;
61
65
import { userFilters } from '../user' ;
66
+ import { User } from '../user/dto' ;
62
67
import {
63
68
CreateInternshipEngagement ,
64
69
CreateLanguageEngagement ,
@@ -80,7 +85,10 @@ export type LanguageOrEngagementId = MergeExclusive<
80
85
81
86
@Injectable ( )
82
87
export class EngagementRepository extends CommonRepository {
83
- constructor ( private readonly privileges : Privileges ) {
88
+ constructor (
89
+ private readonly privileges : Privileges ,
90
+ private readonly files : FileService ,
91
+ ) {
84
92
super ( ) ;
85
93
}
86
94
@@ -192,10 +200,9 @@ export class EngagementRepository extends CommonRepository {
192
200
) ;
193
201
}
194
202
195
- // CREATE ///////////////////////////////////////////////////////////
196
-
197
203
async createLanguageEngagement (
198
204
input : CreateLanguageEngagement ,
205
+ session : Session ,
199
206
changeset ?: ID ,
200
207
) {
201
208
const pnpId = await generateId < FileId > ( ) ;
@@ -219,6 +226,17 @@ export class EngagementRepository extends CommonRepository {
219
226
canDelete : true ,
220
227
} ;
221
228
229
+ await this . verifyRelationshipEligibility (
230
+ projectId ,
231
+ languageId ,
232
+ false ,
233
+ changeset ,
234
+ ) ;
235
+
236
+ if ( input . firstScripture ) {
237
+ await this . verifyFirstScripture ( { languageId } ) ;
238
+ }
239
+
222
240
const query = this . db
223
241
. query ( )
224
242
. apply ( await createNode ( LanguageEngagement , { initialProps } ) )
@@ -238,11 +256,26 @@ export class EngagementRepository extends CommonRepository {
238
256
throw new ServerException ( 'Could not create Language Engagement' ) ;
239
257
}
240
258
241
- return { id : result . id , pnpId } ;
259
+ await this . files . createDefinedFile (
260
+ pnpId ,
261
+ `PNP` ,
262
+ session ,
263
+ result . id ,
264
+ 'pnp' ,
265
+ input . pnp ,
266
+ 'engagement.pnp' ,
267
+ ) ;
268
+
269
+ return ( await this . readOne (
270
+ result . id ,
271
+ session ,
272
+ viewOfChangeset ( changeset ) ,
273
+ ) ) as UnsecuredDto < LanguageEngagement > ;
242
274
}
243
275
244
276
async createInternshipEngagement (
245
277
input : CreateInternshipEngagement ,
278
+ session : Session ,
246
279
changeset ?: ID ,
247
280
) {
248
281
const growthPlanId = await generateId < FileId > ( ) ;
@@ -268,6 +301,13 @@ export class EngagementRepository extends CommonRepository {
268
301
canDelete : true ,
269
302
} ;
270
303
304
+ await this . verifyRelationshipEligibility (
305
+ projectId ,
306
+ internId ,
307
+ true ,
308
+ changeset ,
309
+ ) ;
310
+
271
311
const query = this . db
272
312
. query ( )
273
313
. apply ( await createNode ( InternshipEngagement , { initialProps } ) )
@@ -287,67 +327,124 @@ export class EngagementRepository extends CommonRepository {
287
327
. return < { id : ID } > ( 'node.id as id' ) ;
288
328
const result = await query . first ( ) ;
289
329
if ( ! result ) {
290
- throw new NotFoundException ( ) ;
330
+ if ( mentorId && ! ( await this . getBaseNode ( mentorId , User ) ) ) {
331
+ throw new NotFoundException (
332
+ 'Could not find mentor' ,
333
+ 'engagement.mentorId' ,
334
+ ) ;
335
+ }
336
+
337
+ if (
338
+ countryOfOriginId &&
339
+ ! ( await this . getBaseNode ( countryOfOriginId , Location ) )
340
+ ) {
341
+ throw new NotFoundException (
342
+ 'Could not find country of origin' ,
343
+ 'engagement.countryOfOriginId' ,
344
+ ) ;
345
+ }
346
+
347
+ throw new ServerException ( 'Could not create Internship Engagement' ) ;
291
348
}
292
349
293
- return { id : result . id , growthPlanId } ;
350
+ await this . files . createDefinedFile (
351
+ growthPlanId ,
352
+ `Growth Plan` ,
353
+ session ,
354
+ result . id ,
355
+ 'growthPlan' ,
356
+ input . growthPlan ,
357
+ 'engagement.growthPlan' ,
358
+ ) ;
359
+
360
+ return ( await this . readOne (
361
+ result . id ,
362
+ session ,
363
+ viewOfChangeset ( changeset ) ,
364
+ ) ) as UnsecuredDto < InternshipEngagement > ;
294
365
}
295
366
296
- // UPDATE ///////////////////////////////////////////////////////////
297
-
298
367
getActualLanguageChanges = getChanges ( LanguageEngagement ) ;
299
368
300
369
async updateLanguage (
301
- existing : LanguageEngagement | UnsecuredDto < LanguageEngagement > ,
302
- changes : ChangesOf < LanguageEngagement , UpdateLanguageEngagement > ,
370
+ changes : UpdateLanguageEngagement ,
371
+ session : Session ,
303
372
changeset ?: ID ,
304
- ) : Promise < void > {
305
- const { pnp, ...simpleChanges } = changes ;
373
+ ) {
374
+ const { id, pnp, ...simpleChanges } = changes ;
375
+
376
+ if ( pnp ) {
377
+ const engagement = await this . readOne ( id , session ) ;
378
+ if ( engagement . pnp ) {
379
+ await this . files . createFileVersion (
380
+ {
381
+ ...pnp ,
382
+ parentId : engagement . pnp . id ,
383
+ } ,
384
+ session ,
385
+ ) ;
386
+ }
387
+ }
388
+
389
+ if ( changes . firstScripture ) {
390
+ await this . verifyFirstScripture ( { engagementId : id } ) ;
391
+ }
306
392
307
393
await this . db . updateProperties ( {
308
394
type : LanguageEngagement ,
309
- object : existing ,
395
+ object : { id } ,
310
396
changes : simpleChanges ,
311
397
changeset,
312
398
} ) ;
399
+
400
+ return await this . readOne ( id , session ) ;
313
401
}
314
402
315
403
getActualInternshipChanges = getChanges ( InternshipEngagement ) ;
316
404
317
405
async updateInternship (
318
- existing : InternshipEngagement | UnsecuredDto < InternshipEngagement > ,
319
- changes : ChangesOf < InternshipEngagement , UpdateInternshipEngagement > ,
406
+ changes : UpdateInternshipEngagement ,
407
+ session : Session ,
320
408
changeset ?: ID ,
321
- ) : Promise < void > {
322
- const {
323
- mentorId,
324
- countryOfOriginId,
325
- growthPlan : _ ,
326
- ...simpleChanges
327
- } = changes ;
409
+ ) {
410
+ const { id, mentorId, countryOfOriginId, growthPlan, ...simpleChanges } =
411
+ changes ;
412
+
413
+ if ( growthPlan ) {
414
+ const engagement = await this . readOne ( id , session ) ;
415
+ if ( engagement . growthPlan ) {
416
+ await this . files . createFileVersion (
417
+ {
418
+ ...growthPlan ,
419
+ parentId : engagement . growthPlan . id ,
420
+ } ,
421
+ session ,
422
+ ) ;
423
+ }
424
+ }
328
425
329
426
if ( mentorId !== undefined ) {
330
- await this . updateRelation ( 'mentor' , 'User' , existing . id , mentorId ) ;
427
+ await this . updateRelation ( 'mentor' , 'User' , id , mentorId ) ;
331
428
}
332
429
333
430
if ( countryOfOriginId !== undefined ) {
334
431
await this . updateRelation (
335
432
'countryOfOrigin' ,
336
433
'Location' ,
337
- existing . id ,
434
+ id ,
338
435
countryOfOriginId ,
339
436
) ;
340
437
}
341
438
342
439
await this . db . updateProperties ( {
343
440
type : InternshipEngagement ,
344
- object : existing ,
441
+ object : { id } ,
345
442
changes : simpleChanges ,
346
443
changeset,
347
444
} ) ;
348
- }
349
445
350
- // LIST ///////////////////////////////////////////////////////////
446
+ return await this . readOne ( id , session ) ;
447
+ }
351
448
352
449
async list ( input : EngagementListInput , session : Session , changeset ?: ID ) {
353
450
const result = await this . db
@@ -425,18 +522,19 @@ export class EngagementRepository extends CommonRepository {
425
522
return rows . map ( ( r ) => r . id ) ;
426
523
}
427
524
428
- async verifyRelationshipEligibility (
525
+ protected async verifyRelationshipEligibility (
429
526
projectId : ID ,
430
527
otherId : ID ,
431
- isTranslation : boolean ,
432
- property : 'language' | 'intern' ,
528
+ isInternship : boolean ,
433
529
changeset ?: ID ,
434
530
) {
435
- return await this . db
531
+ const property = isInternship ? 'intern' : 'language' ;
532
+
533
+ const result = await this . db
436
534
. query ( )
437
535
. optionalMatch ( node ( 'project' , 'Project' , { id : projectId } ) )
438
536
. optionalMatch (
439
- node ( 'other' , isTranslation ? 'Language' : 'User' , {
537
+ node ( 'other' , ! isInternship ? 'Language' : 'User' , {
440
538
id : otherId ,
441
539
} ) ,
442
540
)
@@ -465,9 +563,48 @@ export class EngagementRepository extends CommonRepository {
465
563
engagement ?: Node ;
466
564
} > ( )
467
565
. first ( ) ;
566
+
567
+ if ( ! result ?. project ) {
568
+ throw new NotFoundException (
569
+ 'Could not find project' ,
570
+ 'engagement.projectId' ,
571
+ ) ;
572
+ }
573
+
574
+ const isActuallyInternship =
575
+ result . project . properties . type === ProjectType . Internship ;
576
+ if ( isActuallyInternship !== isInternship ) {
577
+ throw new InputException (
578
+ `Only ${
579
+ isInternship ? 'Internship' : 'Language'
580
+ } Engagements can be created on ${
581
+ isInternship ? 'Internship' : 'Translation'
582
+ } Projects`,
583
+ `engagement.${ property } Id` ,
584
+ ) ;
585
+ }
586
+
587
+ const label = isInternship ? 'person' : 'language' ;
588
+ if ( ! result ?. other ) {
589
+ throw new NotFoundException (
590
+ `Could not find ${ label } ` ,
591
+ `engagement.${ property } Id` ,
592
+ ) ;
593
+ }
594
+
595
+ if ( result . engagement ) {
596
+ throw new DuplicateException (
597
+ `engagement.${ property } Id` ,
598
+ `Engagement for this project and ${ label } already exists` ,
599
+ ) ;
600
+ }
601
+
602
+ return result ;
468
603
}
469
604
470
- async doesLanguageHaveExternalFirstScripture ( id : LanguageOrEngagementId ) {
605
+ private async doesLanguageHaveExternalFirstScripture (
606
+ id : LanguageOrEngagementId ,
607
+ ) {
471
608
const result = await this . db
472
609
. query ( )
473
610
. apply ( this . matchLanguageOrEngagement ( id ) )
@@ -481,7 +618,9 @@ export class EngagementRepository extends CommonRepository {
481
618
return ! ! result ;
482
619
}
483
620
484
- async doOtherEngagementsHaveFirstScripture ( id : LanguageOrEngagementId ) {
621
+ private async doOtherEngagementsHaveFirstScripture (
622
+ id : LanguageOrEngagementId ,
623
+ ) {
485
624
const result = await this . db
486
625
. query ( )
487
626
. apply ( this . matchLanguageOrEngagement ( id ) )
@@ -513,6 +652,26 @@ export class EngagementRepository extends CommonRepository {
513
652
: query . match ( [ node ( 'language' , 'Language' , { id : languageId } ) ] ) ;
514
653
}
515
654
655
+ /**
656
+ * if firstScripture is true, validate that the engagement
657
+ * is the only engagement for the language that has firstScripture=true
658
+ * that the language doesn't have hasExternalFirstScripture=true
659
+ */
660
+ private async verifyFirstScripture ( id : LanguageOrEngagementId ) {
661
+ if ( await this . doesLanguageHaveExternalFirstScripture ( id ) ) {
662
+ throw new InputException (
663
+ 'First scripture has already been marked as having been done externally' ,
664
+ 'languageEngagement.firstScripture' ,
665
+ ) ;
666
+ }
667
+ if ( await this . doOtherEngagementsHaveFirstScripture ( id ) ) {
668
+ throw new InputException (
669
+ 'Another engagement has already been marked as having done the first scripture' ,
670
+ 'languageEngagement.firstScripture' ,
671
+ ) ;
672
+ }
673
+ }
674
+
516
675
@OnIndex ( )
517
676
private createIndexes ( ) {
518
677
return this . getConstraintsFor ( IEngagement ) ;
0 commit comments