{{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>
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';
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 */
@Input() patron: IdlObject;
primaryCard: number;
+ myPerms: {[name: string]: boolean} = {};
constructor(
private modal: NgbModal,
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()) {
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);
}
</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>
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,
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;
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
) {}
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);
+ }
+ });
+ }
}
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',
'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> {
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) {
<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 -->