LP1904036 Dupe searches wip
authorBill Erickson <berickxx@gmail.com>
Mon, 29 Mar 2021 16:31:20 +0000 (12:31 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:28 +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/barcodes.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/barcodes.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html

index aa0cf1e..dd83803 100644 (file)
         {{card.barcode()}}
       </div>
       <div class="col-lg-4" i18n>
-        <input type="checkbox" 
+        <input type="checkbox" [disabled]="!myPerms.UPDATE_PATRON_ACTIVE_CARD"
           (ngModelChange)="activeChange(card, $event)"
           class="form-check-input ml-0" [ngModel]="card.active() == 't'">
       </div>
       <div class="col-lg-4" i18n>
         <input type="radio" name="primary-card" [value]="card.id()"
+          [disabled]="!myPerms.UPDATE_PATRON_PRIMARY_CARD"
           class="form-check-input ml-0" [(ngModel)]="primaryCard">
       </div>
     </div>
index b252284..913e760 100644 (file)
@@ -1,5 +1,5 @@
 import {Component, OnInit, Input, ViewChild} from '@angular/core';
-import {Observable, empty} from 'rxjs';
+import {Observable, from, empty} from 'rxjs';
 import {switchMap, tap} from 'rxjs/operators';
 import {IdlObject, IdlService} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
@@ -12,6 +12,9 @@ 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 {PatronContextService} from './patron.service';
+import {PermService} from '@eg/core/perm.service';
+
+const PERMS = ['UPDATE_PATRON_ACTIVE_CARD', 'UPDATE_PATRON_PRIMARY_CARD'];
 
 /* Add/Remove Secondary Groups */
 
@@ -25,6 +28,7 @@ export class PatronBarcodesDialogComponent
 
     @Input() patron: IdlObject;
     primaryCard: number;
+    myPerms: {[name: string]: boolean} = {};
 
     constructor(
         private modal: NgbModal,
@@ -35,17 +39,13 @@ export class PatronBarcodesDialogComponent
         private pcrud: PcrudService,
         private org: OrgService,
         private auth: AuthService,
+        private perms: PermService,
         private context: PatronContextService
     ) { super(modal); }
 
     ngOnInit() {
     }
 
-    /* todo check perms
-    'UPDATE_PATRON_ACTIVE_CARD',
-    'UPDATE_PATRON_PRIMARY_CARD'
-    */
-
     open(ops: NgbModalOptions): Observable<any> {
         this.patron.cards().some(card => {
             if (card.id() === this.patron.card().id()) {
@@ -53,6 +53,13 @@ export class PatronBarcodesDialogComponent
                 return true;
             }
         });
+
+        this.perms.hasWorkPermAt(PERMS, true).then(perms => {
+            PERMS.forEach(p => {
+                this.myPerms[p] = perms[p].includes(this.patron.home_ou());
+            });
+        })
+
         return super.open(ops);
     }
 
index 1655dd8..5af7748 100644 (file)
     </div>
   </div>
 </div>
+
+<div class="row pb-2 pt-2" *ngIf="dupeSearches.length > 0">
+  <div class="col-lg-12 d-flex">
+    <div class="ml-auto mr-2">
+      <div *ngFor="let dupe of dupeSearches"
+        class="alert alert-danger ml-auto p-2 mt-2" i18n>
+        {{dupe.count}} patron(s) with same
+        <ng-container [ngSwitch]="dupe.category">
+          <span *ngSwitchCase="'phone'">phone</span>
+          <span *ngSwitchCase="'email'">email</span>
+          <span *ngSwitchCase="'name'">name</span>
+          <span *ngSwitchCase="'ident'">identifier</span>
+          <span *ngSwitchCase="'address'">address</span>
+        </ng-container>
+      </div>
+    </div>
+  </div>
+</div>
index 0569ce3..32a3329 100644 (file)
@@ -3,8 +3,10 @@ import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
 import {PatronService} from '@eg/staff/share/patron/patron.service';
 import {PatronContextService} from './patron.service';
+import {PatronSearchFieldSet} from '@eg/staff/share/patron/search.component';
 
 export enum VisibilityLevel {
     ALL_FIELDS = 0,
@@ -12,12 +14,22 @@ export enum VisibilityLevel {
     REQUIRED_FIELDS = 2
 }
 
+type SearchCategory = 'name' | 'email' | 'phone' | 'ident' | 'address';
+
+interface DupeSearch {
+    category: SearchCategory;
+    count: number;
+    search: PatronSearchFieldSet;
+}
+
 @Component({
   templateUrl: 'edit-toolbar.component.html',
   selector: 'eg-patron-edit-toolbar'
 })
 export class EditToolbarComponent implements OnInit {
 
+    @Input() patronId: number;
+
     disableSave = false;
     visibilityLevel: VisibilityLevel = VisibilityLevel.ALL_FIELDS;
 
@@ -27,9 +39,12 @@ export class EditToolbarComponent implements OnInit {
     saveCloneClicked: EventEmitter<void> = new EventEmitter<void>();
     printClicked: EventEmitter<void> = new EventEmitter<void>();
 
+    dupeSearches: DupeSearch[] = [];
+
     constructor(
         private org: OrgService,
         private net: NetService,
+        private auth: AuthService,
         private patronService: PatronService,
         public context: PatronContextService
     ) {}
@@ -42,5 +57,39 @@ export class EditToolbarComponent implements OnInit {
     changeFields(v: VisibilityLevel) {
         this.visibilityLevel = v;
     }
+
+    checkDupes(category: string, search: PatronSearchFieldSet) {
+
+        this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.patron.search.advanced',
+            this.auth.token(),
+            search,
+            1000, // limit
+            null, // sort
+            true  // as id
+        ).subscribe(ids => {
+            ids = ids.filter(id => Number(id) !== this.patronId);
+            const count = ids.length;
+
+            if (count > 0) {
+                const existing =
+                    this.dupeSearches.filter(s => s.category === category)[0];
+                if (existing) {
+                    existing.search = search;
+                    existing.count = count;
+                } else {
+                    this.dupeSearches.push({
+                        category: category as SearchCategory,
+                        search: search,
+                        count: count
+                    });
+                }
+            } else {
+                this.dupeSearches =
+                    this.dupeSearches.filter(s => s.category !== category);
+            }
+        });
+    }
 }
 
index afa4d6a..50553da 100644 (file)
@@ -19,6 +19,7 @@ import {PermService} from '@eg/core/perm.service';
 import {SecondaryGroupsDialogComponent} from './secondary-groups.component';
 import {ServerStoreService} from '@eg/core/server-store.service';
 import {EditToolbarComponent, VisibilityLevel} from './edit-toolbar.component';
+import {PatronSearchFieldSet} from '@eg/staff/share/patron/search.component';
 
 const COMMON_USER_SETTING_TYPES = [
   'circ.holds_behind_desk',
@@ -247,10 +248,13 @@ export class EditComponent implements OnInit, AfterViewInit {
             'open-ils.actor.user.get_groups',
             this.auth.token(), this.patronId
 
-        ).pipe(concatMap(maps => this.pcrud.search('pgt',
-            {id: maps.map(m => m.grp())}, {}, {atomic: true})
+        ).pipe(concatMap(maps => {
+            if (maps.length === 0) { return []; }
 
-        )).pipe(tap(grps => this.secondaryGroups = grps)).toPromise();
+            return this.pcrud.search('pgt',
+                {id: maps.map(m => m.grp())}, {}, {atomic: true});
+
+        })).pipe(tap(grps => this.secondaryGroups = grps)).toPromise();
     }
 
     setIdentTypes(): Promise<any> {
@@ -508,14 +512,87 @@ export class EditComponent implements OnInit, AfterViewInit {
 
         switch (field) {
             // TODO: do many more
+            // open-ils.actor.barcode.exists / ditto username
 
             case 'profile':
                 this.setExpireDate();
                 break;
+
+            case 'day_phone':
+                // TODO: patron.password.use_phone
+                // TODO: hold related contact info
+                this.dupeValueChange(field, value);
+                break;
+
+            case 'evening_phone':
+            case 'other_phone':
+                // TODO hold related contact info
+                this.dupeValueChange(field, value);
+                break;
+
+            case 'ident_value':
+            case 'ident_value2':
+            case 'first_given_name':
+            case 'family_name':
+            case 'email':
+                this.dupeValueChange(field, value);
+                break;
+
+            case 'street1':
+            case 'street2':
+            case 'city':
+                // dupe search on address wants the address object as the value.
+                this.dupeValueChange('address', obj);
+                // TODO address_alert(obj);
+                break;
         }
+
         this.adjustSaveSate();
     }
 
+    dupeValueChange(name: string, value: any) {
+
+        if (name.match(/phone/)) { name = 'phone'; }
+        if (name.match(/name/)) { name = 'name'; }
+        if (name.match(/ident/)) { name = 'ident'; }
+
+        let search: PatronSearchFieldSet;
+        switch (name) {
+
+            case 'name':
+                const fname = this.patron.first_given_name();
+                const lname = this.patron.family_name();
+                search = {
+                    first_given_name : {value : fname, group : 0},
+                    family_name : {value : lname, group : 0}
+                };
+                break;
+
+            case 'email':
+                search = {email : {value : value, group : 0}};
+                break;
+
+            case 'ident':
+                search = {ident : {value : value, group : 2}};
+                break;
+
+            case 'phone':
+                search = {phone : {value : value, group : 2}};
+                break;
+
+            case 'address':
+                search = {};
+                ['street1', 'street2', 'city', 'post_code'].forEach(field => {
+                    if (value[field]()) {
+                        search[field] = {value : value[field](), group: 1};
+                    }
+                });
+                break;
+        }
+
+        this.toolbar.checkDupes(name, search);
+    }
+
     showField(field: string): boolean {
 
         if (this.fieldVisibility[field] === undefined) {
index c67b31e..e21324a 100644 (file)
 
       <ng-container *ngIf="patronTab === 'edit'">
         <!-- put the editor toolbar up here in the sticky section -->
-        <eg-patron-edit-toolbar #editorToolbar></eg-patron-edit-toolbar>
+        <eg-patron-edit-toolbar #editorToolbar [patronId]="patronId">
+        </eg-patron-edit-toolbar>
       </ng-container>
 
     </div><!-- end of sticky top -->