LP1904036 Hold notify mods
authorBill Erickson <berickxx@gmail.com>
Thu, 1 Apr 2021 16:28:27 +0000 (12:28 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:29 +0000 (20:13 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts

index e052f0c..d8951c8 100644 (file)
@@ -24,6 +24,8 @@
   i18n-dialogBody dialogBody="An address is required during registration">
 </eg-alert-dialog>
 
+<eg-hold-notify-update-dialog #holdNotifyUpdateDialog>
+</eg-hold-notify-update-dialog>
 
 <div class="row" *ngIf="loading">
   <div class="col-lg-6 offset-lg-3">
index 7471da3..45ee036 100644 (file)
@@ -1,5 +1,6 @@
 import {Component, OnInit, AfterViewInit, Input, ViewChild} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {empty, from} from 'rxjs';
 import {concatMap, tap} from 'rxjs/operators';
 import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {OrgService} from '@eg/core/org.service';
@@ -21,6 +22,7 @@ import {ServerStoreService} from '@eg/core/server-store.service';
 import {EditToolbarComponent, VisibilityLevel} from './edit-toolbar.component';
 import {PatronSearchFieldSet} from '@eg/staff/share/patron/search.component';
 import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {HoldNotifyUpdateDialogComponent} from './hold-notify-update.component';
 
 const COMMON_USER_SETTING_TYPES = [
   'circ.holds_behind_desk',
@@ -28,7 +30,7 @@ const COMMON_USER_SETTING_TYPES = [
   'opac.hold_notify',
   'opac.default_phone',
   'opac.default_pickup_location',
-  'opac.default_sms_carrier',
+  'opac.default_sms_carrier_id',
   'opac.default_sms_notify'
 ];
 
@@ -122,6 +124,8 @@ export class EditComponent implements OnInit, AfterViewInit {
         private profileSelect: ProfileSelectComponent;
     @ViewChild('secondaryGroupsDialog')
         private secondaryGroupsDialog: SecondaryGroupsDialogComponent;
+    @ViewChild('holdNotifyUpdateDialog')
+        private holdNotifyUpdateDialog: HoldNotifyUpdateDialogComponent;
     @ViewChild('addrAlert') private addrAlert: AlertDialogComponent;
     @ViewChild('addrRequiredAlert')
         private addrRequiredAlert: AlertDialogComponent;
@@ -160,6 +164,18 @@ export class EditComponent implements OnInit, AfterViewInit {
 
     fieldVisibility: {[key: string]: FieldVisibility} = {};
 
+    holdNotifyValues = {
+        day_phone: null,
+        other_phone: null,
+        evening_phone: null,
+        default_phone: null,
+        default_sms: null,
+        default_sms_carrier_id: null,
+        phone_notify: false,
+        email_notify: false,
+        sms_notify: false
+    };
+
     // All locations we have the specified permissions
     permOrgs: {[name: string]: number[]};
 
@@ -348,7 +364,6 @@ export class EditComponent implements OnInit, AfterViewInit {
                 this.fieldDoc[doc.fm_class()] = {};
             }
             this.fieldDoc[doc.fm_class()][doc.field()] = doc.string();
-            console.log(this.fieldDoc);
         })).toPromise();
     }
 
@@ -495,28 +510,49 @@ export class EditComponent implements OnInit, AfterViewInit {
     }
 
     absorbPatronData() {
+
+        const usets = this.userSettings;
+        let setting;
+
+        this.holdNotifyValues.day_phone = this.patron.day_phone();
+        this.holdNotifyValues.other_phone = this.patron.other_phone();
+        this.holdNotifyValues.evening_phone = this.patron.evening_phone();
+
         this.patron.settings().forEach(setting => {
             const value = setting.value();
             if (value !== '' && value !== null) {
-                this.userSettings[setting.name()] = JSON.parse(value);
+                usets[setting.name()] = JSON.parse(value);
             }
         });
 
-        const holdNotify = this.userSettings['opac.hold_notify'];
+        const holdNotify = usets['opac.hold_notify'];
+
         if (holdNotify) {
-            this.holdNotifyTypes.email = holdNotify.match(/email/) !== null;
-            this.holdNotifyTypes.phone = holdNotify.match(/phone/) !== null;
-            this.holdNotifyTypes.sms = holdNotify.match(/sms/) !== null;
+            this.holdNotifyTypes.email = this.holdNotifyValues.email_notify
+                = holdNotify.match(/email/) !== null;
+
+            this.holdNotifyTypes.phone = this.holdNotifyValues.phone_notify
+                = holdNotify.match(/phone/) !== null;
+
+            this.holdNotifyTypes.sms = this.holdNotifyValues.sms_notify
+                = holdNotify.match(/sms/) !== null;
+        }
+
+        if (setting = usets['opac.default_sms_carrier_id']) {
+            setting = usets['opac.default_sms_carrier_id'] = Number(setting);
+            this.holdNotifyValues.default_sms_carrier_id = setting;
         }
 
-        if (this.userSettings['opac.default_sms_carrier']) {
-            this.userSettings['opac.default_sms_carrier'] =
-                Number(this.userSettings['opac.default_sms_carrier']);
+        if (setting = usets['opac.default_phone']) {
+            this.holdNotifyValues.default_phone = setting;
         }
 
-        if (this.userSettings['opac.default_pickup_location']) {
-            this.userSettings['opac.default_pickup_location'] =
-                Number(this.userSettings['opac.default_pickup_location']);
+        if (setting = usets['opac.default_sms_notify']) {
+            this.holdNotifyValues.default_sms = setting;
+        }
+
+        if (setting = usets['opac.default_pickup_location']) {
+            usets['opac.default_pickup_location'] = Number(setting);
         }
 
         this.expireDate = new Date(this.patron.expire_date());
@@ -574,6 +610,8 @@ export class EditComponent implements OnInit, AfterViewInit {
         this.patron = patron;
     }
 
+
+
     objectFromPath(path: string, index: number): IdlObject {
         const base = path ? this.patron[path]() : this.patron;
         if (index === null || index === undefined) {
@@ -648,15 +686,6 @@ export class EditComponent implements OnInit, AfterViewInit {
 
     userSettingChange(name: string, value: any) {
         this.userSettings[name] = value;
-
-        switch (name) {
-            case 'opac.default_phone':
-            case 'opac.default_sms_notify':
-            case 'opac.default_sms_carrier':
-                // TODO hold related contact info updated
-                break;
-        }
-
         this.adjustSaveSate();
     }
 
@@ -776,7 +805,6 @@ export class EditComponent implements OnInit, AfterViewInit {
 
     handlePhoneChange(field: string, value: string) {
         this.dupeValueChange(field, value);
-        // TODO: hold contact info stuff
 
         const pwUsePhone =
             this.context.settingsCache['patron.password.use_phone'];
@@ -1167,6 +1195,7 @@ export class EditComponent implements OnInit, AfterViewInit {
         this.loading = true;
         return this.saveUser()
         .then(_ => this.saveUserSettings())
+        .then(_ => this.updateHoldPrefs())
         .then(_ => this.postSaveRedirect());
     }
 
@@ -1236,6 +1265,66 @@ export class EditComponent implements OnInit, AfterViewInit {
         ).toPromise();
     }
 
+
+    updateHoldPrefs(): Promise<any> {
+        if (this.patron.isnew()) { return Promise.resolve(); }
+
+        return this.collectHoldNotifyChange()
+        .then(mods => {
+            if (mods.length === 0) { return Promise.resolve(); }
+
+            this.holdNotifyUpdateDialog.smsCarriers = this.smsCarriers;
+            this.holdNotifyUpdateDialog.mods = mods;
+
+            return this.holdNotifyUpdateDialog.open().toPromise();
+        });
+    }
+
+    // Compare current values with those collected at patron load time.
+    // For any that have changed, ask the server if the original values
+    // are used on active holds.
+    collectHoldNotifyChange(): Promise<any[]> {
+        const mods = [];
+        const holdNotify = this.userSettings['opac.hold_notify'] || '';
+
+        return from(Object.keys(this.holdNotifyValues))
+        .pipe(concatMap(field => {
+
+            let newValue, matches;
+
+            if (field.match(/default_/)) {
+                newValue = this.userSettings[`opac.${field}`] || null;
+
+            } else if (field.match(/_phone/)) {
+                newValue = this.patron[field]();
+
+            } else if (matches = field.match(/(\w+)_notify/)) {
+                newValue = this.userSettings['opac.hold_notify'].match(matches[1]) !== null;
+            }
+
+            const oldValue = this.holdNotifyValues[field];
+
+            // No change to apply?
+            if (newValue === oldValue) { return empty(); }
+
+            return this.net.request(
+                'open-ils.circ',
+                'open-ils.circ.holds.retrieve_by_notify_staff',
+                this.auth.token(), this.patronId, newValue, field
+            ).pipe(tap(holds => {
+                if (holds && holds.length > 0) {
+                    mods.push({
+                        field: field,
+                        newValue: newValue,
+                        oldValue: oldValue,
+                        holds: holds
+                    });
+                };
+            }));
+        })).toPromise().then(_ => mods);
+    }
+
+
     printPatron() {
         // TODO
     }
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.html
new file mode 100644 (file)
index 0000000..3c8236e
--- /dev/null
@@ -0,0 +1,44 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h4 class="modal-title" i18n>Update Hold Notification Info?</h4>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <div class="row mt-2" *ngFor="let mod of mods">
+      <div class="col-lg-12">
+
+        <!-- TODO pref change wording -->
+
+        <div class="form-check form-check-inline">
+          <input class="form-check-input" type="checkbox" 
+            id="{{mod.field}}-checkbox" [(ngModel)]="selected[mod.field]"/>
+          <label class="form-check-label" for="{{mod.field}}-checkbox">
+            <ng-container *ngIf="isCarrierChange(mod)" i18n>
+              A carrier other than "{{carrierName(mod.oldValue)}}" 
+              is currently used in {{mod.holds.length}} hold(s). 
+              Update to "{{carrierName(mod.newValue)}}"?
+            </ng-container>
+            <ng-container *ngIf="isBoolChange(mod)" i18n>
+              {{mod.holds.length}} hold(s) have it set to 
+              <eg-bool [value]="mod.oldValue"></eg-bool>.
+              Update to <eg-bool [value]="mod.newValue"></eg-bool>.
+            </ng-container>
+            <ng-container *ngIf="isPhoneChange(mod)" i18n>
+              {{mod.oldValue}} is used for {{mod.holds.length}} hold(s).
+              Update to {{mod.newValue}}?
+            </ng-container>
+          </label>
+        </div>
+      </div>
+    </div>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-success"
+      (click)="applyChanges()" i18n>Apply Changes</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/hold-notify-update.component.ts
new file mode 100644 (file)
index 0000000..41e73e4
--- /dev/null
@@ -0,0 +1,72 @@
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable, empty} from 'rxjs';
+import {switchMap, tap} from 'rxjs/operators';
+import {IdlObject, IdlService} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.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 {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+/* Apply notification changes to affected holds */
+
+export interface HoldNotifyMod {
+    field: string,
+    newValue: any,
+    oldValue: any,
+    holds: any[]
+}
+
+@Component({
+  selector: 'eg-hold-notify-update-dialog',
+  templateUrl: 'hold-notify-update.component.html'
+})
+
+export class HoldNotifyUpdateDialogComponent
+    extends DialogComponent implements OnInit {
+
+    // Values provided directly by our parent component
+    smsCarriers: ComboboxEntry[];
+    mods: HoldNotifyMod[] = [];
+
+    selected: {[field: string]: boolean} = {};
+
+    constructor(
+        private modal: NgbModal,
+        private toast: ToastService,
+        private net: NetService,
+        private idl: IdlService,
+        private evt: EventService,
+        private pcrud: PcrudService,
+        private org: OrgService,
+        private auth: AuthService) {
+        super(modal);
+    }
+
+    applyChanges() {
+    }
+
+    isPhoneChange(mod: HoldNotifyMod): boolean {
+        return mod.field.match(/_phone/) !== null;
+    }
+
+    isBoolChange(mod: HoldNotifyMod): boolean {
+        return mod.field.match(/_notify/) !== null && !this.isCarrierChange(mod);
+    }
+
+    isCarrierChange(mod: HoldNotifyMod): boolean {
+        return mod.field.match(/carrier/) !== null;
+    }
+
+    carrierName(id: number): string {
+        const entry = this.smsCarriers.filter(e => e.id === id)[0];
+        return entry ? entry.label : '';
+    }
+}
+
+
index 439059f..2b2d450 100644 (file)
@@ -29,6 +29,7 @@ import {PatronGroupComponent} from './group.component';
 import {RegisterPatronComponent} from './register.component';
 import {SecondaryGroupsDialogComponent} from './secondary-groups.component';
 import {PatronBarcodesDialogComponent} from './barcodes.component';
+import {HoldNotifyUpdateDialogComponent} from './hold-notify-update.component';
 
 @NgModule({
   declarations: [
@@ -49,7 +50,8 @@ import {PatronBarcodesDialogComponent} from './barcodes.component';
     RegisterPatronComponent,
     PatronStatCatsComponent,
     PatronBarcodesDialogComponent,
-    SecondaryGroupsDialogComponent
+    SecondaryGroupsDialogComponent,
+    HoldNotifyUpdateDialogComponent
   ],
   imports: [
     StaffCommonModule,