+
+<!-- Location / Barcode cell template -->
<ng-template #locationTemplate let-row="row" let-userContext="userContext">
<!-- pl-* is doubled for added impact -->
<div class="pl-{{row.locationDepth}}">
</div>
</ng-template>
+<!-- TODO: create a generic yes/no template -->
<ng-template #holdableTemplate let-row="row" let-userContext="userContext">
<ng-container *ngIf="row.copy">
<ng-container *ngIf="userContext.copyIsHoldable(row.copy); else notHoldable">
<eg-mark-damaged-dialog #markDamagedDialog></eg-mark-damaged-dialog>
<eg-mark-missing-dialog #markMissingDialog></eg-mark-missing-dialog>
+<!-- holdings grid -->
<div class='eg-copies w-100 mt-3'>
<eg-grid #holdingsGrid [dataSource]="gridDataSource"
(onRowActivate)="onRowActivate($event)" [disablePaging]="true"
</eg-grid-toolbar-action>
<!-- fields -->
+ <!-- NOTE column names were added to match the names from the AngJS grid
+ so grid settings would propagate -->
<eg-grid-column path="index" [hidden]="true" [index]="true">
</eg-grid-column>
- <eg-grid-column path="copy.id" [hidden]="true" label="Copy ID" i18n-label>
+ <eg-grid-column name="id" path="copy.id" [hidden]="true" label="Copy ID" i18n-label>
</eg-grid-column>
<eg-grid-column path="volume.id" [hidden]="true" label="Volume ID" i18n-label>
</eg-grid-column>
- <eg-grid-column name="location_barcode" [flex]="4"
+ <eg-grid-column name="owner_label" [flex]="4"
[cellTemplate]="locationTemplate" [cellContext]="gridTemplateContext"
label="Location/Barcode" [disableTooltip]="true" i18n-label>
</eg-grid-column>
</eg-grid-column>
<eg-grid-column path="copyCount" datatype="number" label="Copies" i18n-label>
</eg-grid-column>
- <eg-grid-column path="callNumberLabel" label="Call Number" i18n-label>
+ <eg-grid-column path="volume._label" name="call_number.label" label="Call Number" i18n-label>
+ </eg-grid-column>
+ <eg-grid-column path="copy.barcode" name="barcode" label="Barcode" i18n-label>
</eg-grid-column>
<eg-grid-column i18n-label label="Circ Library" path="copy.circ_lib"
- datatype="org_unit"></eg-grid-column>
- <eg-grid-column i18n-label label="Owning Library" path="volume.owning_lib"
+ name="circ_lib.name" datatype="org_unit"></eg-grid-column>
+ <eg-grid-column i18n-label label="Owning Library" name="owner_label" path="volume.owning_lib"
datatype="org_unit"></eg-grid-column>
<eg-grid-column i18n-label label="Due Date" path="circ.due_date"
datatype="timestamp"></eg-grid-column>
- <eg-grid-column i18n-label label="Shelving Location" path="copy.location.name">
+ <eg-grid-column i18n-label label="Shelving Location" path="copy.location.name" name="location.name">
</eg-grid-column>
- <eg-grid-column i18n-label label="Circulation Modifier" path="copy.circ_modifier">
+ <eg-grid-column i18n-label label="Circulation Modifier"
+ path="copy.circ_modifier" name="circ_modifier">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Copy Number" path="copy.copy_number" name="copy_number" [hidden]="true">
</eg-grid-column>
- <eg-grid-column i18n-label label="Status" path="copy.status.name">
+ <eg-grid-column i18n-label label="Status" path="copy.status.name" name="status_name">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Call Number Prefix"
+ path="volume.prefix.label" name="call_number.prefix.label" [hidden]="true">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Call Number Suffix"
+ path="volume.suffix.label" name="call_number.suffix.label" [hidden]="true">
</eg-grid-column>
<eg-grid-column i18n-label label="Active/Create Date"
path="copy.active_date" datatype="timestamp">
</eg-grid-column>
<eg-grid-column i18n-label label="Age Hold Protection"
- path="copy.age_protect.name"></eg-grid-column>
+ path="copy.age_protect.name" name="age_protect.name"></eg-grid-column>
+ <eg-grid-column i18n-label label="Copy Price"
+ path="copy.price" name="price" [hidden]="true"></eg-grid-column>
+ <eg-grid-column i18n-label label="Circulate" path="copy.circualte"
+ name="circulate" datatype="bool" [hidden]="true"></eg-grid-column>
+ <eg-grid-column i18n-label label="Deposit" path="copy.deposit"
+ name="deposit" datatype="bool" [hidden]="true"></eg-grid-column>
+ <eg-grid-column i18n-label label="Deposit Amount" path="copy.deposit_amount"
+ name="deposit_amount" datatype="money" [hidden]="true"></eg-grid-column>
<eg-grid-column i18n-label label="Holdable?" name="holdable"
[cellTemplate]="holdableTemplate" [cellContext]="gridTemplateContext">
</eg-grid-column>
-
+ <eg-grid-column i18n-label label="Reference?" path="copy.ref"
+ name="ref" datatype="bool" [hidden]="true"></eg-grid-column>
+ <eg-grid-column i18n-label label="Last Inventory Date"
+ path="copy.latest_inventory.inventory_date"
+ name="latest_inventory.inventory_date" datatype="timestamp" [hidden]="true">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Last Inventory Workstation"
+ path="copy.latest_inventory.inventory_workstation.name"
+ name="latest_inventory.inventory_workstation.name" [hidden]="true">
+ </eg-grid-column>
</eg-grid>
</div>
})
export class HoldingsMaintenanceComponent implements OnInit {
- recId: number;
initDone = false;
gridDataSource: GridDataSource;
gridTemplateContext: any;
@ViewChild('markMissingDialog')
private markMissingDialog: MarkMissingDialogComponent;
- contextOrg: IdlObject;
+
holdingsTree: HoldingsTree;
- holdingsTreeOrgCache: {[id: number]: HoldingsTreeNode};
+
+ // nodeType => id => tree node cache
+ treeNodeCache: {[nodeType: string]: {[id: number]: HoldingsTreeNode}};
+
+ // When true and a grid reload is called, the holdings data will be
+ // re-fetched from the server.
refreshHoldings: boolean;
+
+ // Used as a row identifier in th grid, since we're mixing object types.
gridIndex: number;
// List of copies whose due date we need to retrieve.
// When not true, render based on the current "expanded" state of each node.
// Rendering from prefs happens on initial load and when any prefs change.
renderFromPrefs: boolean;
+
rowClassCallback: (row: any) => string;
+ private _recId: number;
@Input() set recordId(id: number) {
- this.recId = id;
+ this._recId = id;
// Only force new data collection when recordId()
// is invoked after ngInit() has already run.
if (this.initDone) {
- this.refreshHoldings = true;
- this.holdingsGrid.reload();
+ this.hardRefresh();
+ }
+ }
+ get recordId(): number {
+ return this._recId;
+ }
+
+ // Allows the caller to update the context org unit
+ private _co: IdlObject;
+ @Input() set contextOrg(org: IdlObject) {
+ this._co = org;
+ if (this.initDone) {
+ this.hardRefresh();
}
}
+ get contextOrg(): IdlObject {
+ return this._co;
+ }
constructor(
private net: NetService,
private store: ServerStoreService
) {
// Set some sane defaults before settings are loaded.
- this.contextOrg = this.org.get(this.auth.user().ws_ou());
this.gridDataSource = new GridDataSource();
this.refreshHoldings = true;
this.renderFromPrefs = true;
ngOnInit() {
this.initDone = true;
+ if (!this.contextOrg) {
+ this.contextOrg = this.org.get(this.auth.user().ws_ou());
+ }
+
// These are pre-cached via the resolver.
const settings = this.store.getItemBatchCached([
'cat.holdings_show_empty_org',
};
}
- ngAfterViewInit() {
-
+ hardRefresh() {
+ this.renderFromPrefs = true;
+ this.refreshHoldings = true;
+ this.initHoldingsTree();
+ this.holdingsGrid.reload();
}
toggleShowCopies(value: boolean) {
initHoldingsTree() {
+ const visibleOrgs = this.org.fullPath(this.contextOrg, true);
+
// The initial tree simply matches the org unit tree
const traverseOrg = (node: HoldingsTreeNode) => {
- node.expanded = true;
node.target.children().forEach((org: IdlObject) => {
+ if (visibleOrgs.indexOf(org.id()) == -1) {
+ return; // Org is outside of scope
+ }
const nodeChild = new HoldingsTreeNode();
nodeChild.nodeType = 'org';
nodeChild.target = org;
nodeChild.parentNode = node;
node.children.push(nodeChild);
- this.holdingsTreeOrgCache[org.id()] = nodeChild;
+ this.treeNodeCache.org[org.id()] = nodeChild;
traverseOrg(nodeChild);
});
}
+ this.treeNodeCache = {
+ org: {},
+ volume: {},
+ copy: {}
+ };
+
this.holdingsTree = new HoldingsTree();
this.holdingsTree.root.nodeType = 'org';
this.holdingsTree.root.target = this.org.root();
-
- this.holdingsTreeOrgCache = {};
- this.holdingsTreeOrgCache[this.org.root().id()] = this.holdingsTree.root;
+ this.treeNodeCache.org[this.org.root().id()] = this.holdingsTree.root;
traverseOrg(this.holdingsTree.root);
}
// Org node children are sorted with any child org nodes pushed to the
// front, followed by the call number nodes sorted alphabetcially by label.
- // TODO: prefix/suffix
sortOrgNodeChildren(node: HoldingsTreeNode) {
node.children = node.children.sort((a, b) => {
if (a.nodeType === 'org') {
} else if (b.nodeType === 'org') {
return 1;
} else {
- return a.target.label() < b.target.label() ? -1 : 1;
+ // TODO: should this use label sortkey instead of
+ // the compiled volume label?
+ return a.target._label < b.target._label ? -1 : 1;
}
});
}
switch(node.nodeType) {
case 'org':
- if (this.renderFromPrefs && node.volumeCount === 0
+ if (node.volumeCount === 0
&& !this.emptyLibsCheckbox.checked()) {
return;
}
break;
case 'volume':
- entry.locationLabel = node.target.label(); // TODO prefix/suffix
+ if (this.renderFromPrefs) {
+ if (!this.volsCheckbox.checked()) {
+ return;
+ }
+ if (node.copyCount === 0
+ && !this.emptyVolsCheckbox.checked()) {
+ return;
+ }
+ }
+ entry.locationLabel = node.target._label;
entry.locationDepth = node.parentNode.target.ou_type().depth() + 1;
entry.callNumberLabel = entry.locationLabel;
entry.volume = node.target;
this.renderFromPrefs = false;
}
- // Find an existing tree node by id and type
- findNode(targetId: number, nodeType: string): HoldingsTreeNode {
- const id = Number(targetId);
-
- const search = (node: HoldingsTreeNode): HoldingsTreeNode => {
- if (!node) return null;
-
- if (node.nodeType === nodeType && Number(node.target.id()) === id) {
- return node;
- }
- // for loop for early exit
- for (let idx = 0; idx < node.children.length; idx++) {
- const found = search(node.children[idx]);
- if (found) { return found; }
- }
- }
-
- return search(this.holdingsTree.root);
- }
-
-
+ // Grab volumes, copies, and related data.
fetchHoldings(pager: Pager): Observable<any> {
- if (!this.recId) { return of([]); }
+ if (!this.recordId) { return of([]); }
return new Observable<any>(observer => {
this.itemCircsNeeded = [];
this.pcrud.search('acn',
- { record: this.recId,
- owning_lib: this.org.ancestors(this.contextOrg, true),
+ { record: this.recordId,
+ owning_lib: this.org.fullPath(this.contextOrg, true),
deleted: 'f',
label: {'!=' : '##URI##'}
}, {
})).toPromise();
}
+ // Compile prefix + label + suffix into field volume._label;
+ setVolumeLabel(volume: IdlObject) {
+ const pfx = volume.prefix() ? volume.prefix().label() : '';
+ const sfx = volume.suffix() ? volume.suffix().label() : '';
+ volume._label = pfx ? pfx + ' ' : '';
+ volume._label += volume.label();
+ volume._label += sfx ? ' ' + sfx : '';
+ }
+
// Create the tree node for the volume if it doesn't already exist.
// Do the same for its linked copies.
appendVolume(volume: IdlObject) {
+ let volNode = this.treeNodeCache.volume[volume.id()];
+ this.setVolumeLabel(volume);
- let volNode = this.findNode(volume.id(), 'volume');
if (volNode) {
- const pNode = this.holdingsTreeOrgCache[volume.owning_lib()];
+ const pNode = this.treeNodeCache.org[volume.owning_lib()]
if (volNode.parentNode.target.id() !== pNode.target.id()) {
// Volume owning library changed. Un-link it from the previous
// org unit collection before adding to the new one.
} else {
volNode = new HoldingsTreeNode();
volNode.nodeType = 'volume';
- volNode.parentNode = this.holdingsTreeOrgCache[volume.owning_lib()];
+ volNode.parentNode = this.treeNodeCache.org[volume.owning_lib()]
volNode.parentNode.children.push(volNode);
+ this.treeNodeCache.volume[volume.id()] = volNode;
}
volNode.target = volume;
.forEach((copy: IdlObject) => this.appendCopy(volNode, copy));
}
+ // Find or create a copy node.
appendCopy(volNode: HoldingsTreeNode, copy: IdlObject) {
- let copyNode = this.findNode(copy.id(), 'copy');
+ let copyNode = this.treeNodeCache.copy[copy.id()];
if (copyNode) {
const oldParent = copyNode.parentNode;
copyNode.nodeType = 'copy';
volNode.children.push(copyNode);
copyNode.parentNode = volNode;
+ this.treeNodeCache.copy[copy.id()] = copyNode;
}
copyNode.target = copy;
}
}
+ // Which copies in the grid are selected.
selectedCopyIds(rows: HoldingsEntry[], skipStatus?: number): number[] {
let copyRows = rows.filter(r => Boolean(r.copy)).map(r => r.copy);
if (skipStatus) {