From 7303c2d61d5d09964c189585373cde47ee2d5d87 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 30 Mar 2021 17:19:42 -0400 Subject: [PATCH] LP1904036 juve flag; perm groups editing, phone pw, misc. Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- .../src/app/staff/circ/patron/bills.component.html | 8 +- .../src/app/staff/circ/patron/bills.component.ts | 10 +- .../src/app/staff/circ/patron/edit.component.html | 8 +- .../src/app/staff/circ/patron/edit.component.ts | 152 +++++++++++++++++---- .../src/app/staff/circ/patron/routing.module.ts | 9 +- .../app/staff/circ/patron/statcats.component.ts | 2 + 6 files changed, 156 insertions(+), 33 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html index 531cc4b642..0087916528 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.html @@ -20,7 +20,7 @@ + on transactions {{billGrid.context.rowSelector.selected().join(',')}}?"> Total Owed:
- {{summary.balance_owed() | currency}}
+ {{summary.balance_owed() || 0 | currency}}
Total Billed:
-
{{summary.total_owed() | currency}}
+
{{summary.total_owed() || 0 | currency}}
Total Paid/Credited:
-
{{summary.total_paid() | currency}}
+
{{summary.total_paid() || 0 | currency}}
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts index a87bc6790b..23223c076c 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bills.component.ts @@ -3,7 +3,7 @@ import {Router, ActivatedRoute, ParamMap} from '@angular/router'; import {from, empty, range} from 'rxjs'; import {concatMap, tap, takeLast} from 'rxjs/operators'; import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; -import {IdlObject} from '@eg/core/idl.service'; +import {IdlObject, IdlService} from '@eg/core/idl.service'; import {EventService} from '@eg/core/event.service'; import {OrgService} from '@eg/core/org.service'; import {NetService} from '@eg/core/net.service'; @@ -78,6 +78,7 @@ export class BillsComponent implements OnInit, AfterViewInit { private net: NetService, private pcrud: PcrudService, private auth: AuthService, + private idl: IdlService, private printer: PrintService, private serverStore: ServerStoreService, private circ: CircService, @@ -200,6 +201,13 @@ export class BillsComponent implements OnInit, AfterViewInit { })).toPromise() .then(_ => { + + if (!this.summary) { + // If the patron has no billing history, there will be + // no money summary. + this.summary = this.idl.create('mus'); + } + if (!refreshXacts) { return; } // Refreshing means some transactions may be removed from the list diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html index bcd8ba5546..e052f0c7f4 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.html @@ -19,6 +19,12 @@ i18n-dialogTitle dialogTitle="Address Alert"> + + + +
@@ -122,7 +128,7 @@
- +
{ @@ -198,6 +203,7 @@ export class EditComponent implements OnInit, AfterViewInit { .then(_ => this.getCloneUser()) .then(_ => this.getSecondaryGroups()) .then(_ => this.applyPerms()) + .then(_ => this.setEditProfiles()) .then(_ => this.setIdentTypes()) .then(_ => this.setInetLevels()) .then(_ => this.setOptInSettings()) @@ -206,6 +212,44 @@ export class EditComponent implements OnInit, AfterViewInit { .then(_ => this.loading = false); } + setEditProfiles(): Promise { + return this.pcrud.retrieveAll('pgt', {}, {atomic: true}).toPromise() + .then(list => this.grpList = list) + .then(_ => this.applyEditProfiles()); + } + + // TODO + // Share the set of forbidden groups with the 2ndary groups selector. + applyEditProfiles(): Promise { + const appPerms = []; + const failedPerms = []; + const profiles = this.grpList; + + // extract the application permissions + profiles.forEach(grp => { + if (grp.application_perm()) { + appPerms.push(grp.application_perm()); + } + }); + + const traverseTree = (grp: IdlObject, failed: boolean) => { + if (!grp) { return; } + + failed = failed || failedPerms.includes(grp.application_perm()); + + if (!failed) { this.editProfiles.push(grp.id()); } + + const children = profiles.filter(p => p.parent() === grp.id()); + children.forEach(child => traverseTree(child, failed)); + } + + return this.perms.hasWorkPermAt(appPerms, true).then(orgs => { + appPerms.forEach(p => { + if (orgs[p].length === 0) { failedPerms.push(p); } + traverseTree(this.grpList[0], false); + }); + }); + } getCloneUser(): Promise { if (!this.cloneId) { return Promise.resolve(); } @@ -506,9 +550,9 @@ export class EditComponent implements OnInit, AfterViewInit { createNewPatron() { const patron = this.idl.create('au'); patron.isnew(true); - patron.addresses([]); + patron.id(-1); + patron.home_ou(this.auth.user().ws_ou()); patron.settings([]); - patron.cards([]); patron.waiver_entries([]); patron.stat_cat_entries([]); @@ -516,7 +560,16 @@ export class EditComponent implements OnInit, AfterViewInit { card.isnew(true); card.usr(-1); patron.card(card); - patron.cards().push(card); + patron.cards([card]); + + const addr = this.idl.create('aua'); + addr.isnew(true); + addr.usr(-1); + addr.valid('t'); + addr.country(this.context.settingsCache['ui.patron.default_country']); + patron.billing_address(addr); + patron.mailing_address(addr); + patron.addresses([addr]); this.patron = patron; } @@ -557,14 +610,12 @@ export class EditComponent implements OnInit, AfterViewInit { const invalidInput = document.querySelector('.ng-invalid'); - if (invalidInput) { - console.debug('Field is invalid', invalidInput.id); - } - const canSave = ( - invalidInput === null && - !this.dupeBarcode && - !this.dupeUsername + invalidInput === null + && !this.dupeBarcode + && !this.dupeUsername + && !this.selfEditForbidden() + && !this.groupEditForbidden() ); this.toolbar.disableSaveStateChanged.emit(!canSave); @@ -597,6 +648,15 @@ 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(); } @@ -648,22 +708,19 @@ export class EditComponent implements OnInit, AfterViewInit { `Modifying field path=${path || ''} field=${field} value=${value}`); switch (field) { - // TODO: do many more + + case 'dob': + this.maintainJuvFlag(); + break; 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); + this.handlePhoneChange(field, value); break; case 'ident_value': @@ -698,6 +755,39 @@ export class EditComponent implements OnInit, AfterViewInit { this.adjustSaveSate(); } + maintainJuvFlag() { + + if (!this.patron.dob()) { return; } + + const interval = + this.context.settingsCache['global.juvenile_age_threshold'] + || '18 years'; + + const cutoff = new Date(); + + cutoff.setTime(cutoff.getTime() - + Number(DateUtil.intervalToSeconds(interval) + '000')); + + const isJuve = new Date(this.patron.dob()) > cutoff; + + this.fieldValueChange(null, null, 'juvenile', isJuve); + this.afterFieldChange(null, null, 'juvenile'); + } + + handlePhoneChange(field: string, value: string) { + this.dupeValueChange(field, value); + // TODO: hold contact info stuff + + const pwUsePhone = + this.context.settingsCache['patron.password.use_phone']; + + if (field === 'day_phone' && value && + this.patron.isnew() && !this.patron.passwd() && pwUsePhone) { + this.fieldValueChange(null, null, 'passwd', value.substr(-4)); + this.afterFieldChange(null, null, 'passwd'); + } + } + handlePostCodeChange(addr: IdlObject, postCode: any) { this.net.request( 'open-ils.search', 'open-ils.search.zip', postCode @@ -750,6 +840,7 @@ export class EditComponent implements OnInit, AfterViewInit { // Propagate username with barcode value by default. // This will apply the value and fire the dupe checker + this.updateUsernameRegex(); this.fieldValueChange(null, null, 'usrname', value); this.afterFieldChange(null, null, 'usrname'); } @@ -1025,8 +1116,9 @@ export class EditComponent implements OnInit, AfterViewInit { } promise.then(required => { + if (required) { - // TODO alert and exit + this.addrRequiredAlert.open(); return; } @@ -1173,10 +1265,6 @@ export class EditComponent implements OnInit, AfterViewInit { setFieldPatterns() { let regex; - if (regex = this.context.settingsCache['opac.username_regex']) { - this.fieldPatterns.au.usrname = new RegExp(regex); - } - if (regex = this.context.settingsCache['ui.patron.edit.ac.barcode.regex']) { this.fieldPatterns.ac.barcode = new RegExp(regex); @@ -1204,6 +1292,8 @@ export class EditComponent implements OnInit, AfterViewInit { const name = parts[2]; this.fieldPatterns[cls][name] = new RegExp(val); }); + + this.updateUsernameRegex(); } // The username must match either the configured regex or the @@ -1224,6 +1314,20 @@ export class EditComponent implements OnInit, AfterViewInit { this.fieldPatterns.au.usrname = new RegExp('.*'); } } + + selfEditForbidden(): boolean { + return ( + this.patron.id() === this.auth.user().id() + && !this.hasPerm.EDIT_SELF_IN_CLIENT + ); + } + + groupEditForbidden(): boolean { + return ( + this.patron.profile() + && !this.editProfiles.includes(this.patron.profile()) + ); + } } diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts index 2ce1e1feb7..87fecbe331 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts @@ -16,13 +16,16 @@ const routes: Routes = [{ import('./event-log/event-log.module').then(m => m.EventLogModule) }, { path: 'register', - component: RegisterPatronComponent + component: RegisterPatronComponent, + resolve: {resolver : PatronResolver} }, { path: 'register/clone/:cloneId', - component: RegisterPatronComponent + component: RegisterPatronComponent, + resolve: {resolver : PatronResolver} }, { path: 'register/stage/:stageUsername', - component: RegisterPatronComponent + component: RegisterPatronComponent, + resolve: {resolver : PatronResolver} }, { path: 'credentials', component: TestPatronPasswordComponent diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/statcats.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/statcats.component.ts index d6e777d67e..2a48ff892e 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/statcats.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/statcats.component.ts @@ -40,6 +40,8 @@ export class PatronStatCatsComponent implements OnInit { this.auth.token(), this.patronId, ['stat_cat_entries']).toPromise() .then(user => { const catIds = user.stat_cat_entries().map(e => e.stat_cat()); + if (catIds.length === 0) { return; } + this.pcrud.search('actsc', {id: catIds}) .subscribe(cat => { const map = user.stat_cat_entries() -- 2.11.0