--- /dev/null
+<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"
+ (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="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>
--- /dev/null
+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;
+
+ @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;
+
+ if (!this.itemBarcode) { return Promise.resolve(); }
+
+ return this.holdings.getItemIdFromBarcode(this.itemBarcode)
+ .then(id => {
+ this.itemId = id;
+ return this.getItemById();
+ });
+ }
+
+ getItemById(): Promise<any> {
+ this.circNotFound = false;
+
+ if (!this.itemId) { 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();
+
+ }).then(_ =>
+ setTimeout(() =>
+ this.renderer.selectRootElement('#item-barcode-input').select())
+ );
+ }
+
+ 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;
+ }
+}
+
+
+