From 9fcb06d3a6fbb167b6732b053c5316e286e4b471 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 16 Mar 2021 16:41:21 -0400 Subject: [PATCH] LP1904036 Patron groups; patron edit Signed-off-by: Bill Erickson Signed-off-by: Jane Sandberg Signed-off-by: Galen Charlton --- .../staff/circ/patron/edit-toolbar.component.html | 9 +- .../staff/circ/patron/edit-toolbar.component.ts | 6 +- .../src/app/staff/circ/patron/edit.component.css | 6 + .../src/app/staff/circ/patron/edit.component.html | 163 +++++++++++++++++++++ .../src/app/staff/circ/patron/edit.component.ts | 86 ++++++++++- .../src/app/staff/circ/patron/group.component.html | 40 ++++- .../src/app/staff/circ/patron/group.component.ts | 103 +++++++++---- .../app/staff/circ/patron/patron.component.html | 4 +- .../eg2/src/app/staff/circ/patron/patron.module.ts | 2 + .../src/app/staff/circ/patron/patron.service.ts | 24 ++- .../app/staff/circ/patron/register.component.html | 9 ++ .../app/staff/circ/patron/register.component.ts | 50 +++++++ .../src/app/staff/circ/patron/routing.module.ts | 10 ++ .../app/staff/circ/patron/summary.component.html | 36 +++-- .../src/app/staff/circ/patron/summary.component.ts | 4 + 15 files changed, 493 insertions(+), 59 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.ts diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html index 6a1647b235..8b625b9de0 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.html @@ -1,7 +1,10 @@
- - - + + +
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts index 39d180d5ad..15c9d1e164 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit-toolbar.component.ts @@ -1,4 +1,4 @@ -import {Component, OnInit, Input} from '@angular/core'; +import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; import {Router, ActivatedRoute, ParamMap} from '@angular/router'; import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; import {OrgService} from '@eg/core/org.service'; @@ -12,6 +12,10 @@ import {PatronContextService} from './patron.service'; }) export class EditToolbarComponent implements OnInit { + @Output() saveClicked: EventEmitter = new EventEmitter(); + @Output() saveCloneClicked: EventEmitter = new EventEmitter(); + @Output() printClicked: EventEmitter = new EventEmitter(); + constructor( private org: OrgService, private net: NetService, diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.css b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.css new file mode 100644 index 0000000000..2bc88ae4f1 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.css @@ -0,0 +1,6 @@ + + +.patron-edit-container { + +} + 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 139597f9cb..4e29981ff6 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 @@ -1,2 +1,165 @@ +Show: +Required Fields +Suggested Fields +All Fields + + + +
+ + +
+
+ + +
+ +
+
+ + +
+
+ + + + +
+
+ + + + +
+
+ + + + +
+ +
+
+ +
+
+ + +
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts index 18092eceec..2ccd57c4c6 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts @@ -2,24 +2,108 @@ import {Component, OnInit, Input} from '@angular/core'; import {Router, ActivatedRoute, ParamMap} from '@angular/router'; import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; import {OrgService} from '@eg/core/org.service'; +import {IdlService, IdlObject} from '@eg/core/idl.service'; import {NetService} from '@eg/core/net.service'; import {PatronService} from '@eg/staff/share/patron/patron.service'; import {PatronContextService} from './patron.service'; +const FLESH_PATRON_FIELDS = { + flesh: 1, + flesh_fields: { + au: ['card', 'mailing_address', 'billing_address', 'addresses'] + } +}; + @Component({ templateUrl: 'edit.component.html', - selector: 'eg-patron-edit' + selector: 'eg-patron-edit', + styleUrls: ['edit.component.css'] }) export class EditComponent implements OnInit { + @Input() patronId: number; + @Input() cloneId: number; + @Input() stageUsername: string; + + patron: IdlObject; + changeHandlerNeeded = false; + nameTab = 'primary'; + constructor( private org: OrgService, private net: NetService, + private idl: IdlService, public patronService: PatronService, public context: PatronContextService ) {} ngOnInit() { + + if (this.patronId) { + this.patronService.getById(this.patronId, FLESH_PATRON_FIELDS) + .then(patron => this.patron = patron); + } else { + this.createNewPatron(); + } + } + + createNewPatron() { + const patron = this.idl.create('au'); + patron.isnew(true); + + const card = this.idl.create('ac'); + card.isnew(true); + card.usr(-1); + patron.card(card); + + this.patron = patron; + } + + objectFromPath(path: string): IdlObject { + return path ? this.patron[path]() : this.patron; + } + + getFieldLabel(idlClass: string, field: string, override?: string): string { + return override ? override : + this.idl.classes[idlClass].field_map[field].label; + } + + fieldValueChange(path: string, field: string, value: any) { + this.changeHandlerNeeded = true; + this.objectFromPath(path)[field](value); + } + + fieldMaybeModified(path: string, field: string) { + if (!this.changeHandlerNeeded) { return; } // no changes applied + + // TODO: set dirty = true + + this.changeHandlerNeeded = false; + + console.debug(`Modifying field path=${path} field=${field}`); + + // check stuff here.. + + const obj = path ? this.patron[path]() : this.patron; + } + + fieldRequired(idlClass: string, field: string): boolean { + // TODO + return false; + } + + fieldPattern(idlClass: string, field: string): string { + // TODO + return null; + } + + generatePassword() { + this.fieldValueChange(null, + 'passwd', Math.floor(Math.random()*9000) + 1000); + + // Normally this is called on (blur), but the input is not + // focused when using the generate button. + this.fieldMaybeModified(null, 'passwd'); } } diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html index 73ceeea4f2..9c3678026c 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.html @@ -18,28 +18,60 @@ +
-
+
- + + + {{r.card().barcode()}} + + + + + + + + + + + + + + + + + + +
diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts index af69cc64c1..1cbd00adaa 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts @@ -1,7 +1,8 @@ import {Component, Input, OnInit, AfterViewInit, ViewChild} from '@angular/core'; +import {Location} from '@angular/common'; import {Router, ActivatedRoute, ParamMap} from '@angular/router'; -import {from, empty, range} from 'rxjs'; -import {concatMap, tap, takeLast} from 'rxjs/operators'; +import {of, from, empty, range} from 'rxjs'; +import {concatMap, map, tap, takeLast} from 'rxjs/operators'; import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; import {IdlObject} from '@eg/core/idl.service'; import {EventService} from '@eg/core/event.service'; @@ -31,6 +32,7 @@ export class PatronGroupComponent implements OnInit { totalOverdue = 0; usergroup: number; + cellTextGenerator: GridCellTextGenerator; dataSource: GridDataSource = new GridDataSource(); @ViewChild('groupGrid') private groupGrid: GridComponent; @ViewChild('moveToGroupDialog') private moveToGroupDialog: PromptDialogComponent; @@ -43,12 +45,17 @@ export class PatronGroupComponent implements OnInit { private auth: AuthService, private org: OrgService, private pcrud: PcrudService, + private ngLocation: Location, private patronService: PatronService, private context: PatronContextService ) {} ngOnInit() { + this.cellTextGenerator = { + barcode: row => row.card().barcode() + } + this.dataSource.getRows = (pager: Pager, sort: any[]) => from(this.patrons.slice(pager.offset, pager.offset + pager.limit)); @@ -61,12 +68,14 @@ export class PatronGroupComponent implements OnInit { } } - getGroupUsers(usergroup: number) { + getGroupUsers(usergroup: number): Promise { this.usergroup = usergroup; this.patrons = []; - this.pcrud.search('au', - {usrgroup: usergroup, deleted: 'f'}, {authoritative: true}) + return this.pcrud.search('au', + {usrgroup: usergroup, deleted: 'f'}, + {flesh: 1, flesh_fields: {au: ['card']}}, + {authoritative: true}) .pipe(concatMap(u => { const promise = this.context.getPatronVitalStats(u.id()) @@ -80,10 +89,14 @@ export class PatronGroupComponent implements OnInit { return from(promise); - })).subscribe(null, null, () => this.groupGrid.reload()); + })).toPromise().then(_ => this.groupGrid.reload()); } - movePatronToGroup() { + // If rows are present, we are moving selected rows to a different group + // Otherwise, we are moving another user into this group. + movePatronToGroup(rows?: IdlObject[]) { + + this.moveToGroupDialog.promptValue = ''; this.moveToGroupDialog.open().subscribe(barcode => { if (!barcode) { return null; } @@ -95,31 +108,63 @@ export class PatronGroupComponent implements OnInit { return null; } - resp.usrgroup(this.usergroup); - resp.ischanged(true); - - return this.net.request( - 'open-ils.actor', - 'open-ils.actor.patron.update', - this.auth.token(), resp - ).toPromise(); - }) - .then(resp => { - if (resp === null) { return null; } - - const evt = this.evt.parse(resp); - if (evt) { - console.error(evt); - alert(evt); - return null; + let users: IdlObject[] = [resp]; + let usergroup: number = this.usergroup; + if (rows) { + users = rows; + usergroup = resp.usrgroup(); } - return this.getGroupUsers(this.usergroup); - }) - .then(resp => { - if (resp === null) { return null; } - this.groupGrid.reload(); + let allOk = true; + from(users).pipe(concatMap(user => { + + user.usrgroup(usergroup); + user.ischanged(true); + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.patron.update', + this.auth.token(), user + ); + })).subscribe( + resp => { if (this.evt.parse(resp)) { allOk = false; } }, + err => console.error(err), + () => { if (allOk) { this.refresh(); } } + ); }); }); } + + refresh() { + this.context.refreshPatron() + .then(_ => this.usergroup = this.context.patron.usrgroup()) + .then(_ => this.getGroupUsers(this.usergroup)) + .then(_ => this.groupGrid.reload()); + } + + removeSelected(rows: IdlObject[]) { + + from(rows.map(r => r.id())).pipe(concatMap(id => { + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.usergroup.new', + this.auth.token(), id, true + ); + })) + .subscribe(null, null, () => this.refresh()); + } + + onRowActivate(row: IdlObject) { + const url = this.ngLocation.prepareExternalUrl( + `/staff/circ/patron/${row.id()}/checkout`); + window.open(url); + } + + cloneSelected(rows: IdlObject[]) { + if (rows.length) { + const url = this.ngLocation.prepareExternalUrl( + `/staff/circ/patron/register/clone/${rows[0].id()}`); + window.open(url); + } + } } diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html index dc63f84d83..e830e7a4ad 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html @@ -104,9 +104,7 @@
  • Edit - - +
  • diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts index 384726a633..dc31125ca8 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts @@ -26,6 +26,7 @@ import {TestPatronPasswordComponent} from './test-password.component'; import {PatronSurveyResponsesComponent} from './surveys.component'; import {PatronStatCatsComponent} from './statcats.component'; import {PatronGroupComponent} from './group.component'; +import {RegisterPatronComponent} from './register.component'; @NgModule({ declarations: [ @@ -43,6 +44,7 @@ import {PatronGroupComponent} from './group.component'; TestPatronPasswordComponent, PatronSurveyResponsesComponent, PatronGroupComponent, + RegisterPatronComponent, PatronStatCatsComponent ], imports: [ diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts index 4e9ae397d4..f8fd34e014 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts @@ -44,7 +44,10 @@ const PATRON_FLESH_FIELDS = [ ]; interface PatronStats { - fines: {balance_owed: number}; + fines: { + balance_owed: number, + group_balance_owed: number + }; checkouts: { overdue: number, claims_returned: number, @@ -177,18 +180,33 @@ export class PatronContextService { .then(stats => this.patronStats = stats) .then(_ => { - if (!this.patron) { return; } return this.net.request( 'open-ils.circ', 'open-ils.circ.open_non_cataloged_circulation.user.authoritative', - this.auth.token(), id).toPromise(); + this.auth.token(), id + ).toPromise(); }).then(noncats => { + if (!this.patron) { return; } + if (noncats && this.patronStats) { this.patronStats.checkouts.noncat = noncats.length; } + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.usergroup.members.balance_owed.authoritative', + this.auth.token(), this.patron.usrgroup() + ).toPromise(); + + }).then(fines => { + if (!this.patron) { return; } + + let total = 0; + fines.forEach(f => total += Number(f.balance_owed) * 100); + this.patronStats.fines.group_balance_owed = total / 100; }); } diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html new file mode 100644 index 0000000000..fd7f2834b5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html @@ -0,0 +1,9 @@ + + + + +
    + + + +
    diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.ts new file mode 100644 index 0000000000..23cbe21614 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.ts @@ -0,0 +1,50 @@ +import {Component, Input, OnInit, AfterViewInit, ViewChild} from '@angular/core'; +import {Location} from '@angular/common'; +import {Router, ActivatedRoute, ParamMap} from '@angular/router'; +import {of, from, empty, range} from 'rxjs'; +import {concatMap, map, tap, takeLast} from 'rxjs/operators'; +import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap'; +import {IdlObject} 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'; +import {PcrudService, PcrudContext} from '@eg/core/pcrud.service'; +import {AuthService} from '@eg/core/auth.service'; +import {PatronService} from '@eg/staff/share/patron/patron.service'; +import {PatronContextService} from './patron.service'; +import {GridDataSource, GridColumn, GridCellTextGenerator} from '@eg/share/grid/grid'; +import {GridComponent} from '@eg/share/grid/grid.component'; +import {Pager} from '@eg/share/util/pager'; +import {PromptDialogComponent} from '@eg/share/dialog/prompt.component'; +import {AlertDialogComponent} from '@eg/share/dialog/alert.component'; +import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; + +@Component({ + templateUrl: 'register.component.html' +}) +export class RegisterPatronComponent implements OnInit { + + stageUsername: string; + cloneId: number; + + constructor( + private router: Router, + private route: ActivatedRoute, + private evt: EventService, + private net: NetService, + private auth: AuthService, + private org: OrgService, + private pcrud: PcrudService, + private ngLocation: Location, + private patronService: PatronService, + private context: PatronContextService + ) {} + + ngOnInit() { + this.route.paramMap.subscribe((params: ParamMap) => { + this.stageUsername = params.get('stageUsername'); + this.cloneId = +params.get('cloneId'); + }); + } +} + 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 3c2da465ac..2ce1e1feb7 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 @@ -4,6 +4,7 @@ import {PatronComponent} from './patron.component'; import {BcSearchComponent} from './bcsearch.component'; import {PatronResolver} from './resolver.service'; import {TestPatronPasswordComponent} from './test-password.component'; +import {RegisterPatronComponent} from './register.component'; const routes: Routes = [{ path: '', @@ -14,6 +15,15 @@ const routes: Routes = [{ loadChildren: () => import('./event-log/event-log.module').then(m => m.EventLogModule) }, { + path: 'register', + component: RegisterPatronComponent + }, { + path: 'register/clone/:cloneId', + component: RegisterPatronComponent + }, { + path: 'register/stage/:stageUsername', + component: RegisterPatronComponent + }, { path: 'credentials', component: TestPatronPasswordComponent }, { diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html index df7435f35e..c2352c65d9 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html @@ -84,48 +84,54 @@
    - +
    + [ngClass]="{'alert alert-danger p-0': stats().fines.balance_owed > 0}">
    Fines Owed
    -
    {{context.patronStats.fines.balance_owed | currency}}
    +
    {{stats().fines.balance_owed | currency}}
    - + +
    +
    Group Fines
    +
    {{stats().fines.group_balance_owed | currency}}
    +
    +
    Items Out
    -
    {{context.patronStats.checkouts.total_out}}
    +
    {{stats().checkouts.total_out}}
    + [ngClass]="{'alert alert-danger p-0': stats().checkouts.overdue > 0}">
    Overdue
    -
    {{context.patronStats.checkouts.overdue}}
    +
    {{stats().checkouts.overdue}}
    + [ngClass]="{'alert alert-danger p-0': stats().checkouts.long_overdue > 0}">
    Long Overdue
    -
    {{context.patronStats.checkouts.long_overdue}}
    +
    {{stats().checkouts.long_overdue}}
    + [ngClass]="{'alert alert-danger p-0': stats().checkouts.claims_returned > 0}">
    Claimed Returned
    -
    {{context.patronStats.checkouts.claims_returned}}
    +
    {{stats().checkouts.claims_returned}}
    + [ngClass]="{'alert alert-danger p-0': stats().checkouts.lost > 0}">
    Lost
    -
    {{context.patronStats.checkouts.lost}}
    +
    {{stats().checkouts.lost}}
    Non-Cataloged
    -
    {{context.patronStats.checkouts.noncat}}
    +
    {{stats().checkouts.noncat}}
    Holds
    - {{context.patronStats.holds.ready}} / {{context.patronStats.holds.total}} + {{stats().holds.ready}} / {{stats().holds.total}}
    diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts index 79d6957285..119e1cf648 100644 --- a/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts @@ -30,6 +30,10 @@ export class SummaryComponent implements OnInit { return this.context.patron; } + stats(): any { + return this.context.patronStats; + } + hasPrefName(): boolean { if (this.patron()) { return ( -- 2.11.0