-<div ngbDropdown>
+<div ngbDropdown [placement]="placement">
<button class="btn btn-outline-dark" id="{{domId}}"
ngbDropdownToggle i18n>{{label}}</button>
- <div ngbDropdownMenu>
+ <div ngbDropdownMenu class="scrollable-menu">
<button class="dropdown-item" *ngFor="let entry of menuEntries"
[disabled]="entry.disabled"
(click)="performAction(entry)" tabindex="0">
@Input() domId = 'grouped-menu-' + GroupedMenuComponent.autoId++;
+ // https://ng-bootstrap.github.io/#/components/dropdown/api
+ @Input() placement = 'bottom-right';
+
menuEntries: GroupedMenuEntry[] = [];
constructor(
<eg-grouped-menu-entry i18n-label label="Add Record To Bucket"
i18n-group group="Add" (entryClicked)="addRecordToBucket([item])">
</eg-grouped-menu-entry>
+ <eg-grouped-menu-entry
+ i18n-group group="Add" i18n-label label="Add Items"
+ (entryClicked)="addItems([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry
+ i18n-group group="Add" i18n-label label="Add Call Numbers and Items"
+ (entryClicked)="addVols([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry
+ i18n-group group="Add" i18n-label label="Add Item Alerts"
+ (entryClicked)="itemAlerts([item], 'create')">
+ </eg-grouped-menu-entry>
<!-- Edit -->
<eg-grouped-menu-entry i18n-label label="Delete Items"
i18n-group group="Edit" (entryClicked)="deleteItems([item])">
</eg-grouped-menu-entry>
+ <eg-grouped-menu-entry i18n-label label="Edit Call Numbers"
+ i18n-group group="Edit" (entryClicked)="editVols([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry i18n-label label="Edit Items"
+ i18n-group group="Edit" (entryClicked)="editItems([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry i18n-label label="Edit Call Numbers and Items"
+ i18n-group group="Edit" (entryClicked)="editVolsAndItems([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry i18n-label label="Replace Barcodes"
+ i18n-group group="Edit" (entryClicked)="replaceBarcodes([item])">
+ </eg-grouped-menu-entry>
+ <eg-grouped-menu-entry
+ i18n-group group="Edit" i18n-label label="Manage Item Alerts"
+ (entryClicked)="itemAlerts([item], 'manage')">
+ </eg-grouped-menu-entry>
<!-- Mark -->
<eg-grouped-menu-entry i18n-label label="Mark Item as Damaged"
<eg-grouped-menu-entry i18n-label label="Mark Item as Discard/Weed"
i18n-group group="Mark" (entryClicked)="discardWeed([item])">
</eg-grouped-menu-entry>
-
+ <eg-grouped-menu-entry i18n-label label="Mark Item as Missing"
+ i18n-group group="Mark" (entryClicked)="markMissing([item])">
+ </eg-grouped-menu-entry>
<!-- Show -->
<eg-grouped-menu-entry i18n-label label="Show Record Holds"
i18n-group group="Show" (entryClicked)="showAcq([item])">
</eg-grouped-menu-entry>
-
<!-- Booking -->
<eg-grouped-menu-entry i18n-label label="Make Items Bookable"
i18n-group group="Booking" (entryClicked)="makeItemsBookable([item])">
i18n-group group="Circulation" (entryClicked)="cancelTransits([item])">
</eg-grouped-menu-entry>
+ <!-- Transfer -->
+ <eg-grouped-menu-entry i18n-label
+ label="Transfer Items to Previously Marked Library"
+ i18n-group group="Transfer" (entryClicked)="transferItemsToLib([item])">
+ </eg-grouped-menu-entry>
+
+ <eg-grouped-menu-entry i18n-label
+ label="Transfer Items to Previously Marked Call Number"
+ i18n-group group="Transfer" (entryClicked)="transferItemsToCn([item])">
+ </eg-grouped-menu-entry>
+
</eg-grouped-menu>
<!-- grouped menu todo -->
import {AuthService} from '@eg/core/auth.service';
import {NetService} from '@eg/core/net.service';
import {PrintService} from '@eg/share/print/print.service';
+import {StoreService} from '@eg/core/store.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';
private net: NetService,
private idl: IdlService,
private printer: PrintService,
+ private store: StoreService,
private pcrud: PcrudService,
private auth: AuthService,
private circ: CircService,
.toPromise().then(item => {
this.item = item;
this.itemId = item.id();
+ this.mungeIsbns();
this.selectInput();
});
}
+ // A bit of cleanup to make the ISBN's look friendlier
+ mungeIsbns() {
+ const item = this.item;
+ const isbn = item.call_number().record().simple_record().isbn();
+ if (isbn) {
+ item._isbns = isbn.match(/"(.*?)"/g).map(i => i.replace(/"/g, ''));
+ } else {
+ item._isbns = [item.dummy_isbn()];
+ }
+ }
+
openProgressDialog(copies: IdlObject[]): ProgressDialogComponent {
this.progressDialog.update({value: 0, max: copies.length});
this.progressDialog.open();
discardWeed(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
let modified = false;
this.markItemsDialog.markAs = 'discard';
this.markItemsDialog.copies = copies;
this.markItemsDialog.open()
.subscribe(
- copyId => {
- if (copyId !== null) { modified = true; }
- },
+ copyId => modified = true,
null,
() => {
if (modified) { this.load(); }
}
);
}
+
+ markMissing(copies: IdlObject[]) {
+ copies = copies.filter(c => c.status() !== 4 /* Missing */);
+ if (copies.length === 0) { return; }
+
+ let modified = false;
+
+ this.markMissingDialog.copies = copies;
+ this.markMissingDialog.open()
+ .subscribe(
+ copyId => modified = true,
+ null,
+ () => {
+ if (modified) { this.load(); }
+ }
+ );
+ }
+
+ addItems(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ const callNumIds = copies.map(c => c.call_number().id());
+ this.holdings.spawnAddHoldingsUi(null, callNumIds);
+ }
+
+ editItems(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ const copyIds = copies.map(c => c.id());
+ this.holdings.spawnAddHoldingsUi(null, null, null, copyIds);
+ }
+
+ editVols(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ const callNumIds = copies.map(c => c.call_number().id());
+ this.holdings.spawnAddHoldingsUi(null, callNumIds, null, null, true);
+ }
+
+ editVolsAndItems(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ const callNumIds = copies.map(c => c.call_number().id());
+ const copyIds = copies.map(c => c.id());
+ this.holdings.spawnAddHoldingsUi(null, callNumIds, null, copyIds);
+ }
+
+
+ // Only the first item is used as the basis for new
+ // call numbers.
+ addVols(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ const copy = copies[0];
+ const cnData = [{owner: copy.call_number().owning_lib()}];
+ this.holdings.spawnAddHoldingsUi(
+ copy.call_number().record().id(), null, cnData);
+ }
+
+ itemAlerts(copies: IdlObject[], mode: 'create' | 'manage') {
+ if (copies.length === 0) { return; }
+
+ this.copyAlertsDialog.copyIds = copies.map(c => c.id());
+ this.copyAlertsDialog.mode = mode;
+ this.copyAlertsDialog.open({size: 'lg'}).subscribe(
+ modified => {
+ if (modified) {
+ this.load();
+ }
+ }
+ );
+ }
+
+ replaceBarcodes(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+ this.replaceBarcode.copyIds = copies.map(c => c.id());
+ this.replaceBarcode.open({}).subscribe(
+ modified => {
+ if (modified) {
+ this.load();
+ }
+ }
+ );
+ }
+
+ transferItemsToLib(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+
+ const orgId = this.store.getLocalItem('eg.cat.transfer_target_lib');
+
+ const recId =
+ this.store.getLocalItem('eg.cat.transfer_target_record')
+ || copies[0].call_number().record().id();
+
+ if (!orgId) {
+ return this.transferAlert.open().toPromise();
+ }
+
+ copies = this.idl.clone(copies); // avoid tweaking active data
+ this.transferItems.autoTransferItems(copies, recId, orgId)
+ .then(success => success ? this.load() : null);
+ }
+
+
+ transferItemsToCn(copies: IdlObject[]) {
+ if (copies.length === 0) { return; }
+
+ const cnId =
+ this.store.getLocalItem('eg.cat.transfer_target_vol');
+
+ if (!cnId) {
+ return this.transferAlert.open().toPromise();
+ }
+
+ this.transferItems.transferItems(copies.map(c => c.id()), cnId)
+ .then(success => success ? this.load() : null);
+ }
}
<div class="well-row">
<div class="well-label" i18n>ISBN</div>
<div class="well-value">
- {{item.call_number().record().simple_record().isbn() || item.dummy_isbn()}}
+ <ng-container *ngFor="let i of item._isbns">{{i}}<br/></ng-container>
</div>
<div class="well-label" i18n>Loan Duration</div>
items.forEach(i => i.call_number(
this.treeNodeCache.callNum[i.call_number()].target));
- console.log(items);
promise = this.transferItems.autoTransferItems(items, recId, orgId);
} else {
}));
}
- // Returns a stream of copy IDs. Any non-null ID is a copy that
- // was successfully modified.
+ // Returns a stream of copy IDs for successfully modified copies.
markItems() {
if (!this.copies || this.copies.length === 0) {
this.close();
from(this.copies).pipe(concatMap(copy => {
return this.markOneItem(copy)
- .pipe(map(modified => this.respond(modified ? copy.id() : null)));
+ .pipe(map(modified => {
+ if (modified) {
+ this.numSucceeded++;
+ this.respond(copy.id());
+ } else {
+ this.numFailed++;
+ }
+ }));
})).toPromise().then(_ => this.close());
}
}
+<eg-mark-items-dialog #markItemsDialog></eg-mark-items-dialog>
-<eg-string #successMsg
- text="Successfully Marked Item Missing" i18n-text></eg-string>
-<eg-string #errorMsg
- text="Failed To Mark Item Missing" i18n-text></eg-string>
-
-<ng-template #dialogContent>
- <div class="modal-header bg-info">
- <h4 class="modal-title">
- <span i18n>Mark Item Missing</span>
- </h4>
- <button type="button" class="close"
- i18n-aria-label aria-label="Close" (click)="close()">
- <span aria-hidden="true">×</span>
- </button>
- </div>
- <div class="modal-body">
- <div class="row d-flex justify-content-center">
- <h5>Mark {{copyIds.length}} Item(s) Missing?</h5>
- </div>
- <div class="row" *ngIf="numSucceeded > 0">
- <div class="col-lg-12" i18n>
- {{numSucceeded}} Items(s) Successfully Marked Missing
- </div>
- </div>
- <div class="row" *ngIf="numFailed > 0">
- <div class="col-lg-12">
- <div class="alert alert-warning">
- {{numFailed}} Items(s) Failed to be Marked Missing
- </div>
- </div>
- </div>
- </div>
- <div class="modal-footer">
- <ng-container *ngIf="!chargeResponse">
- <button type="button" class="btn btn-warning"
- (click)="close()" i18n>Cancel</button>
- <button type="button" class="btn btn-success"
- (click)="markItemsMissing()" i18n>Mark Missing</button>
- </ng-container>
- </div>
- </ng-template>
-
import {Component, Input, ViewChild} from '@angular/core';
-import {Observable} from 'rxjs';
+import {of, Observable} from 'rxjs';
+import {concatMap} from 'rxjs/operators';
import {NetService} from '@eg/core/net.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
import {EventService} from '@eg/core/event.service';
import {ToastService} from '@eg/share/toast/toast.service';
import {AuthService} from '@eg/core/auth.service';
import {DialogComponent} from '@eg/share/dialog/dialog.component';
import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
import {StringComponent} from '@eg/share/string/string.component';
+import {MarkItemsDialogComponent} from './mark-items-dialog.component';
+
/**
* Dialog for marking items missing.
+ *
+ * This now simply invokes the generic Mark Items Dialog.
*/
@Component({
extends DialogComponent {
@Input() copyIds: number[];
+ @Input() copies: IdlObject[];
- numSucceeded: number;
- numFailed: number;
-
- @ViewChild('successMsg', { static: true })
- private successMsg: StringComponent;
-
- @ViewChild('errorMsg', { static: true })
- private errorMsg: StringComponent;
+ @ViewChild('markItemsDialog')
+ private markItemsDialog: MarkItemsDialogComponent;
constructor(
- private modal: NgbModal, // required for passing to parent
- private toast: ToastService,
- private net: NetService,
- private evt: EventService,
- private auth: AuthService) {
- super(modal); // required for subclassing
- }
+ private pcrud: PcrudService,
+ private modal: NgbModal
+ ) { super(modal); }
- open(args: NgbModalOptions): Observable<boolean> {
- this.numSucceeded = 0;
- this.numFailed = 0;
- return super.open(args);
- }
+ ngOnInit() {}
- async markOneItemMissing(ids: number[]): Promise<any> {
- if (ids.length === 0) {
- return Promise.resolve();
- }
+ open(args?: NgbModalOptions): Observable<boolean> {
+ let obs: Observable<IdlObject[]>;
- const id = ids.pop();
+ if (this.copies) {
+ obs = of(this.copies);
+ } else {
+ obs = this.pcrud.search(
+ 'acp', {id: this.copyIds}, {}, {atomic: true});
+ }
- return this.net.request(
- 'open-ils.circ',
- 'open-ils.circ.mark_item_missing',
- this.auth.token(), id
- ).toPromise().then(async(result) => {
- if (Number(result) === 1) {
- this.numSucceeded++;
- this.toast.success(await this.successMsg.current());
- } else {
- this.numFailed++;
- console.error('Mark missing failed ', this.evt.parse(result));
- this.toast.warning(await this.errorMsg.current());
- }
- return this.markOneItemMissing(ids);
- });
- }
+ return obs.pipe(concatMap(copies => {
+ this.markItemsDialog.markAs = 'missing';
+ this.markItemsDialog.copies = copies;
+ return this.markItemsDialog.open(args);
+ }));
- async markItemsMissing(): Promise<any> {
- this.numSucceeded = 0;
- this.numFailed = 0;
- const ids = [].concat(this.copyIds);
- await this.markOneItemMissing(ids);
- this.close(this.numSucceeded > 0);
}
}
-
-