LPLP1904036 Adding renew interface and entry points
authorBill Erickson <berickxx@gmail.com>
Mon, 10 May 2021 22:02:57 +0000 (18:02 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:34 +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/renew/renew.component.css [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/renew/renew.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/renew/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/share/circ/circ.service.ts
Open-ILS/src/eg2/src/app/staff/share/circ/components.component.html
Open-ILS/src/eg2/src/app/staff/share/worklog/worklog.service.ts
Open-ILS/src/templates/staff/navbar.tt2

diff --git a/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.css b/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.css
new file mode 100644 (file)
index 0000000..64d3930
--- /dev/null
@@ -0,0 +1,4 @@
+
+.badge {
+  font-size: 110%;
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.html b/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.html
new file mode 100644 (file)
index 0000000..2808938
--- /dev/null
@@ -0,0 +1,174 @@
+<eg-staff-banner i18n-bannerText bannerText="Renew Items">
+</eg-staff-banner>
+
+<eg-circ-components></eg-circ-components>
+<eg-progress-dialog #progressDialog></eg-progress-dialog>
+<eg-barcode-select #barcodeSelect></eg-barcode-select>
+<eg-copy-alerts-dialog #copyAlertsDialog></eg-copy-alerts-dialog>
+<eg-cancel-transit-dialog #cancelTransitDialog></eg-cancel-transit-dialog>
+<eg-worklog-strings-components></eg-worklog-strings-components>
+<eg-string #itemNeverCircedStr i18n-text 
+  text="Item '{{itemNeverCirced}}' has never circulated."></eg-string>
+
+<div class="row mb-3 pb-3 border-bottom">
+  <div class="col-lg-12 d-flex">
+    <div class="form-inline">
+      <div class="input-group">
+        <div class="input-group-prepend">
+          <span class="input-group-text" i18n>Barcode</span>
+        </div>
+        <input type="text" class="form-control" id="barcode-input"
+          placeholder="Barcode..." i18n-placeholder [(ngModel)]="barcode"
+          i18n-aria-label aria-label="Barcode Input" (keydown.enter)="renew()" />
+        <div class="input-group-append">
+          <button class="btn btn-outline-dark" (keydown.enter)="renew()" 
+            (click)="renew()" i18n>Submit</button>
+        </div>
+      </div>
+    </div>
+    <div class="flex-1"></div>
+    <div class="mr-2">
+      <div class="form-inline">
+        <div class="form-check form-check-inline">
+          <input class="form-check-input" type="checkbox" id="use-date-cbox" 
+            [(ngModel)]="useDueDate"/>
+          <label class="form-check-label" 
+            for="use-date-cbox" i18n>Specific Due Date</label>
+        </div>
+        <eg-date-select [initialIso]="dueDate" 
+          (onChangeAsIso)="dueDate = $event"></eg-date-select>
+      </div>
+    </div>
+  </div>
+</div>
+
+<!-- doc_id below because checkin returns an MVR -->
+<ng-template #titleTemplate let-r="row">
+  <ng-container *ngIf="r.record">
+    <a routerLink="/staff/catalog/record/{{r.record.doc_id()}}">{{r.title}}</a>
+  </ng-container>
+  <ng-container *ngIf="!r.record">{{r.title}}</ng-container>
+</ng-template>
+
+<ng-template #barcodeTemplate let-r="row">
+  <ng-container *ngIf="r.copy">
+    <a href="/eg/staff/cat/item/{{r.copy.id()}}">{{r.copy.barcode()}}</a>
+  </ng-container>
+</ng-template>
+
+<div class="row">
+  <div class="col-lg-12">
+    <eg-grid #grid [dataSource]="gridDataSource" [sortable]="true"
+      [useLocalSort]="true" [cellTextGenerator]="cellTextGenerator"
+      [disablePaging]="true" persistKey="circ.renew">
+
+      <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="Circulation" i18n-label label="Cancel Transits"
+        (onClick)="cancelTransits($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Show" i18n-label 
+        label="Retrieve Last Patron Who Circulated Item"
+        [disabled]="grid.context.rowSelector.selected().length !== 1"
+        (onClick)="retrieveLastPatron($event)">
+      </eg-grid-toolbar-action>
+
+      <eg-grid-toolbar-action
+        i18n-group group="Show" i18n-label 
+        label="Show Last Few Circs"
+        [disabled]="grid.context.rowSelector.selected().length !== 1"
+        (onClick)="showRecentCircs($event)">
+      </eg-grid-toolbar-action>
+
+      <!-- COLUMNS -->
+
+      <eg-grid-column path="index" [index]="true" 
+        label="Row Index" i18n-label [hidden]="true"></eg-grid-column>
+
+      <eg-grid-column path="mbts.balance_owed" label="Balance Owed" 
+        datatype="money" i18n-label></eg-grid-column>
+
+      <eg-grid-column path="copy.barcode" label="Barcode" 
+        i18n-label [cellTemplate]="barcodeTemplate"></eg-grid-column>
+
+      <eg-grid-column path="circ.id" label="Bill #" i18n-label>
+      </eg-grid-column>
+
+      <eg-grid-column path="circ.checkin_time" label="Checkin Date" i18n-label
+        datatype="timestamp" [datePlusTime]="true"></eg-grid-column>
+
+      <eg-grid-column path="patron.family_name" label="Family Name" i18n-label>
+      </eg-grid-column>
+
+      <eg-grid-column path="circ.xact_finish" label="Finish" i18n-label
+        datatype="timestamp" [datePlusTime]="true"></eg-grid-column>
+
+      <eg-grid-column path="copy.location.name" label="Location" i18n-label>
+      </eg-grid-column>
+
+      <eg-grid-column name="routeTo" label="Route To" i18n-label>
+      </eg-grid-column>
+
+      <eg-grid-column path="circ.xact_start" label="Start" i18n-label
+        datatype="timestamp" [datePlusTime]="true"></eg-grid-column>
+
+      <eg-grid-column path="title" label="Title" i18n-label 
+        [cellTemplate]="titleTemplate"></eg-grid-column>
+
+      <eg-grid-column path="copy.circ_modifier" 
+        label="Circulation Modifier" i18n-label></eg-grid-column>
+
+      <eg-grid-column path="copy.circ_lib.shortname"
+        label="Circulation Library" i18n-label></eg-grid-column>
+
+      <eg-grid-column path="copy.status.name"
+        label="Item Status" i18n-label></eg-grid-column>
+
+    </eg-grid>
+  </div>
+</div>
+
+<div class="row mt-3 pt-3">
+  <div class="col-lg-12 d-flex">
+    <div class="flex-1"></div>
+    <div class="mr-3">
+      <button class="btn btn-outline-dark" 
+        (click)="printReceipt()" i18n>Print Receipt</button>
+    </div>
+    <div class="mr-3">
+      <div class="form-check form-check-inline">
+        <input class="form-check-input" type="checkbox" 
+          id="trim-list-cbox" [(ngModel)]="trimList"/>
+        <label class="form-check-label" 
+          for="trim-list-cbox" i18n>Trim List (20)</label>
+      </div>
+    </div>
+    <div class="mr-3">
+      <div class="form-check form-check-inline">
+        <input class="form-check-input" type="checkbox" 
+          (ngModelChange)="toggleStrictBarcode($event)"
+          id="strict-barcode-cbox" [(ngModel)]="strictBarcode"/>
+        <label class="form-check-label" 
+          for="strict-barcode-cbox" i18n>Strict Barcode</label>
+      </div>
+    </div>
+  </div>
+</div>
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.component.ts
new file mode 100644 (file)
index 0000000..ea40697
--- /dev/null
@@ -0,0 +1,291 @@
+import {Component, ViewChild, OnInit, AfterViewInit, HostListener} from '@angular/core';
+import {Location} from '@angular/common';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {empty, 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';
+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, CheckoutParams, CheckoutResult
+    } from '@eg/staff/share/circ/circ.service';
+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 {CancelTransitDialogComponent
+    } from '@eg/staff/share/circ/cancel-transit-dialog.component';
+import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
+import {AnonCacheService} from '@eg/share/util/anon-cache.service';
+
+
+interface RenewGridEntry extends CheckoutResult {
+    // May need to extend...
+    foo?: number; // Empty interfaces are not allowed.
+}
+
+const TRIM_LIST_TO = 20;
+
+@Component({
+  templateUrl: 'renew.component.html',
+  styleUrls: ['renew.component.css']
+})
+export class RenewComponent implements OnInit, AfterViewInit {
+    renewals: RenewGridEntry[] = [];
+    autoIndex = 0;
+
+    barcode: string;
+    dueDate: string;
+    useDueDate = false;
+    fineTally = 0;
+    strictBarcode = false;
+    trimList = false;
+    itemNeverCirced: string;
+
+    gridDataSource: GridDataSource = new GridDataSource();
+    cellTextGenerator: GridCellTextGenerator;
+
+    private copiesInFlight: {[barcode: string]: boolean} = {};
+
+    @ViewChild('grid') private grid: GridComponent;
+    @ViewChild('barcodeSelect') private barcodeSelect: BarcodeSelectComponent;
+    @ViewChild('markDamagedDialog') private markDamagedDialog: MarkDamagedDialogComponent;
+    @ViewChild('copyAlertsDialog') private copyAlertsDialog: CopyAlertsDialogComponent;
+    @ViewChild('itemNeverCircedStr') private itemNeverCircedStr: StringComponent;
+    @ViewChild('cancelTransitDialog') private cancelTransitDialog: CancelTransitDialogComponent;
+
+    constructor(
+        private router: Router,
+        private route: ActivatedRoute,
+        private ngLocation: Location,
+        private net: NetService,
+        private org: OrgService,
+        private auth: AuthService,
+        private store: ServerStoreService,
+        private circ: CircService,
+        private toast: ToastService,
+        private printer: PrintService,
+        private holdings: HoldingsService,
+        private anonCache: AnonCacheService,
+        public patronService: PatronService
+    ) {}
+
+    ngOnInit() {
+
+        this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+            return from(this.renewals);
+        };
+
+        this.store.getItemBatch(['circ.renew.strict_barcode'])
+        .then(sets => {
+            this.strictBarcode = sets['circ.renew.strict_barcode'];
+        }).then(_ => this.circ.applySettings());
+    }
+
+    ngAfterViewInit() {
+        this.focusInput();
+    }
+
+    focusInput() {
+        const input = document.getElementById('barcode-input');
+        if (input) { input.focus(); }
+    }
+
+    renew(params?: CheckoutParams, override?: boolean): Promise<CheckoutResult> {
+        if (!this.barcode) { return Promise.resolve(null); }
+
+        const promise = params ? Promise.resolve(params) : this.collectParams();
+
+        return promise.then((collectedParams: CheckoutParams) => {
+            if (!collectedParams) { return null; }
+
+            if (this.copiesInFlight[this.barcode]) {
+                console.debug('Item ' + this.barcode + ' is already mid-renewal');
+                return null;
+            }
+
+            this.copiesInFlight[this.barcode] = true;
+            return this.circ.renew(collectedParams);
+        })
+
+        .then((result: CheckoutResult) => {
+            if (result && result.success) {
+                this.gridifyResult(result);
+            }
+            delete this.copiesInFlight[this.barcode];
+            this.resetForm();
+            return result;
+        })
+
+        .finally(() => delete this.copiesInFlight[this.barcode]);
+    }
+
+    collectParams(): Promise<CheckoutParams> {
+
+        const params: CheckoutParams = {
+            copy_barcode: this.barcode,
+            due_date: this.useDueDate ? this.dueDate : null,
+            _renewal: true
+        };
+
+        return this.barcodeSelect.getBarcode('asset', this.barcode)
+        .then(selection => {
+            if (selection) {
+                params.copy_id = selection.id;
+                params.copy_barcode = selection.barcode;
+                return params;
+            } else {
+                // User canceled the multi-match selection dialog.
+                return null;
+            }
+        });
+    }
+
+    resetForm() {
+        this.barcode = '';
+        this.focusInput();
+    }
+
+    gridifyResult(result: CheckoutResult) {
+        const entry: RenewGridEntry = result;
+        entry.index = this.autoIndex++;
+
+        if (result.copy) {
+            result.copy.circ_lib(this.org.get(result.copy.circ_lib()));
+        }
+
+        if (result.mbts) {
+            this.fineTally =
+                ((this.fineTally * 100) + (result.mbts.balance_owed() * 100)) / 100;
+        }
+
+        this.renewals.unshift(entry);
+
+        if (this.trimList && this.renewals.length >= TRIM_LIST_TO) {
+            this.renewals.length = TRIM_LIST_TO;
+        }
+        this.grid.reload();
+    }
+
+    toggleStrictBarcode(active: boolean) {
+        if (active) {
+            this.store.setItem('circ.checkin.strict_barcode', true);
+        } else {
+            this.store.removeItem('circ.checkin.strict_barcode');
+        }
+    }
+
+    printReceipt() {
+        if (this.renewals.length === 0) { return; }
+
+        this.printer.print({
+            printContext: 'default',
+            templateName: 'renew',
+            contextData: {renewals: this.renewals}
+        });
+    }
+
+    getCopyIds(rows: RenewGridEntry[], skipStatus?: number): number[] {
+        return this.getCopies(rows, skipStatus).map(c => Number(c.id()));
+    }
+
+    getCopies(rows: RenewGridEntry[], 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: RenewGridEntry[]) {
+        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: RenewGridEntry[]) {
+        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: RenewGridEntry[]) {
+        const copyIds = this.getCopyIds(rows);
+        if (copyIds.length === 0) { return; }
+
+        this.copyAlertsDialog.copyIds = copyIds;
+        this.copyAlertsDialog.mode = 'manage';
+        this.copyAlertsDialog.open({size: 'lg'}).subscribe();
+    }
+
+    retrieveLastPatron(rows: RenewGridEntry[]) {
+        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));
+            }
+        });
+    }
+
+    cancelTransits(rows: RenewGridEntry[]) {
+
+        rows = rows.filter(row => row.copy && row.copy.status().id() === 6);
+
+        // Copies in transit are not always accompanied by their transit.
+        from(rows).pipe(concatMap(row => {
+            return from(
+                this.circ.findCopyTransit(row)
+                .then(transit => row.transit = transit)
+            );
+        }))
+        .pipe(concatMap(_ => {
+
+            const ids = rows
+                .filter(row => Boolean(row.transit))
+                .map(row => row.transit.id());
+
+            if (ids.length > 0) {
+                this.cancelTransitDialog.transitIds = ids;
+                return this.cancelTransitDialog.open();
+            } else {
+                return empty();
+            }
+
+        })).subscribe();
+    }
+
+    showRecentCircs(rows: RenewGridEntry[]) {
+        const copyId = this.getCopyIds(rows)[0];
+        if (copyId) {
+            const url = `/eg/staff/cat/item/${copyId}/circs`;
+            window.open(url);
+        }
+    }
+}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/renew/renew.module.ts
new file mode 100644 (file)
index 0000000..a2c304d
--- /dev/null
@@ -0,0 +1,37 @@
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {RenewRoutingModule} from './routing.module';
+import {FmRecordEditorModule} from '@eg/share/fm-editor/fm-editor.module';
+import {HoldsModule} from '@eg/staff/share/holds/holds.module';
+import {BillingModule} from '@eg/staff/share/billing/billing.module';
+import {CircModule} from '@eg/staff/share/circ/circ.module';
+import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {BookingModule} from '@eg/staff/share/booking/booking.module';
+import {PatronModule} from '@eg/staff/share/patron/patron.module';
+import {BarcodesModule} from '@eg/staff/share/barcodes/barcodes.module';
+import {RenewComponent} from './renew.component';
+import {WorkLogModule} from '@eg/staff/share/worklog/worklog.module';
+
+@NgModule({
+  declarations: [
+    RenewComponent
+  ],
+  imports: [
+    StaffCommonModule,
+    RenewRoutingModule,
+    FmRecordEditorModule,
+    BillingModule,
+    CircModule,
+    HoldsModule,
+    HoldingsModule,
+    BookingModule,
+    PatronModule,
+    BarcodesModule,
+    WorkLogModule
+  ],
+  providers: [
+  ]
+})
+
+export class RenewModule {}
+
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/renew/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/renew/routing.module.ts
new file mode 100644 (file)
index 0000000..3f6718c
--- /dev/null
@@ -0,0 +1,15 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {RenewComponent} from './renew.component';
+
+const routes: Routes = [{
+    path: '',
+    component: RenewComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class RenewRoutingModule {}
index 5122ec2..9a151cf 100644 (file)
@@ -17,6 +17,10 @@ const routes: Routes = [{
   path: 'checkin',
   loadChildren: () =>
     import('./checkin/checkin.module').then(m => m.CheckinModule)
+}, {
+  path: 'renew',
+  loadChildren: () =>
+    import('./renew/renew.module').then(m => m.RenewModule)
 }];
 
 @NgModule({
index b7afa93..cf29ea9 100644 (file)
             <span class="material-icons" aria-hidden="true">autorenew</span>
             <span i18n>Renew Items</span>
           </a>
+          <a class="dropdown-item" routerLink="/staff/circ/renew">
+            <span class="material-icons" aria-hidden="true">autorenew</span>
+            <span i18n>Renew Items (Experimental)</span>
+          </a>
           <a class="dropdown-item" href="/eg/staff/circ/patron/register"
             egAccessKey keyCtx="navbar" i18n-keySpec i18n-keyDesc
             keySpec="shift+f1" keyDesc="Register Patron">
index cf8736c..2f4cf01 100644 (file)
@@ -148,6 +148,7 @@ export interface CircResultCommon {
     patron?: IdlObject;
     transit?: IdlObject;
     copyAlerts?: IdlObject[];
+    mbts?: IdlObject;
 
     // Calculated values
     title?: string;
@@ -187,7 +188,6 @@ export interface CheckinParams {
 
 export interface CheckinResult extends CircResultCommon {
     params: CheckinParams;
-    mbts?: IdlObject;
     routeTo?: string; // org name or in-branch destination
     destOrg?: IdlObject;
     destAddress?: IdlObject;
@@ -437,7 +437,7 @@ export class CircService {
         result.nonCatCirc = payload.noncat_circ;
 
         return this.fleshCommonData(result).then(_ => {
-            const action = params._renewal ? 'renewal' :
+            const action = params._renewal ? 'renew' :
                 (params.noncat ? 'noncat_checkout' : 'checkout');
             this.addWorkLog(action, result);
             return result;
@@ -482,6 +482,13 @@ export class CircService {
                     barcode: result.params.copy_barcode
                 });
 
+            case 'ASSET_COPY_NOT_FOUND':
+                this.audio.play(`error.${key}.not_found`);
+                return this.exitAlert({
+                    textcode: result.firstEvent.textcode,
+                    barcode: result.params.copy_barcode
+                });
+
             default:
                 this.audio.play(`error.${key}.unknown`);
                 return this.exitAlert({
index 59f4373..fa41b3c 100644 (file)
@@ -50,6 +50,9 @@
 <eg-string key="staff.circ.events.PATRON_ACCOUNT_EXPIRED" i18n-text
   text="This account has expired and may not circulate items."></eg-string>
 
+<eg-string key="staff.circ.events.ASSET_COPY_NOT_FOUND" i18n-text
+  text="Item was not found and cannot be renewed."></eg-string>
+
 <ng-template #claimsReturnsTmpl>
   Item "{{barcode}}" is marked as Claims Returned</ng-template>
 <eg-string key="staff.circ.events.CIRC_CLAIMS_RETURNED" 
index 8fe3cc1..af0818b 100644 (file)
@@ -44,6 +44,7 @@ export class WorkLogService {
 
     record(entry: WorkLogEntry) {
 
+console.log('1');
         if (this.maxEntries === null) {
             throw new Error('WorkLogService.loadSettings() required');
             return;
@@ -54,27 +55,35 @@ export class WorkLogService {
                 'Add <eg-worklog-strings-components/> to your component for worklog support');
             return;
         }
+console.log('1');
 
         entry.when = new Date();
         entry.actor = this.auth.user().usrname();
+        console.log(`worklog_${entry.action}`);
+        console.log(this.workLogStrings[`worklog_${entry.action}`]);
         entry.msg = this.workLogStrings[`worklog_${entry.action}`].text;
+console.log('1');
 
         const workLog: WorkLogEntry[] =
             this.store.getLocalItem('eg.work_log') || [];
 
         let patronLog: WorkLogEntry[] =
             this.store.getLocalItem('eg.patron_log') || [];
+console.log('1');
 
         workLog.push(entry);
         if (workLog.length > this.maxEntries) {
             workLog.shift();
         }
+console.log('1');
 
         this.store.setLocalItem('eg.work_log', workLog);
+console.log('1');
 
         if (entry.patron_id) {
             // Remove existing entries that match this patron
             patronLog = patronLog.filter(e => e.patron_id !== entry.patron_id);
+console.log('1');
 
             patronLog.push(entry);
             if (patronLog.length > this.maxPatrons) {
index bc1d4e1..faba109 100644 (file)
               [% l('Renew Items') %]
             </a>
           </li>
+          <li ng-if="username">
+            <a href="/eg2/staff/circ/renew">
+              <span class="glyphicon glyphicon-refresh" aria-hidden="true"></span>
+              [% l('Renew Items (Experimental)') %]
+            </a>
+          </li>
+
           <li ng-if="!username">
             <a href="" ng-click="rs.active_tab('renew')" target="_self"
               eg-accesskey="[% l('ctrl+f2') %]"