LP1889128 Staffcat support placing multiple holds
authorBill Erickson <berickxx@gmail.com>
Tue, 28 Jul 2020 15:27:05 +0000 (11:27 -0400)
committerJane Sandberg <sandbej@linnbenton.edu>
Wed, 6 Jan 2021 00:48:27 +0000 (16:48 -0800)
Adds support for the org unit setting 'circ.holds.max_duplicate_holds',
which allows staff to place multiple holds per target in the staff
catalog hold placement UI.

Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Michele Morgan <mmorgan@noblenet.org>
Signed-off-by: Jane Sandberg <sandbej@linnbenton.edu>

Open-ILS/src/eg2/src/app/staff/catalog/hold/hold.component.html
Open-ILS/src/eg2/src/app/staff/catalog/hold/hold.component.ts

index 6645443..48c2bed 100644 (file)
           </eg-date-select>
         </div>
       </div>
+      <div class="row mt-2">
+        <div class="col-lg-6">
+          <label for='multi-hold-count' i18n>Number of copies:</label>
+        </div>
+        <div class="col-lg-6">
+          <select class="form-control" name="multi-hold-count"
+            id="multi-hold-count" [(ngModel)]="multiHoldCount">
+            <option [value]="num" 
+              *ngFor="let num of holdCountRange()">{{num}}</option>
+          </select>
+        </div>
+      </div>
+
     </div><!-- left column -->
     <div class="col-lg-6">
       <div class="card">
index 31da8bc..9564587 100644 (file)
@@ -35,6 +35,12 @@ class HoldContext {
            langs: {}
         };
     }
+
+    clone(target: number): HoldContext {
+        const ctx = new HoldContext(target);
+        ctx.holdMeta = this.holdMeta;
+        return ctx;
+    }
 }
 
 @Component({
@@ -65,6 +71,8 @@ export class HoldComponent implements OnInit {
     smsCarriers: ComboboxEntry[];
 
     smsEnabled: boolean;
+    maxMultiHolds = 0;
+    multiHoldCount = 1;
     placeHoldsClicked: boolean;
 
     puLibWsFallback = false;
@@ -134,23 +142,34 @@ export class HoldComponent implements OnInit {
 
         this.getTargetMeta();
 
-        this.org.settings('sms.enable').then(sets => {
+        this.org.settings(['sms.enable', 'circ.holds.max_duplicate_holds'])
+        .then(sets => {
+
             this.smsEnabled = sets['sms.enable'];
-            if (!this.smsEnabled) { return; }
 
-            this.pcrud.search('csc', {active: 't'}, {order_by: {csc: 'name'}})
-            .subscribe(carrier => {
-                this.smsCarriers.push({
-                    id: carrier.id(),
-                    label: carrier.name()
+            if (this.smsEnabled) {
+                this.pcrud.search(
+                    'csc', {active: 't'}, {order_by: {csc: 'name'}})
+                .subscribe(carrier => {
+                    this.smsCarriers.push({
+                        id: carrier.id(),
+                        label: carrier.name()
+                    });
                 });
-            });
+            }
+
+            const max = sets['circ.holds.max_duplicate_holds'];
+            if (Number(max) > 0) { this.maxMultiHolds = max; }
         });
 
         setTimeout(() => // Focus barcode input
             this.renderer.selectRootElement('#patron-barcode').focus());
     }
 
+    holdCountRange(): number[] {
+        return [...Array(this.maxMultiHolds).keys()].map(n => n + 1);
+    }
+
     // Load the bib, call number, copy, etc. data associated with each target.
     getTargetMeta() {
         this.holds.getHoldTargetMeta(this.holdType, this.holdTargets)
@@ -336,20 +355,44 @@ export class HoldComponent implements OnInit {
 
     // Attempt hold placement on all targets
     placeHolds(idx?: number) {
-        if (!idx) { idx = 0; }
-        if (!this.holdTargets[idx]) {
+        if (!idx) {
+            idx = 0;
+            if (this.multiHoldCount > 1) {
+                this.addMultHoldContexts();
+            }
+        }
+
+        if (!this.holdContexts[idx]) {
             this.placeHoldsClicked = false;
             return;
         }
-        this.placeHoldsClicked = true;
 
-        const target = this.holdTargets[idx];
-        const ctx = this.holdContexts.filter(
-            c => c.holdTarget === target)[0];
+        this.placeHoldsClicked = true;
 
+        const ctx = this.holdContexts[idx];
         this.placeOneHold(ctx).then(() => this.placeHolds(idx + 1));
     }
 
+    // When placing holds on multiple copies per target, add a hold
+    // context for each instance of the request.
+    addMultHoldContexts() {
+        const newContexts = [];
+
+        this.holdContexts.forEach(ctx => {
+            for (let idx = 2; idx <= this.multiHoldCount; idx++) {
+                const newCtx = ctx.clone(ctx.holdTarget);
+                newContexts.push(newCtx);
+            }
+        });
+
+        // Group the contexts by hold target
+        this.holdContexts = this.holdContexts.concat(newContexts)
+            .sort((h1, h2) =>
+                h1.holdTarget === h2.holdTarget ? 0 :
+                    h1.holdTarget < h2.holdTarget ? -1 : 1
+            );
+    }
+
     placeOneHold(ctx: HoldContext, override?: boolean): Promise<any> {
 
         ctx.processing = true;