LP1904036 checkin grid actions
authorBill Erickson <berickxx@gmail.com>
Thu, 15 Apr 2021 21:39:11 +0000 (17:39 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:31 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/checkin/checkin.component.html
Open-ILS/src/eg2/src/app/staff/circ/checkin/checkin.component.ts
Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/circ/circ.module.ts
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts

index 8ce201b..b424e80 100644 (file)
@@ -3,6 +3,10 @@
 <eg-progress-dialog #progressDialog></eg-progress-dialog>
 <eg-barcode-select #barcodeSelect></eg-barcode-select>
 <eg-copy-alerts-dialog #copyAlertsDialog></eg-copy-alerts-dialog>
+<eg-bucket-dialog #bucketDialog></eg-bucket-dialog>
+<eg-string #itemNeverCircedStr i18n-text 
+  text="Item CONC40000598 has never circulated."></eg-string>
+<eg-backdate-dialog #backdateDialog></eg-backdate-dialog>
 
 <div class="row" *ngIf="hasAlerts()">
   <div class="col-lg-12 alert alert-danger p-1">
       [useLocalSort]="true" [cellTextGenerator]="cellTextGenerator"
       [disablePaging]="true" persistKey="circ.checkin">
 
-      <!--
-                       <eg-grid-toolbar-action
-                               i18n-group group="Add" i18n-label label="Add Item Alerts"
-                               (onClick)="openItemAlerts($event, 'create')">
-                       </eg-grid-toolbar-action>
-
-                       <eg-grid-toolbar-action
-                               i18n-group group="Add" i18n-label label="Manage Item Alerts"
-        [disabled]="checkinsGrid.context.rowSelector.selected().length !== 1"
-                               (onClick)="openItemAlerts($event, 'manage')">
-                       </eg-grid-toolbar-action>
-      -->
+      <eg-grid-toolbar-action
+        group="Mark" i18n-group i18n-label label="Mark Item Damaged"
+        (onClick)="markDamaged($event)"></eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Edit" i18n-label label="Manage Item Alerts"
+        [disabled]="grid.context.rowSelector.selected().length !== 1"
+        (onClick)="manageItemAlerts($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Add" i18n-label label="Add Item Alerts"
+        (onClick)="addItemAlerts($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Add" i18n-label label="Add Items To Bucket"
+        (onClick)="openBucketDialog($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Edit" i18n-label label="Backdate Post-Checkin"
+        (onClick)="backdatePostCheckin($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Retrieve" i18n-label 
+        label="Retrieve Last Patron Who Circulated Item"
+        [disabled]="grid.context.rowSelector.selected().length !== 1"
+        (onClick)="retrieveLastPatron($event)">
+      </eg-grid-toolbar-action>
+
+      <!-- COLUMNS -->
 
       <eg-grid-column path="index" [index]="true" 
         label="Row Index" i18n-label [hidden]="true"></eg-grid-column>
index f8be484..af8ef2e 100644 (file)
@@ -1,6 +1,8 @@
 import {Component, ViewChild, OnInit, AfterViewInit, HostListener} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {from} from 'rxjs';
+import {concatMap} from 'rxjs/operators';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {OrgService} from '@eg/core/org.service';
 import {AuthService} from '@eg/core/auth.service';
@@ -8,12 +10,21 @@ import {ServerStoreService} from '@eg/core/server-store.service';
 import {PatronService} from '@eg/staff/share/patron/patron.service';
 import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
+import {Pager} from '@eg/share/util/pager';
 import {CircService, CircDisplayInfo, CheckinParams, CheckinResult
     } from '@eg/staff/share/circ/circ.service';
-import {Pager} from '@eg/share/util/pager';
 import {BarcodeSelectComponent
     } from '@eg/staff/share/barcodes/barcode-select.component';
 import {PrintService} from '@eg/share/print/print.service';
+import {MarkDamagedDialogComponent
+    } from '@eg/staff/share/holdings/mark-damaged-dialog.component';
+import {CopyAlertsDialogComponent
+    } from '@eg/staff/share/holdings/copy-alerts-dialog.component';
+import {BucketDialogComponent
+    } from '@eg/staff/share/buckets/bucket-dialog.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {BackdateDialogComponent} from '@eg/staff/share/circ/backdate-dialog.component';
 
 interface CheckinGridEntry extends CheckinResult {
     // May need to extend...
@@ -51,6 +62,7 @@ export class CheckinComponent implements OnInit, AfterViewInit {
     isHoldCapture = false;
     strictBarcode = false;
     trimList = false;
+    itemNeverCirced: string;
 
     gridDataSource: GridDataSource = new GridDataSource();
     cellTextGenerator: GridCellTextGenerator;
@@ -61,6 +73,11 @@ export class CheckinComponent implements OnInit, AfterViewInit {
 
     @ViewChild('grid') private grid: GridComponent;
     @ViewChild('barcodeSelect') private barcodeSelect: BarcodeSelectComponent;
+    @ViewChild('markDamagedDialog') private markDamagedDialog: MarkDamagedDialogComponent;
+    @ViewChild('copyAlertsDialog') private copyAlertsDialog: CopyAlertsDialogComponent;
+    @ViewChild('bucketDialog') private bucketDialog: BucketDialogComponent;
+    @ViewChild('itemNeverCircedStr') private itemNeverCircedStr: StringComponent;
+    @ViewChild('backdateDialog') private backdateDialog: BackdateDialogComponent;
 
     constructor(
         private router: Router,
@@ -70,6 +87,7 @@ export class CheckinComponent implements OnInit, AfterViewInit {
         private auth: AuthService,
         private store: ServerStoreService,
         private circ: CircService,
+        private toast: ToastService,
         private printer: PrintService,
         public patronService: PatronService
     ) {}
@@ -223,5 +241,82 @@ export class CheckinComponent implements OnInit, AfterViewInit {
             this.modifiers.auto_print_holds_transits
         );
     }
+
+    getCopyIds(rows: CheckinGridEntry[], skipStatus?: number): number[] {
+        return this.getCopies(rows, skipStatus).map(c => Number(c.id()));
+    }
+
+    getCopies(rows: CheckinGridEntry[], skipStatus?: number): IdlObject[] {
+        let copies = rows.filter(r => r.copy).map(r => r.copy);
+        if (skipStatus) {
+            copies = copies.filter(
+                c => Number(c.status().id()) !== Number(skipStatus));
+        }
+        return copies;
+    }
+
+
+    markDamaged(rows: CheckinGridEntry[]) {
+        const copyIds = this.getCopyIds(rows, 14 /* ignore damaged */);
+        if (copyIds.length === 0) { return; }
+
+        from(copyIds).pipe(concatMap(id => {
+            this.markDamagedDialog.copyId = id;
+            return this.markDamagedDialog.open({size: 'lg'});
+        }));
+    }
+
+    addItemAlerts(rows: CheckinGridEntry[]) {
+        const copyIds = this.getCopyIds(rows);
+        if (copyIds.length === 0) { return; }
+
+        this.copyAlertsDialog.copyIds = copyIds;
+        this.copyAlertsDialog.mode = 'create'
+        this.copyAlertsDialog.open({size: 'lg'}).subscribe();
+    }
+
+    manageItemAlerts(rows: CheckinGridEntry[]) {
+        const copyIds = this.getCopyIds(rows);
+        if (copyIds.length === 0) { return; }
+
+        this.copyAlertsDialog.copyIds = copyIds;
+        this.copyAlertsDialog.mode = 'manage';
+        this.copyAlertsDialog.open({size: 'lg'}).subscribe();
+    }
+
+    openBucketDialog(rows: CheckinGridEntry[]) {
+        const copyIds = this.getCopyIds(rows);
+        if (copyIds.length > 0) {
+            this.bucketDialog.bucketClass = 'copy';
+            this.bucketDialog.itemIds = copyIds;
+            this.bucketDialog.open({size: 'lg'});
+        }
+    }
+
+    retrieveLastPatron(rows: CheckinGridEntry[]) {
+        const copy = this.getCopies(rows).pop();
+        if (!copy) { return; }
+
+        this.circ.lastCopyCirc(copy.id()).then(circ => {
+            if (circ) {
+                this.router.navigate(['/staff/circ/patron', circ.usr(), 'checkout']);
+            } else {
+                this.itemNeverCirced = copy.barcode();
+                setTimeout(() => this.toast.danger(this.itemNeverCircedStr.text));
+            }
+        });
+    }
+
+    backdatePostCheckin(rows: CheckinGridEntry[]) {
+        const circs = rows.map(r => r.circ).filter(circ => Boolean(circ));
+        if (circs.length === 0) { return; }
+
+        this.backdateDialog.circIds = circs.map(c => c.id());
+        this.backdateDialog.open().subscribe(backdate => {
+            if (backdate) {
+                circs.forEach(circ => circ.checkin_time(backdate));
+            }
+        });
+    }
 }
 
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.html
new file mode 100644 (file)
index 0000000..d4dc7e3
--- /dev/null
@@ -0,0 +1,38 @@
+
+<eg-string #successMsg text="Successfully Marked Claims Returned" i18n-text></eg-string>
+<eg-string #errorMsg text="Failed To Mark Claims Returned" i18n-text></eg-string>
+
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title">
+      <span i18n>Backdate Already Checked-In Circulations
+      </span>
+    </h4>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <h5 i18n>Backdating {{circIds.length}} circulaion(s)</h5>
+    <div class="row mt-3">
+      <div class="col-lg-5" i18n>Effective Date:</div>
+      <div class="col-lg-7">
+        <eg-date-select [required]="true" [initialIso]="backdate" 
+          (onChangeAsIso)="backdate = $event">
+        </eg-date-select>
+      </div>
+    </div>
+    <div class="row" *ngIf="updateCount > 0">
+      <div class="col-lg-6 offset-lg-3">
+        <span i18n>Updated {{updateCount}} of {{circIds.length}}...</span>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success" 
+      [disabled]="!backdate" (click)="modifyBatch()" i18n>Submit</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/circ/backdate-dialog.component.ts
new file mode 100644 (file)
index 0000000..4c7e8eb
--- /dev/null
@@ -0,0 +1,47 @@
+import {Component, OnInit, Output, Input, ViewChild, EventEmitter} from '@angular/core';
+import {Observable, empty, of, from, throwError} from 'rxjs';
+import {concatMap, mergeMap, map} from 'rxjs/operators';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+import {EventService} from '@eg/core/event.service';
+
+@Component({
+  templateUrl: 'backdate-dialog.component.html',
+  selector: 'eg-backdate-dialog'
+})
+export class BackdateDialogComponent extends DialogComponent implements OnInit {
+
+    circIds: number[];
+    backdate: string;
+    updateCount = 0;
+
+    constructor(
+        private modal: NgbModal,
+        private net: NetService,
+        private auth: AuthService,
+        private evt: EventService
+    ) { super(modal); }
+
+    ngOnInit() {
+        this.onOpen$.subscribe(_ => {
+            this.updateCount = 0;
+            this.backdate = new Date().toISOString();
+        });
+    }
+
+    modifyBatch() {
+        this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.post_checkin_backdate.batch',
+            this.auth.token(), this.circIds, this.backdate
+        ).subscribe(
+            res => this.updateCount++,
+            err => console.error(err),
+            ()  => this.close(this.backdate)
+        );
+    }
+}
+
+
index 8c8fef8..b712fdd 100644 (file)
@@ -12,6 +12,7 @@ import {CircEventsComponent} from './events-dialog.component';
 import {OpenCircDialogComponent} from './open-circ-dialog.component';
 import {RouteDialogComponent} from './route-dialog.component';
 import {CopyInTransitDialogComponent} from './in-transit-dialog.component';
+import {BackdateDialogComponent} from './backdate-dialog.component';
 
 @NgModule({
     declarations: [
@@ -22,6 +23,7 @@ import {CopyInTransitDialogComponent} from './in-transit-dialog.component';
         ClaimsReturnedDialogComponent,
         CircEventsComponent,
         RouteDialogComponent,
+        BackdateDialogComponent,
         CopyInTransitDialogComponent,
         OpenCircDialogComponent
     ],
@@ -32,6 +34,7 @@ import {CopyInTransitDialogComponent} from './in-transit-dialog.component';
     ],
     exports: [
         CircGridComponent,
+        BackdateDialogComponent,
         CircComponentsComponent
     ],
     providers: [
index 3485152..ae558ef 100644 (file)
@@ -1038,5 +1038,12 @@ export class CircService {
             return Promise.resolve();
         });
     }
+
+    lastCopyCirc(copyId: number): Promise<IdlObject> {
+        return this.pcrud.search('circ',
+            {target_copy : copyId},
+            {order_by : {circ : 'xact_start desc' }, limit : 1}
+        ).toPromise();
+    }
 }