LP1904036 dupe barcode/username checking; various bugs
authorBill Erickson <berickxx@gmail.com>
Tue, 30 Mar 2021 16:31:08 +0000 (12:31 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:29 +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/edit.component.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/register.component.html
Open-ILS/src/sql/Pg/upgrade/XXXX.data.angular-patron.sql

index e3d95c0..73ffee8 100644 (file)
       {cls: 'ac', path: 'card', field: 'barcode', disabled: !patron.card().isnew()}}">
     </ng-container>
     <div class="col-lg-6">
-      <button class="btn btn-outline-dark" (click)="replaceBarcode()" i18n>
-        Replace Barcode
-      </button>
-      <button class="btn btn-outline-dark ml-2" (click)="barcodesDialog.open()" i18n>
-        See All
-      </button>
+      <ng-container *ngIf="!patron.isnew()">
+        <button class="btn btn-outline-dark" (click)="replaceBarcode()" i18n>
+          Replace Barcode
+        </button>
+        <button class="btn btn-outline-dark ml-2" (click)="barcodesDialog.open()" i18n>
+          See All
+        </button>
+      </ng-container>
+      <span *ngIf="dupeBarcode" class="text-danger font-weight-bold ml-2" i18n>
+        Barcode is already in use
+      </span>
+    </div>
+  </div>
+
+  <div class="row pt-1 pb-1 mt-1" *ngIf="showField('au.usrname')">
+    <ng-container *ngTemplateOutlet="fieldLabel; context: {args: {field: 'usrname'}}">
+    </ng-container>
+    <ng-container *ngTemplateOutlet="fieldInput; context: {args: {field: 'usrname'}}">
+    </ng-container>
+    <div class="col-lg-6">
+      <span *ngIf="dupeUsername" class="text-danger font-weight-bold ml-2" i18n>
+        Username is already in use
+      </span>
     </div>
   </div>
 
-  <ng-container *ngTemplateOutlet="fieldRow; context: 
-    {args: {template: fieldInput, field: 'usrname'}}">
-  </ng-container>
   <div class="row pt-1 pb-1 mt-1">
     <ng-container *ngTemplateOutlet="fieldLabel; context: {args: {field: 'passwd'}}">
     </ng-container>
         fieldName="au-home_ou-input"
         [initialOrgId]="patron.home_ou()"
         [disableOrgs]="cannotHaveUsersOrgs()"
+        [required]="true"
         (onChange)="
           fieldValueChange(null, null, 'home_ou', $event ? $event.id() : null); 
           afterFieldChange(null, null, 'home_ou')">
       <eg-profile-select #profileSelect 
         [useDisplayEntries]="true"
         [initialGroupId]="patron.profile()" 
+        [required]="true"
         (profileChange)="
           fieldValueChange(null, null, 'profile', $event ? $event.id() : null); 
           afterFieldChange(null, null, 'profile')">
 
   <button class="btn btn-success" (click)="newAddr()" i18n>New Address</button>
 
-  <ng-container *ngIf="showField('stat_cats')">
+  <ng-container *ngIf="showField('stat_cats') && statCats.length > 0">
     <div class="alert alert-success pt-2 pb-2 mt-3 mb-3 d-flex">
       <div class="m-auto font-weight-bold" i18n>Statistical Categories</div>
     </div>
   </ng-container>
 
 
-  <ng-container *ngIf="showField('surveys')">
+  <ng-container *ngIf="showField('surveys') && surveys.length > 0">
     <div class="alert alert-success pt-2 pb-2 mt-2 mb-2 d-flex">
       <div class="m-auto font-weight-bold" i18n>Surveys</div>
     </div>
index c7fb749..55728cd 100644 (file)
@@ -141,6 +141,9 @@ export class EditComponent implements OnInit, AfterViewInit {
     secondaryGroups: IdlObject[];
     expireDate: Date;
     changesPending = false;
+    dupeBarcode = false;
+    dupeUsername = false;
+    origUsername: string;
 
     fieldPatterns: {[cls: string]: {[field: string]: RegExp}} = {
         au: {},
@@ -436,6 +439,7 @@ export class EditComponent implements OnInit, AfterViewInit {
             return this.patronService.getFleshedById(this.patronId)
             .then(patron => {
                 this.patron = patron;
+                this.origUsername = patron.usrname();
                 this.absorbPatronData();
             });
         } else {
@@ -543,11 +547,25 @@ export class EditComponent implements OnInit, AfterViewInit {
         // Avoid responding to any value changes while we are loading
         if (this.loading) { return; }
 
-        // TODO other checks
+        // Timeout gives the form a chance to mark fields as (in)valid
+        setTimeout(() => {
 
-        this.changesPending = true;
-        const canSave = document.querySelector('.ng-invalid') === null;
-        this.toolbar.disableSaveStateChanged.emit(!canSave);
+            this.changesPending = true;
+
+            const invalidInput = document.querySelector('.ng-invalid');
+
+            if (invalidInput) {
+                console.debug('Field is invalid', invalidInput.id);
+            }
+
+            const canSave = (
+                invalidInput === null &&
+                !this.dupeBarcode &&
+                !this.dupeUsername
+            );
+
+            this.toolbar.disableSaveStateChanged.emit(!canSave);
+        });
     }
 
     userStatCatChange(cat: IdlObject, entry: ComboboxEntry) {
@@ -662,22 +680,56 @@ export class EditComponent implements OnInit, AfterViewInit {
                 break;
 
             case 'barcode':
-                // TODO check for dupes open-ils.actor.barcode.exists
-                if (!this.patron.usrname()) {
-                    // This will apply the value and fire the dupe checker
-                    this.fieldValueChange(null, null, 'usrname', value);
-                    this.afterFieldChange(null, null, 'usrname');
-                }
+                this.handleBarcodeChange(value);
                 break;
 
             case 'usrname':
-                // TODO check for dupes open-ils.actor.username.exists
+                this.handleUsernameChange(value);
                 break;
         }
 
         this.adjustSaveSate();
     }
 
+    handleUsernameChange(value: any) {
+        this.dupeUsername = false;
+
+        if (!value || value === this.origUsername) {
+            // In case the usrname changes then changes back.
+            return;
+        }
+
+        this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.username.exists',
+            this.auth.token(), value
+        ).subscribe(resp => this.dupeUsername = Boolean(resp));
+    }
+
+    handleBarcodeChange(value: any) {
+        this.dupeBarcode = false;
+
+        if (!value) { return; }
+
+        this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.barcode.exists',
+            this.auth.token(), value
+        ).subscribe(resp => {
+            if (Number(resp) === 1) {
+                this.dupeBarcode = true;
+            } else {
+
+                if (this.patron.usrname()) { return; }
+
+                // Propagate username with barcode value by default.
+                // This will apply the value and fire the dupe checker
+                this.fieldValueChange(null, null, 'usrname', value);
+                this.afterFieldChange(null, null, 'usrname');
+            }
+        });
+    }
+
     dupeValueChange(name: string, value: any) {
 
         if (name.match(/phone/)) { name = 'phone'; }
@@ -814,8 +866,8 @@ export class EditComponent implements OnInit, AfterViewInit {
         const nowEpoch = new Date().getTime();
         const newDate = new Date(nowEpoch + (seconds * 1000 /* millis */));
         this.expireDate = newDate;
-        this.fieldValueChange(null, null, 'profile', newDate.toISOString());
-        this.afterFieldChange(null, null, 'profile');
+        this.fieldValueChange(null, null, 'expire_date', newDate.toISOString());
+        this.afterFieldChange(null, null, 'expire_date');
     }
 
     handleBoolResponse(success: boolean,
@@ -1051,7 +1103,8 @@ export class EditComponent implements OnInit, AfterViewInit {
         } else {
 
             // Create settings for all non-null setting values for new patrons.
-            this.userSettings.forEach( (val, key) => {
+            Object.keys(this.userSettings).forEach(key => {
+                const val = this.userSettings[key];
                 if (val !== null) { settings[key] = val; }
             });
         }
@@ -1145,7 +1198,6 @@ export class EditComponent implements OnInit, AfterViewInit {
             this.fieldPatterns.au.usrname = new RegExp('.*');
         }
     }
-
 }
 
 
index 94284b6..f78b25a 100644 (file)
@@ -1,10 +1,11 @@
 <eg-staff-banner i18n-bannerText bannerText="Register New Patron">
 </eg-staff-banner>
 
-
 <div class="sticky-top-with-nav bg-white">
   <eg-patron-edit-toolbar #editorToolbar></eg-patron-edit-toolbar>
-  <eg-patron-edit [stageUsername]="stageUsername" 
-    [cloneId]="cloneId" [toolbar]="editorToolbar">
-  </eg-patron-edit>
 </div>
+
+<eg-patron-edit [stageUsername]="stageUsername" 
+  [cloneId]="cloneId" [toolbar]="editorToolbar">
+</eg-patron-edit>
+
index 18c63ff..85a9389 100644 (file)
@@ -19,8 +19,8 @@ eg.circ.patron.holds.prefetch
 eg.grid.circ.patron.holds
 
 holds_for_patron print template
+*/
 
-items out print template
 
 -- insert then update for easier iterative development tweaks
 INSERT INTO config.print_template 
@@ -127,7 +127,6 @@ $TEMPLATE$ WHERE name = 'bills_current';
 INSERT INTO config.print_template 
     (name, label, owner, active, locale, content_type, template)
 VALUES ('bills_payment', 'Bills, Payment', 1, TRUE, 'en-US', 'text/html', '');
-*/
 
 UPDATE config.print_template SET template = $TEMPLATE$
 [%