LP1933275 Staff catalog holdings view shows correct counts
authorBill Erickson <berickxx@gmail.com>
Thu, 8 Jul 2021 14:19:43 +0000 (10:19 -0400)
committerJane Sandberg <sandbergja@gmail.com>
Fri, 3 Dec 2021 17:48:25 +0000 (09:48 -0800)
teaches the Holdings view to determine the number of copies and call
numbers attached to each org unit based on the full data set (via new
API) instead of copies in hand, since we may only have copies in hand
for a subset of child org units.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Mary Llewellyn <mllewell@biblio.org>
Signed-off-by: Jane Sandberg <sandbergja@gmail.com>
Open-ILS/src/eg2/src/app/staff/catalog/record/holdings.component.ts
Open-ILS/src/perlmods/lib/OpenILS/Application/Search/Biblio.pm

index 8ba8583..d767860 100644 (file)
@@ -2,7 +2,7 @@ import {Component, OnInit, Input, ViewChild, ViewEncapsulation
     } from '@angular/core';
 import {Router} from '@angular/router';
 import {Observable, Observer, of, empty} from 'rxjs';
-import {map} from 'rxjs/operators';
+import {map, tap, concatMap} from 'rxjs/operators';
 import {Pager} from '@eg/share/util/pager';
 import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {StaffCatalogService} from '../catalog.service';
@@ -158,6 +158,8 @@ export class HoldingsMaintenanceComponent implements OnInit {
     orgClassCallback: (orgId: number) => string;
     marked_orgs: number[] = [];
 
+    copyCounts: {[orgId: number]: {}} = {};
+
     private _recId: number;
     @Input() set recordId(id: number) {
         this._recId = id;
@@ -419,8 +421,8 @@ export class HoldingsMaintenanceComponent implements OnInit {
     setTreeCounts(node: HoldingsTreeNode) {
 
         if (node.nodeType === 'org') {
-            node.copyCount = 0;
-            node.callNumCount = 0;
+            node.copyCount = this.copyCounts[node.target.id() + ''].copies;
+            node.callNumCount = this.copyCounts[node.target.id() + ''].call_numbers;
         } else if (node.nodeType === 'callNum') {
             node.copyCount = 0;
         }
@@ -430,13 +432,9 @@ export class HoldingsMaintenanceComponent implements OnInit {
         node.children.forEach(child => {
             this.setTreeCounts(child);
             if (node.nodeType === 'org') {
-                node.copyCount += child.copyCount;
-                if (child.nodeType === 'callNum') {
-                    node.callNumCount++;
-                } else {
+                if (child.nodeType !== 'callNum') {
                     hasChildOrgWithData = child.callNumCount > 0;
                     hasChildOrgSansData = child.callNumCount === 0;
-                    node.callNumCount += child.callNumCount;
                 }
             } else if (node.nodeType === 'callNum') {
                 node.copyCount = node.children.length;
@@ -543,22 +541,32 @@ export class HoldingsMaintenanceComponent implements OnInit {
             // any that were deleted in an out-of-band update.
             const volsFetched: number[] = [];
 
-            this.pcrud.search('acn',
-                {   record: this.recordId,
-                    owning_lib: this.org.fullPath(this.contextOrg, true),
-                    deleted: 'f',
-                    label: {'!=' : '##URI##'}
-                }, {
-                    flesh: 3,
-                    flesh_fields: {
-                        acp: ['status', 'location', 'circ_lib', 'parts', 'notes',
-                            'tags', 'age_protect', 'copy_alerts', 'latest_inventory',
-                            'total_circ_count', 'last_circ'],
-                        acn: ['prefix', 'suffix', 'copies'],
-                        acli: ['inventory_workstation']
-                    }
-                },
-                {authoritative: true}
+            return this.net.request(
+                'open-ils.search',
+                'open-ils.search.biblio.record.copy_counts.global.staff',
+                this.recordId
+            ).pipe(
+                tap(counts => this.copyCounts = counts),
+                concatMap(_ => {
+
+                    return this.pcrud.search('acn',
+                        {   record: this.recordId,
+                            owning_lib: this.org.fullPath(this.contextOrg, true),
+                            deleted: 'f',
+                            label: {'!=' : '##URI##'}
+                        }, {
+                            flesh: 3,
+                            flesh_fields: {
+                                acp: ['status', 'location', 'circ_lib', 'parts', 'notes',
+                                     'tags', 'age_protect', 'copy_alerts', 'latest_inventory',
+                                     'total_circ_count', 'last_circ'],
+                                acn: ['prefix', 'suffix', 'copies'],
+                                acli: ['inventory_workstation']
+                            }
+                        },
+                        {authoritative: true}
+                    );
+                })
             ).subscribe(
                 callNum => {
                     this.appendCallNum(callNum);
@@ -572,7 +580,7 @@ export class HoldingsMaintenanceComponent implements OnInit {
                         ok => this.flattenHoldingsTree(observer)
                     );
                 }
-            );
+             );
         });
     }
 
index 26ccaac..20cec4e 100644 (file)
@@ -3308,6 +3308,78 @@ sub get_one_record_summary {
     };
 }
 
+__PACKAGE__->register_method(
+    method    => 'record_copy_counts_global',
+    api_name  => 'open-ils.search.biblio.record.copy_counts.global.staff',
+    signature => {
+        desc   => q/Returns a count of copies and call numbers for each org
+                    unit, including items attached to each org unit plus
+                    a sum of counts for all descendants./,
+        params => [
+            {desc => 'Record ID', type => 'number'}
+        ],
+        return => {
+            desc => 'Hash of org unit ID  => {copy: $count, call_number: $id}'
+        }
+    }
+);
+
+sub record_copy_counts_global {
+    my ($self, $client, $rec_id) = @_;
+
+    my $copies = new_editor()->json_query({
+        select => {
+            acp => [{column => 'id', alias => 'copy_id'}, 'circ_lib'],
+            acn => [{column => 'id', alias => 'cn_id'}, 'owning_lib']
+        },
+        from => {acn => {acp => {type => 'left'}}},
+        where => {
+            '+acp' => {
+                '-or' => [
+                    {deleted => 'f'},
+                    {id => undef} # left join
+                ]
+            },
+            '+acn' => {deleted => 'f', record => $rec_id}
+        }
+    });
+
+    my $hash = {};
+    my %seen_cn;
+
+    for my $copy (@$copies) {
+        my $org = $copy->{circ_lib} || $copy->{owning_lib};
+        $hash->{$org} = {copies => 0, call_numbers => 0} unless $hash->{$org};
+        $hash->{$org}->{copies}++ if $copy->{circ_lib};
+
+        if (!$seen_cn{$copy->{cn_id}}) {
+            $seen_cn{$copy->{cn_id}} = 1;
+            $hash->{$org}->{call_numbers}++;
+        }
+    }
+
+    my $sum;
+    $sum = sub {
+        my $node = shift;
+        my $h = $hash->{$node->id} || {copies => 0, call_numbers => 0};
+        delete $h->{cn_id};
+
+        for my $child (@{$node->children}) {
+            my $vals = $sum->($child);
+            $h->{copies} += $vals->{copies};
+            $h->{call_numbers} += $vals->{call_numbers};
+        }
+
+        $hash->{$node->id} = $h;
+
+        return $h;
+    };
+
+    $sum->($U->get_org_tree);
+
+    return $hash;
+}
+
 
 1;