LP1904036 Mark missing; initial checkin api
authorBill Erickson <berickxx@gmail.com>
Tue, 23 Feb 2021 23:10:10 +0000 (18:10 -0500)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:24 +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/share/circ/circ.service.ts
Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.html
Open-ILS/src/eg2/src/app/staff/share/circ/grid.component.ts

index 7bede2f..3c513d0 100644 (file)
@@ -1,5 +1,5 @@
 import {Injectable} from '@angular/core';
-import {Observable} from 'rxjs';
+import {Observable, empty} from 'rxjs';
 import {map, mergeMap} from 'rxjs/operators';
 import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
@@ -39,6 +39,22 @@ export interface CheckoutResult {
     record?: IdlObject;
 }
 
+export interface CheckinParams {
+    noop?: boolean;
+    copy_id?: number;
+    copy_barcode?: string;
+}
+
+export interface CheckinResult {
+    index: number;
+    evt: EgEvent;
+    params: CheckinParams;
+    success: boolean;
+    copy?: IdlObject;
+    circ?: IdlObject;
+    record?: IdlObject;
+}
+
 @Injectable()
 export class CircService {
     static resultIndex = 0;
@@ -109,5 +125,82 @@ export class CircService {
 
         return Promise.resolve(result);
     }
+
+    checkin(params: CheckinParams, override?: boolean): Promise<CheckinResult> {
+
+        console.debug('checking in with', params);
+
+        let method = 'open-ils.circ.checkin';
+        if (override) { method += '.override'; }
+
+        return this.net.request(
+            'open-ils.circ', method,
+            this.auth.token(), params).toPromise()
+        .then(result => this.processCheckinResult(params, result));
+    }
+
+    processCheckinResult(
+        params: CheckinParams, response: any): Promise<CheckinResult> {
+
+        console.debug('checkout resturned', response);
+
+        if (Array.isArray(response)) { response = response[0]; }
+
+        const evt = this.evt.parse(response);
+        const payload = evt.payload;
+
+        if (!payload) {
+            this.audio.play('error.unknown.no_payload');
+            return Promise.reject();
+        }
+
+        switch (evt.textcode) {
+            case 'ITEM_NOT_CATALOGED':
+                // alert, etc.
+        }
+
+        const result: CheckinResult = {
+            index: CircService.resultIndex++,
+            evt: evt,
+            params: params,
+            success: evt.textcode === 'SUCCESS', // or route, no change, etc.
+            circ: payload.circ,
+            copy: payload.copy,
+            record: payload.record
+        };
+
+        return Promise.resolve(result);
+    }
+
+    // The provided params (minus the copy_id) will be used
+    // for all items.
+    checkinBatch(copyIds: number[], params?: CheckinParams): Observable<CheckinResult> {
+        if (copyIds.length === 0) { return empty(); }
+
+        if (!params) { params = {}; }
+        const ids = [].concat(copyIds); // clone
+
+        let observer;
+        const observable = new Observable<CheckinResult>(o => observer = o);
+
+        const checkinOne = (ids: number[]): Promise<CheckinResult> => {
+            if (ids.length === 0) {
+                observer.complete();
+                return Promise.resolve(null);
+            }
+
+            const cparams = Object.assign(params, {}); // clone
+            cparams.copy_id = ids.pop();
+
+            return this.checkin(cparams).then(result => {
+                observer.next(result);
+                return checkinOne(ids);
+            });
+        }
+
+        checkinOne(ids);
+
+        return observable;
+    }
 }
 
index 2b8d81d..5317727 100644 (file)
@@ -5,6 +5,12 @@
 <eg-due-date-dialog #dueDateDialog></eg-due-date-dialog>
 <eg-mark-damaged-dialog #markDamagedDialog [handleCheckin]="true">
 </eg-mark-damaged-dialog>
+<eg-mark-missing-dialog #markMissingDialog></eg-mark-missing-dialog>
+<eg-confirm-dialog #itemsOutConfirm
+  i18n-dialogTitle i18n-dialogBody
+  dialogTitle="Items Checked Out"
+  dialogBody="The selected items are checked out.  Check them in before continuing?">
+</eg-confirm-dialog>
 
 <ng-template #titleTemplate let-r="row">
   <ng-container *ngIf="r.record">
     group="Mark" i18n-group i18n-label label="Mark Item Damaged"
     (onClick)="showMarkDamagedDialog($event)"></eg-grid-toolbar-action>
 
+  <eg-grid-toolbar-action
+    group="Mark" i18n-group i18n-label label="Mark Item Missing"
+    (onClick)="showMarkMissingDialog($event)"></eg-grid-toolbar-action>
+
+  <eg-grid-toolbar-action i18n-label label="Checkin" (onClick)="checkin($event)">
+  </eg-grid-toolbar-action>
+
   <eg-grid-column [index]="true" path="index" [hidden]="true"
     label="Row Index" i18n-label></eg-grid-column>
 
index 56bebe1..43d1f1f 100644 (file)
@@ -6,8 +6,10 @@ import {IdlObject} from '@eg/core/idl.service';
 import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
 import {PcrudService} from '@eg/core/pcrud.service';
-import {CheckoutParams, CheckoutResult, CircService} from './circ.service';
+import {CheckoutParams, CheckoutResult, CheckinParams, CheckinResult,
+    CircService} from './circ.service';
 import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
 import {GridDataSource, GridColumn, GridCellTextGenerator,
     GridRowFlairEntry} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
@@ -23,6 +25,8 @@ import {StringComponent} from '@eg/share/string/string.component';
 import {DueDateDialogComponent} from './due-date-dialog.component';
 import {MarkDamagedDialogComponent
     } from '@eg/staff/share/holdings/mark-damaged-dialog.component';
+import {MarkMissingDialogComponent
+    } from '@eg/staff/share/holdings/mark-missing-dialog.component';
 
 export interface CircGridEntry {
     index: string; // class + id -- row index
@@ -90,6 +94,10 @@ export class CircGridComponent implements OnInit {
     @ViewChild('dueDateDialog') private dueDateDialog: DueDateDialogComponent;
     @ViewChild('markDamagedDialog')
         private markDamagedDialog: MarkDamagedDialogComponent;
+    @ViewChild('markMissingDialog')
+        private markMissingDialog: MarkMissingDialogComponent;
+    @ViewChild('itemsOutConfirm')
+        private itemsOutConfirm: ConfirmDialogComponent;
 
     constructor(
         private org: OrgService,
@@ -322,6 +330,39 @@ export class CircGridComponent implements OnInit {
         });
     }
 
+    showMarkMissingDialog(rows: CircGridEntry[]) {
+        const copyIds = this.getCopyIds(rows, 4 /* ignore missing */);
+
+        if (copyIds.length === 0) { return; }
+
+        // This assumes all of our items our checked out, since this is
+        // a circ grid.  If we add support later for showing completed
+        // circulations, there may be cases where we can skip the items
+        // out confirmation alert and subsequent checkin
+        this.itemsOutConfirm.open().subscribe(confirmed => {
+            if (!confirmed) { return; }
+
+            this.checkin(rows, {noop: true}, true).then(_ => {
+
+                this.markMissingDialog.copyIds = copyIds;
+                this.markMissingDialog.open({}).subscribe(
+                    rowsModified => {
+                        if (rowsModified) {
+                            this.emitReloadRequest();
+                        }
+                    }
+                );
+            });
+        });
+    }
+
+    // TODO: progress dialog
+    // Same params will be used for each copy
+    checkin(rows: CircGridEntry[], params?: CheckinParams, noReload?: boolean): Promise<any> {
+        return this.circ.checkinBatch(this.getCopyIds(rows), params).toPromise()
+        .then(_ => { if (!noReload) { this.emitReloadRequest(); } });
+    }
+
     emitReloadRequest() {
         this.entries = null;
         this.reloadRequested.emit();