LP1904036 Patron groups; patron edit
authorBill Erickson <berickxx@gmail.com>
Tue, 16 Mar 2021 20:41:21 +0000 (16:41 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:27 +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>
15 files changed:
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.css [new file with mode: 0644]
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/group.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/group.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.module.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/summary.component.ts

index 6a1647b..8b625b9 100644 (file)
@@ -1,7 +1,10 @@
 <div class="row pb-2 pt-2">
   <div class="ml-auto">
-    <button class="btn btn-outline-dark" i18n>Print</button>
-    <button class="btn btn-outline-dark ml-3" i18n>Save</button>
-    <button class="btn btn-outline-dark ml-3" i18n>Save &amp; Clone</button>
+    <button class="btn btn-outline-dark" 
+      (click)="printClicked.emit()" i18n>Print</button>
+    <button class="btn btn-outline-dark ml-3" 
+      (click)="saveClicked.emit()" i18n>Save</button>
+    <button class="btn btn-outline-dark ml-3" 
+      (click)="saveCloneClicked.emit()" i18n>Save &amp; Clone</button>
   </div>
 </div>
index 39d180d..15c9d1e 100644 (file)
@@ -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<void> = new EventEmitter<void>();
+    @Output() saveCloneClicked: EventEmitter<void> = new EventEmitter<void>();
+    @Output() printClicked: EventEmitter<void> = new EventEmitter<void>();
+
     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 (file)
index 0000000..2bc88ae
--- /dev/null
@@ -0,0 +1,6 @@
+
+
+.patron-edit-container {
+  
+}
+
index 139597f..4e29981 100644 (file)
@@ -1,2 +1,165 @@
 
+<span class="font-weight-bold" i18n>Show:</span>
+<a class="ml-2" href="javascript:;'" (click)="showFields='required'" i18n>Required Fields</a>
+<a class="ml-2" href="javascript:;'" (click)="showFields='suggested'" i18n>Suggested Fields</a>
+<a class="ml-2" href="javascript:;'" (click)="showFields='all'" i18n>All Fields</a>
+
+
+<ng-template #fieldLabel
+  let-cls="cls" let-field="field" let-override="override">
+  <div class="col-lg-3 field-label">
+    <label for="{{cls}}-{{field}}-input">
+      {{getFieldLabel(cls, field, override)}}
+    </label>
+    <!-- TODO doc links -->
+  </div>
+</ng-template>
+
+<ng-template #fieldInput let-cls="cls" let-field="field" 
+  let-type="type" let-disabled="disabled" let-path="path">
+  <div class="col-lg-3">
+    <input 
+      type="{{type || 'text'}}"
+      class="form-control" 
+      name="{{cls}}-{{field}}-input"
+      id="{{cls}}-{{field}}-input"
+      [ngModel]="objectFromPath(path)[field]()"
+      (ngModelChange)="fieldValueChange(path, field, $event)"
+      (change)="fieldMaybeModified(path, field)"
+      [required]="fieldRequired(cls, field)"
+      [pattern]="fieldPattern(cls, field)"
+      [disabled]="disabled"
+    />
+  </div>
+</ng-template>
+
+
+<div class="mt-3 striped-rows-even patron-edit-container" *ngIf="patron">
+  <div class="row pt-1 pb-1 mt-2">
+    <ng-container 
+      *ngTemplateOutlet="fieldLabel; context: {cls: 'ac', field: 'barcode'}">
+    </ng-container>
+    <ng-container 
+      *ngTemplateOutlet="fieldInput; context: 
+        {cls: 'ac', field: 'barcode', disabled: !patron.isnew(), path: 'card'}">
+    </ng-container>
+  </div>
+  <div class="row pt-1 pb-1 mt-2">
+    <ng-container 
+      *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'usrname'}">
+    </ng-container>
+    <ng-container 
+      *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'usrname'}">
+    </ng-container>
+  </div>
+  <div class="row pt-1 pb-1 mt-2">
+    <ng-container 
+      *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'passwd'}">
+    </ng-container>
+    <ng-container 
+      *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'passwd'}">
+    </ng-container>
+    <div class="col-lg-3">
+      <button class="btn btn-outline-dark" (click)="generatePassword()" i18n>
+        Generate Password
+      </button>
+    </div>
+  </div>
+  <ul ngbNav #nameNav="ngbNav" class="nav-tabs" [activeId]="nameTab">
+    <li ngbNavItem="primary">
+      <a ngbNavLink i18n>Primary Name</a>
+      <ng-template ngbNavContent>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'prefix'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'prefix'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'first_given_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'first_given_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'second_given_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'second_given_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'family_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'family_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'suffix'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'suffix'}">
+          </ng-container>
+        </div>
+      </ng-template>
+    </li>
+    <li ngbNavItem="preferred">
+      <a ngbNavLink i18n>Preferred Name</a>
+      <ng-template ngbNavContent>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'pref_prefix'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'pref_prefix'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'pref_first_given_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'pref_first_given_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'pref_second_given_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'pref_second_given_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'pref_family_name'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'pref_family_name'}">
+          </ng-container>
+        </div>
+        <div class="row pt-1 pb-1 mt-2">
+          <ng-container 
+            *ngTemplateOutlet="fieldLabel; context: {cls: 'au', field: 'pref_suffix'}">
+          </ng-container>
+          <ng-container 
+            *ngTemplateOutlet="fieldInput; context: {cls: 'au', field: 'pref_suffix'}">
+          </ng-container>
+        </div>
+      </ng-template>
+    </li>
+  </ul>
+  <div [ngbNavOutlet]="nameNav"></div>
+  <hr class="m-2"/>
+
+
+</div>
 
index 18092ec..2ccd57c 100644 (file)
@@ -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');
     }
 }
 
index 73ceeea..9c36780 100644 (file)
   </div>
 </div>
 
+<hr class="m-2"/>
 
-<div class="mt-4">
+<div class="mt-3">
 
-  <eg-grid idlClass="au" #groupGrid
-    [dataSource]="dataSource" [sortable]="true" [useLocalSort]="true"
-    showFields="active,barred,dob,family_name,first_given_name,
+  <ng-template #barcodeTemplate let-r="row">
+    <ng-container *ngIf="r.card()">
+      <a routerLink="/staff/circ/patron/{{r.id()}}/checkout">
+        {{r.card().barcode()}}
+      </a>
+    </ng-container>
+  </ng-template>
+
+  <eg-grid idlClass="au" #groupGrid 
+    [cellTextGenerator]="cellTextGenerator"
+    [dataSource]="dataSource" 
+    [sortable]="true" 
+    [useLocalSort]="true"
+    (onRowActivate)="onRowActivate($event)" 
+    showFields="active,barred,dob,family_name,first_given_name,barcode,
       master_account,second_given_name,balance_owed,total_out,overdue">
 
     <eg-grid-toolbar-button label="Move Another Patron To This Group" 
       i18n-label (onClick)="movePatronToGroup()">
     </eg-grid-toolbar-button>
 
+    <eg-grid-toolbar-action 
+      label="Register a New Group Member By Cloning Selected Patron"
+      i18n-label (onClick)="cloneSelected($event)">
+    </eg-grid-toolbar-action>
+
+    <eg-grid-toolbar-action label="Remove Selected From Group"
+      i18n-label (onClick)="removeSelected($event)">
+    </eg-grid-toolbar-action>
+
+    <eg-grid-toolbar-action label="Move Selected To Another Patron's Group"
+      i18n-label (onClick)="movePatronToGroup($event)">
+    </eg-grid-toolbar-action>
+
     <eg-grid-column name="balance_owed" path="_stats.fines.balance_owed" 
       datatype="currency" label="Balance Owed" i18n-label>
     </eg-grid-column>
+
     <eg-grid-column name="total_out" path="_stats.checkouts.total_out" 
       label="Items Out" i18n-label>
     </eg-grid-column>
+
     <eg-grid-column name="overdue" path="_stats.checkouts.overdue" 
       label="Items Overdue" i18n-label>
     </eg-grid-column>
 
+    <eg-grid-column name="barcode" [cellTemplate]="barcodeTemplate"
+      label="Barcode" i18n-label>
+    </eg-grid-column>
+
   </eg-grid>
 
 </div>
index af69cc6..1cbd00a 100644 (file)
@@ -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<any> {
         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);
+        }
+    }
 }
index dc63f84..e830e7a 100644 (file)
         <li ngbNavItem="edit" [disabled]="!context.patron">
           <a ngbNavLink i18n>Edit</a>
           <ng-template ngbNavContent>
-            <eg-patron-edit 
-              contentPaneClass="patron-content-pane div-scroll-vert">
-            </eg-patron-edit> 
+            <eg-patron-edit [patronId]="patronId"></eg-patron-edit> 
           </ng-template>
         </li>
 
index 384726a..dc31125 100644 (file)
@@ -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: [
index 4e9ae39..f8fd34e 100644 (file)
@@ -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 (file)
index 0000000..fd7f283
--- /dev/null
@@ -0,0 +1,9 @@
+<eg-staff-banner i18n-bannerText bannerText="Register New Patron">
+</eg-staff-banner>
+
+
+<div class="sticky-top-with-nav bg-white">
+  <eg-patron-edit-toolbar></eg-patron-edit-toolbar>
+  <eg-patron-edit [stageUsername]="stageUsername" [cloneId]="cloneId">
+  </eg-patron-edit>
+</div>
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 (file)
index 0000000..23cbe21
--- /dev/null
@@ -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');
+        });
+    }
+}
+
index 3c2da46..2ce1e1f 100644 (file)
@@ -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
   }, {
index df7435f..c2352c6 100644 (file)
 
   <hr class="m-1"/>
 
-  <ng-container *ngIf="context.patronStats">
+  <ng-container *ngIf="stats()">
 
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.fines.balance_owed > 0}">
+      [ngClass]="{'alert alert-danger p-0': stats().fines.balance_owed > 0}">
       <div class="col-lg-5" i18n>Fines Owed</div>
-      <div class="col-lg-7">{{context.patronStats.fines.balance_owed | currency}}</div>
+      <div class="col-lg-7">{{stats().fines.balance_owed | currency}}</div>
     </div>
 
-    <!-- TODO GROUP FINES -->
+    <ng-container 
+      *ngIf="stats().fines.group_balance_owed > stats().fines.balance_owed">
+      <div class="row mb-1 alert alert-danger p-0">
+        <div class="col-lg-5" i18n>Group Fines</div>
+        <div class="col-lg-7">{{stats().fines.group_balance_owed | currency}}</div>
+      </div>
+    </ng-container>
 
     <div class="row mb-1">
       <div class="col-lg-5" i18n>Items Out</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.total_out}}</div>
+      <div class="col-lg-7">{{stats().checkouts.total_out}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.checkouts.overdue > 0}">
+      [ngClass]="{'alert alert-danger p-0': stats().checkouts.overdue > 0}">
       <div class="col-lg-5" i18n>Overdue</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.overdue}}</div>
+      <div class="col-lg-7">{{stats().checkouts.overdue}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.checkouts.long_overdue > 0}">
+      [ngClass]="{'alert alert-danger p-0': stats().checkouts.long_overdue > 0}">
       <div class="col-lg-5" i18n>Long Overdue</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.long_overdue}}</div>
+      <div class="col-lg-7">{{stats().checkouts.long_overdue}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.checkouts.claims_returned > 0}">
+      [ngClass]="{'alert alert-danger p-0': stats().checkouts.claims_returned > 0}">
       <div class="col-lg-5" i18n>Claimed Returned</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.claims_returned}}</div>
+      <div class="col-lg-7">{{stats().checkouts.claims_returned}}</div>
     </div>
     <div class="row mb-1"
-      [ngClass]="{'alert alert-danger p-0': context.patronStats.checkouts.lost > 0}">
+      [ngClass]="{'alert alert-danger p-0': stats().checkouts.lost > 0}">
       <div class="col-lg-5" i18n>Lost</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.lost}}</div>
+      <div class="col-lg-7">{{stats().checkouts.lost}}</div>
     </div>
     <div class="row mb-1">
       <div class="col-lg-5" i18n>Non-Cataloged</div>
-      <div class="col-lg-7">{{context.patronStats.checkouts.noncat}}</div>
+      <div class="col-lg-7">{{stats().checkouts.noncat}}</div>
     </div>
     <div class="row">
       <div class="col-lg-5" i18n>Holds</div>
       <div class="col-lg-7">
-        {{context.patronStats.holds.ready}} / {{context.patronStats.holds.total}}
+        {{stats().holds.ready}} / {{stats().holds.total}}
       </div>
     </div>
 
index 79d6957..119e1cf 100644 (file)
@@ -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 (