LP1865898 Scan Item as Missing Pieces Angular Port
authorBill Erickson <berickxx@gmail.com>
Mon, 2 Mar 2020 19:17:55 +0000 (14:17 -0500)
committerGalen Charlton <gmc@equinoxinitiative.org>
Mon, 8 Mar 2021 19:53:22 +0000 (14:53 -0500)
Port the 'Scan Item As Missing Pieces' staff client interface to
Angular.  Interface displays additional data (title/author/callnum)
during the staff confirmation step.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Mike Risher <mrisher@catalyte.io>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>
14 files changed:
Open-ILS/src/eg2/src/app/staff/cat/item/item.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/item/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/cat/routing.module.ts
Open-ILS/src/eg2/src/app/staff/nav.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts
Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts
Open-ILS/src/eg2/src/app/staff/share/holdings/holdings.service.ts
Open-ILS/src/eg2/src/app/staff/share/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/templates/staff/navbar.tt2

diff --git a/Open-ILS/src/eg2/src/app/staff/cat/item/item.module.ts b/Open-ILS/src/eg2/src/app/staff/cat/item/item.module.ts
new file mode 100644 (file)
index 0000000..9c501fb
--- /dev/null
@@ -0,0 +1,24 @@
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {CommonWidgetsModule} from '@eg/share/common-widgets.module';
+import {ItemRoutingModule} from './routing.module';
+import {HoldingsModule} from '@eg/staff/share/holdings/holdings.module';
+import {PatronModule} from '@eg/staff/share/patron/patron.module';
+import {MarkItemMissingPiecesComponent} from './missing-pieces.component';
+
+@NgModule({
+  declarations: [
+    MarkItemMissingPiecesComponent
+  ],
+  imports: [
+    StaffCommonModule,
+    CommonWidgetsModule,
+    ItemRoutingModule,
+    HoldingsModule,
+    PatronModule
+  ],
+  providers: [
+  ]
+})
+
+export class ItemModule {}
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.html b/Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.html
new file mode 100644 (file)
index 0000000..2d09622
--- /dev/null
@@ -0,0 +1,85 @@
+<eg-staff-banner i18n-bannerText bannerText="Mark Item Missing Pieces">
+</eg-staff-banner>
+
+<eg-patron-penalty-dialog #penaltyDialog></eg-patron-penalty-dialog>
+
+<div class="row">
+  <div class="col-lg-12 form-inline">
+    <div class="input-group">
+      <div class="input-group-prepend">
+        <span class="input-group-text" id='barcode-label' i18n>Barcode</span>
+      </div>
+      <input type="text" class="form-control" id="item-barcode-input" 
+        (keydown)="noSuchItem=false; true;"
+        (keyup.enter)="getItemByBarcode()" [(ngModel)]="itemBarcode" 
+        aria-describedby="barcode-label"/>
+    </div>
+    <button class="btn btn-outline-dark" 
+      (click)="getItemByBarcode()" i18n>Submit</button>
+  </div>
+</div>
+
+<div class="mt-3 mb-3 p-2" *ngIf="item">
+  <div class="row">
+    <div class="col-lg-2" i18n>Title: </div>
+    <div class="col-lg-10">{{display('title')}}</div>
+  </div>
+  <div class="row">
+    <div class="col-lg-2" i18n>Author: </div>
+    <div class="col-lg-10">{{display('author')}}</div>
+  </div>
+  <div class="row">
+    <div class="col-lg-2" i18n>Call Number: </div>
+    <div class="col-lg-10">{{item.call_number().label()}}</div>
+  </div>
+  <div class="row mt-2">
+    <div class="col-lg-12">
+      <button class="btn btn-success" (click)="processItem()" i18n>
+        Mark Item as Missing Pieces?
+      </button>
+      <button class="btn btn-warning ml-2" (click)="reset()" i18n>
+        Cancel
+      </button>
+    </div>
+  </div>
+</div>
+
+<div class="row m-1" *ngIf="noSuchItem">
+  <div class="col-lg-6 offset-lg-3">
+    <div class="alert alert-warning" i18n>
+     No item with barcode "{{itemBarcode}}".
+    </div>
+  </div>
+</div>
+
+<div class="row m-1" *ngIf="circNotFound">
+  <div class="col-lg-6 offset-lg-3">
+    <div class="alert alert-warning" i18n>
+     No circulation found for item with barcode {{itemBarcode}}.
+     Item not modified.
+    </div>
+  </div>
+</div>
+
+<div class="row m-1" *ngIf="processing">
+  <div class="col-lg-6 offset-lg-3">
+    <eg-progress-inline></eg-progress-inline>
+  </div>
+</div>
+
+<div *ngIf="letter">
+  <div class="row">
+    <div class="col-lg-3">
+      <button class="btn btn-outline-dark" (click)="printLetter()" i18n>
+        Print Letter
+      </button>
+    </div>
+  </div>
+  <div class="row m-1">
+    <div class="col-lg-8">
+      <textarea [(ngModel)]="letter" 
+        rows="{{letterRowCount()}}" class="form-control">
+      </textarea>
+    </div>
+  </div>
+</div>
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.ts b/Open-ILS/src/eg2/src/app/staff/cat/item/missing-pieces.component.ts
new file mode 100644 (file)
index 0000000..638db28
--- /dev/null
@@ -0,0 +1,164 @@
+import {Component, Input, AfterViewInit, ViewChild, Renderer2} from '@angular/core';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {PrintService} from '@eg/share/print/print.service';
+import {HoldingsService} from '@eg/staff/share/holdings/holdings.service';
+import {EventService} from '@eg/core/event.service';
+import {PatronPenaltyDialogComponent} from '@eg/staff/share/patron/penalty-dialog.component';
+
+@Component({
+  templateUrl: 'missing-pieces.component.html'
+})
+export class MarkItemMissingPiecesComponent implements AfterViewInit {
+
+    itemId: number;
+    itemBarcode: string;
+    item: IdlObject;
+    letter: string;
+    circNotFound = false;
+    processing = false;
+    noSuchItem = false;
+
+    @ViewChild('penaltyDialog', {static: false})
+    penaltyDialog: PatronPenaltyDialogComponent;
+
+    constructor(
+        private route: ActivatedRoute,
+        private renderer: Renderer2,
+        private net: NetService,
+        private printer: PrintService,
+        private pcrud: PcrudService,
+        private auth: AuthService,
+        private evt: EventService,
+        private holdings: HoldingsService
+    ) {
+        this.itemId = +this.route.snapshot.paramMap.get('id');
+    }
+
+    ngAfterViewInit() {
+        if (this.itemId) { this.getItemById(); }
+        this.renderer.selectRootElement('#item-barcode-input').focus();
+    }
+
+    getItemByBarcode(): Promise<any> {
+        this.itemId = null;
+        this.item = null;
+
+        if (!this.itemBarcode) { return Promise.resolve(); }
+
+        return this.holdings.getItemIdFromBarcode(this.itemBarcode)
+        .then(id => {
+            this.noSuchItem = (id === null);
+            this.itemId = id;
+            return this.getItemById();
+        });
+    }
+
+    selectInput() {
+        setTimeout(() =>
+            this.renderer.selectRootElement('#item-barcode-input').select());
+    }
+
+    getItemById(): Promise<any> {
+        this.circNotFound = false;
+
+        if (!this.itemId) {
+            this.selectInput();
+            return Promise.resolve();
+        }
+
+        const flesh = {
+            flesh: 3,
+            flesh_fields: {
+                acp: ['call_number'],
+                acn: ['record'],
+                bre: ['flat_display_entries']
+            }
+        };
+
+        return this.pcrud.retrieve('acp', this.itemId, flesh)
+        .toPromise().then(item => {
+            this.item = item;
+            this.itemId = item.id();
+            this.itemBarcode = item.barcode();
+            this.selectInput();
+        });
+    }
+
+    display(field: string): string {
+        if (!this.item) { return ''; }
+
+        const entry = this.item.call_number().record()
+            .flat_display_entries()
+            .filter(fde => fde.name() === field)[0];
+
+        return entry ? entry.value() : '';
+    }
+
+    reset() {
+        this.item = null;
+        this.itemId = null;
+        this.itemBarcode = null;
+        this.circNotFound = false;
+    }
+
+    processItem() {
+        this.circNotFound = false;
+
+        if (!this.item) { return; }
+
+        this.processing = true;
+
+        this.net.request(
+            'open-ils.circ',
+            'open-ils.circ.mark_item_missing_pieces',
+            this.auth.token(), this.itemId
+        ).subscribe(resp => {
+            const evt = this.evt.parse(resp); // always returns event
+            this.processing = false;
+
+            if (evt.textcode === 'ACTION_CIRCULATION_NOT_FOUND') {
+                this.circNotFound = true;
+                return;
+            }
+
+            const payload = evt.payload;
+
+            if (payload.letter) {
+                this.letter = payload.letter.template_output().data();
+            }
+
+            if (payload.slip) {
+                this.printer.print({
+                    printContext: 'default',
+                    contentType: 'text/html',
+                    text: payload.slip.template_output().data()
+                });
+            }
+
+            if (payload.circ) {
+                this.penaltyDialog.patronId = payload.circ.usr();
+                this.penaltyDialog.open().subscribe(
+                    penId => console.debug('Applied penalty ', penId));
+            }
+        });
+    }
+
+    printLetter() {
+        this.printer.print({
+            printContext: 'default',
+            contentType: 'text/plain',
+            text: this.letter
+        });
+    }
+
+    letterRowCount(): number {
+        return this.letter ? this.letter.split(/\n/).length + 2 : 20;
+    }
+}
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/cat/item/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/cat/item/routing.module.ts
new file mode 100644 (file)
index 0000000..b3e7759
--- /dev/null
@@ -0,0 +1,20 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {MarkItemMissingPiecesComponent} from './missing-pieces.component';
+
+const routes: Routes = [{
+    path: 'missing_pieces',
+    component: MarkItemMissingPiecesComponent
+  }, {
+    path: 'missing_pieces/:id',
+    component: MarkItemMissingPiecesComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule],
+  providers: []
+})
+
+export class ItemRoutingModule {}
+
index 67cf5c6..084c01f 100644 (file)
@@ -15,6 +15,9 @@ const routes: Routes = [
     loadChildren: () =>
       import('./marcbatch/marcbatch.module').then(m => m.MarcBatchModule)
   }, {
+    path: 'item',
+    loadChildren: () => import('./item/item.module').then(m => m.ItemModule)
+  }, {
     path: 'bib-from/:identType',
     component: BibByIdentComponent
   }
index eead26d..7215c62 100644 (file)
             <span class="material-icons" aria-hidden="true">question_answer</span>
             <span i18n>Item Status</span>
           </a>
-          <a class="dropdown-item" href="/eg/staff/cat/item/missing_pieces">
+          <a class="dropdown-item" routerLink="/staff/cat/item/missing_pieces">
             <span class="material-icons" aria-hidden="true">grid_on</span>
             <span i18n>Scan Item as Missing Pieces</span>
           </a>
index 9a93b3e..647cb0c 100644 (file)
 </div>
 
 <div class="mt-4 mb-4">
+  <h4>Add Patron Penalty</h4>
+  <eg-patron-penalty-dialog #penaltyDialog patronId="1"></eg-patron-penalty-dialog>
+  <button class="btn btn-outline-dark" (click)="openPenalty()" i18n>
+    Open Penalty Dialog
+  </button>
+</div>
+
+<div class="mt-4 mb-4">
   <h4>Grid Stock Selector Display and Filtering</h4>
   <eg-grid #eventsGrid idlClass="atevdef"
     [dataSource]="eventsDataSource"
index 7e31ec6..874e769 100644 (file)
@@ -52,6 +52,8 @@ export class SandboxComponent implements OnInit {
     @ViewChild('bresvEditor', { static: true })
     private bresvEditor: FmRecordEditorComponent;
 
+    @ViewChild('penaltyDialog', {static: false}) penaltyDialog;
+
 
     // @ViewChild('helloStr') private helloStr: StringComponent;
 
@@ -478,5 +480,10 @@ export class SandboxComponent implements OnInit {
             printContext: 'default'
         });
     }
+
+    openPenalty() {
+        this.penaltyDialog.open()
+        .subscribe(val => console.log('penalty value', val));
+    }
 }
 
index 15be7f3..57e0bd0 100644 (file)
@@ -8,6 +8,7 @@ import {ReactiveFormsModule} from '@angular/forms';
 import {SampleDataService} from '@eg/share/util/sample-data.service';
 import {OrgFamilySelectModule} from '@eg/share/org-family-select/org-family-select.module';
 import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
+import {PatronModule} from '@eg/staff/share/patron/patron.module';
 
 @NgModule({
   declarations: [
@@ -20,7 +21,8 @@ import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-loca
     OrgFamilySelectModule,
     ItemLocationSelectModule,
     SandboxRoutingModule,
-    ReactiveFormsModule
+    ReactiveFormsModule,
+    PatronModule
   ],
   providers: [
     SampleDataService
index f61d3d4..0f2070b 100644 (file)
@@ -4,7 +4,9 @@
 import {Injectable, EventEmitter} from '@angular/core';
 import {NetService} from '@eg/core/net.service';
 import {AnonCacheService} from '@eg/share/util/anon-cache.service';
+import {PcrudService} from '@eg/core/pcrud.service';
 import {AuthService} from '@eg/core/auth.service';
+import {IdlObject} from '@eg/core/idl.service';
 import {EventService} from '@eg/core/event.service';
 
 interface NewCallNumData {
@@ -20,6 +22,7 @@ export class HoldingsService {
     constructor(
         private net: NetService,
         private auth: AuthService,
+        private pcrud: PcrudService,
         private evt: EventService,
         private anonCache: AnonCacheService
     ) {}
@@ -59,5 +62,22 @@ export class HoldingsService {
             });
         });
     }
+
+    // Using open-ils.actor.get_barcodes
+    getItemIdFromBarcode(barcode: string): Promise<number> {
+        return this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.get_barcodes',
+            this.auth.token(), this.auth.user().ws_ou(), 'asset', barcode
+        ).toPromise().then(resp => {
+            if (this.evt.parse(resp)) {
+                return Promise.reject(resp);
+            } else if (resp.length === 0) {
+                return null;
+            } else {
+                return resp[0].id;
+            }
+        });
+    }
 }
 
index ac6e9b3..9987c64 100644 (file)
@@ -5,12 +5,14 @@ import {PatronService} from './patron.service';
 import {PatronSearchComponent} from './search.component';
 import {PatronSearchDialogComponent} from './search-dialog.component';
 import {ProfileSelectComponent} from './profile-select.component';
+import {PatronPenaltyDialogComponent} from './penalty-dialog.component';
 
 @NgModule({
     declarations: [
         PatronSearchComponent,
         PatronSearchDialogComponent,
-        ProfileSelectComponent
+        ProfileSelectComponent,
+        PatronPenaltyDialogComponent
     ],
     imports: [
         StaffCommonModule,
@@ -19,7 +21,8 @@ import {ProfileSelectComponent} from './profile-select.component';
     exports: [
         PatronSearchComponent,
         PatronSearchDialogComponent,
-        ProfileSelectComponent
+        ProfileSelectComponent,
+        PatronPenaltyDialogComponent
     ],
     providers: [
         PatronService
diff --git a/Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.html
new file mode 100644 (file)
index 0000000..b66e562
--- /dev/null
@@ -0,0 +1,59 @@
+<eg-string #successMsg i18n-text text="Penalty Successfully Applied"></eg-string>
+<eg-string #errorMsg i18n-text text="Failed To Apply New Penalty"></eg-string>
+
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title">
+      <span i18n>Apply Standing Penalty / Message</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">
+    <div class="row d-flex p-3" *ngIf="dataLoaded">
+      <span i18n>
+        Apply penalty to patron <b>{{patron.family_name()}}, {{patron.first_given_name()}}</b>
+      </span>
+    </div>
+    <div class="row d-flex p-3">
+      <div>
+        <button class="btn mr-1 {{buttonClass(SILENT_NOTE)}}" 
+          (click)="penaltyTypeFromButton=SILENT_NOTE" i18n>Note</button> 
+        <button class="btn mr-1 {{buttonClass(ALERT_NOTE)}}" 
+          (click)="penaltyTypeFromButton=ALERT_NOTE" i18n >Alert</button> 
+        <button class="btn mr-1 {{buttonClass(STAFF_CHR)}}" 
+          (click)="penaltyTypeFromButton=STAFF_CHR" i18n>Block</button> 
+      </div>
+      <div class="flex-1"></div>
+      <div>
+        <select class="form-control" 
+          [(ngModel)]="penaltyTypeFromSelect">
+          <option value='' i18n>Penalty Type...</option>
+          <option value="{{pen.id()}}" *ngFor="let pen of penaltyTypes">
+            {{pen.label()}}
+          </option>
+        </select>
+      </div>
+    </div>
+    <div class="row">
+      <div class="col-lg-12">
+        <textarea class="form-control" [(ngModel)]="noteText"></textarea>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer flex">
+    <div *ngIf="requireInitials" class="form-validated">
+      <input type="text" class="form-control" size="3" required
+        i18n-placeholder placeholder="Initials..." [(ngModel)]="initials"/>
+    </div>
+    <div class="flex-1"></div>
+    <!-- initials.. disable -->
+    <button type="button" class="btn btn-success" 
+      [disabled]="requireInitials && !initials" (click)="apply()" i18n>OK</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/patron/penalty-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/patron/penalty-dialog.component.ts
new file mode 100644 (file)
index 0000000..12cdb5f
--- /dev/null
@@ -0,0 +1,130 @@
+import {Component, OnInit, Input, Output, ViewChild} from '@angular/core';
+import {merge, from, Observable} from 'rxjs';
+import {tap, take, switchMap} from 'rxjs/operators';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {StringComponent} from '@eg/share/string/string.component';
+
+
+/**
+ * Dialog container for patron penalty/message application
+ *
+ * <eg-patron-penalty-dialog [patronId]="myPatronId">
+ * </eg-patron-penalty-dialog>
+ */
+
+@Component({
+  selector: 'eg-patron-penalty-dialog',
+  templateUrl: 'penalty-dialog.component.html'
+})
+
+export class PatronPenaltyDialogComponent
+    extends DialogComponent implements OnInit {
+
+    @Input() patronId: number;
+    @Input() penaltyNote = '';
+
+    ALERT_NOTE = 20;
+    SILENT_NOTE = 21;
+    STAFF_CHR = 25;
+
+    staffInitials: string;
+    penaltyTypes: IdlObject[];
+    penaltyTypeFromSelect = '';
+    penaltyTypeFromButton;
+    patron: IdlObject;
+    dataLoaded = false;
+    requireInitials = false;
+    initials: string;
+    noteText = '';
+
+    @ViewChild('successMsg', {static: false}) successMsg: StringComponent;
+    @ViewChild('errorMsg', {static: false}) errorMsg: StringComponent;
+
+    constructor(
+        private modal: NgbModal,
+        private idl: IdlService,
+        private org: OrgService,
+        private net: NetService,
+        private evt: EventService,
+        private toast: ToastService,
+        private auth: AuthService,
+        private pcrud: PcrudService) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.onOpen$.subscribe(_ =>
+            this.init().subscribe(__ => this.dataLoaded = true));
+    }
+
+    init(): Observable<any> {
+        this.dataLoaded = false;
+
+        this.penaltyTypeFromButton = this.SILENT_NOTE;
+
+        this.org.settings(['ui.staff.require_initials.patron_standing_penalty'])
+        .then(sets => this.requireInitials =
+            sets['ui.staff.require_initials.patron_standing_penalty']);
+
+        const obs1 = this.pcrud.retrieve('au', this.patronId)
+            .pipe(tap(usr => this.patron = usr));
+
+        if (this.penaltyTypes) { return obs1; }
+
+        return obs1.pipe(switchMap(_ => {
+            return this.pcrud.search('csp', {id: {'>': 100}}, {}, {atomic: true})
+
+            .pipe(tap(ptypes => {
+                this.penaltyTypes =
+                    ptypes.sort((a, b) => a.label() < b.label() ? -1 : 1);
+            }));
+        }));
+    }
+
+    apply() {
+
+        const pen = this.idl.create('ausp');
+        pen.usr(this.patronId);
+        pen.org_unit(this.auth.user().ws_ou());
+        pen.set_date('now');
+        pen.staff(this.auth.user().id());
+
+        pen.note(this.initials ?
+            `${this.noteText} [${this.initials}]` : this.noteText);
+
+        pen.standing_penalty(
+            this.penaltyTypeFromSelect || this.penaltyTypeFromButton);
+
+        this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.user.penalty.apply',
+            this.auth.token(), pen
+        ).subscribe(resp => {
+            const e = this.evt.parse(resp);
+            if (e) {
+                this.errorMsg.current().then(msg => this.toast.danger(msg));
+                this.error(e, true);
+            } else {
+                // resp == penalty ID on success
+                this.successMsg.current().then(msg => this.toast.success(msg));
+                this.close(resp);
+            }
+        });
+    }
+
+    buttonClass(pType: number): string {
+        return this.penaltyTypeFromButton === pType ?
+            'btn-primary' : 'btn-light';
+    }
+}
+
+
+
index ddaa169..5daf52d 100644 (file)
             </a>
           </li>
           <li>
-            <a href="./cat/item/missing_pieces" target="_self">
+            <a href="/eg2/staff/cat/item/missing_pieces">
               <span class="glyphicon glyphicon-th" aria-hidden="true"></span>
               <span>[% l('Scan Item as Missing Pieces') %]</span>
             </a>