From 3e0df1d696f280f20a8755824c1f43c5e38f095d Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Thu, 29 Feb 2024 17:16:34 -0500 Subject: [PATCH 01/10] New component determines which report a transaction *should* be linked to and displays it --- .../transaction-input.component.html | 16 +-- .../amount-input/amount-input.component.html | 3 + .../amount-input/amount-input.component.ts | 5 +- .../linked-report-input.component.html | 7 ++ .../linked-report-input.component.ts | 103 ++++++++++++++++++ .../src/app/shared/services/report.service.ts | 9 ++ front-end/src/app/shared/shared.module.ts | 3 + .../src/app/shared/utils/report-code.utils.ts | 6 +- 8 files changed, 138 insertions(+), 14 deletions(-) create mode 100644 front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html create mode 100644 front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts diff --git a/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html b/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html index 21966b76ad..b201b594dc 100644 --- a/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html +++ b/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html @@ -1,8 +1,6 @@
@@ -188,14 +186,12 @@

{{ transactionType.committeeCandidateHeader }}

- - + + + >LOOKUP + (OPTIONAL) +
+
+ +
diff --git a/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts b/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts index ebae3f1f43..b8f16226ff 100644 --- a/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts +++ b/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts @@ -31,7 +31,10 @@ export class AmountInputComponent extends BaseInputComponent implements OnInit, dateIsOutsideReport = false; // True if transaction date is outside the report dates contributionAmountInputStyleClass = ''; - constructor(private changeDetectorRef: ChangeDetectorRef, private store: Store) { + constructor( + private changeDetectorRef: ChangeDetectorRef, + private store: Store, + ) { super(); } diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html new file mode 100644 index 0000000000..eb1c9192e0 --- /dev/null +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html @@ -0,0 +1,7 @@ + +
+ + + +
+
diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts new file mode 100644 index 0000000000..82dcf6e17a --- /dev/null +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -0,0 +1,103 @@ +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectActiveReport } from 'app/store/active-report.selectors'; +import { firstValueFrom, takeUntil } from 'rxjs'; +import { BaseInputComponent } from '../base-input.component'; +import { Report, ReportTypes } from 'app/shared/models/report.model'; +import { ReportService, getReportFromJSON } from 'app/shared/services/report.service'; +import { Form3X } from 'app/shared/models/form-3x.model'; +import { ListRestResponse } from 'app/shared/models/rest-api.model'; +import { getReportCodeLabel } from 'app/shared/utils/report-code.utils'; +import { FecDatePipe } from 'app/shared/pipes/fec-date.pipe'; + +@Component({ + selector: 'app-linked-report-input', + templateUrl: './linked-report-input.component.html', +}) +export class LinkedReportInputComponent extends BaseInputComponent implements OnInit { + activeReport?: Report; + committeeF3xReports: Promise; + form24ReportType = ReportTypes.F24; + linkedReport?: Report; + + constructor( + private store: Store, + private reportService: ReportService, + ) { + super(); + this.committeeF3xReports = firstValueFrom(this.reportService.getAllReports()); + } + + ngOnInit(): void { + firstValueFrom(this.store.select(selectActiveReport)).then((report) => { + this.activeReport = report; + }); + + this.committeeF3xReports.then(() => { + this.setLinkedReport(); + }); + + this.form + .get(this.templateMap['date']) + ?.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.setLinkedReport(); + }); + this.form + .get(this.templateMap['date2']) + ?.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.setLinkedReport(); + }); + } + + setLinkedReport(): void { + this.getLinkedReport().then((report) => { + this.linkedReport = report; + if (this.linkedReport) { + this.linkedReport.toString = this.getForm3XLabel; + } + }); + } + + async getLinkedReport(): Promise { + const disseminationDate = this.form.get(this.templateMap['date2'])?.value as Date | undefined; + const disbursementDate = this.form.get(this.templateMap['date'])?.value as Date | undefined; + + const date = disbursementDate ?? disseminationDate; + if (date) { + const reports = await this.committeeF3xReports.then((response) => { + return response.results + .map((item) => { + return getReportFromJSON(item); + }) + .filter((report) => { + return report.report_type === ReportTypes.F3X; + }) as Form3X[]; + }); + + for (const report of reports) { + if (report.coverage_from_date && report.coverage_through_date) { + if (date >= report.coverage_from_date && date <= report.coverage_through_date) { + return report; + } + } + } + } + + return undefined; + } + + getForm3XLabel(): string { + const report = this as unknown as Form3X; + const datePipe = new FecDatePipe(); + + let label = getReportCodeLabel(report.report_code); + const stringsToRemove = [' MID-YEAR-REPORT', ' YEAR-END', ' QUARTERLY REPORT', ' MONTHLY REPORT']; + for (const string of stringsToRemove) { + label = label?.replaceAll(string, ''); + } + + return `${label}: ${datePipe.transform(report.coverage_from_date)} - ${datePipe.transform(report.coverage_through_date)}`; + } +} diff --git a/front-end/src/app/shared/services/report.service.ts b/front-end/src/app/shared/services/report.service.ts index d6eff3d357..707337abed 100644 --- a/front-end/src/app/shared/services/report.service.ts +++ b/front-end/src/app/shared/services/report.service.ts @@ -42,6 +42,15 @@ export class ReportService implements TableListService { ); } + public getAllReports(): Observable { + return this.apiService.get(this.apiEndpoint + '/').pipe( + map((response: ListRestResponse) => { + response.results = response.results.map((item) => getReportFromJSON(item)); + return response; + }), + ); + } + public get(reportId: string): Observable { return this.apiService .get(`${this.apiEndpoint}/${reportId}`) diff --git a/front-end/src/app/shared/shared.module.ts b/front-end/src/app/shared/shared.module.ts index 1164f598e4..d0ec5eaeb6 100644 --- a/front-end/src/app/shared/shared.module.ts +++ b/front-end/src/app/shared/shared.module.ts @@ -55,6 +55,7 @@ import { SupportOpposeInputComponent } from './components/inputs/support-oppose- import { SingleClickDirective } from './directives/single-click.directive'; import { RippleModule } from 'primeng/ripple'; import { CardModule } from 'primeng/card'; +import { LinkedReportInputComponent } from './components/inputs/linked-report-input/linked-report-input.component'; @NgModule({ imports: [ @@ -103,6 +104,7 @@ import { CardModule } from 'primeng/card'; EmployerInputComponent, CommitteeInputComponent, AmountInputComponent, + LinkedReportInputComponent, MemoCodeInputComponent, AdditionalInfoInputComponent, ElectionInputComponent, @@ -143,6 +145,7 @@ import { CardModule } from 'primeng/card'; EmployerInputComponent, CommitteeInputComponent, AmountInputComponent, + LinkedReportInputComponent, MemoCodeInputComponent, AdditionalInfoInputComponent, ElectionInputComponent, diff --git a/front-end/src/app/shared/utils/report-code.utils.ts b/front-end/src/app/shared/utils/report-code.utils.ts index fdb45dfb58..29bcbab840 100644 --- a/front-end/src/app/shared/utils/report-code.utils.ts +++ b/front-end/src/app/shared/utils/report-code.utils.ts @@ -38,7 +38,7 @@ export class F3xReportCode { label: string, coverageDatesFunction: | ((year: number, isElectionYear: boolean, filingFrequency: string) => [Date, Date]) - | undefined + | undefined, ) { this.code = code; this.label = label; @@ -50,7 +50,7 @@ function createCoverageFunction( startMonth: number, startDayOfMonth: number, endMonth: number, - endDayOfMonth: number + endDayOfMonth: number, ): (year: number, isElectionYear: boolean, filingFrequency: string) => [Date, Date] { return (year: number) => { return [new Date(year, startMonth, startDayOfMonth), new Date(year, endMonth, endDayOfMonth)]; @@ -81,7 +81,7 @@ export const F3X_REPORT_CODE_MAP = new Map([ ], [ F3xReportCodes.Q3, - new F3xReportCode(F3xReportCodes.Q3, 'OCTOBER 15 QUARTERLY REPORT(Q3)', createCoverageFunction(6, 1, 8, 30)), + new F3xReportCode(F3xReportCodes.Q3, 'OCTOBER 15 QUARTERLY REPORT (Q3)', createCoverageFunction(6, 1, 8, 30)), ], [F3xReportCodes.YE, new F3xReportCode(F3xReportCodes.YE, 'JANUARY 31 YEAR-END (YE)', getYearEndCoverageDates)], [F3xReportCodes.TER, new F3xReportCode(F3xReportCodes.TER, 'TERMINATION REPORT (TER)', undefined)], From 4d45f9fc444b08bdc7be882e82f6e4cd10c92ae4 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 5 Mar 2024 15:06:14 -0500 Subject: [PATCH 02/10] Refactors; adds new error messages; adds unit tests --- .../error-messages.component.html | 4 ++ .../error-messages.component.ts | 24 +++++++ .../linked-report-input.component.html | 12 ++-- .../linked-report-input.component.spec.ts | 72 +++++++++++++++++++ .../linked-report-input.component.ts | 60 +++++++++------- .../src/app/shared/services/report.service.ts | 13 ++-- .../src/app/shared/utils/validators.utils.ts | 19 +++++ 7 files changed, 164 insertions(+), 40 deletions(-) create mode 100644 front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts diff --git a/front-end/src/app/shared/components/error-messages/error-messages.component.html b/front-end/src/app/shared/components/error-messages/error-messages.component.html index e07c17fb8c..4d8685d0eb 100644 --- a/front-end/src/app/shared/components/error-messages/error-messages.component.html +++ b/front-end/src/app/shared/components/error-messages/error-messages.component.html @@ -11,6 +11,10 @@ {{ exclusiveMaxErrorMessage }} {{ exclusiveMinErrorMessage }} {{ invalidDateErrorMessage }} + {{ noDateProvidedErrorMessage }} + {{ + noCorrespondingForm3XErrorMessage + }} -
- - - -
+
+
+ + + +
+
diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts new file mode 100644 index 0000000000..45f70390fd --- /dev/null +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts @@ -0,0 +1,72 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { InputTextModule } from 'primeng/inputtext'; +import { ErrorMessagesComponent } from '../../error-messages/error-messages.component'; +import { testMockStore, testTemplateMap } from 'app/shared/utils/unit-test.utils'; +import { LinkedReportInputComponent } from './linked-report-input.component'; +import { FecDatePipe } from 'app/shared/pipes/fec-date.pipe'; +import { Form24 } from 'app/shared/models/form-24.model'; +import { provideMockStore } from '@ngrx/store/testing'; +import { ReportService } from 'app/shared/services/report.service'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { firstValueFrom, of } from 'rxjs'; +import { Form3X } from 'app/shared/models/form-3x.model'; +import { F3xReportCodes } from 'app/shared/utils/report-code.utils'; + +describe('LinkedReportInputComponent', () => { + let component: LinkedReportInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [LinkedReportInputComponent, ErrorMessagesComponent], + imports: [HttpClientTestingModule, InputTextModule, ReactiveFormsModule, FormsModule], + providers: [ReportService, FecDatePipe, provideMockStore(testMockStore)], + }).compileComponents(); + + fixture = TestBed.createComponent(LinkedReportInputComponent); + component = fixture.componentInstance; + component.templateMap = Object.assign(testTemplateMap, { + date2: 'other_date', + }); + component.activeReport = Form24.fromJSON({}); + component.form = new FormGroup({}); + component.form.addControl('other_date', new FormControl()); + component.form.addControl(testTemplateMap['date'], new FormControl()); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should try to determine the linked F3X report when the dates change', () => { + const spy = spyOn(component, 'setLinkedForm3X'); + + component.form.get('other_date')?.setValue('2025-02-12'); + component.form.get(testTemplateMap['date'])?.setValue('2025-02-12'); + expect(spy).toHaveBeenCalledTimes(2); + }); + + it('should determine the correct label', () => { + const testF3X = Form3X.fromJSON({ + coverage_from_date: '2020-01-15', + coverage_through_date: '2020-04-29', + report_code: F3xReportCodes.Q1, + }); + + component.committeeF3xReports = firstValueFrom(of([testF3X])); + + component.form.get('other_date')?.setValue(new Date('2020-02-21')); + component.getLinkedForm3X().then((report) => { + expect(report).toEqual(testF3X); + expect(component.getForm3XLabel(report)).toEqual('APRIL 15 (Q1): 01/15/2020 - 04/29/2020'); + }); + + component.form.get('other_date')?.setValue(new Date('2022-06-22')); + component.getLinkedForm3X().then((report) => { + expect(report).toEqual(undefined); + expect(component.getForm3XLabel(report)).toEqual(''); + }); + }); +}); diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index 82dcf6e17a..64262496f6 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -4,11 +4,12 @@ import { selectActiveReport } from 'app/store/active-report.selectors'; import { firstValueFrom, takeUntil } from 'rxjs'; import { BaseInputComponent } from '../base-input.component'; import { Report, ReportTypes } from 'app/shared/models/report.model'; -import { ReportService, getReportFromJSON } from 'app/shared/services/report.service'; +import { ReportService } from 'app/shared/services/report.service'; import { Form3X } from 'app/shared/models/form-3x.model'; -import { ListRestResponse } from 'app/shared/models/rest-api.model'; import { getReportCodeLabel } from 'app/shared/utils/report-code.utils'; import { FecDatePipe } from 'app/shared/pipes/fec-date.pipe'; +import { FormControl } from '@angular/forms'; +import { buildCorrespondingForm3XValidator } from 'app/shared/utils/validators.utils'; @Component({ selector: 'app-linked-report-input', @@ -16,64 +17,68 @@ import { FecDatePipe } from 'app/shared/pipes/fec-date.pipe'; }) export class LinkedReportInputComponent extends BaseInputComponent implements OnInit { activeReport?: Report; - committeeF3xReports: Promise; + committeeF3xReports: Promise; form24ReportType = ReportTypes.F24; - linkedReport?: Report; + linkedF3x?: Form3X; + linkedF3xLabel?: string; constructor( private store: Store, private reportService: ReportService, + private datePipe: FecDatePipe, ) { super(); - this.committeeF3xReports = firstValueFrom(this.reportService.getAllReports()); + this.committeeF3xReports = this.reportService.getAllReports(); } ngOnInit(): void { + this.form.addControl('linkedF3x', new FormControl()); + const dateControl = this.form.get(this.templateMap['date']); + const date2Control = this.form.get(this.templateMap['date2']); + if (dateControl && date2Control) { + this.form.get('linkedF3x')?.addValidators(buildCorrespondingForm3XValidator(dateControl, date2Control)); + } + firstValueFrom(this.store.select(selectActiveReport)).then((report) => { this.activeReport = report; }); this.committeeF3xReports.then(() => { - this.setLinkedReport(); + this.setLinkedForm3X(); }); this.form .get(this.templateMap['date']) ?.valueChanges.pipe(takeUntil(this.destroy$)) .subscribe(() => { - this.setLinkedReport(); + this.setLinkedForm3X(); }); this.form .get(this.templateMap['date2']) ?.valueChanges.pipe(takeUntil(this.destroy$)) .subscribe(() => { - this.setLinkedReport(); + this.setLinkedForm3X(); }); } - setLinkedReport(): void { - this.getLinkedReport().then((report) => { - this.linkedReport = report; - if (this.linkedReport) { - this.linkedReport.toString = this.getForm3XLabel; - } + setLinkedForm3X(): void { + this.getLinkedForm3X().then((report) => { + this.linkedF3x = report; + this.form.get('linkedF3x')?.setValue(this.getForm3XLabel(this.linkedF3x)); + this.form.get('linkedF3x')?.markAsTouched(); }); } - async getLinkedReport(): Promise { + async getLinkedForm3X(): Promise { const disseminationDate = this.form.get(this.templateMap['date2'])?.value as Date | undefined; const disbursementDate = this.form.get(this.templateMap['date'])?.value as Date | undefined; const date = disbursementDate ?? disseminationDate; if (date) { - const reports = await this.committeeF3xReports.then((response) => { - return response.results - .map((item) => { - return getReportFromJSON(item); - }) - .filter((report) => { - return report.report_type === ReportTypes.F3X; - }) as Form3X[]; + const reports = await this.committeeF3xReports.then((reports) => { + return reports.filter((report) => { + return report.report_type === ReportTypes.F3X; + }) as Form3X[]; }); for (const report of reports) { @@ -88,9 +93,8 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On return undefined; } - getForm3XLabel(): string { - const report = this as unknown as Form3X; - const datePipe = new FecDatePipe(); + getForm3XLabel(report: Form3X | undefined): string { + if (!report) return ''; let label = getReportCodeLabel(report.report_code); const stringsToRemove = [' MID-YEAR-REPORT', ' YEAR-END', ' QUARTERLY REPORT', ' MONTHLY REPORT']; @@ -98,6 +102,8 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On label = label?.replaceAll(string, ''); } - return `${label}: ${datePipe.transform(report.coverage_from_date)} - ${datePipe.transform(report.coverage_through_date)}`; + return `${label}: ${this.datePipe.transform(report.coverage_from_date)} - ${this.datePipe.transform( + report.coverage_through_date, + )}`; } } diff --git a/front-end/src/app/shared/services/report.service.ts b/front-end/src/app/shared/services/report.service.ts index 707337abed..e5029ae943 100644 --- a/front-end/src/app/shared/services/report.service.ts +++ b/front-end/src/app/shared/services/report.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { map, Observable, tap } from 'rxjs'; +import { firstValueFrom, map, Observable, tap } from 'rxjs'; import { Store } from '@ngrx/store'; import { setActiveReportAction } from 'app/store/active-report.actions'; import { Report, ReportTypes } from '../models/report.model'; @@ -42,13 +42,10 @@ export class ReportService implements TableListService { ); } - public getAllReports(): Observable { - return this.apiService.get(this.apiEndpoint + '/').pipe( - map((response: ListRestResponse) => { - response.results = response.results.map((item) => getReportFromJSON(item)); - return response; - }), - ); + public getAllReports(): Promise { + return firstValueFrom(this.apiService.get(this.apiEndpoint + '/')).then((rawReports) => { + return rawReports.map((item) => getReportFromJSON(item)); + }); } public get(reportId: string): Observable { diff --git a/front-end/src/app/shared/utils/validators.utils.ts b/front-end/src/app/shared/utils/validators.utils.ts index 4d07a1895c..2e79c097c2 100644 --- a/front-end/src/app/shared/utils/validators.utils.ts +++ b/front-end/src/app/shared/utils/validators.utils.ts @@ -124,6 +124,25 @@ function getCoverageOverlapError(collision: F3xCoverageDates): ValidationErrors return { invaliddate: { msg: message } }; } +export function buildCorrespondingForm3XValidator( + dateControl: AbstractControl, + date2Control: AbstractControl, +): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + if (!dateControl.value && !date2Control.value) { + return { + noDateProvided: true, + }; + } else if (!control.value) { + return { + noCorrespondingForm3X: true, + }; + } + + return null; + }; +} + export function buildWithinReportDatesValidator(coverage_from_date?: Date, coverage_through_date?: Date): ValidatorFn { return (control: AbstractControl): ValidationErrors | null => { const date = control.value; From 023fa99091b8da786d74698ce0866431c47f054a Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 5 Mar 2024 15:26:28 -0500 Subject: [PATCH 03/10] Undoes prettier changes to the transaction input component --- .../transaction-input.component.html | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html b/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html index b201b594dc..21966b76ad 100644 --- a/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html +++ b/front-end/src/app/reports/transactions/transaction-input/transaction-input.component.html @@ -1,6 +1,8 @@
@@ -186,12 +188,14 @@

{{ transactionType.committeeCandidateHeader }}

- - + + + >LOOKUP + (OPTIONAL) + Date: Tue, 5 Mar 2024 16:31:17 -0500 Subject: [PATCH 04/10] Restores the amount input typescript file --- .../components/inputs/amount-input/amount-input.component.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts b/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts index b8f16226ff..ebae3f1f43 100644 --- a/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts +++ b/front-end/src/app/shared/components/inputs/amount-input/amount-input.component.ts @@ -31,10 +31,7 @@ export class AmountInputComponent extends BaseInputComponent implements OnInit, dateIsOutsideReport = false; // True if transaction date is outside the report dates contributionAmountInputStyleClass = ''; - constructor( - private changeDetectorRef: ChangeDetectorRef, - private store: Store, - ) { + constructor(private changeDetectorRef: ChangeDetectorRef, private store: Store) { super(); } From f49e65af6140ab725504e5717da4af4b1baa81d5 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 5 Mar 2024 17:40:37 -0500 Subject: [PATCH 05/10] Adds text and buttons at the top of IE Transaction forms when on a Form24; adds a tooltip explaining the linkedF3x field --- .../double-transaction-detail.component.html | 2 +- .../transaction-detail.component.html | 1 + .../triple-transaction-detail.component.html | 1 + ...xpenditure-create-f3x-input.component.html | 12 ++++++ ...xpenditure-create-f3x-input.component.scss | 17 +++++++++ ...nditure-create-f3x-input.component.spec.ts | 30 +++++++++++++++ ...-expenditure-create-f3x-input.component.ts | 38 +++++++++++++++++++ .../linked-report-input.component.html | 8 ++++ .../linked-report-input.component.ts | 6 +++ front-end/src/app/shared/shared.module.ts | 3 ++ 10 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.html create mode 100644 front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.scss create mode 100644 front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.spec.ts create mode 100644 front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.ts diff --git a/front-end/src/app/reports/transactions/double-transaction-detail/double-transaction-detail.component.html b/front-end/src/app/reports/transactions/double-transaction-detail/double-transaction-detail.component.html index 33bbeccffb..79d92bfb0b 100644 --- a/front-end/src/app/reports/transactions/double-transaction-detail/double-transaction-detail.component.html +++ b/front-end/src/app/reports/transactions/double-transaction-detail/double-transaction-detail.component.html @@ -1,5 +1,6 @@

{{ transaction?.transactionType?.title }}

{{ transactionType?.subTitle }}
+

READ ONLY

{{ transactionType?.description }}

@@ -66,4 +67,3 @@

{{ childTransactionType?.contactTitle }}

- diff --git a/front-end/src/app/reports/transactions/transaction-detail/transaction-detail.component.html b/front-end/src/app/reports/transactions/transaction-detail/transaction-detail.component.html index 793c0879da..175c7ce79f 100644 --- a/front-end/src/app/reports/transactions/transaction-detail/transaction-detail.component.html +++ b/front-end/src/app/reports/transactions/transaction-detail/transaction-detail.component.html @@ -6,6 +6,7 @@

{{ transactionType?.subTitle }}

+

READ ONLY

DEBT REPAYMENT

{{ transactionType?.contactTitle || 'Contact' }}

diff --git a/front-end/src/app/reports/transactions/triple-transaction-detail/triple-transaction-detail.component.html b/front-end/src/app/reports/transactions/triple-transaction-detail/triple-transaction-detail.component.html index e52244f8f7..169b747cdd 100644 --- a/front-end/src/app/reports/transactions/triple-transaction-detail/triple-transaction-detail.component.html +++ b/front-end/src/app/reports/transactions/triple-transaction-detail/triple-transaction-detail.component.html @@ -1,5 +1,6 @@

{{ transaction?.transactionType?.title }}

{{ transactionType?.subTitle }}
+

READ ONLY

{{ transactionType?.description }}

diff --git a/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.html b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.html new file mode 100644 index 0000000000..8f7924fb85 --- /dev/null +++ b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.html @@ -0,0 +1,12 @@ + +

+ Independent expenditures (IE) submitted on a Form 24 must also be submitted on the regularly scheduled Form 3X + report. In order to create an IE on the Form 24, the IE's date of disbursement must fall within the corresponding + Form 3X coverage dates. If date of disbursement is not available, date of dissemination will be used. IEs already + created on a Form 3X can be added to an existing Form 24. +

+ + +
diff --git a/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.scss b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.scss new file mode 100644 index 0000000000..7dae38bb0f --- /dev/null +++ b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.scss @@ -0,0 +1,17 @@ + +.ie-explanation { + margin-top: 16px; + margin-bottom: 8px; + max-width: 1000px; +} + +.ie-explanation-button-create { + margin-left: 0px; + margin-right: 8px; + margin-bottom: 16px; +} + +.ie-explanation-button-cancel { + margin-left: 8px; + margin-bottom: 16px; +} \ No newline at end of file diff --git a/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.spec.ts b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.spec.ts new file mode 100644 index 0000000000..c0d432437b --- /dev/null +++ b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.spec.ts @@ -0,0 +1,30 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { testMockStore } from 'app/shared/utils/unit-test.utils'; +import { Form24 } from 'app/shared/models/form-24.model'; +import { provideMockStore } from '@ngrx/store/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { IndependentExpenditureCreateF3xInputComponent } from './independent-expenditure-create-f3x-input.component'; +import { TooltipModule } from 'primeng/tooltip'; +import { RouterTestingModule } from '@angular/router/testing'; + +describe('IndependentExpenditureCreateF3xInputComponent', () => { + let component: IndependentExpenditureCreateF3xInputComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [IndependentExpenditureCreateF3xInputComponent], + imports: [HttpClientTestingModule, TooltipModule], + providers: [provideMockStore(testMockStore), RouterTestingModule], + }).compileComponents(); + + fixture = TestBed.createComponent(IndependentExpenditureCreateF3xInputComponent); + component = fixture.componentInstance; + component.activeReport = Form24.fromJSON({}); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.ts b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.ts new file mode 100644 index 0000000000..1a9865eb00 --- /dev/null +++ b/front-end/src/app/shared/components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component.ts @@ -0,0 +1,38 @@ +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { selectActiveReport } from 'app/store/active-report.selectors'; +import { firstValueFrom } from 'rxjs'; +import { BaseInputComponent } from '../base-input.component'; +import { Report, ReportTypes } from 'app/shared/models/report.model'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-independent-expenditure-create-f3x-input', + styleUrls: ['./independent-expenditure-create-f3x-input.component.scss'], + templateUrl: './independent-expenditure-create-f3x-input.component.html', +}) +export class IndependentExpenditureCreateF3xInputComponent extends BaseInputComponent implements OnInit { + activeReport?: Report; + form24ReportType = ReportTypes.F24; + + constructor( + private store: Store, + private router: Router, + ) { + super(); + } + + ngOnInit(): void { + firstValueFrom(this.store.select(selectActiveReport)).then((report) => { + this.activeReport = report; + }); + } + + cancel(): void { + this.router.navigateByUrl('/reports'); + } + + create(): void { + this.router.navigateByUrl('/reports/f3x/create/step1'); + } +} diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html index e141b5a5bd..6f70bfc523 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.html @@ -2,6 +2,14 @@
+
diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index 64262496f6..20439e8aea 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -22,6 +22,12 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On linkedF3x?: Form3X; linkedF3xLabel?: string; + tooltipText = + 'Transactions created in Form 24 must be linked to a Form 3X with corresponding coverage dates. \ + To determine coverage dates, calculations rely on an IE’s date of disbursement. If date of disbursement is not\ + available, date of dissemination will be used. Before saving this transaction, create a Form 3X with \ + corresponding coverage dates.'; + constructor( private store: Store, private reportService: ReportService, diff --git a/front-end/src/app/shared/shared.module.ts b/front-end/src/app/shared/shared.module.ts index d0ec5eaeb6..61273be727 100644 --- a/front-end/src/app/shared/shared.module.ts +++ b/front-end/src/app/shared/shared.module.ts @@ -56,6 +56,7 @@ import { SingleClickDirective } from './directives/single-click.directive'; import { RippleModule } from 'primeng/ripple'; import { CardModule } from 'primeng/card'; import { LinkedReportInputComponent } from './components/inputs/linked-report-input/linked-report-input.component'; +import { IndependentExpenditureCreateF3xInputComponent } from './components/inputs/independent-expenditure-create-f3x-input/independent-expenditure-create-f3x-input.component'; @NgModule({ imports: [ @@ -105,6 +106,7 @@ import { LinkedReportInputComponent } from './components/inputs/linked-report-in CommitteeInputComponent, AmountInputComponent, LinkedReportInputComponent, + IndependentExpenditureCreateF3xInputComponent, MemoCodeInputComponent, AdditionalInfoInputComponent, ElectionInputComponent, @@ -146,6 +148,7 @@ import { LinkedReportInputComponent } from './components/inputs/linked-report-in CommitteeInputComponent, AmountInputComponent, LinkedReportInputComponent, + IndependentExpenditureCreateF3xInputComponent, MemoCodeInputComponent, AdditionalInfoInputComponent, ElectionInputComponent, From 9a62464be057e18d1a3e18f39ca2cee6e82211f6 Mon Sep 17 00:00:00 2001 From: Elaine Krauss Date: Tue, 5 Mar 2024 17:46:49 -0500 Subject: [PATCH 06/10] Converts from a multi-line string to a series of concatenated strings for the sake of browser compatability --- .../linked-report-input/linked-report-input.component.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index 20439e8aea..b78e40b32d 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -23,10 +23,10 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On linkedF3xLabel?: string; tooltipText = - 'Transactions created in Form 24 must be linked to a Form 3X with corresponding coverage dates. \ - To determine coverage dates, calculations rely on an IE’s date of disbursement. If date of disbursement is not\ - available, date of dissemination will be used. Before saving this transaction, create a Form 3X with \ - corresponding coverage dates.'; + 'Transactions created in Form 24 must be linked to a Form 3X with corresponding coverage dates. ' + + 'To determine coverage dates, calculations rely on an IE’s date of disbursement. If date of disbursement is not ' + + 'available, date of dissemination will be used. Before saving this transaction, create a Form 3X with ' + + 'corresponding coverage dates.'; constructor( private store: Store, From 84cf025a48788c0fe950fa9b3565518378077e0b Mon Sep 17 00:00:00 2001 From: toddlees Date: Wed, 6 Mar 2024 13:50:46 -0500 Subject: [PATCH 07/10] use var for controls. simplify subscription --- .../linked-report-input.component.ts | 50 +++++++------------ 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index 64262496f6..1b5b5a84ea 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { selectActiveReport } from 'app/store/active-report.selectors'; -import { firstValueFrom, takeUntil } from 'rxjs'; +import { combineLatestWith, firstValueFrom, startWith, takeUntil } from 'rxjs'; import { BaseInputComponent } from '../base-input.component'; import { Report, ReportTypes } from 'app/shared/models/report.model'; import { ReportService } from 'app/shared/services/report.service'; @@ -19,8 +19,7 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On activeReport?: Report; committeeF3xReports: Promise; form24ReportType = ReportTypes.F24; - linkedF3x?: Form3X; - linkedF3xLabel?: string; + linkedF3xControl = new FormControl(); constructor( private store: Store, @@ -32,47 +31,32 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On } ngOnInit(): void { - this.form.addControl('linkedF3x', new FormControl()); - const dateControl = this.form.get(this.templateMap['date']); - const date2Control = this.form.get(this.templateMap['date2']); - if (dateControl && date2Control) { - this.form.get('linkedF3x')?.addValidators(buildCorrespondingForm3XValidator(dateControl, date2Control)); - } + this.form.addControl('linkedF3x', this.linkedF3xControl); + const dateControl = this.form.get(this.templateMap['date']) ?? new FormControl(); + const date2Control = this.form.get(this.templateMap['date2']) ?? new FormControl(); + this.linkedF3xControl.addValidators(buildCorrespondingForm3XValidator(dateControl, date2Control)); firstValueFrom(this.store.select(selectActiveReport)).then((report) => { this.activeReport = report; }); - this.committeeF3xReports.then(() => { - this.setLinkedForm3X(); - }); - - this.form - .get(this.templateMap['date']) - ?.valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.setLinkedForm3X(); - }); - this.form - .get(this.templateMap['date2']) - ?.valueChanges.pipe(takeUntil(this.destroy$)) - .subscribe(() => { - this.setLinkedForm3X(); - }); + dateControl.valueChanges + .pipe( + startWith(undefined), + combineLatestWith(date2Control.valueChanges.pipe(startWith(undefined))), + takeUntil(this.destroy$), + ) + .subscribe(this.setLinkedForm3X); } - setLinkedForm3X(): void { - this.getLinkedForm3X().then((report) => { - this.linkedF3x = report; - this.form.get('linkedF3x')?.setValue(this.getForm3XLabel(this.linkedF3x)); + setLinkedForm3X([disbursementDate, disseminationDate]: (Date | undefined)[]): void { + this.getLinkedForm3X(disbursementDate, disseminationDate).then((report) => { + this.form.get('linkedF3x')?.setValue(this.getForm3XLabel(report)); this.form.get('linkedF3x')?.markAsTouched(); }); } - async getLinkedForm3X(): Promise { - const disseminationDate = this.form.get(this.templateMap['date2'])?.value as Date | undefined; - const disbursementDate = this.form.get(this.templateMap['date'])?.value as Date | undefined; - + async getLinkedForm3X(disbursementDate?: Date, disseminationDate?: Date): Promise { const date = disbursementDate ?? disseminationDate; if (date) { const reports = await this.committeeF3xReports.then((reports) => { From a481383531923784bd57a6e9d29756befeaf5a10 Mon Sep 17 00:00:00 2001 From: toddlees Date: Wed, 6 Mar 2024 14:06:48 -0500 Subject: [PATCH 08/10] bind this --- .../inputs/linked-report-input/linked-report-input.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index 1b5b5a84ea..a956c6d69b 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -46,7 +46,7 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On combineLatestWith(date2Control.valueChanges.pipe(startWith(undefined))), takeUntil(this.destroy$), ) - .subscribe(this.setLinkedForm3X); + .subscribe(this.setLinkedForm3X.bind(this)); } setLinkedForm3X([disbursementDate, disseminationDate]: (Date | undefined)[]): void { From d2b23bcb33c0fa46797ea4272babe63c22d50e7d Mon Sep 17 00:00:00 2001 From: toddlees Date: Wed, 6 Mar 2024 15:06:23 -0500 Subject: [PATCH 09/10] use control value --- .../linked-report-input/linked-report-input.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts index a956c6d69b..ad694c7004 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.ts @@ -42,8 +42,8 @@ export class LinkedReportInputComponent extends BaseInputComponent implements On dateControl.valueChanges .pipe( - startWith(undefined), - combineLatestWith(date2Control.valueChanges.pipe(startWith(undefined))), + startWith(dateControl.value), + combineLatestWith(date2Control.valueChanges.pipe(startWith(date2Control.value))), takeUntil(this.destroy$), ) .subscribe(this.setLinkedForm3X.bind(this)); From d68ddc199235aa89cf0596940f6fbb3c24d7b455 Mon Sep 17 00:00:00 2001 From: toddlees Date: Wed, 6 Mar 2024 22:27:12 -0500 Subject: [PATCH 10/10] coverage --- .../linked-report-input.component.spec.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts index 45f70390fd..f54bfff01f 100644 --- a/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts +++ b/front-end/src/app/shared/components/inputs/linked-report-input/linked-report-input.component.spec.ts @@ -33,6 +33,7 @@ describe('LinkedReportInputComponent', () => { component.form = new FormGroup({}); component.form.addControl('other_date', new FormControl()); component.form.addControl(testTemplateMap['date'], new FormControl()); + component.ngOnInit(); fixture.detectChanges(); }); @@ -40,12 +41,15 @@ describe('LinkedReportInputComponent', () => { expect(component).toBeTruthy(); }); - it('should try to determine the linked F3X report when the dates change', () => { - const spy = spyOn(component, 'setLinkedForm3X'); + it('should try to determine the linked F3X report when the dates change', async () => { + const spy = spyOn(component, 'getLinkedForm3X').and.returnValue(Promise.resolve(Form3X.fromJSON({}))); component.form.get('other_date')?.setValue('2025-02-12'); component.form.get(testTemplateMap['date'])?.setValue('2025-02-12'); - expect(spy).toHaveBeenCalledTimes(2); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalledTimes(4); }); it('should determine the correct label', () => { @@ -58,13 +62,13 @@ describe('LinkedReportInputComponent', () => { component.committeeF3xReports = firstValueFrom(of([testF3X])); component.form.get('other_date')?.setValue(new Date('2020-02-21')); - component.getLinkedForm3X().then((report) => { + component.getLinkedForm3X(undefined, new Date('2020-02-21')).then((report) => { expect(report).toEqual(testF3X); expect(component.getForm3XLabel(report)).toEqual('APRIL 15 (Q1): 01/15/2020 - 04/29/2020'); }); component.form.get('other_date')?.setValue(new Date('2022-06-22')); - component.getLinkedForm3X().then((report) => { + component.getLinkedForm3X(undefined, new Date('2022-06-22')).then((report) => { expect(report).toEqual(undefined); expect(component.getForm3XLabel(report)).toEqual(''); });