LP2002343: cash reports end date cannot come before start date user/sandbergja/lp2002343-validate-cash-reports-dates
authorJane Sandberg <js7389@princeton.edu>
Sun, 14 May 2023 07:58:17 +0000 (00:58 -0700)
committerJane Sandberg <js7389@princeton.edu>
Sun, 14 May 2023 08:01:58 +0000 (01:01 -0700)
To test:
1. Log in as a staff member with a branch-level home library (global admin account will work only if you change it from CONS to, say, BR1)
2. As that staff member, bill some patrons and then accept some payments from them
3. Go to admin > local admin > cash reports
4. Try to set the end date before the start date.
5. Note that you get an alert, and cannot press the Submit button.
6. Confirm that when you enter valid dates and select your staff member's home ou, the reports display as usual.

Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/cash-reports/cash-reports.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/cash-reports/cash-reports.component.spec.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/local/cash-reports/cash-reports.component.ts

index 5de9628..07acc59 100644 (file)
@@ -1,4 +1,4 @@
-import {Component, OnInit, Input, Output, ViewChild, EventEmitter, forwardRef} from '@angular/core';
+import {Component, OnInit, Input, Output, EventEmitter, forwardRef} from '@angular/core';
 import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
 import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
 import {DateUtil} from '@eg/share/util/date';
index 251fc93..d4224cf 100644 (file)
@@ -2,25 +2,29 @@
 </eg-staff-banner>
 
 <div class="mb-5">
-  <div>
+  <form #criteria="ngForm" egDateFieldOrderList="startDate,endDate">
     <div class="row">
       <div class="input-group col-lg-3">
-          <div class="input-group-text" i18n>Start Date</div> 
-          <eg-date-select [initialDate]="today" (onChangeAsYmd)="onStartDateChange($event)"></eg-date-select>
+          <label class="input-group-text" i18n for="start-date">Start Date</label>
+          <eg-date-select [initialDate]="today" domId="start-date" name="startDate" [(ngModel)]="startDate"></eg-date-select>
       </div>
       <div class="input-group col-lg-3">
-          <div class="input-group-text" i18n>End Date</div> 
-          <eg-date-select [initialDate]="today" (onChangeAsYmd)="onEndDateChange($event)"></eg-date-select>
+          <label class="input-group-text" i18n for="end-date">End Date</label>
+          <eg-date-select [initialDate]="today" domId="end-date" name="endDate" [(ngModel)]="endDate"></eg-date-select>
       </div>
       <div class="input-group col-lg-4">
           <div class="input-group-text" i18n>View reports for</div>
         <eg-org-select [applyDefault]="true" [disableOrgs]="disabledOrgs" (onChange)="onOrgChange($event)"></eg-org-select>
       </div>
       <div class="col-lg-2">
-        <button class="btn btn-primary" (click)="searchForData(startDate, endDate)">Submit</button>
+        <button class="btn btn-primary" (click)="searchForData()" [disabled]="!criteria.valid">Submit</button>
       </div>
     </div>
-  </div>
+    <div role="alert" class="alert alert-danger" id="dateOutOfOrderAlert" *ngIf="criteria.errors?.['datesOutOfOrder'] && (criteria.touched || criteria.dirty)">
+      <span class="material-icons" aria-hidden="true">error</span>
+      <span i18n>Start date must be before end date</span>
+    </div>
+  </form>
 </div>
 
 <ul ngbNav #cashReportsNav="ngbNav" class="nav-tabs"  [keyboard]="true" [roles]="false" role="tablist"
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/local/cash-reports/cash-reports.component.spec.ts b/Open-ILS/src/eg2/src/app/staff/admin/local/cash-reports/cash-reports.component.spec.ts
new file mode 100644 (file)
index 0000000..ca734e1
--- /dev/null
@@ -0,0 +1,60 @@
+import { TestBed } from '@angular/core/testing';
+import { AuthService } from '@eg/core/auth.service';
+import { IdlService } from '@eg/core/idl.service';
+import { NetService } from '@eg/core/net.service';
+import { OrgService } from '@eg/core/org.service';
+import { CashReportsComponent } from './cash-reports.component';
+import { DateSelectComponent } from '@eg/share/date-select/date-select.component';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { NgbDatepickerModule, NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
+import { of } from 'rxjs';
+import { FormsModule } from '@angular/forms';
+import { DatesInOrderValidatorDirective } from '@eg/share/validators/dates_in_order_validator.directive';
+
+const mockIdlObject = {a: null,
+    classname: null,
+    _isfieldmapper: null,
+    id: () => {null},
+    ws_ou: () => {null}};
+const mockNet = jasmine.createSpyObj<NetService>(['request']);
+mockNet.request.and.returnValue(of());
+const mockOrg = jasmine.createSpyObj<OrgService>(['get', 'filterList']);
+const mockAuth = jasmine.createSpyObj<AuthService>(['user', 'token']);
+mockAuth.user.and.returnValue(mockIdlObject);
+
+describe('CashReportsComponent', () => {
+  it('alerts the user if end date is before start date', async () => {
+    await TestBed.configureTestingModule({
+        declarations: [
+            CashReportsComponent,
+            DateSelectComponent,
+            DatesInOrderValidatorDirective
+        ],
+        providers: [
+            {provide: IdlService, useValue: {}},
+            {provide: NetService, useValue: mockNet},
+            {provide: OrgService, useValue: mockOrg},
+            {provide: AuthService, useValue: mockAuth}
+        ],
+        imports: [
+            NgbNavModule,
+            NgbDatepickerModule,
+            FormsModule
+        ],
+        schemas: [CUSTOM_ELEMENTS_SCHEMA]
+    }).compileComponents();
+
+    const fixture = TestBed.createComponent(CashReportsComponent);
+    const component = fixture.componentInstance;
+    const element = fixture.nativeElement;
+    component.selectedOrg = mockIdlObject;
+    fixture.detectChanges();
+    
+    element.querySelector('#start-date').value = '2022-01-01';
+    element.querySelector('#end-date').value = '2021-01-01';
+    component.criteria.form.setErrors({datesOutOfOrder: true});
+    component.criteria.form.markAsDirty();
+    fixture.detectChanges();
+    expect(element.querySelector('#dateOutOfOrderAlert').innerText).toContain('Start date must be before end date');
+  });
+});
index cef34be..00e0738 100644 (file)
@@ -5,6 +5,7 @@ import {IdlService} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {AuthService} from '@eg/core/auth.service';
 import {OrgService} from '@eg/core/org.service';
+import { NgForm } from '@angular/forms';
 
 class DeskTotals {
     cash_payment = 0;
@@ -31,9 +32,8 @@ export class CashReportsComponent implements OnInit {
     deskIdlClass = 'mwps';
     userIdlClass = 'mups';
     selectedOrg = this.org.get(this.auth.user().ws_ou());
-    today = new Date();
-    startDate = `${this.today.getFullYear()}-${String(this.today.getMonth() + 1).padStart(2, '0')}-${String(this.today.getDate()).padStart(2, '0')}`;
-    endDate = `${this.today.getFullYear()}-${String(this.today.getMonth() + 1).padStart(2, '0')}-${String(this.today.getDate()).padStart(2, '0')}`;
+    startDate = new Date();
+    endDate = new Date();
     deskTotals = new DeskTotals();
     userTotals = new UserTotals();
     disabledOrgs = [];
@@ -46,6 +46,7 @@ export class CashReportsComponent implements OnInit {
     @ViewChild('deskPaymentGrid') deskPaymentGrid: GridComponent;
     @ViewChild('userPaymentGrid') userPaymentGrid: GridComponent;
     @ViewChild('userGrid') userGrid: GridComponent;
+    @ViewChild('criteria') criteria: NgForm;
 
     constructor(
         private idl: IdlService,
@@ -55,26 +56,26 @@ export class CashReportsComponent implements OnInit {
 
     ngOnInit() {
         this.disabledOrgs = this.getFilteredOrgList();
-        this.searchForData(this.startDate, this.endDate);
+        this.searchForData();
 
         this.cellTextGenerator = {
             card: row => row.user.card()
         };
     }
 
-    searchForData(start, end) {
+    searchForData() {
         this.userDataSource.data = [];
         this.fillGridData(this.deskIdlClass, 'deskPaymentDataSource',
             this.net.request(
                 'open-ils.circ',
                 'open-ils.circ.money.org_unit.desk_payments',
-                this.auth.token(), this.selectedOrg.id(), start, end));
+                this.auth.token(), this.selectedOrg.id(), this.startDate.toISOString().split('T')[0], this.endDate.toISOString().split('T')[0]));
 
         this.fillGridData(this.userIdlClass, 'userPaymentDataSource',
             this.net.request(
                 'open-ils.circ',
                 'open-ils.circ.money.org_unit.user_payments',
-                this.auth.token(), this.selectedOrg.id(), start, end));
+                this.auth.token(), this.selectedOrg.id(), this.startDate.toISOString().split('T')[0], this.endDate.toISOString().split('T')[0]));
     }
 
     fillGridData(idlClass, dataSource, data) {
@@ -142,16 +143,8 @@ export class CashReportsComponent implements OnInit {
         return this.org.filterList(orgFilter, true);
     }
 
-    onStartDateChange(date) {
-        this.startDate = date;
-    }
-
-    onEndDateChange(date) {
-        this.endDate = date;
-    }
-
     onOrgChange(org) {
         this.selectedOrg = org;
-        this.searchForData(this.startDate, this.endDate);
+        this.searchForData();
     }
 }