Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public ScheduledReportListSummary FromDomain(ReportSchedule reportScheduleModel)
SubmitDate = reportScheduleModel.SubmitReportDateTime,
ReportTypes = reportScheduleModel.ReportTypes,
Frequency = reportScheduleModel.Frequency,
AdHocType = reportScheduleModel.AdHocType,
PayloadRootUri = reportScheduleModel.PayloadRootUri,
CreatedDate = reportScheduleModel.CreateDate
};
Expand Down
3 changes: 3 additions & 0 deletions DotNet/Report/Entities/ReportSchedule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ public class ReportSchedule
public bool EnableSubmission { get; set; } = true;
public bool EndOfReportPeriodJobHasRun { get; set; } = false;
public List<string> ReportTypes { get; set; } = new List<string>();

public AdHocType? AdHocType { get; set; }
Comment thread
arianamihailescu marked this conversation as resolved.

public Frequency Frequency { get; set; }
public string? PayloadRootUri { get; set; }
public ScheduleStatus Status { get; set; } = ScheduleStatus.New;
Expand Down
3 changes: 3 additions & 0 deletions DotNet/Report/Listeners/GenerateReportListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ await consumer.ConsumeWithInstrumentation(async (result, consumeCancellationToke
DateTimeKind.Utc
);

bool isAutomatic = !value.Regenerate && (value.PatientIds == null || value.PatientIds.Count == 0);

// Create ReportSchedule for AdHoc Report
var reportSchedule = new ReportSchedule
{
Expand All @@ -207,6 +209,7 @@ await consumer.ConsumeWithInstrumentation(async (result, consumeCancellationToke
ReportStartDate = startDate.Value,
ReportEndDate = endDate.Value,
Frequency = Frequency.Adhoc,
AdHocType = isAutomatic ? AdHocType.Automatic : AdHocType.Manual,
Comment thread
arianamihailescu marked this conversation as resolved.
ReportTypes = reportTypes,
EndOfReportPeriodJobHasRun = true,
EnableSubmission = !value.BypassSubmission,
Expand Down
14 changes: 14 additions & 0 deletions DotNet/Shared/Application/Models/AdHocType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ο»Ώusing LantanaGroup.Link.Shared.Application.Utilities;
using System.Text.Json.Serialization;

namespace LantanaGroup.Link.Shared.Application.Models;


[JsonConverter(typeof(JsonStringEnumConverter))]
public enum AdHocType
{
[StringValue("Manual")]
Manual = 0,
[StringValue("Automatic")]
Automatic = 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class ScheduledReportListSummary
public DateTime? SubmitDate { get; set; }
public List<string> ReportTypes { get; set; } = [];
public Frequency Frequency { get; set; }

public AdHocType? AdHocType { get; set; }
public int CensusCount { get; set; }
public int InitialPopulationCount { get; set; }
public ScheduledReportMetrics? ReportMetrics { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ <h2 class="mat-headline">Generate Ad-Hoc Report</h2>
</mat-option>
}
</mat-select>
<mat-error *ngIf="reportTypesControl.hasError('required')">
At least one report type is required
</mat-error>
</mat-form-field>

<mat-checkbox formControlName="bypassSubmission">Bypass submission?</mat-checkbox>
Expand All @@ -63,20 +66,31 @@ <h2 class="mat-headline">Generate Ad-Hoc Report</h2>
<input matInput [matDatepicker]="startPicker" formControlName="startDate" required/>
<mat-datepicker-toggle matSuffix [for]="startPicker"></mat-datepicker-toggle>
<mat-datepicker #startPicker></mat-datepicker>
@if (startDateControl.touched && startDateControl.hasError('required')) {
<mat-error>
Start date is required
</mat-error>
}
</mat-form-field>

<mat-form-field appearance="outline" class="full-width">
<mat-label>End Date</mat-label>
<input matInput [matDatepicker]="endPicker" formControlName="endDate" required/>
<mat-datepicker-toggle matSuffix [for]="endPicker"></mat-datepicker-toggle>
<mat-datepicker #endPicker></mat-datepicker>
@if (endDateControl.touched && endDateControl.hasError('required')) {
<mat-error>
End date is required
</mat-error>
}
</mat-form-field>
</div>

<div class="row">
<mat-radio-group formControlName="selectedForm">
<mat-radio-button value="fileUpload">File Upload</mat-radio-button>
<mat-radio-button value="censusPatients">Census Patients</mat-radio-button>
<mat-radio-button value="manualPatients">Enter Patients</mat-radio-button>
<mat-radio-button value="censusPatients">Use Census (Automatic) </mat-radio-button>
</mat-radio-group>
</div>
<!-- File Upload Form -->
Expand All @@ -94,7 +108,7 @@ <h3>File Upload</h3>
}

<!-- Census Patients Form -->
@if (selectedFormControl.value === 'censusPatients') {
@if (selectedFormControl.value === 'manualPatients') {
<div>
<h3>Census Patients</h3>
<div>
Expand Down Expand Up @@ -132,7 +146,7 @@ <h3>Census Patients</h3>
</div>
<!-- Button -->
<div class="text-center mt-6" style="margin-top: 50px;">
<button type="button" mat-raised-button color="primary" [disabled]="generateReportForm.invalid"
<button type="button" mat-raised-button color="primary" [disabled]="!canGenerateReport"
(click)="generateReport()">Generate Report
</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import {ComponentFixture, TestBed, fakeAsync, tick, waitForAsync, flushMicrotasks} from '@angular/core/testing';
import {GenerateReportFormComponent} from './generate-report-form.component';
import {FormBuilder, ReactiveFormsModule} from '@angular/forms';
import {async, of, throwError} from 'rxjs';
import {MatSnackBar} from '@angular/material/snack-bar';
import {TenantService} from '../../../services/gateway/tenant/tenant.service';
import {MeasureDefinitionService} from '../../../services/gateway/measure-definition/measure.service';
import {Router} from '@angular/router';


import {MatDatepickerModule} from '@angular/material/datepicker';
import {MatNativeDateModule} from '@angular/material/core';


describe('GenerateReportFormComponent', () => {
let component: GenerateReportFormComponent;
let fixture: ComponentFixture<GenerateReportFormComponent>;
let tenantService: jasmine.SpyObj<TenantService>;
let measureDefinitionService: jasmine.SpyObj<MeasureDefinitionService>;
let snackBar: jasmine.SpyObj<MatSnackBar>;
let router: jasmine.SpyObj<Router>;

beforeEach(waitForAsync(() => {
// Create spies for services
tenantService = jasmine.createSpyObj('TenantService', ['autocompleteFacilities', 'getFacilityConfiguration', 'generateAdHocReport', 'checkFacility']);
measureDefinitionService = jasmine.createSpyObj('MeasureDefinitionService', ['getMeasureDefinitionConfigurations']);
snackBar = jasmine.createSpyObj('MatSnackBar', ['open']);
router = jasmine.createSpyObj('Router', ['navigate']);

// Mock service responses
tenantService.autocompleteFacilities.and.returnValue(of({facility1: 'Facility 1', facility2: 'Facility 2'}));

tenantService.generateAdHocReport.and.returnValue(of({reportId: 'mockReportId'}));
tenantService.checkFacility.and.returnValue(of(true));


tenantService.getFacilityConfiguration.and.returnValue(
of({
facilityId: 'facility1',
facilityName: 'Mock Facility',
timeZone: 'UTC',
scheduledReports: {
daily: ['Daily Report 1', 'Daily Report 2'],
monthly: ['Monthly Report 1'],
weekly: ['Weekly Report 1', 'Weekly Report 2']
}
})
);


measureDefinitionService.getMeasureDefinitionConfigurations.and.returnValue(
of([{id: 'reportType1'}, {id: 'reportType2'}])
);

TestBed.configureTestingModule({
imports: [ReactiveFormsModule, GenerateReportFormComponent, MatDatepickerModule, // Import for the datepicker
MatNativeDateModule // Import for the Native adapter
],
declarations: [],
providers: [
FormBuilder,
{provide: TenantService, useValue: tenantService},
{provide: MeasureDefinitionService, useValue: measureDefinitionService},
{provide: MatSnackBar, useValue: snackBar},
{provide: Router, useValue: router}
]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(GenerateReportFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

describe('Component Initialization', () => {
it('should create the component', () => {
expect(component).toBeTruthy();
});

it('should initialize the form with default values and controls', () => {
expect(component.generateReportForm.contains('facilityId')).toBeTrue();
expect(component.generateReportForm.contains('startDate')).toBeTrue();
expect(component.generateReportForm.contains('endDate')).toBeTrue();
expect(component.generateReportForm.contains('reportTypes')).toBeTrue();
expect(component.generateReportForm.contains('bypassSubmission')).toBeTrue();
});

it('should initialize reportTypes from MeasureDefinitionService', fakeAsync(() => {
tick();
expect(component.reportTypes).toEqual(['reportType1', 'reportType2']);
}));

it('should initialize filteredFacilities as an observable', fakeAsync(() => {
component.facilityInputControl.setValue('Facility');
tick(300); // Simulate debounce

component.filteredFacilities.subscribe(filtered => {
expect(filtered).toEqual([
{facilityId: 'facility1', facilityName: 'Facility 1'},
{facilityId: 'facility2', facilityName: 'Facility 2'}
]);
});
}));
});

describe('Form Validation', () => {
it('should mark facilityId as required', () => {
const facilityIdControl = component.facilityIdControl;
facilityIdControl.setValue('');
expect(facilityIdControl.valid).toBeFalse();
expect(facilityIdControl.errors).toEqual({required: true});

facilityIdControl.setValue('validFacility');
expect(facilityIdControl.valid).toBeTrue();
});

it('should validate startDate and endDate', () => {
const startDateControl = component.startDateControl;
const endDateControl = component.endDateControl;

startDateControl.setValue('');
expect(startDateControl.valid).toBeFalse();

endDateControl.setValue('');
expect(endDateControl.valid).toBeFalse();

startDateControl.setValue(new Date());
endDateControl.setValue(new Date());
expect(startDateControl.valid).toBeTrue();
expect(endDateControl.valid).toBeTrue();
});
});

describe('Facility Input Handling', () => {
it('should handle facility input blur and validate facility', fakeAsync(() => {
component.facilities = [
{facilityId: 'facility1', facilityName: 'Facility 1'},
{facilityId: 'facility2', facilityName: 'Facility 2'}
];

tenantService.checkFacility.and.returnValue(of(false));

component.facilityInputControl.setValue('Invalid Facility');

component.onFacilityInputBlur();
tick(200); // Allow time for the blur timeout

expect(component.facilityIdControl.errors).toEqual({notFound: true});
expect(component.facilityIdControl.value).toEqual('Invalid Facility');
}));

it('should handle facility selection', () => {
const facility = {facilityId: 'facility1', facilityName: 'Facility 1'};
component.facilities = [facility]; // Mock the facilities

component.onFacilitySelected(facility);

expect(component.facilityIdControl.value).toBe(facility.facilityId); // Expect facilityId
expect(component.facilityInputControl.value).toBe(facility.facilityId); // Expect facilityId as well (matches the implementation)

});
});

describe('Form Submission', () => {
beforeEach(() => {
component.generateReportForm.patchValue({
facilityId: 'facility1',
startDate: new Date(2023, 0, 1),
endDate: new Date(2023, 0, 31),
reportTypes: ['reportType1'],
bypassSubmission: false,
facilityInput: '',
patients: [],
selectedForm: 'manualPatients'
});
});

it('should submit the form successfully when valid', async () => {

const mockReportId = 'mockReportId';

tenantService.generateAdHocReport.and.returnValue(of({reportId: 'mockReportId'}));

await component.generateReport();
// tick();

expect(component.lastGeneratedReport).toEqual({
facilityId: 'facility1',
reportId: mockReportId
});

expect(component.formSubmitted).toBeTrue();
expect(tenantService.generateAdHocReport).toHaveBeenCalledWith(
'facility1',
jasmine.objectContaining({
reportTypes: ['reportType1'],
bypassSubmission: false
})
);
});

it('should display an error when submission fails', fakeAsync(() => {

tenantService.generateAdHocReport.and.returnValue(
throwError(() => new Error('Failed to generate the report'))
);

component.generateReport();
flushMicrotasks();
tick();

//expect(component.errorMessage).toBe('Submission failed');
expect(component.formSubmitted).toBeTrue();

expect(tenantService.generateAdHocReport).toHaveBeenCalled();

}));
});

describe('Utility Functions', () => {
it('should reset the form correctly', () => {
component.generateReportForm.patchValue({
facilityId: 'facility1',
startDate: new Date(),
endDate: new Date(),
reportTypes: ['type1']
});
(component as any).resetForm();

expect(component.generateReportForm.get('facilityId')?.value).toBeNull();
expect(component.generateReportForm.get('startDate')?.value).toBeNull();
expect(component.generateReportForm.get('endDate')?.value).toBeNull();
expect(component.generateReportForm.get('reportTypes')?.value).toBeNull();
});
});
});
Loading
Loading