diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.html b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.html index 6edac53289..73bb4593c3 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.html +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.html @@ -4,7 +4,7 @@ Application
- + @@ -12,6 +12,6 @@ Notice of Intent
- +
diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.ts b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.ts index 552f9ef4d1..30faf56ce6 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.ts +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-container.component.ts @@ -1,6 +1,8 @@ import { Component } from '@angular/core'; import { ApplicationDecisionConditionTypesService } from '../../../services/application/application-decision-condition-types/application-decision-condition-types.service'; import { NoticeofIntentDecisionConditionTypesService } from '../../../services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service'; +import { NoticeOfIntentDecisionConditionService } from '../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; +import { ApplicationDecisionConditionService } from '../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service'; @Component({ selector: 'app-decision-condition-container', @@ -8,15 +10,20 @@ import { NoticeofIntentDecisionConditionTypesService } from '../../../services/n styleUrls: ['./decision-condition-container.component.scss'], }) export class DecisionConditionContainerComponent { - applicationService: ApplicationDecisionConditionTypesService; + applicationConditionService: ApplicationDecisionConditionService; noiService: NoticeofIntentDecisionConditionTypesService; + noiConditionService: NoticeOfIntentDecisionConditionService; constructor( private aplicationDecisionConditionTypesService: ApplicationDecisionConditionTypesService, + private aplicationDecisionConditionService: ApplicationDecisionConditionService, private noiDecisionConditionTypesService: NoticeofIntentDecisionConditionTypesService, + private noiDecisionConditionService: NoticeOfIntentDecisionConditionService, ) { this.applicationService = this.aplicationDecisionConditionTypesService; + this.applicationConditionService = this.aplicationDecisionConditionService; this.noiService = this.noiDecisionConditionTypesService; + this.noiConditionService = this.noiDecisionConditionService; } } diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.html b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.html index 764a14adab..6544e82a1b 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.html +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.html @@ -75,23 +75,41 @@

{{ isEdit ? 'Edit' : 'Create' }} Decision Condition Type

- Single Date - Select date label +
+ Date + +
-
- + Mark Field as Required (*) - +
-
- - Due Date - End Date - +
+ + Single - Select date label +
+ + Due Date + End Date + +
+ Multiple - Date label will be 'Due Date' +
diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.scss b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.scss index 970a9bdd5f..b08bb4a4ed 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.scss +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.scss @@ -35,6 +35,16 @@ flex-direction: row; justify-content: space-between; align-items: center; + + app-error-message { + position: relative; + left: 11px; + } + } + + .single-date-label-toggle { + width: 90%; + margin-left: 45px; } .condition-field-input { @@ -42,6 +52,16 @@ margin-left: 25px; } + .condition-date-types { + display: flex; + flex-direction: column; + padding-left: 20px; + } + + :host::ng-deep & .mat-mdc-checkbox.ng-invalid .mdc-checkbox__background { + border-color: colors.$error-color !important; + } + ::ng-deep .mdc-checkbox { --mdc-checkbox-selected-icon-color: #{colors.$primary-color}; --mdc-checkbox-selected-focus-icon-color: #{colors.$primary-color}; @@ -63,6 +83,21 @@ --mdc-switch-selected-pressed-track-color: #94c6ac61; --mdc-switch-selected-track-color: #94c6ac61; } + + ::ng-deep .mat-mdc-radio-button.mat-accent { + --mdc-radio-disabled-selected-icon-color: black; + --mdc-radio-disabled-unselected-icon-color: black; + --mdc-radio-unselected-hover-icon-color: #212121; + --mdc-radio-unselected-icon-color: rgba(0, 0, 0, 0.54); + --mdc-radio-unselected-pressed-icon-color: rgba(0, 0, 0, 0.54); + --mdc-radio-selected-focus-icon-color: #{colors.$primary-color}; + --mdc-radio-selected-hover-icon-color: #{colors.$primary-color}; + --mdc-radio-selected-icon-color: #{colors.$primary-color}; + --mdc-radio-selected-pressed-icon-color: #{colors.$primary-color}; + --mat-radio-ripple-color: black; + --mat-radio-checked-ripple-color: #{colors.$primary-color}; + --mat-radio-disabled-label-color: rgba(0, 0, 0, 0.38); + } } } diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.ts b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.ts index 653b9896be..e0c6466fef 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.ts +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types-dialog/decision-condition-types-dialog.component.ts @@ -1,13 +1,22 @@ import { Component, Inject } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { + ApplicationDecisionConditionDto, ApplicationDecisionConditionTypeDto, DateLabel, + DateType, } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { ApplicationDecisionConditionTypesService } from '../../../../services/application/application-decision-condition-types/application-decision-condition-types.service'; -import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { AbstractControl, AsyncValidatorFn, FormControl, FormGroup, Validators } from '@angular/forms'; import { DecisionDialogDataInterface } from '../decision-dialog-data.interface'; import { NoticeofIntentDecisionConditionTypesService } from '../../../../services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service'; +import { + NoticeOfIntentDecisionConditionDto, + NoticeOfIntentDecisionConditionTypeDto, +} from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; +import { ApplicationDecisionConditionService } from '../../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service'; +import { NoticeOfIntentDecisionConditionService } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; +import { catchError, debounceTime, map, Observable, of, switchMap } from 'rxjs'; import { codeExistsValidator } from '../../../../shared/validators/code-exists-validator'; @Component({ @@ -16,6 +25,9 @@ import { codeExistsValidator } from '../../../../shared/validators/code-exists-v styleUrls: ['./decision-condition-types-dialog.component.scss'], }) export class DecisionConditionTypesDialogComponent { + // Reference for use in templates + DateType = DateType; + conditionTypeForm: FormGroup; isLoading = false; @@ -23,12 +35,14 @@ export class DecisionConditionTypesDialogComponent { showWarning = false; service: ApplicationDecisionConditionTypesService | NoticeofIntentDecisionConditionTypesService | undefined; + conditionService: ApplicationDecisionConditionService | NoticeOfIntentDecisionConditionService | undefined; constructor( @Inject(MAT_DIALOG_DATA) public data: DecisionDialogDataInterface | undefined, private dialogRef: MatDialogRef, ) { this.service = data?.service; + this.conditionService = data?.conditionService; this.isEdit = !!data?.content; this.conditionTypeForm = new FormGroup({ description: new FormControl(this.data?.content?.description ? this.data.content.description : '', [ @@ -61,12 +75,13 @@ export class DecisionConditionTypesDialogComponent { administrativeFeeAmount: new FormControl( this.data?.content?.administrativeFeeAmount ? this.data.content.administrativeFeeAmount : '', ), - isSingleDateChecked: new FormControl( - this.data?.content?.isSingleDateChecked ? this.data.content.isSingleDateChecked : false, - ), - isSingleDateRequired: new FormControl( - this.data?.content?.isSingleDateRequired ? this.data.content.isSingleDateRequired : false, + isDateChecked: new FormControl( + this.data?.content?.isDateChecked ? this.data.content.isDateChecked : false, + [], + [this.conditionAsyncValidator()], ), + isDateRequired: new FormControl(this.data?.content?.isDateRequired ? this.data.content.isDateRequired : false), + dateType: new FormControl(this.data?.content?.dateType), singleDateLabel: new FormControl( this.data?.content?.singleDateLabel ? this.data.content.singleDateLabel : DateLabel.DUE_DATE, ), @@ -95,16 +110,21 @@ export class DecisionConditionTypesDialogComponent { async onSubmit() { this.isLoading = true; - const dto: ApplicationDecisionConditionTypeDto | NoticeofIntentDecisionConditionTypesService = { + const dto: ApplicationDecisionConditionTypeDto | NoticeOfIntentDecisionConditionTypeDto = { code: this.conditionTypeForm.get('code')?.value, label: this.conditionTypeForm.get('label')?.value, description: this.conditionTypeForm.get('description')?.value, isActive: this.conditionTypeForm.get('isActive')?.value, isAdministrativeFeeAmountChecked: this.conditionTypeForm.get('isAdministrativeFeeAmountChecked')?.value, isAdministrativeFeeAmountRequired: this.conditionTypeForm.get('isAdministrativeFeeAmountRequired')?.value, - isSingleDateChecked: this.conditionTypeForm.get('isSingleDateChecked')?.value, - isSingleDateRequired: this.conditionTypeForm.get('isSingleDateRequired')?.value, - singleDateLabel: this.conditionTypeForm.get('singleDateLabel')?.value, + administrativeFeeAmount: this.conditionTypeForm.get('administrativeFeeAmount')?.value, + isDateChecked: this.conditionTypeForm.get('isDateChecked')?.value, + isDateRequired: this.conditionTypeForm.get('isDateRequired')?.value, + dateType: this.conditionTypeForm.get('dateType')?.value, + singleDateLabel: + this.conditionTypeForm.get('dateType')?.value === DateType.SINGLE + ? this.conditionTypeForm.get('singleDateLabel')?.value + : null, isSecurityAmountChecked: this.conditionTypeForm.get('isSecurityAmountChecked')?.value, isSecurityAmountRequired: this.conditionTypeForm.get('isSecurityAmountRequired')?.value, }; @@ -112,7 +132,6 @@ export class DecisionConditionTypesDialogComponent { if (this.conditionTypeForm.get('administrativeFeeAmount')?.value !== '') { dto.administrativeFeeAmount = this.conditionTypeForm.get('administrativeFeeAmount')?.value; } - if (!this.service) return; if (this.isEdit) { await this.service.update(dto.code, dto); @@ -122,4 +141,30 @@ export class DecisionConditionTypesDialogComponent { this.isLoading = false; this.dialogRef.close(true); } + + conditionAsyncValidator(): AsyncValidatorFn { + return (control: AbstractControl): Observable<{ [key: string]: any } | null> => { + return of(control.value).pipe( + debounceTime(300), + switchMap((isDateChecked) => { + if (!this.conditionService) { + throw Error('Condition service not found'); + } + return this.conditionService.fetchByTypeCode(this.conditionTypeForm.controls['code'].value); + }), + map((conditions) => + !control.value && conditions && this.hasAnyDates(conditions) ? { hasConditions: true } : null, + ), + catchError((e) => { + return of({ hasConditions: true }); + }), + ); + }; + } + + hasAnyDates( + conditions: Partial[] | Partial[], + ): boolean { + return conditions.some((condition) => condition.dates && condition.dates.length > 0); + } } diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types.component.ts b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types.component.ts index bfd2f9d9b1..feb85a92a5 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types.component.ts +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-condition-types.component.ts @@ -7,6 +7,8 @@ import { DecisionConditionTypesDialogComponent } from './decision-condition-type import { ApplicationDecisionConditionTypesService } from '../../../services/application/application-decision-condition-types/application-decision-condition-types.service'; import { NoticeofIntentDecisionConditionTypesService } from '../../../services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service'; import { NoticeOfIntentDecisionConditionTypeDto } from '../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; +import { ApplicationDecisionConditionService } from '../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service'; +import { NoticeOfIntentDecisionConditionService } from '../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; @Component({ selector: 'app-decision-condition-types', @@ -19,6 +21,11 @@ export class DecisionConditionTypesComponent implements OnInit { | NoticeofIntentDecisionConditionTypesService | undefined; + @Input() public conditionService: + | ApplicationDecisionConditionService + | NoticeOfIntentDecisionConditionService + | undefined; + destroy = new Subject(); decisionConditionTypeDtos: ApplicationDecisionConditionTypeDto[] | NoticeOfIntentDecisionConditionTypeDto[] = []; @@ -45,6 +52,7 @@ export class DecisionConditionTypesComponent implements OnInit { width: '70%', data: { service: this.service, + conditionService: this.conditionService, existingCodes: this.decisionConditionTypeDtos.map((dct) => dct.code), }, }); @@ -62,6 +70,7 @@ export class DecisionConditionTypesComponent implements OnInit { width: '70%', data: { service: this.service, + conditionService: this.conditionService, content: dto, existingCodes: this.decisionConditionTypeDtos.map((dct) => dct.code), }, diff --git a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-dialog-data.interface.ts b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-dialog-data.interface.ts index 69e6c87b21..751c90a4c6 100644 --- a/alcs-frontend/src/app/features/admin/decision-condition-types/decision-dialog-data.interface.ts +++ b/alcs-frontend/src/app/features/admin/decision-condition-types/decision-dialog-data.interface.ts @@ -1,9 +1,12 @@ import { NoticeofIntentDecisionConditionTypesService } from '../../../services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service'; import { ApplicationDecisionConditionTypesService } from '../../../services/application/application-decision-condition-types/application-decision-condition-types.service'; import { ApplicationDecisionConditionTypeDto } from '../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { ApplicationDecisionConditionService } from '../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service'; +import { NoticeOfIntentDecisionConditionService } from '../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; export interface DecisionDialogDataInterface { service: ApplicationDecisionConditionTypesService | NoticeofIntentDecisionConditionTypesService; + conditionService: ApplicationDecisionConditionService | NoticeOfIntentDecisionConditionService; content: ApplicationDecisionConditionTypeDto; existingCodes: string[]; } diff --git a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html index a7f9f54e8b..7d1647f509 100644 --- a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.html @@ -1,9 +1,9 @@

{{ condition.type.label }}

- + - +
@@ -28,20 +28,6 @@

{{ condition.type.label }}

-
-
{{ singleDateLabel }}
- {{ singleDateFormated }} - -
- -
-
Completion Date
- -
-
diff --git a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.ts b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.ts index 4e77b18c9c..e9b87993a2 100644 --- a/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.ts +++ b/alcs-frontend/src/app/features/application/decision/conditions/condition/condition.component.ts @@ -7,6 +7,8 @@ import { APPLICATION_DECISION_COMPONENT_TYPE, ApplicationDecisionComponentDto, UpdateApplicationDecisionConditionDto, + DateType, + ApplicationDecisionConditionDateDto, } from '../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { DECISION_CONDITION_COMPLETE_LABEL, @@ -19,7 +21,6 @@ import { } from '../conditions.component'; import { environment } from '../../../../../../environments/environment'; - type Condition = ApplicationDecisionConditionWithStatus & { componentLabelsStr?: string; componentLabels?: ConditionComponentLabels[]; @@ -35,6 +36,10 @@ export class ConditionComponent implements OnInit, AfterViewInit { @Input() isDraftDecision!: boolean; @Input() fileNumber!: string; + DateType = DateType; + + dates: ApplicationDecisionConditionDateDto[] = []; + incompleteLabel = DECISION_CONDITION_INCOMPLETE_LABEL; completeLabel = DECISION_CONDITION_COMPLETE_LABEL; @@ -48,24 +53,27 @@ export class ConditionComponent implements OnInit, AfterViewInit { isReadMoreClicked = false; isReadMoreVisible = false; - conditionStatus: string = ''; isRequireSurveyPlan = false; subdComponent?: ApplicationDecisionComponentDto; planNumbers: ApplicationDecisionConditionToComponentPlanNumberDto[] = []; constructor( private conditionService: ApplicationDecisionConditionService, - private conditionLotService: ApplicationDecisionComponentToConditionLotService + private conditionLotService: ApplicationDecisionComponentToConditionLotService, ) {} ngOnInit() { - this.updateStatus(); if (this.condition) { - this.singleDateFormated = this.condition.singleDate ? moment(this.condition.singleDate).format(environment.dateFormat) : undefined; + this.fetchDates(this.condition.uuid); + this.singleDateLabel = this.condition.type?.singleDateLabel ? this.condition.type?.singleDateLabel : 'End Date'; - this.showSingleDateField = this.condition.type?.isSingleDateChecked ? this.condition.type?.isSingleDateChecked : false; - this.showAdmFeeField = this.condition.type?.isAdministrativeFeeAmountChecked ? this.condition.type?.isAdministrativeFeeAmountChecked : false; - this.showSecurityAmountField = this.condition.type?.isSecurityAmountChecked ? this.condition.type?.isSecurityAmountChecked : false; + this.showSingleDateField = this.condition.type?.dateType === DateType.SINGLE; + this.showAdmFeeField = this.condition.type?.isAdministrativeFeeAmountChecked + ? this.condition.type?.isAdministrativeFeeAmountChecked + : false; + this.showSecurityAmountField = this.condition.type?.isSecurityAmountChecked + ? this.condition.type?.isSecurityAmountChecked + : false; this.condition = { ...this.condition, componentLabelsStr: this.condition.conditionComponentsLabels?.flatMap((e) => e.label).join(';\n'), @@ -80,7 +88,7 @@ export class ConditionComponent implements OnInit, AfterViewInit { async loadLots() { if (this.condition.components) { const subdComponent = this.condition.components.find( - (component) => component.applicationDecisionComponentTypeCode === APPLICATION_DECISION_COMPONENT_TYPE.SUBD + (component) => component.applicationDecisionComponentTypeCode === APPLICATION_DECISION_COMPONENT_TYPE.SUBD, ); if (subdComponent && subdComponent.uuid) { const planNumbers = await this.conditionLotService.fetchConditionLots(this.condition.uuid, subdComponent.uuid); @@ -99,23 +107,23 @@ export class ConditionComponent implements OnInit, AfterViewInit { async loadPlanNumber() { const subdComponent = this.condition.components?.find( - (component) => component.applicationDecisionComponentTypeCode === APPLICATION_DECISION_COMPONENT_TYPE.SUBD + (component) => component.applicationDecisionComponentTypeCode === APPLICATION_DECISION_COMPONENT_TYPE.SUBD, ); if ( this.condition.components && this.condition.components.some( - (component) => component.applicationDecisionComponentTypeCode !== APPLICATION_DECISION_COMPONENT_TYPE.SUBD + (component) => component.applicationDecisionComponentTypeCode !== APPLICATION_DECISION_COMPONENT_TYPE.SUBD, ) && this.isRequireSurveyPlan ) { const planNumbers = (await this.conditionService.fetchPlanNumbers(this.condition.uuid)).filter( - (planNumber) => planNumber.applicationDecisionComponentUuid !== subdComponent?.uuid + (planNumber) => planNumber.applicationDecisionComponentUuid !== subdComponent?.uuid, ); this.planNumbers = this.condition.components ?.filter( - (component) => component.applicationDecisionComponentTypeCode !== APPLICATION_DECISION_COMPONENT_TYPE.SUBD + (component) => component.applicationDecisionComponentTypeCode !== APPLICATION_DECISION_COMPONENT_TYPE.SUBD, ) .map( (component) => @@ -123,9 +131,9 @@ export class ConditionComponent implements OnInit, AfterViewInit { applicationDecisionComponentUuid: component.uuid, applicationDecisionConditionUuid: this.condition.uuid, planNumbers: planNumbers.find( - (planNumber) => planNumber.applicationDecisionComponentUuid === component.uuid + (planNumber) => planNumber.applicationDecisionComponentUuid === component.uuid, )?.planNumbers, - } as ApplicationDecisionConditionToComponentPlanNumberDto) + }) as ApplicationDecisionConditionToComponentPlanNumberDto, ) ?? []; } } @@ -136,7 +144,7 @@ export class ConditionComponent implements OnInit, AfterViewInit { async onUpdateCondition( field: keyof UpdateApplicationDecisionConditionDto, - value: string[] | string | number | null + value: string[] | string | number | null, ) { const condition = this.condition; @@ -147,8 +155,6 @@ export class ConditionComponent implements OnInit, AfterViewInit { const labels = this.condition.componentLabelsStr; this.condition = { ...update, componentLabelsStr: labels } as Condition; - - this.updateStatus(); } } @@ -166,16 +172,6 @@ export class ConditionComponent implements OnInit, AfterViewInit { return this.isReadMoreClicked || this.isEllipsisActive(this.condition.uuid + 'Description'); } - updateStatus() { - const today = moment().startOf('day').toDate().getTime(); - - if (this.condition.completionDate && this.condition.completionDate <= today) { - this.conditionStatus = CONDITION_STATUS.COMPLETE; - } else { - this.conditionStatus = CONDITION_STATUS.INCOMPLETE; - } - } - async savePlanNumbers(lotUuid: string, conditionUuid: string, planNumbers: string | null) { if (this.subdComponent && this.subdComponent.uuid && this.subdComponent?.lots) { await this.conditionLotService.update(lotUuid, conditionUuid, planNumbers); @@ -191,4 +187,15 @@ export class ConditionComponent implements OnInit, AfterViewInit { getComponentLabel(componentUuid: string) { return this.condition.conditionComponentsLabels?.find((e) => e.componentUuid === componentUuid)?.label; } + + async fetchDates(uuid: string | undefined) { + if (!uuid) { + return; + } + + this.dates = await this.conditionService.getDates(uuid); + + this.singleDateFormated = + this.dates[0] && this.dates[0].date ? moment(this.dates[0].date).format(environment.dateFormat) : undefined; + } } diff --git a/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.spec.ts b/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.spec.ts index f3116b4875..8a80363628 100644 --- a/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.spec.ts +++ b/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.spec.ts @@ -8,6 +8,7 @@ import { ApplicationDto } from '../../../../services/application/application.dto import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { ConditionsComponent } from './conditions.component'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('ConditionsComponent', () => { let component: ConditionsComponent; @@ -42,6 +43,7 @@ describe('ConditionsComponent', () => { }, ], schemas: [NO_ERRORS_SCHEMA], + imports: [HttpClientTestingModule], }).compileComponents(); fixture = TestBed.createComponent(ConditionsComponent); diff --git a/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.ts b/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.ts index 2817cbb5b2..14aa25ed25 100644 --- a/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.ts +++ b/alcs-frontend/src/app/features/application/decision/conditions/conditions.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import moment from 'moment'; -import { Subject, takeUntil } from 'rxjs'; +import { from, map, Subject, switchMap, takeUntil } from 'rxjs'; import { ApplicationDetailService } from '../../../../services/application/application-detail.service'; import { ApplicationDto } from '../../../../services/application/application.dto'; import { @@ -9,6 +9,7 @@ import { ApplicationDecisionDto, ApplicationDecisionWithLinkedResolutionDto, ApplicationDecisionCodesDto, + ApplicationDecisionConditionDateDto, } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; import { ApplicationDecisionV2Service } from '../../../../services/application/decision/application-decision-v2/application-decision-v2.service'; import { @@ -17,6 +18,7 @@ import { RECON_TYPE_LABEL, RELEASED_DECISION_TYPE_LABEL, } from '../../../../shared/application-type-pill/application-type-pill.constants'; +import { ApplicationDecisionConditionService } from '../../../../services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service'; export type ConditionComponentLabels = { label: string[]; @@ -63,7 +65,8 @@ export class ConditionsComponent implements OnInit { constructor( private applicationDetailService: ApplicationDetailService, private decisionService: ApplicationDecisionV2Service, - private activatedRouter: ActivatedRoute + private conditionService: ApplicationDecisionConditionService, + private activatedRouter: ActivatedRoute, ) { this.today = moment().startOf('day').toDate().getTime(); } @@ -82,26 +85,52 @@ export class ConditionsComponent implements OnInit { async loadDecisions(fileNumber: string) { this.codes = await this.decisionService.fetchCodes(); - this.decisionService.$decisions.pipe(takeUntil(this.$destroy)).subscribe((decisions) => { - this.decisions = decisions.map((decision) => { - if (decision.uuid === this.decisionUuid) { - const conditions = this.mapConditions(decision, decisions); - - this.sortConditions(decision, conditions); - - this.decision = decision as ApplicationDecisionWithConditionComponentLabels; - } + this.decisionService.$decisions + .pipe(takeUntil(this.$destroy)) + .pipe( + switchMap((decisions) => + from(this.getDatesByConditionUuid(decisions)).pipe( + map((datesByConditionUuid) => ({ + decisions, + datesByConditionUuid, + })), + ), + ), + ) + .subscribe(({ decisions, datesByConditionUuid }) => { + this.decisions = decisions.map((decision) => { + if (decision.uuid === this.decisionUuid) { + const conditions = this.mapConditions(decision, datesByConditionUuid, decisions); + + this.sortConditions(decision, conditions); + + this.decision = decision as ApplicationDecisionWithConditionComponentLabels; + } - return decision as ApplicationDecisionWithConditionComponentLabels; + return decision as ApplicationDecisionWithConditionComponentLabels; + }); }); - }); this.decisionService.loadDecisions(fileNumber); } + private async getDatesByConditionUuid( + decisions: ApplicationDecisionWithLinkedResolutionDto[], + ): Promise> { + let datesByConditionUuid = new Map(); + + for (const decision of decisions) { + for (const condition of decision.conditions) { + datesByConditionUuid.set(condition.uuid, await this.conditionService.getDates(condition.uuid)); + } + } + + return datesByConditionUuid; + } + private sortConditions( decision: ApplicationDecisionWithLinkedResolutionDto, - conditions: ApplicationDecisionConditionWithStatus[] + conditions: ApplicationDecisionConditionWithStatus[], ) { decision.conditions = conditions.sort((a, b) => { const order = [CONDITION_STATUS.INCOMPLETE, CONDITION_STATUS.COMPLETE]; @@ -119,17 +148,19 @@ export class ConditionsComponent implements OnInit { private mapConditions( decision: ApplicationDecisionWithLinkedResolutionDto, - decisions: ApplicationDecisionWithLinkedResolutionDto[] + datesByConditionUuid: Map, + decisions: ApplicationDecisionWithLinkedResolutionDto[], ) { return decision.conditions.map((condition) => { - const status = this.getStatus(condition, decision); + const dates = datesByConditionUuid.get(condition.uuid) ?? []; + const status = this.getStatus(dates, decision); return { ...condition, status, conditionComponentsLabels: condition.components?.map((c) => { const matchingType = this.codes.decisionComponentTypes.find( - (type) => type.code === c.applicationDecisionComponentTypeCode + (type) => type.code === c.applicationDecisionComponentTypeCode, ); const componentsDecision = decisions.find((d) => d.uuid === c.applicationDecisionUuid); @@ -149,9 +180,13 @@ export class ConditionsComponent implements OnInit { }); } - private getStatus(condition: ApplicationDecisionConditionDto, decision: ApplicationDecisionWithLinkedResolutionDto) { + private getStatus( + dates: ApplicationDecisionConditionDateDto[], + decision: ApplicationDecisionWithLinkedResolutionDto, + ) { let status = ''; - if (condition.completionDate && condition.completionDate <= this.today) { + status = CONDITION_STATUS.COMPLETE; + if (dates.length > 0 && dates.every((date) => date.completedDate && date.completedDate <= this.today)) { status = CONDITION_STATUS.COMPLETE; } else if (decision.isDraft === false) { status = CONDITION_STATUS.INCOMPLETE; diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.html new file mode 100644 index 0000000000..18830a1d1d --- /dev/null +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.html @@ -0,0 +1,63 @@ +
+

{{ isAdding ? 'Add' : 'Edit' }} Due Dates

+
+ +
+ + + + + + + + + + + + + + + + + + + +
# + {{ i + 1 }} + Due Dates + + + + + + Action + + + +
+ + + + +
+ + +
+ + +
+
+
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.scss new file mode 100644 index 0000000000..5e57112341 --- /dev/null +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.scss @@ -0,0 +1,17 @@ +@use '../../../../../../../../../styles/colors.scss'; + +.date-table { + .mat-mdc-header-cell, + .mat-mdc-cell { + padding: 5px 10px !important; + border: 0; + } +} + +.remove-button { + color: colors.$error-color; +} + +.add-button { + margin-top: 16px !important; +} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.ts new file mode 100644 index 0000000000..39d83ce0db --- /dev/null +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component.ts @@ -0,0 +1,62 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { ApplicationDecisionConditionDateDto } from '../../../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { NoticeOfIntentDecisionConditionDateDto } from '../../../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; +import { MatTableDataSource } from '@angular/material/table'; +import { MatPaginator } from '@angular/material/paginator'; +import moment, { Moment } from 'moment'; + +export interface DueDate { + uuid?: string; + date?: Moment; +} + +@Component({ + selector: 'app-decision-condition-date-dialog', + templateUrl: './decision-condition-date-dialog.component.html', + styleUrls: ['./decision-condition-date-dialog.component.scss'], +}) +export class DecisionConditionDateDialogComponent { + displayedColumns = ['index', 'date', 'actions']; + dates: DueDate[] = []; + tableData = new MatTableDataSource([]); + isAdding: boolean = false; + isRequired: boolean = false; + + constructor( + @Inject(MAT_DIALOG_DATA) + public data: { + dates: ApplicationDecisionConditionDateDto[] | NoticeOfIntentDecisionConditionDateDto[]; + isAdding: boolean; + isRequired: boolean; + }, + private dialogRef: MatDialogRef, + ) { + if (data) { + this.dates = + data.dates.length > 0 + ? data.dates.map(({ uuid, date }) => ({ + uuid, + date: date ? moment(date) : undefined, + })) + : [{}]; + this.tableData = new MatTableDataSource(this.dates); + this.isAdding = data.isAdding; + this.isRequired = data.isRequired; + } + } + + addDueDate() { + this.dates.push({}); + this.tableData = new MatTableDataSource(this.dates); + } + + removeDueDate(i: number) { + this.dates.splice(i, 1); + this.tableData = new MatTableDataSource(this.dates); + } + + async onSubmit() { + this.dialogRef.close(this.dates); + } +} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index 06b9604a7f..312752a9b9 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -1,6 +1,22 @@
{{ data.type?.label }}
- +
+ +
+ + +
+
+ +
@@ -59,3 +75,13 @@
{{ data.type?.label }}
+ + +
+ Due Dates: +
{{ formatDate(date.date) }}
+ +
+
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss index 3b4c9e86c7..672255710b 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss @@ -1,3 +1,5 @@ +@use '../../../../../../../../styles/colors.scss'; + form { margin-top: 12px; } @@ -12,3 +14,31 @@ form { grid-column: 1/3; } } + +.buttons { + display: flex; + flex-direction: row; + gap: 12px; + + .add-date-button-wrapper { + display: flex; + flex-direction: column; + align-items: end; + } +} + +.date-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin-top: 16px; + + .date-chip { + padding: 5px 8px; + border-radius: 3px; + border: 1px solid colors.$grey; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + } +} diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts index bce13f4089..50798a222c 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts @@ -3,6 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DecisionConditionComponent } from './decision-condition.component'; import { StartOfDayPipe } from '../../../../../../../shared/pipes/startOfDay.pipe'; +import { DateType } from '../../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('DecisionConditionComponent', () => { let component: DecisionConditionComponent; @@ -12,6 +14,7 @@ describe('DecisionConditionComponent', () => { await TestBed.configureTestingModule({ declarations: [DecisionConditionComponent, StartOfDayPipe], schemas: [NO_ERRORS_SCHEMA], + imports: [HttpClientTestingModule], }).compileComponents(); fixture = TestBed.createComponent(DecisionConditionComponent); @@ -23,7 +26,9 @@ describe('DecisionConditionComponent', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }, }; diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts index c877d8e973..15d9f19cd3 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts @@ -1,7 +1,17 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SelectableComponent, TempApplicationDecisionConditionDto } from '../decision-conditions.component'; -import { formatDateForApi } from '../../../../../../../shared/utils/api-date-formatter'; +import { + ApplicationDecisionConditionDateDto, + ApplicationDecisionConditionTypeDto, + DateType, +} from '../../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { MatDialog } from '@angular/material/dialog'; +import { + DecisionConditionDateDialogComponent, + DueDate, +} from './decision-condition-date-dialog/decision-condition-date-dialog.component'; +import moment, { Moment } from 'moment'; @Component({ selector: 'app-app-decision-condition', @@ -10,14 +20,26 @@ import { formatDateForApi } from '../../../../../../../shared/utils/api-date-for }) export class DecisionConditionComponent implements OnInit, OnChanges { @Input() data!: TempApplicationDecisionConditionDto; + _showDateError = false; + @Input() set showDateError(value: boolean) { + this._showDateError = value; + } + get showDateError(): boolean { + return this._showDateError && this.isDateRequired && (!this.dates || this.dates.length === 0); + } @Output() dataChange = new EventEmitter(); @Output() remove = new EventEmitter(); @Input() selectableComponents: SelectableComponent[] = []; + uuid: string | undefined; + + dates: ApplicationDecisionConditionDateDto[] = []; + singleDateLabel = 'End Date'; showSingleDateField = false; - isShowSingleDateRequired = false; + showMultiDateUi = false; + isDateRequired = false; showAdmFeeField = false; isAdmFeeFieldRequired = false; showSecurityAmountField = false; @@ -25,12 +47,11 @@ export class DecisionConditionComponent implements OnInit, OnChanges { numberOfSelectedConditions = 0; componentsToCondition = new FormControl(null, [Validators.required]); - approvalDependant = new FormControl(null, [Validators.required]); + description = new FormControl(null, [Validators.required]); securityAmount = new FormControl(null); administrativeFee = new FormControl(null); - description = new FormControl(null, [Validators.required]); - singleDate = new FormControl(null, [Validators.required]); + singleDate = new FormControl(null); minDate = new Date(0); form = new FormGroup({ @@ -41,90 +62,117 @@ export class DecisionConditionComponent implements OnInit, OnChanges { componentsToCondition: this.componentsToCondition, }); + constructor(protected dialog: MatDialog) {} + ngOnInit(): void { - if (this.data) { - this.singleDateLabel = this.data.type?.singleDateLabel ? this.data.type?.singleDateLabel : 'End Date'; - this.showSingleDateField = this.data.type?.isSingleDateChecked ? this.data.type?.isSingleDateChecked : false; - if (this.data.type?.isSingleDateRequired) { - this.singleDate.addValidators(Validators.required); - this.isShowSingleDateRequired = true; - } else { - this.singleDate.removeValidators(Validators.required); - this.isShowSingleDateRequired = false; - } + this.uuid = this.data.uuid; + this.dates = this.data.dates ?? []; - this.showAdmFeeField = this.data.type?.isAdministrativeFeeAmountChecked - ? this.data.type?.isAdministrativeFeeAmountChecked - : false; - if (this.data.type?.isAdministrativeFeeAmountRequired) { - this.administrativeFee.addValidators(Validators.required); - this.isAdmFeeFieldRequired = true; - } else { - this.administrativeFee.removeValidators(Validators.required); - this.isAdmFeeFieldRequired = false; - } + if (this.data.type) { + this.initDateUi(this.data.type); + this.initOptionalFields(this.data.type); + } - this.showSecurityAmountField = this.data.type?.isSecurityAmountChecked - ? this.data.type?.isSecurityAmountChecked - : false; - if (this.data.type?.isSecurityAmountRequired) { - this.securityAmount.addValidators(Validators.required); - this.isSecurityAmountFieldRequired = true; - } else { - this.securityAmount.removeValidators(Validators.required); - this.isSecurityAmountFieldRequired = false; - } + this.initComponentField(this.data); - if (this.showSingleDateField) { - this.numberOfSelectedConditions++; - } - if (this.showAdmFeeField) { - this.numberOfSelectedConditions++; + this.form.patchValue({ + securityAmount: this.data.securityAmount?.toString() ?? null, + administrativeFee: this.data.administrativeFee + ? this.data.administrativeFee?.toString() + : this.data.type?.administrativeFeeAmount?.toString(), + description: this.data.description ?? null, + }); + + if (this.showSingleDateField && this.dates.length > 0 && this.dates[0].date) { + this.form.patchValue({ singleDate: moment(this.dates[0].date) }); + } + + this.form.valueChanges.subscribe(this.emitChanges.bind(this)); + } + + emitChanges() { + const selectedOptions = this.selectableComponents + .filter((component) => this.componentsToCondition.value?.includes(component.tempId)) + .map((e) => ({ + componentDecisionUuid: e.decisionUuid, + componentToConditionType: e.code, + tempId: e.tempId, + })); + + const conditionDto: TempApplicationDecisionConditionDto = { + type: this.data.type, + tempUuid: this.data.tempUuid, + uuid: this.data.uuid, + securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, + administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, + description: this.description.value ?? undefined, + componentsToCondition: selectedOptions, + }; + + if (this.showSingleDateField) { + const singleDateDto: ApplicationDecisionConditionDateDto = {}; + + if (this.singleDate.value) { + singleDateDto.date = this.singleDate.value.toDate().getTime(); } - if (this.showSecurityAmountField) { - this.numberOfSelectedConditions++; + + conditionDto.dates = [singleDateDto]; + } else { + conditionDto.dates = this.dates; + } + + this.dataChange.emit(conditionDto); + } + + initDateUi(type: ApplicationDecisionConditionTypeDto) { + if (!type.isDateChecked) { + return; + } + + this.isDateRequired = type.isDateRequired ?? false; + this.showSingleDateField = type.dateType === DateType.SINGLE; + this.showMultiDateUi = type.dateType === DateType.MULTIPLE; + + if (this.showSingleDateField) { + this.singleDateLabel = type.singleDateLabel ? type.singleDateLabel : 'End Date'; + + if (this.isDateRequired) { + this.singleDate.addValidators(Validators.required); } + } + } - const selectedOptions = this.selectableComponents - .filter((component) => this.data.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) - .map((e) => ({ - componentDecisionUuid: e.decisionUuid, - componentToConditionType: e.code, - tempId: e.tempId, - })); + initOptionalFields(type: ApplicationDecisionConditionTypeDto) { + this.showAdmFeeField = type.isAdministrativeFeeAmountChecked ?? false; + this.isAdmFeeFieldRequired = type.isAdministrativeFeeAmountRequired ?? false; + this.showSecurityAmountField = type.isSecurityAmountChecked ? type.isSecurityAmountChecked : false; + this.isSecurityAmountFieldRequired = type.isSecurityAmountRequired ?? false; - this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); + if (this.isAdmFeeFieldRequired) { + this.administrativeFee.addValidators(Validators.required); + } - this.form.patchValue({ - securityAmount: this.data.securityAmount?.toString() ?? null, - administrativeFee: this.data.administrativeFee - ? this.data.administrativeFee?.toString() - : this.data.type?.administrativeFeeAmount?.toString(), - description: this.data.description ?? null, - singleDate: this.data.singleDate ? new Date(this.data.singleDate) : undefined, - }); + if (this.isSecurityAmountFieldRequired) { + this.securityAmount.addValidators(Validators.required); } - this.form.valueChanges.subscribe((changes) => { - const selectedOptions = this.selectableComponents - .filter((component) => this.componentsToCondition.value?.includes(component.tempId)) - .map((e) => ({ - componentDecisionUuid: e.decisionUuid, - componentToConditionType: e.code, - tempId: e.tempId, - })); - this.dataChange.emit({ - type: this.data.type, - tempUuid: this.data.tempUuid, - uuid: this.data.uuid, - approvalDependant: this.approvalDependant.value, - securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, - administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, - description: this.description.value ?? undefined, - componentsToCondition: selectedOptions, - singleDate: this.singleDate.value ? formatDateForApi(this.singleDate.value) : undefined, - }); - }); + this.numberOfSelectedConditions += [ + this.showSingleDateField, + this.showAdmFeeField, + this.showSecurityAmountField, + ].reduce((sum, flag) => sum + (flag ? 1 : 0), 0); + } + + initComponentField(condition: TempApplicationDecisionConditionDto) { + const selectedOptions = this.selectableComponents + .filter((component) => condition.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) + .map((e) => ({ + componentDecisionUuid: e.decisionUuid, + componentToConditionType: e.code, + tempId: e.tempId, + })); + + this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); } ngOnChanges(changes: SimpleChanges): void { @@ -145,4 +193,34 @@ export class DecisionConditionComponent implements OnInit, OnChanges { onRemove() { this.remove.emit(); } + + formatDate(timestamp: number | undefined): string { + if (!timestamp) { + return ''; + } + return moment(timestamp).format('YYYY-MMM-DD'); + } + + openDateDialog(isAdding: boolean) { + this.dialog + .open(DecisionConditionDateDialogComponent, { + maxHeight: '80vh', + data: { + dates: this.dates, + isAdding, + isRequired: this.isDateRequired, + }, + }) + .beforeClosed() + .subscribe(async (dates: DueDate[]) => { + if (!this.uuid) { + return; + } + this.dates = dates.map((date) => ({ + uuid: date.uuid, + date: date.date?.toDate().getTime(), + })); + this.emitChanges(); + }); + } } diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html index b64b52539d..43da67356e 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html @@ -29,6 +29,7 @@

Conditions

(dataChange)="onChanges()" (remove)="onRemoveCondition(index)" [selectableComponents]="selectableComponents" + [showDateError]="showDateErrors" >
diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts index 7e18c7b146..42afea1300 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts @@ -41,6 +41,7 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy @Input() components: ApplicationDecisionComponentDto[] = []; @Input() conditions: ApplicationDecisionConditionDto[] = []; @Input() showError = false; + @Input() showDateErrors = false; @ViewChildren(DecisionConditionComponent) conditionComponents: DecisionConditionComponent[] = []; @Output() conditionsChange = new EventEmitter<{ diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html index db81199b77..673f42a098 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -208,6 +208,7 @@

Resolution

[conditions]="conditions" (conditionsChange)="onConditionsChange($event)" [showError]="showErrors && conditionUpdates.length < 1 && showConditions" + [showDateErrors]="showErrors && requiredDatesAreMissing()" > diff --git a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts index fe453c8750..0a833815b5 100644 --- a/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts +++ b/alcs-frontend/src/app/features/application/decision/decision-v2/decision-input/decision-input-v2.component.ts @@ -544,7 +544,8 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { !this.conditionsValid || !this.componentsValid || (this.components.length === 0 && requiresComponents) || - (this.conditionUpdates.length === 0 && requiresConditions) + (this.conditionUpdates.length === 0 && requiresConditions) || + this.requiredDatesAreMissing() ) { this.form.controls.decisionMaker.markAsDirty(); this.toastService.showErrorToast('Please correct all errors before submitting the form'); @@ -558,6 +559,15 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { } } + requiredDatesAreMissing(): boolean { + return this.conditionUpdates.some( + (condition) => + condition.type?.isDateChecked && + condition.type.isDateRequired && + (!condition.dates || condition.dates.length === 0), + ); + } + private scrollToError() { let elements = document.getElementsByClassName('ng-invalid'); let elArray = Array.from(elements).filter((el) => el.nodeName !== 'FORM'); diff --git a/alcs-frontend/src/app/features/application/decision/decision.module.ts b/alcs-frontend/src/app/features/application/decision/decision.module.ts index b83eb46dbf..9ae5697437 100644 --- a/alcs-frontend/src/app/features/application/decision/decision.module.ts +++ b/alcs-frontend/src/app/features/application/decision/decision.module.ts @@ -34,6 +34,7 @@ import { DecisionV2Component } from './decision-v2/decision-v2.component'; import { ReleaseDialogComponent } from './decision-v2/release-dialog/release-dialog.component'; import { RevertToDraftDialogComponent } from './decision-v2/revert-to-draft-dialog/revert-to-draft-dialog.component'; import { DecisionComponent } from './decision.component'; +import { DecisionConditionDateDialogComponent } from './decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component'; export const decisionChildRoutes = [ { @@ -68,6 +69,7 @@ export const decisionChildRoutes = [ DecisionV2Component, DecisionInputV2Component, ReleaseDialogComponent, + DecisionConditionDateDialogComponent, DecisionComponentComponent, DecisionComponentsComponent, DecisionDocumentUploadDialogComponent, diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.html index c7431d46bb..5cfa48d95c 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/condition/condition.component.html @@ -1,9 +1,9 @@

{{ condition.type.label }}

- + - +
@@ -28,20 +28,6 @@

{{ condition.type.label }}

-
-
{{ singleDateLabel }}
- {{ singleDateFormated }} - -
- -
-
Completion Date
- -
-
Description
e.label).join(';\n'), @@ -75,8 +87,6 @@ export class ConditionComponent implements OnInit, AfterViewInit { const labels = this.condition.componentLabelsStr; this.condition = { ...update, componentLabelsStr: labels } as Condition; - - this.updateStatus(); } } @@ -94,13 +104,15 @@ export class ConditionComponent implements OnInit, AfterViewInit { return this.isReadMoreClicked || this.isEllipsisActive(this.condition.uuid + 'Description'); } - updateStatus() { - const today = moment().startOf('day').toDate().getTime(); - - if (this.condition.completionDate && this.condition.completionDate <= today) { - this.conditionStatus = CONDITION_STATUS.COMPLETE; - } else { - this.conditionStatus = CONDITION_STATUS.INCOMPLETE; + async fetchDates(uuid: string | undefined) { + if (!uuid) { + return; } + + this.dates = await this.conditionService.getDates(uuid); + + this.singleDateFormated = this.dates[0].date + ? moment(this.dates[0].date).format(environment.dateFormat) + : undefined; } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.spec.ts index 5f991f7f3b..188e401efb 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.spec.ts @@ -6,6 +6,7 @@ import { BehaviorSubject } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentDetailService } from '../../../../services/notice-of-intent/notice-of-intent-detail.service'; import { NoticeOfIntentDto } from '../../../../services/notice-of-intent/notice-of-intent.dto'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; import { ConditionsComponent } from './conditions.component'; @@ -42,6 +43,7 @@ describe('ConditionsComponent', () => { }, ], schemas: [NO_ERRORS_SCHEMA], + imports: [HttpClientTestingModule], }).compileComponents(); fixture = TestBed.createComponent(ConditionsComponent); diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts index e723834705..41826f10bc 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/conditions/conditions.component.ts @@ -1,10 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import moment from 'moment'; -import { Subject, takeUntil } from 'rxjs'; +import { from, map, Subject, switchMap, takeUntil } from 'rxjs'; import { NoticeOfIntentDecisionV2Service } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-v2.service'; import { NoticeOfIntentDecisionCodesDto, + NoticeOfIntentDecisionConditionDateDto, NoticeOfIntentDecisionConditionDto, NoticeOfIntentDecisionWithLinkedResolutionDto, } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; @@ -15,6 +16,7 @@ import { MODIFICATION_TYPE_LABEL, RELEASED_DECISION_TYPE_LABEL, } from '../../../../shared/application-type-pill/application-type-pill.constants'; +import { NoticeOfIntentDecisionConditionService } from '../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service'; export type ConditionComponentLabels = { label: string[]; @@ -59,6 +61,7 @@ export class ConditionsComponent implements OnInit { constructor( private noticeOfIntentDetailService: NoticeOfIntentDetailService, private decisionService: NoticeOfIntentDecisionV2Service, + private conditionService: NoticeOfIntentDecisionConditionService, private activatedRouter: ActivatedRoute, ) { this.today = moment().startOf('day').toDate().getTime(); @@ -78,23 +81,49 @@ export class ConditionsComponent implements OnInit { async loadDecisions(fileNumber: string) { this.codes = await this.decisionService.fetchCodes(); - this.decisionService.$decisions.pipe(takeUntil(this.$destroy)).subscribe((decisions) => { - this.decisions = decisions.map((decision) => { - if (decision.uuid === this.decisionUuid) { - const conditions = this.mapConditions(decision, decisions); - - this.sortConditions(decision, conditions); - - this.decision = decision as DecisionWithConditionComponentLabels; - } + this.decisionService.$decisions + .pipe(takeUntil(this.$destroy)) + .pipe( + switchMap((decisions) => + from(this.getDatesByConditionUuid(decisions)).pipe( + map((datesByConditionUuid) => ({ + decisions, + datesByConditionUuid, + })), + ), + ), + ) + .subscribe(({ decisions, datesByConditionUuid }) => { + this.decisions = decisions.map((decision) => { + if (decision.uuid === this.decisionUuid) { + const conditions = this.mapConditions(decision, datesByConditionUuid, decisions); + + this.sortConditions(decision, conditions); + + this.decision = decision as DecisionWithConditionComponentLabels; + } - return decision as DecisionWithConditionComponentLabels; + return decision as DecisionWithConditionComponentLabels; + }); }); - }); this.decisionService.loadDecisions(fileNumber); } + private async getDatesByConditionUuid( + decisions: NoticeOfIntentDecisionWithLinkedResolutionDto[], + ): Promise> { + let datesByConditionUuid = new Map(); + + for (const decision of decisions) { + for (const condition of decision.conditions) { + datesByConditionUuid.set(condition.uuid, await this.conditionService.getDates(condition.uuid)); + } + } + + return datesByConditionUuid; + } + private sortConditions( decision: NoticeOfIntentDecisionWithLinkedResolutionDto, conditions: DecisionConditionWithStatus[], @@ -115,10 +144,12 @@ export class ConditionsComponent implements OnInit { private mapConditions( decision: NoticeOfIntentDecisionWithLinkedResolutionDto, + datesByConditionUuid: Map, decisions: NoticeOfIntentDecisionWithLinkedResolutionDto[], ) { return decision.conditions.map((condition) => { - const status = this.getStatus(condition, decision); + const dates = datesByConditionUuid.get(condition.uuid) ?? []; + const status = this.getStatus(dates, decision); return { ...condition, @@ -146,13 +177,14 @@ export class ConditionsComponent implements OnInit { } private getStatus( - condition: NoticeOfIntentDecisionConditionDto, + dates: NoticeOfIntentDecisionConditionDateDto[], decision: NoticeOfIntentDecisionWithLinkedResolutionDto, ) { let status = ''; - if (condition.completionDate && condition.completionDate <= this.today) { + status = CONDITION_STATUS.COMPLETE; + if (dates.length > 0 && dates.every((date) => date.completedDate && date.completedDate <= this.today)) { status = CONDITION_STATUS.COMPLETE; - } else if (!decision.isDraft) { + } else if (decision.isDraft === false) { status = CONDITION_STATUS.INCOMPLETE; } else { status = ''; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html index 06b9604a7f..312752a9b9 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.html @@ -1,6 +1,22 @@
{{ data.type?.label }}
- +
+ +
+ + +
+
+ +
@@ -59,3 +75,13 @@
{{ data.type?.label }}
+ + +
+ Due Dates: +
{{ formatDate(date.date) }}
+ +
+
diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss index 3b4c9e86c7..672255710b 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.scss @@ -1,3 +1,5 @@ +@use '../../../../../../../../styles/colors.scss'; + form { margin-top: 12px; } @@ -12,3 +14,31 @@ form { grid-column: 1/3; } } + +.buttons { + display: flex; + flex-direction: row; + gap: 12px; + + .add-date-button-wrapper { + display: flex; + flex-direction: column; + align-items: end; + } +} + +.date-list { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + gap: 10px; + margin-top: 16px; + + .date-chip { + padding: 5px 8px; + border-radius: 3px; + border: 1px solid colors.$grey; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + } +} diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts index ef07699bf9..aa3e194a98 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.spec.ts @@ -2,6 +2,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { DecisionConditionComponent } from './decision-condition.component'; +import { DateType } from '../../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; describe('DecisionConditionComponent', () => { let component: DecisionConditionComponent; @@ -11,6 +13,7 @@ describe('DecisionConditionComponent', () => { await TestBed.configureTestingModule({ declarations: [DecisionConditionComponent], schemas: [NO_ERRORS_SCHEMA], + imports: [HttpClientTestingModule], }).compileComponents(); fixture = TestBed.createComponent(DecisionConditionComponent); @@ -22,7 +25,9 @@ describe('DecisionConditionComponent', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }, }; diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts index 4a9724ef35..ff311831a6 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition.component.ts @@ -1,7 +1,17 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { SelectableComponent, TempNoticeOfIntentDecisionConditionDto } from '../decision-conditions.component'; -import { formatDateForApi } from '../../../../../../../shared/utils/api-date-formatter'; +import { DateType } from '../../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { + NoticeOfIntentDecisionConditionDateDto, + NoticeOfIntentDecisionConditionTypeDto, +} from '../../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; +import { MatDialog } from '@angular/material/dialog'; +import { + DecisionConditionDateDialogComponent, + DueDate, +} from '../../../../../../application/decision/decision-v2/decision-input/decision-conditions/decision-condition/decision-condition-date-dialog/decision-condition-date-dialog.component'; +import moment, { Moment } from 'moment'; @Component({ selector: 'app-noi-decision-condition', @@ -10,14 +20,26 @@ import { formatDateForApi } from '../../../../../../../shared/utils/api-date-for }) export class DecisionConditionComponent implements OnInit, OnChanges { @Input() data!: TempNoticeOfIntentDecisionConditionDto; + _showDateError = false; + @Input() set showDateError(value: boolean) { + this._showDateError = value; + } + get showDateError(): boolean { + return this._showDateError && this.isDateRequired && (!this.dates || this.dates.length === 0); + } @Output() dataChange = new EventEmitter(); @Output() remove = new EventEmitter(); @Input() selectableComponents: SelectableComponent[] = []; + uuid: string | undefined; + + dates: NoticeOfIntentDecisionConditionDateDto[] = []; + singleDateLabel = 'End Date'; showSingleDateField = false; - isShowSingleDateRequired = false; + showMultiDateUi = false; + isDateRequired = false; showAdmFeeField = false; isAdmFeeFieldRequired = false; showSecurityAmountField = false; @@ -25,12 +47,11 @@ export class DecisionConditionComponent implements OnInit, OnChanges { numberOfSelectedConditions = 0; componentsToCondition = new FormControl(null, [Validators.required]); - approvalDependant = new FormControl(null, [Validators.required]); + description = new FormControl(null, [Validators.required]); securityAmount = new FormControl(null); administrativeFee = new FormControl(null); - description = new FormControl(null, [Validators.required]); - singleDate = new FormControl(null, [Validators.required]); + singleDate = new FormControl(null); minDate = new Date(0); form = new FormGroup({ @@ -41,91 +62,117 @@ export class DecisionConditionComponent implements OnInit, OnChanges { componentsToCondition: this.componentsToCondition, }); + constructor(protected dialog: MatDialog) {} + ngOnInit(): void { - if (this.data) { - this.singleDateLabel = this.data.type?.singleDateLabel ? this.data.type?.singleDateLabel : 'End Date'; - this.showSingleDateField = this.data.type?.isSingleDateChecked ? this.data.type?.isSingleDateChecked : false; - if (this.data.type?.isSingleDateRequired) { - this.singleDate.addValidators(Validators.required); - this.isShowSingleDateRequired = true; - } else { - this.singleDate.removeValidators(Validators.required); - this.isShowSingleDateRequired = false; - } + this.uuid = this.data.uuid; + this.dates = this.data.dates ?? []; - this.showAdmFeeField = this.data.type?.isAdministrativeFeeAmountChecked - ? this.data.type?.isAdministrativeFeeAmountChecked - : false; - if (this.data.type?.isAdministrativeFeeAmountRequired) { - this.administrativeFee.addValidators(Validators.required); - this.isAdmFeeFieldRequired = true; - } else { - this.administrativeFee.removeValidators(Validators.required); - this.isAdmFeeFieldRequired = false; - } + if (this.data.type) { + this.initDateUi(this.data.type); + this.initOptionalFields(this.data.type); + } - this.showSecurityAmountField = this.data.type?.isSecurityAmountChecked - ? this.data.type?.isSecurityAmountChecked - : false; - if (this.data.type?.isSecurityAmountRequired) { - this.securityAmount.addValidators(Validators.required); - this.isSecurityAmountFieldRequired = true; - } else { - this.securityAmount.removeValidators(Validators.required); - this.isSecurityAmountFieldRequired = false; - } + this.initComponentField(this.data); - if (this.showSingleDateField) { - this.numberOfSelectedConditions++; - } - if (this.showAdmFeeField) { - this.numberOfSelectedConditions++; + this.form.patchValue({ + securityAmount: this.data.securityAmount?.toString() ?? null, + administrativeFee: this.data.administrativeFee + ? this.data.administrativeFee?.toString() + : this.data.type?.administrativeFeeAmount?.toString(), + description: this.data.description ?? null, + }); + + if (this.showSingleDateField && this.dates.length > 0 && this.dates[0].date) { + this.form.patchValue({ singleDate: moment(this.dates[0].date) }); + } + + this.form.valueChanges.subscribe(this.emitChanges.bind(this)); + } + + emitChanges() { + const selectedOptions = this.selectableComponents + .filter((component) => this.componentsToCondition.value?.includes(component.tempId)) + .map((e) => ({ + componentDecisionUuid: e.decisionUuid, + componentToConditionType: e.code, + tempId: e.tempId, + })); + + const conditionDto: TempNoticeOfIntentDecisionConditionDto = { + type: this.data.type, + tempUuid: this.data.tempUuid, + uuid: this.data.uuid, + securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, + administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, + description: this.description.value ?? undefined, + componentsToCondition: selectedOptions, + }; + + if (this.showSingleDateField) { + const singleDateDto: NoticeOfIntentDecisionConditionDateDto = {}; + + if (this.singleDate.value) { + singleDateDto.date = this.singleDate.value.toDate().getTime(); } - if (this.showSecurityAmountField) { - this.numberOfSelectedConditions++; + + conditionDto.dates = [singleDateDto]; + } else { + conditionDto.dates = this.dates; + } + + this.dataChange.emit(conditionDto); + } + + initDateUi(type: NoticeOfIntentDecisionConditionTypeDto) { + if (!type.isDateChecked) { + return; + } + + this.isDateRequired = type.isDateRequired ?? false; + this.showSingleDateField = type.dateType === DateType.SINGLE; + this.showMultiDateUi = type.dateType === DateType.MULTIPLE; + + if (this.showSingleDateField) { + this.singleDateLabel = type.singleDateLabel ? type.singleDateLabel : 'End Date'; + + if (this.isDateRequired) { + this.singleDate.addValidators(Validators.required); } + } + } - const selectedOptions = this.selectableComponents - .filter((component) => this.data.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) - .map((e) => ({ - componentDecisionUuid: e.decisionUuid, - componentToConditionType: e.code, - tempId: e.tempId, - })); + initOptionalFields(type: NoticeOfIntentDecisionConditionTypeDto) { + this.showAdmFeeField = type.isAdministrativeFeeAmountChecked ?? false; + this.isAdmFeeFieldRequired = type.isAdministrativeFeeAmountRequired ?? false; + this.showSecurityAmountField = type.isSecurityAmountChecked ? type.isSecurityAmountChecked : false; + this.isSecurityAmountFieldRequired = type.isSecurityAmountRequired ?? false; - this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); + if (this.isAdmFeeFieldRequired) { + this.administrativeFee.addValidators(Validators.required); + } - this.form.patchValue({ - securityAmount: this.data.securityAmount?.toString() ?? null, - administrativeFee: this.data.administrativeFee - ? this.data.administrativeFee?.toString() - : this.data.type?.administrativeFeeAmount?.toString(), - description: this.data.description ?? null, - singleDate: this.data.singleDate ? new Date(this.data.singleDate) : undefined, - }); + if (this.isSecurityAmountFieldRequired) { + this.securityAmount.addValidators(Validators.required); } - this.form.valueChanges.subscribe((changes) => { - const selectedOptions = this.selectableComponents - .filter((component) => this.componentsToCondition.value?.includes(component.tempId)) - .map((e) => ({ - componentDecisionUuid: e.decisionUuid, - componentToConditionType: e.code, - tempId: e.tempId, - })); + this.numberOfSelectedConditions += [ + this.showSingleDateField, + this.showAdmFeeField, + this.showSecurityAmountField, + ].reduce((sum, flag) => sum + (flag ? 1 : 0), 0); + } - this.dataChange.emit({ - type: this.data.type, - tempUuid: this.data.tempUuid, - uuid: this.data.uuid, - approvalDependant: this.approvalDependant.value, - securityAmount: this.securityAmount.value !== null ? parseFloat(this.securityAmount.value) : undefined, - administrativeFee: this.administrativeFee.value !== null ? parseFloat(this.administrativeFee.value) : undefined, - description: this.description.value ?? undefined, - componentsToCondition: selectedOptions, - singleDate: this.singleDate.value ? formatDateForApi(this.singleDate.value) : undefined, - }); - }); + initComponentField(condition: TempNoticeOfIntentDecisionConditionDto) { + const selectedOptions = this.selectableComponents + .filter((component) => condition.componentsToCondition?.map((e) => e.tempId)?.includes(component.tempId)) + .map((e) => ({ + componentDecisionUuid: e.decisionUuid, + componentToConditionType: e.code, + tempId: e.tempId, + })); + + this.componentsToCondition.setValue(selectedOptions.map((e) => e.tempId) ?? null); } ngOnChanges(changes: SimpleChanges): void { @@ -146,4 +193,34 @@ export class DecisionConditionComponent implements OnInit, OnChanges { onRemove() { this.remove.emit(); } + + formatDate(timestamp: number | undefined): string { + if (!timestamp) { + return ''; + } + return moment(timestamp).format('YYYY-MMM-DD'); + } + + openDateDialog(isAdding: boolean) { + this.dialog + .open(DecisionConditionDateDialogComponent, { + maxHeight: '80vh', + data: { + dates: this.dates, + isAdding, + isRequired: this.isDateRequired, + }, + }) + .beforeClosed() + .subscribe(async (dates: DueDate[]) => { + if (!this.uuid) { + return; + } + this.dates = dates.map((date) => ({ + uuid: date.uuid, + date: date.date?.toDate().getTime(), + })); + this.emitChanges(); + }); + } } diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html index d65f361fa7..c51af329cd 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.html @@ -29,6 +29,7 @@

Conditions

(dataChange)="onChanges()" (remove)="onRemoveCondition(index)" [selectableComponents]="selectableComponents" + [showDateError]="showDateErrors" > diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts index 5398bbdee9..978ed509db 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-conditions/decision-conditions.component.ts @@ -20,7 +20,7 @@ import { } from '../../../../../../services/notice-of-intent/decision-v2/notice-of-intent-decision.dto'; import { ConfirmationDialogService } from '../../../../../../shared/confirmation-dialog/confirmation-dialog.service'; import { DecisionConditionComponent } from './decision-condition/decision-condition.component'; -import { DecisionComponentTypeDto } from 'src/app/services/application/decision/application-decision-v2/application-decision-v2.dto'; +import { DecisionComponentTypeDto } from '../../../../../../services/application/decision/application-decision-v2/application-decision-v2.dto'; export type TempNoticeOfIntentDecisionConditionDto = UpdateNoticeOfIntentDecisionConditionDto & { tempUuid?: string }; export type SelectableComponent = { uuid?: string; tempId: string; decisionUuid: string; code: string; label: string }; @@ -36,12 +36,12 @@ export class DecisionConditionsComponent implements OnInit, OnChanges, OnDestroy activeTypes!: NoticeOfIntentDecisionConditionTypeDto[]; @Input() set types(types: NoticeOfIntentDecisionConditionTypeDto[]) { this.activeTypes = types.filter((type) => type.isActive); - console.log(this.activeTypes); } @Input() componentTypes!: DecisionComponentTypeDto[]; @Input() components: NoticeOfIntentDecisionComponentDto[] = []; @Input() conditions: NoticeOfIntentDecisionConditionDto[] = []; @Input() showError = false; + @Input() showDateErrors = false; @ViewChildren(DecisionConditionComponent) conditionComponents: DecisionConditionComponent[] = []; @Output() conditionsChange = new EventEmitter<{ diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html index d1de68c0e8..286922b63a 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.html @@ -187,6 +187,7 @@

Resolution

[conditions]="conditions" (conditionsChange)="onConditionsChange($event)" [showError]="form.touched && conditionUpdates.length < 1 && showConditions" + [showDateErrors]="form.touched && requiredDatesAreMissing()" > diff --git a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts index 1c09049993..97332c0f4d 100644 --- a/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts +++ b/alcs-frontend/src/app/features/notice-of-intent/decision/decision-v2/decision-input/decision-input-v2.component.ts @@ -391,7 +391,8 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { !this.conditionsValid || !this.componentsValid || (this.components.length === 0 && requiresComponents) || - (this.conditionUpdates.length === 0 && requiresConditions) + (this.conditionUpdates.length === 0 && requiresConditions) || + this.requiredDatesAreMissing() ) { this.form.controls.decisionMaker.markAsDirty(); this.toastService.showErrorToast('Please correct all errors before submitting the form'); @@ -405,6 +406,15 @@ export class DecisionInputV2Component implements OnInit, OnDestroy { } } + requiredDatesAreMissing(): boolean { + return this.conditionUpdates.some( + (condition) => + condition.type?.isDateChecked && + condition.type.isDateRequired && + (!condition.dates || condition.dates.length === 0), + ); + } + private scrollToError() { let elements = document.getElementsByClassName('ng-invalid'); let elArray = Array.from(elements).filter((el) => el.nodeName !== 'FORM'); diff --git a/alcs-frontend/src/app/services/admin-board-management/admin-board-management.service.spec.ts b/alcs-frontend/src/app/services/admin-board-management/admin-board-management.service.spec.ts index 982068c39c..11cff93a67 100644 --- a/alcs-frontend/src/app/services/admin-board-management/admin-board-management.service.spec.ts +++ b/alcs-frontend/src/app/services/admin-board-management/admin-board-management.service.spec.ts @@ -47,7 +47,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.post.mockReturnValue( of({ uuid: 'fake', - }) + }), ); await service.create(mockBoard); @@ -59,7 +59,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.post.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.create(mockBoard); @@ -72,7 +72,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.put.mockReturnValue( of({ uuid: 'fake', - }) + }), ); await service.update('fake', mockBoard); @@ -84,7 +84,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.put.mockReturnValue( throwError(() => { new Error(''); - }) + }), ); await service.update('mock', mockBoard); @@ -97,7 +97,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.get.mockReturnValue( of({ uuid: 'fake', - }) + }), ); await service.canDelete('fake'); @@ -109,7 +109,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.get.mockReturnValue( of({ uuid: 'fake', - }) + }), ); await service.getCardTypes(); @@ -121,7 +121,7 @@ describe('AdminBoardManagementService', () => { mockHttpClient.get.mockReturnValue( of({ uuid: 'fake', - }) + }), ); await service.getCardCounts('board'); diff --git a/alcs-frontend/src/app/services/application/application-decision-condition-types/application-decision-condition-types.service.spec.ts b/alcs-frontend/src/app/services/application/application-decision-condition-types/application-decision-condition-types.service.spec.ts index f419172a59..ccaa542968 100644 --- a/alcs-frontend/src/app/services/application/application-decision-condition-types/application-decision-condition-types.service.spec.ts +++ b/alcs-frontend/src/app/services/application/application-decision-condition-types/application-decision-condition-types.service.spec.ts @@ -4,6 +4,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { of, throwError } from 'rxjs'; import { ToastService } from '../../toast/toast.service'; import { ApplicationDecisionConditionTypesService } from './application-decision-condition-types.service'; +import { DateType } from '../decision/application-decision-v2/application-decision-v2.dto'; describe('DecisionConditionTypesService', () => { let service: ApplicationDecisionConditionTypesService; @@ -46,7 +47,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -68,7 +71,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -90,7 +95,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -112,7 +119,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.ts index b919bf2096..f12c4c1f60 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-condition/application-decision-condition.service.ts @@ -4,6 +4,7 @@ import { firstValueFrom } from 'rxjs'; import { environment } from '../../../../../../environments/environment'; import { ToastService } from '../../../../toast/toast.service'; import { + ApplicationDecisionConditionDateDto, ApplicationDecisionConditionDto, ApplicationDecisionConditionToComponentPlanNumberDto, UpdateApplicationDecisionConditionDto, @@ -15,7 +16,21 @@ import { export class ApplicationDecisionConditionService { private url = `${environment.apiUrl}/v2/application-decision-condition`; - constructor(private http: HttpClient, private toastService: ToastService) {} + constructor( + private http: HttpClient, + private toastService: ToastService, + ) {} + + async fetchByTypeCode(typeCode: string): Promise { + try { + return await firstValueFrom( + this.http.get(`${this.url}?type_code=${typeCode}`), + ); + } catch (e) { + this.toastService.showErrorToast('Failed to load conditions'); + throw e; + } + } async update(uuid: string, data: UpdateApplicationDecisionConditionDto) { try { @@ -35,7 +50,7 @@ export class ApplicationDecisionConditionService { async fetchPlanNumbers(uuid: string) { try { const res = await firstValueFrom( - this.http.get(`${this.url}/plan-numbers/${uuid}`) + this.http.get(`${this.url}/plan-numbers/${uuid}`), ); return res; } catch (e) { @@ -49,8 +64,8 @@ export class ApplicationDecisionConditionService { const res = await firstValueFrom( this.http.patch( `${this.url}/plan-numbers/condition/${conditionUuid}/component/${componentUuid}`, - planNumbers - ) + planNumbers, + ), ); this.toastService.showSuccessToast('Condition updated'); return res; @@ -63,4 +78,29 @@ export class ApplicationDecisionConditionService { throw e; } } + + async getDates(conditionUuid: string): Promise { + try { + return await firstValueFrom( + this.http.get(`${this.url}/date?conditionUuid=${conditionUuid}`), + ); + } catch (e: any) { + this.toastService.showErrorToast(e.error?.message ?? 'No dates found'); + throw e; + } + } + + async updateDate( + dateUuid: string, + dateDto: ApplicationDecisionConditionDateDto, + ): Promise { + try { + return await firstValueFrom( + this.http.patch(`${this.url}/date/${dateUuid}`, dateDto), + ); + } catch (e: any) { + this.toastService.showErrorToast(e.error?.message ?? 'Failed to update date'); + throw e; + } + } } diff --git a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts index ab55ce66e6..2a3a061719 100644 --- a/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts +++ b/alcs-frontend/src/app/services/application/decision/application-decision-v2/application-decision-v2.dto.ts @@ -221,6 +221,11 @@ export enum DateLabel { END_DATE = 'End Date', } +export enum DateType { + SINGLE = 'Single', + MULTIPLE = 'Multiple', +} + export interface ApplicationDecisionConditionTypeDto extends BaseCodeDto { isActive: boolean; isComponentToConditionChecked?: boolean | null; @@ -228,8 +233,9 @@ export interface ApplicationDecisionConditionTypeDto extends BaseCodeDto { isAdministrativeFeeAmountChecked: boolean; isAdministrativeFeeAmountRequired?: boolean | null; administrativeFeeAmount?: number | null; - isSingleDateChecked: boolean; - isSingleDateRequired?: boolean | null; + isDateChecked: boolean; + isDateRequired?: boolean | null; + dateType?: DateType | null; singleDateLabel?: DateLabel | null; isSecurityAmountChecked: boolean; isSecurityAmountRequired?: boolean | null; @@ -244,16 +250,9 @@ export interface ApplicationDecisionConditionDto { securityAmount?: number | null; administrativeFee?: number | null; description?: string | null; - completionDate?: number | null; type?: ApplicationDecisionConditionTypeDto | null; components?: ApplicationDecisionComponentDto[] | null; - singleDate?: number | null; -} - -export interface ComponentToCondition { - componentDecisionUuid?: string; - componentToConditionType?: string; - tempId: string; + dates?: ApplicationDecisionConditionDateDto[]; } export interface UpdateApplicationDecisionConditionDto { @@ -263,9 +262,21 @@ export interface UpdateApplicationDecisionConditionDto { securityAmount?: number | null; administrativeFee?: number | null; description?: string | null; - completionDate?: number | null; type?: ApplicationDecisionConditionTypeDto | null; - singleDate?: number | null; + dates?: ApplicationDecisionConditionDateDto[]; +} + +export interface ComponentToCondition { + componentDecisionUuid?: string; + componentToConditionType?: string; + tempId: string; +} + +export interface ApplicationDecisionConditionDateDto { + uuid?: string; + date?: number; + completedDate?: number | null; + comment?: string | null; } export interface ApplicationDecisionComponentToConditionLotDto { diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts index a7d7e4db25..466848705f 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts @@ -4,6 +4,7 @@ import { firstValueFrom } from 'rxjs'; import { environment } from '../../../../../environments/environment'; import { ToastService } from '../../../toast/toast.service'; import { + NoticeOfIntentDecisionConditionDateDto, NoticeOfIntentDecisionConditionDto, UpdateNoticeOfIntentDecisionConditionDto, } from '../notice-of-intent-decision.dto'; @@ -19,6 +20,17 @@ export class NoticeOfIntentDecisionConditionService { private toastService: ToastService, ) {} + async fetchByTypeCode(typeCode: string): Promise { + try { + return await firstValueFrom( + this.http.get(`${this.url}?type_code=${typeCode}`), + ); + } catch (e) { + this.toastService.showErrorToast('Failed to load conditions'); + throw e; + } + } + async update(uuid: string, data: UpdateNoticeOfIntentDecisionConditionDto) { try { const res = await firstValueFrom( @@ -35,4 +47,29 @@ export class NoticeOfIntentDecisionConditionService { throw e; } } + + async getDates(conditionUuid: string): Promise { + try { + return await firstValueFrom( + this.http.get(`${this.url}/date?conditionUuid=${conditionUuid}`), + ); + } catch (e: any) { + this.toastService.showErrorToast(e.error?.message ?? 'No dates found'); + throw e; + } + } + + async updateDate( + dateUuid: string, + dateDto: NoticeOfIntentDecisionConditionDateDto, + ): Promise { + try { + return await firstValueFrom( + this.http.patch(`${this.url}/date/${dateUuid}`, dateDto), + ); + } catch (e: any) { + this.toastService.showErrorToast(e.error?.message ?? 'Failed to update date'); + throw e; + } + } } diff --git a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts index 3f9e6d57f1..28ed59e580 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/decision-v2/notice-of-intent-decision.dto.ts @@ -1,5 +1,5 @@ import { BaseCodeDto } from '../../../shared/dto/base.dto'; -import { DateLabel } from '../../application/decision/application-decision-v2/application-decision-v2.dto'; +import { DateLabel, DateType } from '../../application/decision/application-decision-v2/application-decision-v2.dto'; export interface UpdateNoticeOfIntentDecisionDto { resolutionNumber?: number; @@ -75,8 +75,9 @@ export interface NoticeOfIntentDecisionConditionTypeDto extends BaseCodeDto { isAdministrativeFeeAmountChecked: boolean; isAdministrativeFeeAmountRequired?: boolean | null; administrativeFeeAmount?: number | null; - isSingleDateChecked: boolean; - isSingleDateRequired?: boolean | null; + isDateChecked: boolean; + isDateRequired?: boolean | null; + dateType?: DateType | null; singleDateLabel?: DateLabel | null; isSecurityAmountChecked: boolean; isSecurityAmountRequired?: boolean | null; @@ -90,9 +91,8 @@ export interface NoticeOfIntentDecisionConditionDto { description: string | null; type: NoticeOfIntentDecisionConditionTypeDto; componentUuid: string | null; - completionDate?: number; components?: NoticeOfIntentDecisionComponentDto[]; - singleDate?: number | null; + dates?: NoticeOfIntentDecisionConditionDateDto[]; } export interface ComponentToCondition { @@ -101,6 +101,13 @@ export interface ComponentToCondition { tempId: string; } +export interface NoticeOfIntentDecisionConditionDateDto { + uuid?: string; + date?: number; + completedDate?: number | null; + comment?: string | null; +} + export interface UpdateNoticeOfIntentDecisionConditionDto { uuid?: string; componentsToCondition?: ComponentToCondition[]; @@ -109,8 +116,7 @@ export interface UpdateNoticeOfIntentDecisionConditionDto { administrativeFee?: number | null; description?: string | null; type?: NoticeOfIntentDecisionConditionTypeDto; - completionDate?: number | null; - singleDate?: number | null; + dates?: NoticeOfIntentDecisionConditionDateDto[]; } export interface NoticeOfIntentDecisionComponentTypeDto extends BaseCodeDto {} diff --git a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.spec.ts b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.spec.ts index 08cbfe811f..7384828d69 100644 --- a/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.spec.ts +++ b/alcs-frontend/src/app/services/notice-of-intent/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.spec.ts @@ -4,6 +4,7 @@ import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { of, throwError } from 'rxjs'; import { ToastService } from '../../toast/toast.service'; import { NoticeofIntentDecisionConditionTypesService } from './notice-of-intent-decision-condition-types.service'; +import { DateType } from '../../application/decision/application-decision-v2/application-decision-v2.dto'; describe('DecisionConditionTypesService', () => { let service: NoticeofIntentDecisionConditionTypesService; @@ -46,7 +47,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -68,7 +71,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -90,7 +95,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); @@ -112,7 +119,9 @@ describe('DecisionConditionTypesService', () => { description: '', isActive: true, isAdministrativeFeeAmountChecked: false, - isSingleDateChecked: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, isSecurityAmountChecked: false, }); diff --git a/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.spec.ts b/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.spec.ts index 0b72eab582..286bb3977e 100644 --- a/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.spec.ts +++ b/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.spec.ts @@ -7,6 +7,7 @@ import { Repository } from 'typeorm'; import { ApplicationDecisionConditionType, DateLabel, + DateType, } from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; import { ApplicationDecisionConditionTypesService } from './application-decision-condition-types.service'; import { ServiceValidationException } from '@app/common/exceptions/base.exception'; @@ -56,8 +57,9 @@ describe('ApplicationDecisionConditionTypesService', () => { isAdministrativeFeeAmountChecked: false, isAdministrativeFeeAmountRequired: false, administrativeFeeAmount: null, - isSingleDateChecked: false, - isSingleDateRequired: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, singleDateLabel: DateLabel.DUE_DATE, isSecurityAmountChecked: false, isSecurityAmountRequired: false, @@ -81,8 +83,9 @@ describe('ApplicationDecisionConditionTypesService', () => { isAdministrativeFeeAmountChecked: false, isAdministrativeFeeAmountRequired: false, administrativeFeeAmount: null, - isSingleDateChecked: false, - isSingleDateRequired: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, singleDateLabel: DateLabel.DUE_DATE, isSecurityAmountChecked: false, isSecurityAmountRequired: false, @@ -104,8 +107,9 @@ describe('ApplicationDecisionConditionTypesService', () => { isAdministrativeFeeAmountChecked: false, isAdministrativeFeeAmountRequired: false, administrativeFeeAmount: null, - isSingleDateChecked: false, - isSingleDateRequired: false, + isDateChecked: false, + isDateRequired: false, + dateType: DateType.SINGLE, singleDateLabel: DateLabel.DUE_DATE, isSecurityAmountChecked: false, isSecurityAmountRequired: false, diff --git a/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.ts b/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.ts index 0ab78ee544..db91076282 100644 --- a/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.ts +++ b/services/apps/alcs/src/alcs/admin/application-decision-condition-types/application-decision-condition-types.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { ApplicationDecisionConditionType } from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; +import { + ApplicationDecisionConditionType, + DateType, +} from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; import { ApplicationDecisionConditionTypeDto } from '../../application-decision/application-decision-condition/application-decision-condition.dto'; import { ServiceNotFoundException, @@ -30,8 +33,9 @@ export class ApplicationDecisionConditionTypesService { isAdministrativeFeeAmountChecked: true, isAdministrativeFeeAmountRequired: true, administrativeFeeAmount: true, - isSingleDateChecked: true, - isSingleDateRequired: true, + isDateChecked: true, + isDateRequired: true, + dateType: true, singleDateLabel: true, isSecurityAmountChecked: true, isSecurityAmountRequired: true, @@ -62,9 +66,11 @@ export class ApplicationDecisionConditionTypesService { ? updateDto.administrativeFeeAmount : null; - type.isSingleDateChecked = updateDto.isSingleDateChecked; - type.isSingleDateRequired = updateDto.isSingleDateChecked ? updateDto.isSingleDateRequired : null; - type.singleDateLabel = updateDto.isSingleDateChecked ? updateDto.singleDateLabel : null; + type.isDateChecked = updateDto.isDateChecked; + type.isDateRequired = updateDto.isDateChecked ? updateDto.isDateRequired : null; + + type.dateType = updateDto.isDateChecked ? updateDto.dateType : null; + type.singleDateLabel = updateDto.dateType === DateType.SINGLE ? updateDto.singleDateLabel : null; type.isSecurityAmountChecked = updateDto.isSecurityAmountChecked; type.isSecurityAmountRequired = updateDto.isSecurityAmountChecked ? updateDto.isSecurityAmountRequired : null; @@ -93,9 +99,11 @@ export class ApplicationDecisionConditionTypesService { ? createDto.administrativeFeeAmount : null; - type.isSingleDateChecked = createDto.isSingleDateChecked; - type.isSingleDateRequired = createDto.isSingleDateChecked ? createDto.isSingleDateRequired : null; - type.singleDateLabel = createDto.isSingleDateChecked ? createDto.singleDateLabel : null; + type.isDateChecked = createDto.isDateChecked; + type.isDateRequired = createDto.isDateChecked ? createDto.isDateRequired : null; + + type.dateType = createDto.isDateChecked ? createDto.dateType : null; + type.singleDateLabel = createDto.dateType === DateType.SINGLE ? createDto.singleDateLabel : null; type.isSecurityAmountChecked = createDto.isSecurityAmountChecked; type.isSecurityAmountRequired = createDto.isSecurityAmountChecked ? createDto.isSecurityAmountRequired : null; diff --git a/services/apps/alcs/src/alcs/admin/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.ts b/services/apps/alcs/src/alcs/admin/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.ts index b023d6ffc9..22d216a2d4 100644 --- a/services/apps/alcs/src/alcs/admin/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.ts +++ b/services/apps/alcs/src/alcs/admin/notice-of-intent-decision-condition-types/notice-of-intent-decision-condition-types.service.ts @@ -9,6 +9,7 @@ import { BaseServiceException, ServiceValidationException, } from '@app/common/exceptions/base.exception'; +import { DateType } from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; @Injectable() export class NoticeofIntentDecisionConditionTypesService { @@ -30,8 +31,9 @@ export class NoticeofIntentDecisionConditionTypesService { isAdministrativeFeeAmountChecked: true, isAdministrativeFeeAmountRequired: true, administrativeFeeAmount: true, - isSingleDateChecked: true, - isSingleDateRequired: true, + isDateChecked: true, + isDateRequired: true, + dateType: true, singleDateLabel: true, isSecurityAmountChecked: true, isSecurityAmountRequired: true, @@ -62,9 +64,11 @@ export class NoticeofIntentDecisionConditionTypesService { ? updateDto.administrativeFeeAmount : null; - type.isSingleDateChecked = updateDto.isSingleDateChecked; - type.isSingleDateRequired = updateDto.isSingleDateChecked ? updateDto.isSingleDateRequired : null; - type.singleDateLabel = updateDto.isSingleDateChecked ? updateDto.singleDateLabel : null; + type.isDateChecked = updateDto.isDateChecked; + type.isDateRequired = updateDto.isDateChecked ? updateDto.isDateRequired : null; + + type.dateType = updateDto.isDateChecked ? updateDto.dateType : null; + type.singleDateLabel = updateDto.dateType === DateType.SINGLE ? updateDto.singleDateLabel : null; type.isSecurityAmountChecked = updateDto.isSecurityAmountChecked; type.isSecurityAmountRequired = updateDto.isSecurityAmountChecked ? updateDto.isSecurityAmountRequired : null; @@ -93,9 +97,11 @@ export class NoticeofIntentDecisionConditionTypesService { ? createDto.administrativeFeeAmount : null; - type.isSingleDateChecked = createDto.isSingleDateChecked; - type.isSingleDateRequired = createDto.isSingleDateChecked ? createDto.isSingleDateRequired : null; - type.singleDateLabel = createDto.isSingleDateChecked ? createDto.singleDateLabel : null; + type.isDateChecked = createDto.isDateChecked; + type.isDateRequired = createDto.isDateChecked ? createDto.isDateRequired : null; + + type.dateType = createDto.isDateChecked ? createDto.dateType : null; + type.singleDateLabel = createDto.dateType === DateType.SINGLE ? createDto.singleDateLabel : null; type.isSecurityAmountChecked = createDto.isSecurityAmountChecked; type.isSecurityAmountRequired = createDto.isSecurityAmountChecked ? createDto.isSecurityAmountRequired : null; diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts index 01479f712c..b66524d086 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-code.entity.ts @@ -9,6 +9,11 @@ export enum DateLabel { END_DATE = 'End Date', } +export enum DateType { + SINGLE = 'Single', + MULTIPLE = 'Multiple', +} + @Entity({ comment: 'Code table for the possible application decision condition types', }) @@ -42,11 +47,15 @@ export class ApplicationDecisionConditionType extends BaseCodeEntity { @AutoMap() @Column({ default: false, type: 'boolean' }) - isSingleDateChecked: boolean; + isDateChecked: boolean; @AutoMap() @Column({ nullable: true, type: 'boolean' }) - isSingleDateRequired: boolean | null; + isDateRequired: boolean | null; + + @AutoMap() + @Column({ type: 'enum', enum: DateType, nullable: true }) + dateType: DateType | null; @AutoMap() @Column({ type: 'enum', enum: DateLabel, nullable: true }) diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.controller.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.controller.ts new file mode 100644 index 0000000000..eeedc32b0e --- /dev/null +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.controller.ts @@ -0,0 +1,30 @@ +import { Body, Controller, Get, Param, Patch, Query, UseGuards } from '@nestjs/common'; +import { ApiOAuth2 } from '@nestjs/swagger'; +import * as config from 'config'; +import { RolesGuard } from '../../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../../common/authorization/roles.decorator'; +import { ROLES_ALLOWED_APPLICATIONS } from '../../../../common/authorization/roles'; +import { ApplicationDecisionConditionDateService } from './application-decision-condition-date.service'; +import { ApplicationDecisionConditionDateDto } from './application-decision-condition-date.dto'; + +@Controller('application-decision-condition/date') +@ApiOAuth2(config.get('KEYCLOAK.SCOPES')) +@UseGuards(RolesGuard) +export class ApplicationDecisionConditionDateController { + constructor(private service: ApplicationDecisionConditionDateService) {} + + @Get('') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async fetch(@Query('conditionUuid') conditionUuid): Promise { + return await this.service.fetchByCondition(conditionUuid); + } + + @Patch('/:uuid') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async update( + @Param('uuid') uuid: string, + @Body() dto: ApplicationDecisionConditionDateDto, + ): Promise { + return await this.service.update(uuid, dto); + } +} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.dto.ts new file mode 100644 index 0000000000..c4bbfcc187 --- /dev/null +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.dto.ts @@ -0,0 +1,18 @@ +import { Transform } from 'class-transformer'; +import { IsNumber, IsString } from 'class-validator'; + +export class ApplicationDecisionConditionDateDto { + @IsString() + uuid?: string; + + @Transform(({ value }) => value.getTime()) + @IsNumber() + date?: number; + + @Transform(({ value }) => value && value.getTime()) + @IsNumber() + completedDate?: number | null; + + @IsString() + comment?: string | null; +} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.entity.ts new file mode 100644 index 0000000000..a701450f7f --- /dev/null +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.entity.ts @@ -0,0 +1,33 @@ +import { AutoMap } from 'automapper-classes'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Base } from '../../../../common/entities/base.entity'; +import { ApplicationDecisionCondition } from '../application-decision-condition.entity'; + +@Entity({ comment: 'Due/end dates for conditions' }) +export class ApplicationDecisionConditionDate extends Base { + constructor(data?: Partial) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @Column({ type: 'timestamptz' }) + date: Date; + + @AutoMap() + @Column({ type: 'timestamptz', nullable: true }) + completedDate: Date | null; + + @AutoMap() + @ManyToOne(() => ApplicationDecisionCondition, { + cascade: true, + onDelete: 'CASCADE', + }) + condition: ApplicationDecisionCondition; + + @AutoMap() + @Column({ type: 'text', nullable: true }) + comment: string | null; +} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.service.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.service.ts new file mode 100644 index 0000000000..d99eff10bc --- /dev/null +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.service.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ApplicationDecisionConditionDate } from './application-decision-condition-date.entity'; +import { ServiceNotFoundException, ServiceValidationException } from '@app/common/exceptions/base.exception'; +import { ApplicationDecisionCondition } from '../application-decision-condition.entity'; +import { ApplicationDecisionConditionDateDto } from './application-decision-condition-date.dto'; +import { plainToInstance } from 'class-transformer'; + +@Injectable() +export class ApplicationDecisionConditionDateService { + constructor( + @InjectRepository(ApplicationDecisionConditionDate) + private repository: Repository, + @InjectRepository(ApplicationDecisionCondition) + private conditionRepository: Repository, + ) {} + + async fetchByCondition(conditionUuid: string): Promise { + const condition = await this.conditionRepository.findOne({ + where: { + uuid: conditionUuid, + }, + }); + + if (!condition) { + throw new ServiceNotFoundException('Condition not found.'); + } + + const entities = await this.repository.find({ + where: { + condition: { + uuid: condition.uuid, + }, + }, + }); + + const dtos = plainToInstance(ApplicationDecisionConditionDateDto, entities); + + return dtos; + } + + async update(uuid: string, dto: ApplicationDecisionConditionDateDto): Promise { + const entity = await this.repository.findOneOrFail({ + where: { uuid }, + }); + + if (!entity) { + throw new ServiceNotFoundException('Date not found.'); + } + + this.map(dto, entity); + + const updatedEntity = await this.repository.save(entity); + + return plainToInstance(ApplicationDecisionConditionDateDto, updatedEntity); + } + + map(dto: ApplicationDecisionConditionDateDto, entity: ApplicationDecisionConditionDate) { + if (dto.date) { + entity.date = new Date(dto.date); + } + if (dto.completedDate) { + entity.completedDate = new Date(dto.completedDate); + } + if (dto.comment) { + entity.comment = dto.comment; + } + } +} diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.spec.ts index f68823ec0c..5229461122 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.spec.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.spec.ts @@ -55,7 +55,6 @@ describe('ApplicationDecisionConditionController', () => { securityAmount: 1000, administrativeFee: 50, description: 'example description', - completionDate: date.getTime(), }; const condition = new ApplicationDecisionCondition({ @@ -64,7 +63,6 @@ describe('ApplicationDecisionConditionController', () => { securityAmount: 500, administrativeFee: 25, description: 'existing description', - completionDate: new Date(), }); const updated = new ApplicationDecisionCondition({ @@ -73,7 +71,6 @@ describe('ApplicationDecisionConditionController', () => { securityAmount: updates.securityAmount, administrativeFee: updates.administrativeFee, description: updates.description, - completionDate: date, }); mockApplicationDecisionConditionService.getOneOrFail.mockResolvedValue(condition); @@ -84,9 +81,7 @@ describe('ApplicationDecisionConditionController', () => { expect(mockApplicationDecisionConditionService.getOneOrFail).toHaveBeenCalledWith(uuid); expect(mockApplicationDecisionConditionService.update).toHaveBeenCalledWith(condition, { ...updates, - completionDate: date, }); - expect(new Date(result.completionDate!)).toEqual(updated.completionDate); expect(result.description).toEqual(updated.description); expect(result.administrativeFee).toEqual(updated.administrativeFee); expect(result.securityAmount).toEqual(updated.securityAmount); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.ts index 1487331851..ae312f05ef 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.controller.ts @@ -1,12 +1,11 @@ import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; -import { Body, Controller, Get, Param, Patch, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Patch, Query, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; import * as config from 'config'; import { ANY_AUTH_ROLE } from '../../../common/authorization/roles'; import { RolesGuard } from '../../../common/authorization/roles-guard.service'; import { UserRoles } from '../../../common/authorization/roles.decorator'; -import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; import { ApplicationDecisionConditionComponentPlanNumber } from '../application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity'; import { ApplicationDecisionConditionComponentDto, @@ -25,6 +24,12 @@ export class ApplicationDecisionConditionController { @InjectMapper() private mapper: Mapper, ) {} + @Get() + @UserRoles(...ANY_AUTH_ROLE) + async getByTypeCode(@Query('type_code') typeCode: string) { + return await this.conditionService.getByTypeCode(typeCode); + } + @Patch('/:uuid') @UserRoles(...ANY_AUTH_ROLE) async update(@Param('uuid') uuid: string, @Body() updates: UpdateApplicationDecisionConditionDto) { @@ -35,8 +40,6 @@ export class ApplicationDecisionConditionController { securityAmount: updates.securityAmount, administrativeFee: updates.administrativeFee, description: updates.description, - completionDate: formatIncomingDate(updates.completionDate), - singleDate: formatIncomingDate(updates.singleDate), }); return await this.mapper.mapAsync(updatedCondition, ApplicationDecisionCondition, ApplicationDecisionConditionDto); } diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.dto.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.dto.ts index cbff9ed261..ff390865da 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.dto.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.dto.ts @@ -2,8 +2,9 @@ import { AutoMap } from 'automapper-classes'; import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; import { BaseCodeDto } from '../../../common/dtos/base.dto'; import { ApplicationDecisionComponentDto } from '../application-decision-v2/application-decision/component/application-decision-component.dto'; -import { DateLabel } from './application-decision-condition-code.entity'; +import { DateLabel, DateType } from './application-decision-condition-code.entity'; import { Type } from 'class-transformer'; +import { ApplicationDecisionConditionDateDto } from './application-decision-condition-date/application-decision-condition-date.dto'; export class ApplicationDecisionConditionTypeDto extends BaseCodeDto { @IsBoolean() @@ -35,14 +36,20 @@ export class ApplicationDecisionConditionTypeDto extends BaseCodeDto { @AutoMap() @IsBoolean() - isSingleDateChecked: boolean; + isDateChecked: boolean; @AutoMap() @IsBoolean() - isSingleDateRequired: boolean | null; + isDateRequired: boolean | null; + + @AutoMap() + @IsEnum(DateType) + @IsOptional() + dateType: DateType | null; @AutoMap() @IsEnum(DateLabel) + @IsOptional() singleDateLabel: DateLabel | null; @AutoMap() @@ -77,13 +84,10 @@ export class ApplicationDecisionConditionDto { componentUuid: string | null; @AutoMap() - completionDate?: number; - - @AutoMap() - singleDate?: number; + components?: ApplicationDecisionComponentDto[]; @AutoMap() - components?: ApplicationDecisionComponentDto[]; + dates: ApplicationDecisionConditionDateDto[]; } export class ComponentToConditionDto { @@ -126,12 +130,8 @@ export class UpdateApplicationDecisionConditionDto { type?: ApplicationDecisionConditionTypeDto; @IsOptional() - @IsNumber() - completionDate?: number; - - @IsOptional() - @IsNumber() - singleDate?: number; + @IsArray() + dates?: ApplicationDecisionConditionDateDto[]; } export class UpdateApplicationDecisionConditionServiceDto { @@ -141,8 +141,7 @@ export class UpdateApplicationDecisionConditionServiceDto { securityAmount?: number; administrativeFee?: number; description?: string; - completionDate?: Date | null; - singleDate?: Date | null; + dates?: ApplicationDecisionConditionDateDto[]; } export class ApplicationDecisionConditionComponentDto { diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts index c56615acc1..86f678d39c 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.entity.ts @@ -6,6 +6,7 @@ import { ApplicationDecisionConditionComponentPlanNumber } from '../application- import { ApplicationDecisionComponent } from '../application-decision-v2/application-decision/component/application-decision-component.entity'; import { ApplicationDecision } from '../application-decision.entity'; import { ApplicationDecisionConditionType } from './application-decision-condition-code.entity'; +import { ApplicationDecisionConditionDate } from './application-decision-condition-date/application-decision-condition-date.entity'; @Entity({ comment: 'Fields present on the application decision conditions' }) export class ApplicationDecisionCondition extends Base { @@ -44,14 +45,6 @@ export class ApplicationDecisionCondition extends Base { @Column({ type: 'text', nullable: true }) description: string | null; - @AutoMap(() => String) - @Column({ - type: 'timestamptz', - comment: 'Condition Completion date', - nullable: true, - }) - completionDate?: Date | null; - @ManyToOne(() => ApplicationDecisionConditionType) type: ApplicationDecisionConditionType; @@ -86,11 +79,6 @@ export class ApplicationDecisionCondition extends Base { }) conditionToComponentsWithPlanNumber: ApplicationDecisionConditionComponentPlanNumber[] | null; - @AutoMap() - @Column({ - type: 'timestamptz', - comment: 'Condition single end/due date', - nullable: true, - }) - singleDate?: Date | null; + @OneToMany(() => ApplicationDecisionConditionDate, (d) => d.condition, { cascade: ['insert', 'update'] }) + dates: ApplicationDecisionConditionDate[]; } diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts index bf759c1553..9ccc4e8e6f 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.spec.ts @@ -11,12 +11,8 @@ import { ApplicationDecisionConditionService } from './application-decision-cond describe('ApplicationDecisionConditionService', () => { let service: ApplicationDecisionConditionService; - let mockApplicationDecisionConditionRepository: DeepMocked< - Repository - >; - let mockAppDecCondTypeRepository: DeepMocked< - Repository - >; + let mockApplicationDecisionConditionRepository: DeepMocked>; + let mockAppDecCondTypeRepository: DeepMocked>; let mockApplicationDecisionConditionComponentPlanNumber: DeepMocked< Repository >; @@ -42,23 +38,17 @@ describe('ApplicationDecisionConditionService', () => { useValue: mockAppDecCondTypeRepository, }, { - provide: getRepositoryToken( - ApplicationDecisionConditionComponentPlanNumber, - ), + provide: getRepositoryToken(ApplicationDecisionConditionComponentPlanNumber), useValue: mockApplicationDecisionConditionComponentPlanNumber, }, { - provide: getRepositoryToken( - ApplicationDecisionConditionToComponentLot, - ), + provide: getRepositoryToken(ApplicationDecisionConditionToComponentLot), useValue: mockApplicationDecisionConditionToComponentLot, }, ], }).compile(); - service = module.get( - ApplicationDecisionConditionService, - ); + service = module.get(ApplicationDecisionConditionService); }); it('should be defined', () => { @@ -66,37 +56,25 @@ describe('ApplicationDecisionConditionService', () => { }); it('should call repo to get one or fails with correct parameters', async () => { - mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( - new ApplicationDecisionCondition(), - ); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue(new ApplicationDecisionCondition()); const result = await service.getOneOrFail('fake'); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledTimes(1); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledWith({ + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledTimes(1); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledWith({ where: { uuid: 'fake' }, - relations: { type: true }, + relations: { type: true, dates: true }, }); expect(result).toBeDefined(); }); it('calls remove method for deleted conditions', async () => { - const conditions = [ - new ApplicationDecisionCondition(), - new ApplicationDecisionCondition(), - ]; + const conditions = [new ApplicationDecisionCondition(), new ApplicationDecisionCondition()]; const mockTransaction = jest.fn(); - mockApplicationDecisionConditionRepository.manager.transaction = - mockTransaction; + mockApplicationDecisionConditionRepository.manager.transaction = mockTransaction; - mockApplicationDecisionConditionRepository.remove.mockResolvedValue( - {} as ApplicationDecisionCondition, - ); + mockApplicationDecisionConditionRepository.remove.mockResolvedValue({} as ApplicationDecisionCondition); mockApplicationDecisionConditionToComponentLot.find.mockResolvedValue([]); await service.remove(conditions); @@ -105,9 +83,7 @@ describe('ApplicationDecisionConditionService', () => { }); it('should create new components when given a DTO without a UUID', async () => { - mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( - new ApplicationDecisionCondition(), - ); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue(new ApplicationDecisionCondition()); const updateDtos: UpdateApplicationDecisionConditionDto[] = [{}, {}]; @@ -115,9 +91,7 @@ describe('ApplicationDecisionConditionService', () => { expect(result).toBeDefined(); expect(result.length).toBe(2); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledTimes(0); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledTimes(0); }); it('should update existing components when given a DTO with a UUID', async () => { @@ -135,134 +109,92 @@ describe('ApplicationDecisionConditionService', () => { expect(result).toBeDefined(); expect(result.length).toBe(1); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledTimes(1); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledWith({ + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledTimes(1); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledWith({ where: { uuid: 'uuid' }, - relations: { type: true }, + relations: { type: true, dates: true }, }); expect(result[0].uuid).toEqual(mockDto.uuid); }); it('should persist entity if persist flag is true', async () => { - mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( - new ApplicationDecisionCondition(), - ); - mockApplicationDecisionConditionRepository.save.mockResolvedValue( - new ApplicationDecisionCondition(), - ); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue(new ApplicationDecisionCondition()); + mockApplicationDecisionConditionRepository.save.mockResolvedValue(new ApplicationDecisionCondition()); const updateDtos: UpdateApplicationDecisionConditionDto[] = [{}]; const result = await service.createOrUpdate(updateDtos, [], [], true); expect(result).toBeDefined(); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledTimes(0); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledTimes(0); expect(mockApplicationDecisionConditionRepository.save).toBeCalledTimes(1); }); it('should not persist entity if persist flag is false', async () => { - mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( - new ApplicationDecisionCondition(), - ); - mockApplicationDecisionConditionRepository.save.mockResolvedValue( - new ApplicationDecisionCondition(), - ); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue(new ApplicationDecisionCondition()); + mockApplicationDecisionConditionRepository.save.mockResolvedValue(new ApplicationDecisionCondition()); const updateDtos: UpdateApplicationDecisionConditionDto[] = [{}]; const result = await service.createOrUpdate(updateDtos, [], [], false); expect(result).toBeDefined(); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toBeCalledTimes(0); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toBeCalledTimes(0); expect(mockApplicationDecisionConditionRepository.save).toBeCalledTimes(0); }); it('should call update on the repo and return the updated object', async () => { const mockCondition = new ApplicationDecisionCondition(); - mockApplicationDecisionConditionRepository.update.mockResolvedValue( - {} as any, - ); - mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue( - mockCondition, - ); + mockApplicationDecisionConditionRepository.update.mockResolvedValue({} as any); + mockApplicationDecisionConditionRepository.findOneOrFail.mockResolvedValue(mockCondition); const result = await service.update(mockCondition, { approvalDependant: false, }); - expect( - mockApplicationDecisionConditionRepository.update, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationDecisionConditionRepository.findOneOrFail, - ).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionRepository.update).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionRepository.findOneOrFail).toHaveBeenCalledTimes(1); expect(result).toEqual(mockCondition); }); it('should call findBy on the repo for getPlanNumbers', async () => { - mockApplicationDecisionConditionComponentPlanNumber.findBy.mockResolvedValue( - [], - ); + mockApplicationDecisionConditionComponentPlanNumber.findBy.mockResolvedValue([]); const planNumbers = await service.getPlanNumbers('uuid'); - expect( - mockApplicationDecisionConditionComponentPlanNumber.findBy, - ).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionComponentPlanNumber.findBy).toHaveBeenCalledTimes(1); expect(planNumbers).toBeDefined(); }); it('should create a new join record if one does not exist for updateConditionPlanNumbers', async () => { - mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue( - null, - ); + mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue(null); mockApplicationDecisionConditionComponentPlanNumber.save.mockResolvedValue( new ApplicationDecisionConditionComponentPlanNumber(), ); await service.updateConditionPlanNumbers('uuid', 'uuid', 'plan-number'); - expect( - mockApplicationDecisionConditionComponentPlanNumber.findOneBy, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationDecisionConditionComponentPlanNumber.save, - ).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionComponentPlanNumber.findOneBy).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionComponentPlanNumber.save).toHaveBeenCalledTimes(1); - const savedEntity = - mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; + const savedEntity = mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; expect(savedEntity.planNumbers).toEqual('plan-number'); }); it('should update the existing join record if one does exist for updateConditionPlanNumbers', async () => { const mockNumber = new ApplicationDecisionConditionComponentPlanNumber(); - mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue( - mockNumber, - ); + mockApplicationDecisionConditionComponentPlanNumber.findOneBy.mockResolvedValue(mockNumber); mockApplicationDecisionConditionComponentPlanNumber.save.mockResolvedValue( new ApplicationDecisionConditionComponentPlanNumber(), ); await service.updateConditionPlanNumbers('uuid', 'uuid', 'plan-number'); - expect( - mockApplicationDecisionConditionComponentPlanNumber.findOneBy, - ).toHaveBeenCalledTimes(1); - expect( - mockApplicationDecisionConditionComponentPlanNumber.save, - ).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionComponentPlanNumber.findOneBy).toHaveBeenCalledTimes(1); + expect(mockApplicationDecisionConditionComponentPlanNumber.save).toHaveBeenCalledTimes(1); - const savedEntity = - mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; + const savedEntity = mockApplicationDecisionConditionComponentPlanNumber.save.mock.calls[0][0]; expect(savedEntity).toBe(mockNumber); expect(savedEntity.planNumbers).toEqual('plan-number'); expect(mockNumber.planNumbers).toEqual('plan-number'); diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.ts index c8ef944359..6fef257ea9 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-condition/application-decision-condition.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; import { ServiceValidationException } from '../../../../../../libs/common/src/exceptions/base.exception'; -import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; import { ApplicationDecisionConditionToComponentLot } from '../application-condition-to-component-lot/application-decision-condition-to-component-lot.entity'; import { ApplicationDecisionConditionComponentPlanNumber } from '../application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity'; import { ApplicationDecisionComponent } from '../application-decision-v2/application-decision/component/application-decision-component.entity'; @@ -12,6 +11,7 @@ import { UpdateApplicationDecisionConditionServiceDto, } from './application-decision-condition.dto'; import { ApplicationDecisionCondition } from './application-decision-condition.entity'; +import { ApplicationDecisionConditionDate } from './application-decision-condition-date/application-decision-condition-date.entity'; @Injectable() export class ApplicationDecisionConditionService { @@ -26,11 +26,23 @@ export class ApplicationDecisionConditionService { private conditionComponentLotRepository: Repository, ) {} + async getByTypeCode(typeCode: string): Promise { + return this.repository.find({ + where: { + type: { + code: typeCode, + }, + }, + relations: ['dates'], + }); + } + async getOneOrFail(uuid: string) { return this.repository.findOneOrFail({ where: { uuid }, relations: { type: true, + dates: true, }, }); } @@ -63,20 +75,31 @@ export class ApplicationDecisionConditionService { condition.description = updateDto.description ?? null; condition.securityAmount = updateDto.securityAmount ?? null; condition.approvalDependant = updateDto.approvalDependant ?? null; - condition.singleDate = updateDto.singleDate ? formatIncomingDate(updateDto.singleDate) : null; + if (updateDto.dates) { + condition.dates = updateDto.dates.map((dateDto) => { + const dateEntity = new ApplicationDecisionConditionDate(); - if ( - updateDto.componentsToCondition !== undefined && - updateDto.componentsToCondition.length > 0 - ) { + if (dateDto.date) { + dateEntity.date = new Date(dateDto.date); + } + if (dateDto.completedDate) { + dateEntity.completedDate = new Date(dateDto.completedDate); + } + if (dateDto.comment) { + dateEntity.comment = dateDto.comment; + } + + return dateEntity; + }); + } + + if (updateDto.componentsToCondition !== undefined && updateDto.componentsToCondition.length > 0) { const mappedComponents: ApplicationDecisionComponent[] = []; for (const componentToCondition of updateDto.componentsToCondition) { const matchingComponent = allComponents.find( (component) => - componentToCondition.componentDecisionUuid === - component.applicationDecisionUuid && - componentToCondition.componentToConditionType === - component.applicationDecisionComponentTypeCode, + componentToCondition.componentDecisionUuid === component.applicationDecisionUuid && + componentToCondition.componentToConditionType === component.applicationDecisionComponentTypeCode, ); if (matchingComponent) { @@ -87,8 +110,7 @@ export class ApplicationDecisionConditionService { const matchingComponent2 = newComponents.find( (component) => - componentToCondition.componentToConditionType === - component.applicationDecisionComponentTypeCode, + componentToCondition.componentToConditionType === component.applicationDecisionComponentTypeCode, ); if (matchingComponent2) { @@ -96,9 +118,7 @@ export class ApplicationDecisionConditionService { updatedConditions.push(condition); continue; } - throw new ServiceValidationException( - 'Failed to find matching component', - ); + throw new ServiceValidationException('Failed to find matching component'); } condition.components = mappedComponents; @@ -122,18 +142,13 @@ export class ApplicationDecisionConditionService { }, }); - await this.repository.manager.transaction( - async (transactionalEntityManager) => { - await transactionalEntityManager.remove(lots); - await transactionalEntityManager.remove(conditions); - }, - ); + await this.repository.manager.transaction(async (transactionalEntityManager) => { + await transactionalEntityManager.remove(lots); + await transactionalEntityManager.remove(conditions); + }); } - async update( - existingCondition: ApplicationDecisionCondition, - updates: UpdateApplicationDecisionConditionServiceDto, - ) { + async update(existingCondition: ApplicationDecisionCondition, updates: UpdateApplicationDecisionConditionServiceDto) { await this.repository.update(existingCondition.uuid, updates); return await this.getOneOrFail(existingCondition.uuid); } @@ -144,30 +159,22 @@ export class ApplicationDecisionConditionService { }); } - async updateConditionPlanNumbers( - conditionUuid: string, - componentUuid: string, - planNumbers: string | null, - ) { - let conditionToComponent = - await this.conditionComponentPlanNumbersRepository.findOneBy({ - applicationDecisionComponentUuid: componentUuid, - applicationDecisionConditionUuid: conditionUuid, - }); + async updateConditionPlanNumbers(conditionUuid: string, componentUuid: string, planNumbers: string | null) { + let conditionToComponent = await this.conditionComponentPlanNumbersRepository.findOneBy({ + applicationDecisionComponentUuid: componentUuid, + applicationDecisionConditionUuid: conditionUuid, + }); if (conditionToComponent) { conditionToComponent.planNumbers = planNumbers; } else { - conditionToComponent = - new ApplicationDecisionConditionComponentPlanNumber({ - applicationDecisionComponentUuid: componentUuid, - applicationDecisionConditionUuid: conditionUuid, - planNumbers, - }); + conditionToComponent = new ApplicationDecisionConditionComponentPlanNumber({ + applicationDecisionComponentUuid: componentUuid, + applicationDecisionConditionUuid: conditionUuid, + planNumbers, + }); } - await this.conditionComponentPlanNumbersRepository.save( - conditionToComponent, - ); + await this.conditionComponentPlanNumbersRepository.save(conditionToComponent); } } diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts index 168020a1ce..66aef3127a 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision-v2.module.ts @@ -47,6 +47,9 @@ import { ApplicationDecisionComponentType } from './application-decision/compone import { ApplicationDecisionComponentController } from './application-decision/component/application-decision-component.controller'; import { ApplicationDecisionComponent } from './application-decision/component/application-decision-component.entity'; import { ApplicationDecisionComponentService } from './application-decision/component/application-decision-component.service'; +import { ApplicationDecisionConditionDateService } from '../application-decision-condition/application-decision-condition-date/application-decision-condition-date.service'; +import { ApplicationDecisionConditionDate } from '../application-decision-condition/application-decision-condition-date/application-decision-condition-date.entity'; +import { ApplicationDecisionConditionDateController } from '../application-decision-condition/application-decision-condition-date/application-decision-condition-date.controller'; @Module({ imports: [ @@ -66,6 +69,7 @@ import { ApplicationDecisionComponentService } from './application-decision/comp ApplicationDecisionComponentType, ApplicationDecisionCondition, ApplicationDecisionConditionType, + ApplicationDecisionConditionDate, NaruSubtype, ApplicationSubmissionToSubmissionStatus, ApplicationSubmission, @@ -90,6 +94,7 @@ import { ApplicationDecisionComponentService } from './application-decision/comp ApplicationDecisionProfile, ApplicationDecisionComponentService, ApplicationDecisionConditionService, + ApplicationDecisionConditionDateService, ApplicationDecisionComponentLotService, ApplicationConditionToComponentLotService, ApplicationBoundaryAmendmentService, @@ -99,6 +104,7 @@ import { ApplicationDecisionComponentService } from './application-decision/comp ApplicationDecisionComponentController, ApplicationDecisionConditionController, ApplicationDecisionComponentLotController, + ApplicationDecisionConditionDateController, ApplicationConditionToComponentLotController, ApplicationBoundaryAmendmentController, ApplicationReconsiderationController, diff --git a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/application-decision-v2.service.ts b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/application-decision-v2.service.ts index 99093c442d..1cd7c68cac 100644 --- a/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/application-decision-v2.service.ts +++ b/services/apps/alcs/src/alcs/application-decision/application-decision-v2/application-decision/application-decision-v2.service.ts @@ -113,6 +113,7 @@ export class ApplicationDecisionV2Service { components: { lots: true, }, + dates: true, }, }, }); @@ -201,6 +202,7 @@ export class ApplicationDecisionV2Service { conditions: { type: true, components: true, + dates: true, }, chairReviewOutcome: true, }, @@ -678,7 +680,9 @@ export class ApplicationDecisionV2Service { relations: { application: true, components: true, - conditions: true, + conditions: { + dates: true, + }, }, }); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts index 731116e2ed..bf5ffa711b 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-code.entity.ts @@ -3,7 +3,10 @@ import { BaseCodeEntity } from '../../../common/entities/base.code.entity'; import { AutoMap } from 'automapper-classes'; import { ColumnNumericTransformer } from '../../../utils/column-numeric-transform'; import { NoticeOfIntentDecisionCondition } from './notice-of-intent-decision-condition.entity'; -import { DateLabel } from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; +import { + DateLabel, + DateType, +} from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; @Entity({ comment: 'Decision Condition Types Code Table for Notice of Intents', @@ -38,11 +41,15 @@ export class NoticeOfIntentDecisionConditionType extends BaseCodeEntity { @AutoMap() @Column({ default: false, type: 'boolean' }) - isSingleDateChecked: boolean; + isDateChecked: boolean; @AutoMap() @Column({ nullable: true, type: 'boolean' }) - isSingleDateRequired: boolean | null; + isDateRequired: boolean | null; + + @AutoMap() + @Column({ type: 'enum', enum: DateType, nullable: true }) + dateType: DateType | null; @AutoMap() @Column({ type: 'enum', enum: DateLabel, nullable: true }) diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.controller.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.controller.ts new file mode 100644 index 0000000000..d4b4a76d7c --- /dev/null +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.controller.ts @@ -0,0 +1,30 @@ +import { Body, Controller, Get, Param, Patch, Query, UseGuards } from '@nestjs/common'; +import { ApiOAuth2 } from '@nestjs/swagger'; +import * as config from 'config'; +import { RolesGuard } from '../../../../common/authorization/roles-guard.service'; +import { UserRoles } from '../../../../common/authorization/roles.decorator'; +import { ROLES_ALLOWED_APPLICATIONS } from '../../../../common/authorization/roles'; +import { NoticeOfIntentDecisionConditionDateService } from './notice-of-intent-decision-condition-date.service'; +import { NoticeOfIntentDecisionConditionDateDto } from './notice-of-intent-decision-condition-date.dto'; + +@Controller('notice-of-intent-decision-condition/date') +@ApiOAuth2(config.get('KEYCLOAK.SCOPES')) +@UseGuards(RolesGuard) +export class NoticeOfIntentDecisionConditionDateController { + constructor(private service: NoticeOfIntentDecisionConditionDateService) {} + + @Get('') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async fetch(@Query('conditionUuid') conditionUuid): Promise { + return await this.service.fetchByCondition(conditionUuid); + } + + @Patch('/:uuid') + @UserRoles(...ROLES_ALLOWED_APPLICATIONS) + async update( + @Param('uuid') uuid: string, + @Body() dto: NoticeOfIntentDecisionConditionDateDto, + ): Promise { + return await this.service.update(uuid, dto); + } +} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.dto.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.dto.ts new file mode 100644 index 0000000000..8c77649889 --- /dev/null +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.dto.ts @@ -0,0 +1,18 @@ +import { Transform } from 'class-transformer'; +import { IsNumber, IsString } from 'class-validator'; + +export class NoticeOfIntentDecisionConditionDateDto { + @IsString() + uuid?: string; + + @Transform(({ value }) => value.getTime()) + @IsNumber() + date?: number; + + @Transform(({ value }) => value && value.getTime()) + @IsNumber() + completedDate?: number | null; + + @IsString() + comment?: string | null; +} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity.ts new file mode 100644 index 0000000000..48669bda3a --- /dev/null +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity.ts @@ -0,0 +1,33 @@ +import { AutoMap } from 'automapper-classes'; +import { Column, Entity, ManyToOne } from 'typeorm'; +import { Base } from '../../../../common/entities/base.entity'; +import { NoticeOfIntentDecisionCondition } from '../notice-of-intent-decision-condition.entity'; + +@Entity({ comment: 'Due/end dates for conditions' }) +export class NoticeOfIntentDecisionConditionDate extends Base { + constructor(data?: Partial) { + super(); + if (data) { + Object.assign(this, data); + } + } + + @AutoMap() + @Column({ type: 'timestamptz' }) + date: Date; + + @AutoMap() + @Column({ type: 'timestamptz', nullable: true }) + completedDate: Date | null; + + @AutoMap() + @ManyToOne(() => NoticeOfIntentDecisionCondition, { + cascade: true, + onDelete: 'CASCADE', + }) + condition: NoticeOfIntentDecisionCondition; + + @AutoMap() + @Column({ type: 'text', nullable: true }) + comment: string | null; +} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.service.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.service.ts new file mode 100644 index 0000000000..0f4ce4e6ee --- /dev/null +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.service.ts @@ -0,0 +1,73 @@ +import { Injectable } from '@nestjs/common'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { NoticeOfIntentDecisionConditionDate } from './notice-of-intent-decision-condition-date.entity'; +import { ServiceNotFoundException, ServiceValidationException } from '@app/common/exceptions/base.exception'; +import { NoticeOfIntentDecisionCondition } from '../notice-of-intent-decision-condition.entity'; +import { NoticeOfIntentDecisionConditionDateDto } from './notice-of-intent-decision-condition-date.dto'; +import { plainToInstance } from 'class-transformer'; + +@Injectable() +export class NoticeOfIntentDecisionConditionDateService { + constructor( + @InjectRepository(NoticeOfIntentDecisionConditionDate) + private repository: Repository, + @InjectRepository(NoticeOfIntentDecisionCondition) + private conditionRepository: Repository, + ) {} + + async fetchByCondition(conditionUuid: string): Promise { + const condition = await this.conditionRepository.findOne({ + where: { + uuid: conditionUuid, + }, + }); + + if (!condition) { + throw new ServiceNotFoundException('Condition not found.'); + } + + const entities = await this.repository.find({ + where: { + condition: { + uuid: condition.uuid, + }, + }, + }); + + const dtos = plainToInstance(NoticeOfIntentDecisionConditionDateDto, entities); + + return dtos; + } + + async update( + uuid: string, + dto: NoticeOfIntentDecisionConditionDateDto, + ): Promise { + const entity = await this.repository.findOneOrFail({ + where: { uuid }, + }); + + if (!entity) { + throw new ServiceNotFoundException('Date not found.'); + } + + this.map(dto, entity); + + const updatedEntity = await this.repository.save(entity); + + return plainToInstance(NoticeOfIntentDecisionConditionDateDto, updatedEntity); + } + + map(dto: NoticeOfIntentDecisionConditionDateDto, entity: NoticeOfIntentDecisionConditionDate) { + if (dto.date) { + entity.date = new Date(dto.date); + } + if (dto.completedDate) { + entity.completedDate = new Date(dto.completedDate); + } + if (dto.comment) { + entity.comment = dto.comment; + } + } +} diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.spec.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.spec.ts index 16902375ff..7b5213ef36 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.spec.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.spec.ts @@ -55,7 +55,6 @@ describe('NoticeOfIntentDecisionConditionController', () => { securityAmount: 1000, administrativeFee: 50, description: 'example description', - completionDate: date.getTime(), }; const condition = new NoticeOfIntentDecisionCondition({ @@ -64,7 +63,6 @@ describe('NoticeOfIntentDecisionConditionController', () => { securityAmount: 500, administrativeFee: 25, description: 'existing description', - completionDate: new Date(), }); const updated = new NoticeOfIntentDecisionCondition({ @@ -73,7 +71,6 @@ describe('NoticeOfIntentDecisionConditionController', () => { securityAmount: updates.securityAmount, administrativeFee: updates.administrativeFee, description: updates.description, - completionDate: date, }); mockNOIDecisionConditionService.getOneOrFail.mockResolvedValue(condition); @@ -84,9 +81,7 @@ describe('NoticeOfIntentDecisionConditionController', () => { expect(mockNOIDecisionConditionService.getOneOrFail).toHaveBeenCalledWith(uuid); expect(mockNOIDecisionConditionService.update).toHaveBeenCalledWith(condition, { ...updates, - completionDate: date, }); - expect(new Date(result.completionDate!)).toEqual(updated.completionDate); expect(result.description).toEqual(updated.description); expect(result.administrativeFee).toEqual(updated.administrativeFee); expect(result.securityAmount).toEqual(updated.securityAmount); diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.ts index 8b1aeb3561..fa2b4be1c5 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.controller.ts @@ -1,12 +1,11 @@ import { Mapper } from 'automapper-core'; import { InjectMapper } from 'automapper-nestjs'; -import { Body, Controller, Param, Patch, UseGuards } from '@nestjs/common'; +import { Body, Controller, Get, Param, Patch, Query, UseGuards } from '@nestjs/common'; import { ApiOAuth2 } from '@nestjs/swagger'; import * as config from 'config'; import { ANY_AUTH_ROLE } from '../../../common/authorization/roles'; import { RolesGuard } from '../../../common/authorization/roles-guard.service'; import { UserRoles } from '../../../common/authorization/roles.decorator'; -import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; import { NoticeOfIntentDecisionConditionDto, UpdateNoticeOfIntentDecisionConditionDto, @@ -23,6 +22,12 @@ export class NoticeOfIntentDecisionConditionController { @InjectMapper() private mapper: Mapper, ) {} + @Get() + @UserRoles(...ANY_AUTH_ROLE) + async getByTypeCode(@Query('type_code') typeCode: string) { + return await this.conditionService.getByTypeCode(typeCode); + } + @Patch('/:uuid') @UserRoles(...ANY_AUTH_ROLE) async update(@Param('uuid') uuid: string, @Body() updates: UpdateNoticeOfIntentDecisionConditionDto) { @@ -33,8 +38,6 @@ export class NoticeOfIntentDecisionConditionController { securityAmount: updates.securityAmount, administrativeFee: updates.administrativeFee, description: updates.description, - completionDate: formatIncomingDate(updates.completionDate), - singleDate: formatIncomingDate(updates.singleDate), }); return await this.mapper.mapAsync( updatedCondition, diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.dto.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.dto.ts index ed26400a89..9e291226cf 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.dto.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.dto.ts @@ -2,8 +2,12 @@ import { AutoMap } from 'automapper-classes'; import { IsArray, IsBoolean, IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; import { BaseCodeDto } from '../../../common/dtos/base.dto'; import { NoticeOfIntentDecisionComponentDto } from '../notice-of-intent-decision-component/notice-of-intent-decision-component.dto'; -import { DateLabel } from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; +import { + DateLabel, + DateType, +} from '../../application-decision/application-decision-condition/application-decision-condition-code.entity'; import { Type } from 'class-transformer'; +import { NoticeOfIntentDecisionConditionDateDto } from './notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.dto'; export class NoticeOfIntentDecisionConditionTypeDto extends BaseCodeDto { @IsBoolean() @@ -35,16 +39,22 @@ export class NoticeOfIntentDecisionConditionTypeDto extends BaseCodeDto { @AutoMap() @IsBoolean() - isSingleDateChecked: boolean; + isDateChecked: boolean; @AutoMap() @IsBoolean() - isSingleDateRequired: boolean | null; + isDateRequired: boolean | null; @AutoMap() @IsEnum(DateLabel) + @IsOptional() singleDateLabel: DateLabel | null; + @AutoMap() + @IsEnum(DateType) + @IsOptional() + dateType: DateType | null; + @AutoMap() @IsBoolean() isSecurityAmountChecked: boolean; @@ -77,13 +87,10 @@ export class NoticeOfIntentDecisionConditionDto { componentUuid: string | null; @AutoMap() - completionDate?: number; - - @AutoMap() - singleDate?: number; + components?: NoticeOfIntentDecisionComponentDto[]; @AutoMap() - components?: NoticeOfIntentDecisionComponentDto[]; + dates?: NoticeOfIntentDecisionConditionDateDto[]; } export class ComponentToConditionDto { @@ -126,12 +133,8 @@ export class UpdateNoticeOfIntentDecisionConditionDto { type?: NoticeOfIntentDecisionConditionTypeDto; @IsOptional() - @IsNumber() - completionDate?: number; - - @IsOptional() - @IsNumber() - singleDate?: number; + @AutoMap() + dates?: NoticeOfIntentDecisionConditionDateDto[]; } export class UpdateNoticeOfIntentDecisionConditionServiceDto { @@ -141,6 +144,5 @@ export class UpdateNoticeOfIntentDecisionConditionServiceDto { securityAmount?: number; administrativeFee?: number; description?: string; - completionDate?: Date | null; - singleDate?: Date | null; + dates?: NoticeOfIntentDecisionConditionDateDto[]; } diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts index f0a7fee000..fe5c2ac063 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.entity.ts @@ -1,10 +1,11 @@ import { AutoMap } from 'automapper-classes'; -import { Column, Entity, JoinTable, ManyToMany, ManyToOne } from 'typeorm'; +import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm'; import { Base } from '../../../common/entities/base.entity'; import { ColumnNumericTransformer } from '../../../utils/column-numeric-transform'; import { NoticeOfIntentDecisionComponent } from '../notice-of-intent-decision-component/notice-of-intent-decision-component.entity'; import { NoticeOfIntentDecision } from '../notice-of-intent-decision.entity'; import { NoticeOfIntentDecisionConditionType } from './notice-of-intent-decision-condition-code.entity'; +import { NoticeOfIntentDecisionConditionDate } from './notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity'; @Entity({ comment: 'Decision Conditions for Notice of Intents', @@ -45,14 +46,6 @@ export class NoticeOfIntentDecisionCondition extends Base { @Column({ type: 'text', nullable: true }) description: string | null; - @AutoMap(() => String) - @Column({ - type: 'timestamptz', - comment: 'Condition Completion date', - nullable: true, - }) - completionDate?: Date | null; - @ManyToOne(() => NoticeOfIntentDecisionConditionType) type: NoticeOfIntentDecisionConditionType; @@ -82,11 +75,8 @@ export class NoticeOfIntentDecisionCondition extends Base { }) oatsConditionId: number; - @AutoMap() - @Column({ - type: 'timestamptz', - comment: 'Condition single end/due date', - nullable: true, + @OneToMany(() => NoticeOfIntentDecisionConditionDate, (d) => d.condition, { + cascade: ['insert', 'update'], }) - singleDate?: Date | null; + dates: NoticeOfIntentDecisionConditionDate[]; } diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts index b6272f3c0d..2afecce718 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition.service.ts @@ -9,7 +9,7 @@ import { UpdateNoticeOfIntentDecisionConditionServiceDto, } from './notice-of-intent-decision-condition.dto'; import { NoticeOfIntentDecisionCondition } from './notice-of-intent-decision-condition.entity'; -import { formatIncomingDate } from '../../../utils/incoming-date.formatter'; +import { NoticeOfIntentDecisionConditionDate } from './notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity'; @Injectable() export class NoticeOfIntentDecisionConditionService { @@ -20,6 +20,17 @@ export class NoticeOfIntentDecisionConditionService { private typeRepository: Repository, ) {} + async getByTypeCode(typeCode: string): Promise { + return this.repository.find({ + where: { + type: { + code: typeCode, + }, + }, + relations: ['dates'], + }); + } + async getOneOrFail(uuid: string) { return this.repository.findOneOrFail({ where: { uuid }, @@ -57,20 +68,31 @@ export class NoticeOfIntentDecisionConditionService { condition.description = updateDto.description ?? null; condition.securityAmount = updateDto.securityAmount ?? null; condition.approvalDependant = updateDto.approvalDependant ?? null; - condition.singleDate = updateDto.singleDate ? formatIncomingDate(updateDto.singleDate) : null; + if (updateDto.dates) { + condition.dates = updateDto.dates.map((dateDto) => { + const dateEntity = new NoticeOfIntentDecisionConditionDate(); + + if (dateDto.date) { + dateEntity.date = new Date(dateDto.date); + } + if (dateDto.completedDate) { + dateEntity.completedDate = new Date(dateDto.completedDate); + } + if (dateDto.comment) { + dateEntity.comment = dateDto.comment; + } + + return dateEntity; + }); + } - if ( - updateDto.componentsToCondition !== undefined && - updateDto.componentsToCondition.length > 0 - ) { + if (updateDto.componentsToCondition !== undefined && updateDto.componentsToCondition.length > 0) { const mappedComponents: NoticeOfIntentDecisionComponent[] = []; for (const componentToCondition of updateDto.componentsToCondition) { const matchingComponent = allComponents.find( (component) => - componentToCondition.componentDecisionUuid === - component.noticeOfIntentDecisionUuid && - componentToCondition.componentToConditionType === - component.noticeOfIntentDecisionComponentTypeCode, + componentToCondition.componentDecisionUuid === component.noticeOfIntentDecisionUuid && + componentToCondition.componentToConditionType === component.noticeOfIntentDecisionComponentTypeCode, ); if (matchingComponent) { @@ -81,8 +103,7 @@ export class NoticeOfIntentDecisionConditionService { const matchingComponent2 = newComponents.find( (component) => - componentToCondition.componentToConditionType === - component.noticeOfIntentDecisionComponentTypeCode, + componentToCondition.componentToConditionType === component.noticeOfIntentDecisionComponentTypeCode, ); if (matchingComponent2) { @@ -90,9 +111,7 @@ export class NoticeOfIntentDecisionConditionService { updatedConditions.push(condition); continue; } - throw new ServiceValidationException( - 'Failed to find matching component', - ); + throw new ServiceValidationException('Failed to find matching component'); } condition.components = mappedComponents; diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v2/notice-of-intent-decision-v2.service.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v2/notice-of-intent-decision-v2.service.ts index dc40e9aa67..f70fb1aea9 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v2/notice-of-intent-decision-v2.service.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision-v2/notice-of-intent-decision-v2.service.ts @@ -94,6 +94,7 @@ export class NoticeOfIntentDecisionV2Service { conditions: { type: true, components: true, + dates: true, }, }, }); @@ -159,6 +160,7 @@ export class NoticeOfIntentDecisionV2Service { conditions: { type: true, components: true, + dates: true, }, }, }); @@ -605,17 +607,18 @@ export class NoticeOfIntentDecisionV2Service { } private async getOrFail(uuid: string) { - const existingDecision = - await this.noticeOfIntentDecisionRepository.findOne({ - where: { - uuid, - }, - relations: { - noticeOfIntent: true, - components: true, - conditions: true, + const existingDecision = await this.noticeOfIntentDecisionRepository.findOne({ + where: { + uuid, + }, + relations: { + noticeOfIntent: true, + components: true, + conditions: { + dates: true, }, - }); + }, + }); if (!existingDecision) { throw new ServiceNotFoundException( diff --git a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts index 4583021b7c..1361b06718 100644 --- a/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts +++ b/services/apps/alcs/src/alcs/notice-of-intent-decision/notice-of-intent-decision.module.ts @@ -24,6 +24,9 @@ import { NoticeOfIntentModificationOutcomeType } from './notice-of-intent-modifi import { NoticeOfIntentModificationController } from './notice-of-intent-modification/notice-of-intent-modification.controller'; import { NoticeOfIntentModification } from './notice-of-intent-modification/notice-of-intent-modification.entity'; import { NoticeOfIntentModificationService } from './notice-of-intent-modification/notice-of-intent-modification.service'; +import { NoticeOfIntentDecisionConditionDate } from './notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity'; +import { NoticeOfIntentDecisionConditionDateService } from './notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.service'; +import { NoticeOfIntentDecisionConditionDateController } from './notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.controller'; @Module({ imports: [ @@ -37,6 +40,7 @@ import { NoticeOfIntentModificationService } from './notice-of-intent-modificati NoticeOfIntentDecisionComponentType, NoticeOfIntentDecisionCondition, NoticeOfIntentDecisionConditionType, + NoticeOfIntentDecisionConditionDate, ]), forwardRef(() => BoardModule), CardModule, @@ -49,6 +53,7 @@ import { NoticeOfIntentModificationService } from './notice-of-intent-modificati NoticeOfIntentDecisionV2Service, NoticeOfIntentDecisionComponentService, NoticeOfIntentDecisionConditionService, + NoticeOfIntentDecisionConditionDateService, NoticeOfIntentDecisionProfile, NoticeOfIntentModificationService, ], @@ -57,6 +62,7 @@ import { NoticeOfIntentModificationService } from './notice-of-intent-modificati NoticeOfIntentModificationController, NoticeOfIntentDecisionComponentController, NoticeOfIntentDecisionConditionController, + NoticeOfIntentDecisionConditionDateController, ], exports: [NoticeOfIntentModificationService, NoticeOfIntentDecisionV2Service], }) diff --git a/services/apps/alcs/src/common/automapper/application-decision-v2.automapper.profile.ts b/services/apps/alcs/src/common/automapper/application-decision-v2.automapper.profile.ts index fcaa262470..90cbfdf519 100644 --- a/services/apps/alcs/src/common/automapper/application-decision-v2.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/application-decision-v2.automapper.profile.ts @@ -40,6 +40,8 @@ import { ApplicationDecisionConditionToComponentLotDto } from '../../alcs/applic import { ApplicationDecisionConditionToComponentLot } from '../../alcs/application-decision/application-condition-to-component-lot/application-decision-condition-to-component-lot.entity'; import { ApplicationDecisionConditionComponentPlanNumber } from '../../alcs/application-decision/application-decision-component-to-condition/application-decision-component-to-condition-plan-number.entity'; import { CommissionerDecisionDto } from '../../alcs/commissioner/commissioner.dto'; +import { ApplicationDecisionConditionDate } from '../../alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.entity'; +import { ApplicationDecisionConditionDateDto } from '../../alcs/application-decision/application-decision-condition/application-decision-condition-date/application-decision-condition-date.dto'; @Injectable() export class ApplicationDecisionProfile extends AutomapperProfile { @@ -208,14 +210,6 @@ export class ApplicationDecisionProfile extends AutomapperProfile { mapper, ApplicationDecisionCondition, ApplicationDecisionConditionDto, - forMember( - (ad) => ad.completionDate, - mapFrom((a) => a.completionDate?.getTime()), - ), - forMember( - (ad) => ad.singleDate, - mapFrom((a) => a.singleDate?.getTime()), - ), forMember( (ad) => ad.components, mapFrom((a) => @@ -224,6 +218,32 @@ export class ApplicationDecisionProfile extends AutomapperProfile { : [], ), ), + forMember( + (dto) => dto.dates, + mapFrom((entity) => + entity.dates + ? this.mapper.mapArray( + entity.dates, + ApplicationDecisionConditionDate, + ApplicationDecisionConditionDateDto, + ) + : [], + ), + ), + ); + + createMap( + mapper, + ApplicationDecisionConditionDate, + ApplicationDecisionConditionDateDto, + forMember( + (dto) => dto.date, + mapFrom((entity) => entity.date.getTime()), + ), + forMember( + (dto) => dto.completedDate, + mapFrom((entity) => entity.completedDate && entity.completedDate.getTime()), + ), ); createMap( @@ -241,8 +261,12 @@ export class ApplicationDecisionProfile extends AutomapperProfile { mapFrom((entity) => (entity.administrativeFeeAmount !== null ? entity.administrativeFeeAmount : null)), ), forMember( - (dto) => dto.isSingleDateRequired, - mapFrom((entity) => (entity.isSingleDateRequired !== null ? entity.isSingleDateRequired : null)), + (dto) => dto.isDateRequired, + mapFrom((entity) => (entity.isDateRequired !== null ? entity.isDateRequired : null)), + ), + forMember( + (dto) => dto.dateType, + mapFrom((entity) => (entity.dateType !== null ? entity.dateType : null)), ), forMember( (dto) => dto.singleDateLabel, diff --git a/services/apps/alcs/src/common/automapper/notice-of-intent-decision.automapper.profile.ts b/services/apps/alcs/src/common/automapper/notice-of-intent-decision.automapper.profile.ts index 21bdc1122d..5cfae233ff 100644 --- a/services/apps/alcs/src/common/automapper/notice-of-intent-decision.automapper.profile.ts +++ b/services/apps/alcs/src/common/automapper/notice-of-intent-decision.automapper.profile.ts @@ -36,6 +36,8 @@ import { NoticeOfIntentSubmissionStatusType } from '../../alcs/notice-of-intent/ import { NoticeOfIntentStatusDto } from '../../alcs/notice-of-intent/notice-of-intent-submission-status/notice-of-intent-status.dto'; import { NoticeOfIntent } from '../../alcs/notice-of-intent/notice-of-intent.entity'; import { NoticeOfIntentPortalDecisionDto } from '../../portal/public/notice-of-intent/notice-of-intent-decision.dto'; +import { NoticeOfIntentDecisionConditionDate } from '../../alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.entity'; +import { NoticeOfIntentDecisionConditionDateDto } from '../../alcs/notice-of-intent-decision/notice-of-intent-decision-condition/notice-of-intent-decision-condition-date/notice-of-intent-decision-condition-date.dto'; @Injectable() export class NoticeOfIntentDecisionProfile extends AutomapperProfile { @@ -141,14 +143,6 @@ export class NoticeOfIntentDecisionProfile extends AutomapperProfile { mapper, NoticeOfIntentDecisionCondition, NoticeOfIntentDecisionConditionDto, - forMember( - (ad) => ad.completionDate, - mapFrom((a) => a.completionDate?.getTime()), - ), - forMember( - (ad) => ad.singleDate, - mapFrom((a) => a.singleDate?.getTime()), - ), forMember( (ad) => ad.components, mapFrom((a) => @@ -157,6 +151,32 @@ export class NoticeOfIntentDecisionProfile extends AutomapperProfile { : [], ), ), + forMember( + (dto) => dto.dates, + mapFrom((entity) => + entity.dates + ? this.mapper.mapArray( + entity.dates, + NoticeOfIntentDecisionConditionDate, + NoticeOfIntentDecisionConditionDateDto, + ) + : [], + ), + ), + ); + + createMap( + mapper, + NoticeOfIntentDecisionConditionDate, + NoticeOfIntentDecisionConditionDateDto, + forMember( + (dto) => dto.date, + mapFrom((entity) => entity.date.getTime()), + ), + forMember( + (dto) => dto.completedDate, + mapFrom((entity) => entity.completedDate && entity.completedDate.getTime()), + ), ); createMap( @@ -174,8 +194,12 @@ export class NoticeOfIntentDecisionProfile extends AutomapperProfile { mapFrom((entity) => (entity.administrativeFeeAmount !== null ? entity.administrativeFeeAmount : null)), ), forMember( - (dto) => dto.isSingleDateRequired, - mapFrom((entity) => (entity.isSingleDateRequired !== null ? entity.isSingleDateRequired : null)), + (dto) => dto.isDateRequired, + mapFrom((entity) => (entity.isDateRequired !== null ? entity.isDateRequired : null)), + ), + forMember( + (dto) => dto.dateType, + mapFrom((entity) => (entity.dateType !== null ? entity.dateType : null)), ), forMember( (dto) => dto.singleDateLabel, diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1732822820962-create_condition_date_table.ts b/services/apps/alcs/src/providers/typeorm/migrations/1732822820962-create_condition_date_table.ts new file mode 100644 index 0000000000..3905fbfca6 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1732822820962-create_condition_date_table.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CreateConditionDateTable1732822820962 implements MigrationInterface { + name = 'CreateConditionDateTable1732822820962' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "alcs"."application_decision_condition_date" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "date" TIMESTAMP WITH TIME ZONE NOT NULL, "comment" text NOT NULL DEFAULT '', "condition_uuid" uuid, CONSTRAINT "PK_975ef9c1b95fa790b980eb8038a" PRIMARY KEY ("uuid"))`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."application_decision_condition_date" IS 'Due/end dates for conditions'`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition" DROP COLUMN "is_active"`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ADD CONSTRAINT "FK_b7a541bf441dc27322bd2acc473" FOREIGN KEY ("condition_uuid") REFERENCES "alcs"."application_decision_condition"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" DROP CONSTRAINT "FK_b7a541bf441dc27322bd2acc473"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition" ADD "is_active" boolean NOT NULL DEFAULT true`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."application_decision_condition_date" IS NULL`); + await queryRunner.query(`DROP TABLE "alcs"."application_decision_condition_date"`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1732918376279-add_multiple_date_to_condition_type.ts b/services/apps/alcs/src/providers/typeorm/migrations/1732918376279-add_multiple_date_to_condition_type.ts new file mode 100644 index 0000000000..818d0ffe4a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1732918376279-add_multiple_date_to_condition_type.ts @@ -0,0 +1,77 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddMultipleDateToConditionType1732918376279 implements MigrationInterface { + name = 'AddMultipleDateToConditionType1732918376279'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_single_date_required"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_single_date_required"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_date_checked" boolean NOT NULL DEFAULT false`, + ); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_date_required" boolean`); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_multiple_date_checked" boolean`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_date_checked" boolean NOT NULL DEFAULT false`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_date_required" boolean`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_multiple_date_checked" boolean`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ALTER COLUMN "is_single_date_checked" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ALTER COLUMN "is_single_date_checked" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ALTER COLUMN "is_single_date_checked" DROP NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ALTER COLUMN "is_single_date_checked" DROP DEFAULT`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ALTER COLUMN "is_single_date_checked" SET DEFAULT false`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ALTER COLUMN "is_single_date_checked" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ALTER COLUMN "is_single_date_checked" SET DEFAULT false`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ALTER COLUMN "is_single_date_checked" SET NOT NULL`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_multiple_date_checked"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_date_required"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_date_checked"`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_multiple_date_checked"`, + ); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_date_required"`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_date_checked"`); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_single_date_required" boolean`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_single_date_required" boolean`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1733165843038-convert_condition_date_type_two_bools_to_enum.ts b/services/apps/alcs/src/providers/typeorm/migrations/1733165843038-convert_condition_date_type_two_bools_to_enum.ts new file mode 100644 index 0000000000..814d1edc3a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1733165843038-convert_condition_date_type_two_bools_to_enum.ts @@ -0,0 +1,28 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ConvertConditionDateTypeTwoBoolsToEnum1733165843038 implements MigrationInterface { + name = 'ConvertConditionDateTypeTwoBoolsToEnum1733165843038' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_single_date_checked"`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "is_multiple_date_checked"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_single_date_checked"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "is_multiple_date_checked"`); + await queryRunner.query(`CREATE TYPE "alcs"."application_decision_condition_type_date_type_enum" AS ENUM('Single', 'Multiple')`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" ADD "date_type" "alcs"."application_decision_condition_type_date_type_enum"`); + await queryRunner.query(`CREATE TYPE "alcs"."notice_of_intent_decision_condition_type_date_type_enum" AS ENUM('Single', 'Multiple')`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "date_type" "alcs"."notice_of_intent_decision_condition_type_date_type_enum"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" DROP COLUMN "date_type"`); + await queryRunner.query(`DROP TYPE "alcs"."notice_of_intent_decision_condition_type_date_type_enum"`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" DROP COLUMN "date_type"`); + await queryRunner.query(`DROP TYPE "alcs"."application_decision_condition_type_date_type_enum"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_multiple_date_checked" boolean`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_type" ADD "is_single_date_checked" boolean`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_multiple_date_checked" boolean`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_type" ADD "is_single_date_checked" boolean`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1733167350680-add_noi_condition_dates_table.ts b/services/apps/alcs/src/providers/typeorm/migrations/1733167350680-add_noi_condition_dates_table.ts new file mode 100644 index 0000000000..efea2f3854 --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1733167350680-add_noi_condition_dates_table.ts @@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddNoiConditionDatesTable1733167350680 implements MigrationInterface { + name = 'AddNoiConditionDatesTable1733167350680' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "alcs"."notice_of_intent_decision_condition_date" ("audit_deleted_date_at" TIMESTAMP WITH TIME ZONE, "audit_created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "audit_updated_at" TIMESTAMP WITH TIME ZONE DEFAULT now(), "audit_created_by" character varying NOT NULL, "audit_updated_by" character varying, "uuid" uuid NOT NULL DEFAULT gen_random_uuid(), "date" TIMESTAMP WITH TIME ZONE NOT NULL, "comment" text NOT NULL DEFAULT '', "condition_uuid" uuid, CONSTRAINT "PK_1a1bb2685255cc143b6acbf1ff5" PRIMARY KEY ("uuid"))`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition_date" IS 'Due/end dates for conditions'`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ADD CONSTRAINT "FK_0bd2f73af4ca611761c69769fc2" FOREIGN KEY ("condition_uuid") REFERENCES "alcs"."notice_of_intent_decision_condition"("uuid") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" DROP CONSTRAINT "FK_0bd2f73af4ca611761c69769fc2"`); + await queryRunner.query(`COMMENT ON TABLE "alcs"."notice_of_intent_decision_condition_date" IS NULL`); + await queryRunner.query(`DROP TABLE "alcs"."notice_of_intent_decision_condition_date"`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1733171051779-remove_single_condition_dates.ts b/services/apps/alcs/src/providers/typeorm/migrations/1733171051779-remove_single_condition_dates.ts new file mode 100644 index 0000000000..f71f9cc1bf --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1733171051779-remove_single_condition_dates.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RemoveSingleConditionDates1733171051779 implements MigrationInterface { + name = 'RemoveSingleConditionDates1733171051779'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition" DROP COLUMN "completion_date"`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition" DROP COLUMN "single_date"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition" DROP COLUMN "completion_date"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition" DROP COLUMN "single_date"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition" ADD "single_date" TIMESTAMP WITH TIME ZONE`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."notice_of_intent_decision_condition" ADD "completion_date" TIMESTAMP WITH TIME ZONE`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition" ADD "single_date" TIMESTAMP WITH TIME ZONE`, + ); + await queryRunner.query( + `ALTER TABLE "alcs"."application_decision_condition" ADD "completion_date" TIMESTAMP WITH TIME ZONE`, + ); + } +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1733176559790-add_condition_date_completed_date.ts b/services/apps/alcs/src/providers/typeorm/migrations/1733176559790-add_condition_date_completed_date.ts new file mode 100644 index 0000000000..efc67ca13c --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1733176559790-add_condition_date_completed_date.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddConditionDateCompletedDate1733176559790 implements MigrationInterface { + name = 'AddConditionDateCompletedDate1733176559790' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ADD "completed_date" TIMESTAMP WITH TIME ZONE`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ADD "completed_date" TIMESTAMP WITH TIME ZONE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" DROP COLUMN "completed_date"`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" DROP COLUMN "completed_date"`); + } + +} diff --git a/services/apps/alcs/src/providers/typeorm/migrations/1733774036307-make_condition_date_comment_nullable.ts b/services/apps/alcs/src/providers/typeorm/migrations/1733774036307-make_condition_date_comment_nullable.ts new file mode 100644 index 0000000000..46d85d835a --- /dev/null +++ b/services/apps/alcs/src/providers/typeorm/migrations/1733774036307-make_condition_date_comment_nullable.ts @@ -0,0 +1,20 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class MakeConditionDateCommentNullable1733774036307 implements MigrationInterface { + name = 'MakeConditionDateCommentNullable1733774036307' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ALTER COLUMN "comment" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ALTER COLUMN "comment" DROP DEFAULT`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ALTER COLUMN "comment" DROP NOT NULL`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ALTER COLUMN "comment" DROP DEFAULT`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ALTER COLUMN "comment" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "alcs"."notice_of_intent_decision_condition_date" ALTER COLUMN "comment" SET NOT NULL`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ALTER COLUMN "comment" SET DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "alcs"."application_decision_condition_date" ALTER COLUMN "comment" SET NOT NULL`); + } + +}