Angular holdings maintenance wip
authorBill Erickson <berickxx@gmail.com>
Mon, 18 Mar 2019 21:46:42 +0000 (17:46 -0400)
committerBill Erickson <berickxx@gmail.com>
Mon, 18 Mar 2019 21:46:42 +0000 (17:46 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts
Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.html
Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts
Open-ILS/src/eg2/src/app/staff/share/holds/grid.component.ts

index 1855000..46d25d7 100644 (file)
@@ -47,7 +47,8 @@ import {HoldingsMaintenanceComponent} from './record/holdings.component';
     StaffCommonModule,
     CatalogCommonModule,
     CatalogRoutingModule,
-    HoldsModule
+    HoldsModule,
+    HoldingsModule
   ],
   providers: [
     StaffCatalogService
index 3cfcb27..64f36e5 100644 (file)
   </ng-container>
 </ng-template>
 
+<eg-mark-damaged-dialog #markDamagedDialog></eg-mark-damaged-dialog>
+<eg-mark-missing-dialog #markMissingDialog></eg-mark-missing-dialog>
 
 <div class='eg-copies w-100 mt-3'>
   <eg-grid #holdingsGrid [dataSource]="gridDataSource"
-    (onRowActivate)="onRowActivate($event)"
-    [pageSize]="50" [rowClassCallback]="rowClassCallback"
+    (onRowActivate)="onRowActivate($event)" [disablePaging]="true"
+    [rowClassCallback]="rowClassCallback"
     [sortable]="false" persistKey="cat.holdings">
 
     <!-- checkboxes -->
       #emptyLibsCheckbox (onChange)="toggleShowEmptyLibs($event)">
     </eg-grid-toolbar-checkbox> 
 
+    <!-- actions -->
+
+    <eg-grid-toolbar-action
+      group="Mark" i18n-group i18n-label label="Mark Item Damaged"
+      (onClick)="showMarkDamagedDialog($event)"></eg-grid-toolbar-action>
+
+    <eg-grid-toolbar-action
+      i18n-group group="Mark" i18n-label label="Mark Item Missing"
+      (onClick)="showMarkMissingDialog($event)">
+    </eg-grid-toolbar-action>
+
     <!-- fields -->
+
     <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>
index 0f9e4ad..3bb2efa 100644 (file)
@@ -12,6 +12,10 @@ import {GridDataSource} from '@eg/share/grid/grid';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {GridToolbarCheckboxComponent} from '@eg/share/grid/grid-toolbar-checkbox.component';
 import {ServerStoreService} from '@eg/core/server-store.service';
+import {MarkDamagedDialogComponent
+} from '@eg/staff/share/holdings/mark-damaged-dialog.component';
+import {MarkMissingDialogComponent
+} from '@eg/staff/share/holdings/mark-missing-dialog.component';
 
 
 // The holdings grid models a single HoldingsTree, composed of HoldingsTreeNodes
@@ -64,10 +68,18 @@ export class HoldingsMaintenanceComponent implements OnInit {
     @ViewChild('holdingsGrid') holdingsGrid: GridComponent;
 
     // Manage visibility of various sub-sections
-    @ViewChild('volsCheckbox') volsCheckbox: GridToolbarCheckboxComponent;
-    @ViewChild('copiesCheckbox') copiesCheckbox: GridToolbarCheckboxComponent;
-    @ViewChild('emptyVolsCheckbox') emptyVolsCheckbox: GridToolbarCheckboxComponent;
-    @ViewChild('emptyLibsCheckbox') emptyLibsCheckbox: GridToolbarCheckboxComponent;
+    @ViewChild('volsCheckbox')
+        private volsCheckbox: GridToolbarCheckboxComponent;
+    @ViewChild('copiesCheckbox')
+        private copiesCheckbox: GridToolbarCheckboxComponent;
+    @ViewChild('emptyVolsCheckbox')
+        private emptyVolsCheckbox: GridToolbarCheckboxComponent;
+    @ViewChild('emptyLibsCheckbox')
+        private emptyLibsCheckbox: GridToolbarCheckboxComponent;
+    @ViewChild('markDamagedDialog')
+        private markDamagedDialog: MarkDamagedDialogComponent;
+    @ViewChild('markMissingDialog')
+        private markMissingDialog: MarkMissingDialogComponent;
 
     contextOrg: IdlObject;
     holdingsTree: HoldingsTree;
@@ -155,6 +167,7 @@ export class HoldingsMaintenanceComponent implements OnInit {
         this.emptyVolsCheckbox.checked(settings['cat.holdings_show_empty']);
         this.emptyLibsCheckbox.checked(settings['cat.holdings_show_empty_org']);
 
+        this.initHoldingsTree();
         this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
             return this.fetchHoldings(pager);
         };
@@ -357,6 +370,26 @@ export class HoldingsMaintenanceComponent implements OnInit {
         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);
+    }
+
 
     fetchHoldings(pager: Pager): Observable<any> {
         if (!this.recId) { return of([]); }
@@ -368,7 +401,6 @@ export class HoldingsMaintenanceComponent implements OnInit {
                 return;
             }
 
-            this.initHoldingsTree();
             this.itemCircsNeeded = [];
 
             this.pcrud.search('acn',
@@ -384,7 +416,8 @@ export class HoldingsMaintenanceComponent implements OnInit {
                         acn: ['prefix', 'suffix', 'copies'],
                         acli: ['inventory_workstation']
                     }
-                }
+                },
+                {authoritative: true}
             ).subscribe(
                 vol => this.appendVolume(vol),
                 err => {},
@@ -413,27 +446,114 @@ export class HoldingsMaintenanceComponent implements OnInit {
         })).toPromise();
     }
 
+    // Create the tree node for the volume if it doesn't already exist.
+    // Do the same for its linked copies.
     appendVolume(volume: IdlObject) {
 
-        const volNode = new HoldingsTreeNode();
-        volNode.parentNode = this.holdingsTreeOrgCache[volume.owning_lib()];
-        volNode.parentNode.children.push(volNode);
-        volNode.nodeType = 'volume';
+        let volNode = this.findNode(volume.id(), 'volume');
+        if (volNode) {
+            const pNode = this.holdingsTreeOrgCache[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.
+                // XXX TODO: ^--
+                volNode.parentNode = pNode;
+                volNode.parentNode.children.push(volNode);
+            }
+        } else {
+            volNode = new HoldingsTreeNode();
+            volNode.nodeType = 'volume';
+            volNode.parentNode = this.holdingsTreeOrgCache[volume.owning_lib()];
+            volNode.parentNode.children.push(volNode);
+        }
+
         volNode.target = volume;
 
         volume.copies()
             .sort((a: IdlObject, b: IdlObject) => a.barcode() < b.barcode() ? -1 : 1)
-            .forEach((copy: IdlObject) => {
-                const copyNode = new HoldingsTreeNode();
+            .forEach((copy: IdlObject) => this.appendCopy(volNode, copy));
+    }
+
+    appendCopy(volNode: HoldingsTreeNode, copy: IdlObject) {
+        let copyNode = this.findNode(copy.id(), 'copy');
+
+        if (copyNode) {
+            const oldParent = copyNode.parentNode;
+            if (oldParent.target.id() !== volNode.target.id()) {
+                // TODO: copy changed owning volume.  Remove it from
+                // the previous volume before adding to the new volume.
                 copyNode.parentNode = volNode;
                 volNode.children.push(copyNode);
-                copyNode.nodeType = 'copy';
-                copyNode.target = copy;
-                const stat = Number(copy.status().id());
-                if (stat === 1 /* checked out */ || stat === 16 /* long overdue */) {
-                    this.itemCircsNeeded.push(copy);
-                }
-            });
+            }
+        } else {
+            // New node required
+            copyNode = new HoldingsTreeNode();
+            copyNode.nodeType = 'copy';
+            volNode.children.push(copyNode);
+            copyNode.parentNode = volNode;
+        }
+
+        copyNode.target = copy;
+        const stat = Number(copy.status().id());
+
+        if (stat === 1 /* checked out */ || stat === 16 /* long overdue */) {
+            // Avoid looking up circs on items that are not checked out.
+            this.itemCircsNeeded.push(copy);
+        }
+    }
+
+    selectedCopyIds(rows: HoldingsEntry[], skipStatus?: number): number[] {
+        let copyRows = rows.filter(r => Boolean(r.copy)).map(r => r.copy);
+        if (skipStatus) {
+            copyRows = copyRows.filter(
+                c => Number(c.status().id()) !== Number(skipStatus));
+        }
+        return copyRows.map(c => Number(c.id()));
+    }
+
+    async showMarkDamagedDialog(rows: HoldingsEntry[]) {
+        const copyIds = this.selectedCopyIds(rows, 14 /* ignore damaged */);
+
+        if (copyIds.length === 0) { return; }
+
+        let rowsModified = false;
+
+        const markNext = async(ids: number[]) => {
+            if (ids.length === 0) {
+                return Promise.resolve();
+            }
+
+            this.markDamagedDialog.copyId = ids.pop();
+            return this.markDamagedDialog.open({size: 'lg'}).then(
+                ok => {
+                    if (ok) { rowsModified = true; }
+                    return markNext(ids);
+                },
+                dismiss => markNext(ids)
+            );
+        };
+
+        await markNext(copyIds);
+        if (rowsModified) {
+            this.refreshHoldings = true;
+            this.holdingsGrid.reload();
+        }
+    }
+
+    showMarkMissingDialog(rows: any[]) {
+        const copyIds = this.selectedCopyIds(rows, 4 /* ignore missing */);
+        if (copyIds.length > 0) {
+            this.markMissingDialog.copyIds = copyIds;
+            this.markMissingDialog.open({}).then(
+                rowsModified => {
+                    if (rowsModified) {
+                        this.refreshHoldings = true;
+                        this.holdingsGrid.reload();
+                    }
+                },
+                dismissed => {} // avoid console errors
+            );
+        }
     }
 }
 
index af27574..a95ed85 100644 (file)
@@ -335,7 +335,7 @@ export class HoldsGridComponent implements OnInit {
             }
 
             this.markDamagedDialog.copyId = ids.pop();
-            this.markDamagedDialog.open({size: 'lg'}).then(
+            return this.markDamagedDialog.open({size: 'lg'}).then(
                 ok => {
                     if (ok) { rowsModified = true; }
                     return markNext(ids);
@@ -397,3 +397,5 @@ export class HoldsGridComponent implements OnInit {
 }
 
 
+
+