LP1904036 saving
authorBill Erickson <berickxx@gmail.com>
Mon, 22 Mar 2021 21:58:17 +0000 (17:58 -0400)
committerGalen Charlton <gmc@equinoxOLI.org>
Fri, 28 Oct 2022 00:13:28 +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-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.html
Open-ILS/src/eg2/src/app/staff/circ/patron/edit.component.ts
Open-ILS/src/eg2/src/app/staff/circ/patron/patron.service.ts

index 6a6f19f..222e4d0 100644 (file)
   <div class="col-lg-6 d-flex">
     <div class="ml-auto">
       <button class="btn btn-outline-dark" 
-        (click)="printClicked.emit()" i18n>Print</button>
+        (click)="context.printClicked.emit()" i18n>Print</button>
       <button class="btn btn-outline-dark ml-2" 
-        (click)="saveClicked.emit()" i18n>Save</button>
+        (click)="context.saveClicked.emit()" i18n>Save</button>
       <button class="btn btn-outline-dark ml-2" 
-        (click)="saveCloneClicked.emit()" i18n>Save &amp; Clone</button>
+        (click)="context.saveCloneClicked.emit()" i18n>Save &amp; Clone</button>
     </div>
   </div>
 </div>
index 60f6dba..0c6d9df 100644 (file)
@@ -4,9 +4,7 @@ import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
 import {OrgService} from '@eg/core/org.service';
 import {NetService} from '@eg/core/net.service';
 import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronContextService} from './patron.service';
-
-type FieldOptions = 'required' | 'suggested' | 'all';
+import {PatronContextService, EditorFieldOptions} from './patron.service';
 
 @Component({
   templateUrl: 'edit-toolbar.component.html',
@@ -14,27 +12,21 @@ type FieldOptions = 'required' | 'suggested' | 'all';
 })
 export class EditToolbarComponent implements OnInit {
 
-    showFields: FieldOptions = 'all';
-
-    @Output() saveClicked: EventEmitter<void> = new EventEmitter<void>();
-    @Output() saveCloneClicked: EventEmitter<void> = new EventEmitter<void>();
-    @Output() printClicked: EventEmitter<void> = new EventEmitter<void>();
-    @Output() showFieldsChanged:
-      EventEmitter<FieldOptions> = new EventEmitter<FieldOptions>();
+    showFields: EditorFieldOptions = 'all';
 
     constructor(
         private org: OrgService,
         private net: NetService,
-        public patronService: PatronService,
+        private patronService: PatronService,
         public context: PatronContextService
     ) {}
 
     ngOnInit() {
     }
 
-    changeFields(field: FieldOptions) {
+    changeFields(field: EditorFieldOptions) {
         this.showFields = field;
-        this.showFieldsChanged.emit(field);
+        this.context.showFieldsChanged.emit(field);
     }
 }
 
index 57558f8..3952dda 100644 (file)
     </div>
   </div>
 
-  <div class="alert alert-success p-2 m-3 d-flex">
+  <div class="alert alert-success pt-2 pb-2 mt-3 mb-3 d-flex">
     <div class="m-auto font-weight-bold" i18n>User Settings</div>
   </div>
 
     </div>
   </ng-container>
 
-  <div class="alert alert-success p-2 m-3 d-flex">
+  <div class="alert alert-success pt-2 pb-2 mt-3 mb-3 d-flex">
     <div class="m-auto font-weight-bold" i18n>Addresses</div>
   </div>
 
 
   <button class="btn btn-success" (click)="newAddr()" i18n>New Address</button>
 
-  <div class="alert alert-success p-2 m-3 d-flex">
+  <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>
 
    </div>
   </div>
 
-  <div class="alert alert-success p-2 m-3 d-flex">
+  <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 e96149d..ee0676d 100644 (file)
@@ -8,7 +8,7 @@ import {NetService} from '@eg/core/net.service';
 import {AuthService} from '@eg/core/auth.service';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {PatronService} from '@eg/staff/share/patron/patron.service';
-import {PatronContextService} from './patron.service';
+import {PatronContextService, EditorFieldOptions} from './patron.service';
 import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.component';
 import {DateUtil} from '@eg/share/util/date';
 import {ProfileSelectComponent} from '@eg/staff/share/patron/profile-select.component';
@@ -70,6 +70,7 @@ export class EditComponent implements OnInit {
 
     autoId = -1;
     patron: IdlObject;
+    modifiedPatron: IdlObject;
     changeHandlerNeeded = false;
     nameTab = 'primary';
     loading = false;
@@ -86,6 +87,7 @@ export class EditComponent implements OnInit {
     optInSettingTypes: {[name: string]: IdlObject} = {};
     secondaryGroups: IdlObject[];
     expireDate: Date;
+    visibleFields: EditorFieldOptions;
 
     // All locations we have the specified permissions
     permOrgs: {[name: string]: number[]};
@@ -112,6 +114,13 @@ export class EditComponent implements OnInit {
     ) {}
 
     ngOnInit() {
+
+        // Listen for edit-toolbar events
+        this.context.saveClicked.subscribe(_ => this.save());
+        this.context.saveCloneClicked.subscribe(_ => this.saveClone());
+        this.context.printClicked.subscribe(_ => this.printPatron());
+        this.context.showFieldsChanged.subscribe(f => this.visibleFields = f);
+
         this.load();
     }
 
@@ -301,7 +310,8 @@ export class EditComponent implements OnInit {
                 cboxEntry = {
                     id: null,
                     freetext: true,
-                    label: map.stat_cat_entry()
+                    label: map.stat_cat_entry(),
+                    fm: map
                 };
 
                 stat.entries.unshift(cboxEntry);
@@ -316,6 +326,7 @@ export class EditComponent implements OnInit {
         patron.isnew(true);
         patron.addresses([]);
         patron.settings([]);
+        patron.waiver_entries([]);
 
         const card = this.idl.create('ac');
         card.isnew(true);
@@ -353,7 +364,26 @@ export class EditComponent implements OnInit {
 
     userStatCatChange(cat: IdlObject, entry: ComboboxEntry) {
         // TODO: set dirty
-        // 2-way binding at work, no need to set the value
+
+        let map = this.patron.stat_cat_entries()
+            .filter(m => m.stat_cat() === cat.id())[0];
+
+        if (map) {
+            if (entry) {
+                map.stat_cat_entry(entry.label);
+                map.ischanged(true);
+                map.isdeleted(false);
+            } else {
+                map.isdeleted(true);
+            }
+        } else {
+            map = this.idl.create('actscecm');
+            map.isnew(true);
+            map.stat_cat(cat.id());
+            map.stat_cat_entry(entry.label);
+            map.target_usr(this.patronId);
+            this.patron.stat_cat_entries().push(map);
+        }
     }
 
     userSettingChange(name: string, value: any) {
@@ -403,7 +433,9 @@ export class EditComponent implements OnInit {
 
         // TODO: set dirty
 
+        const obj = this.objectFromPath(path, index);
         const value = this.getFieldValue(path, index, field);
+        obj.ischanged(true); // isnew() supersedes
 
         console.debug(
             `Modifying field path=${path || ''} field=${field} value=${value}`);
@@ -638,6 +670,85 @@ export class EditComponent implements OnInit {
     nonDeletedAddresses(): IdlObject[] {
         return this.patron.addresses().filter(a => !a.isdeleted());
     }
+
+    save(): Promise<any> {
+
+        // TODO clear unload prompt
+
+        this.loading = true;
+        return this.saveUser()
+        .then(_ => this.saveUserSettings())
+        .then(_ => this.postSaveRedirect())
+    }
+
+    postSaveRedirect() {
+        window.location.href = window.location.href;
+    }
+
+    saveClone() {
+        // TODO
+    }
+
+    // Resolves on success, rejects on error
+    saveUser(): Promise<IdlObject> {
+        this.modifiedPatron = null;
+
+        return this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.patron.update',
+            this.auth.token(), this.patron
+        ).toPromise().then(result => {
+
+            if (result && result.classname) {
+                // Successful result returns the patron IdlObject.
+                return this.modifiedPatron = result;
+            }
+
+            const evt = this.evt.parse(result);
+
+            if (evt) {
+                console.error('Patron update failed with', evt);
+                if (evt.textcode === 'XACT_COLLISION') {
+                    // TODO alert
+                }
+            }
+
+            alert('Patron update failed:' + result);
+
+            return Promise.reject('Save Failed');
+        });
+    }
+
+    // Resolves on success, rejects on error
+    saveUserSettings(): Promise<any> {
+
+        let settings: any = {};
+
+        if (this.patronId) {
+            // Update all user editor setting values for existing
+            // users regardless of whether a value changed.
+            settings = this.userSettings;
+
+        } else {
+
+            // Create settings for all non-null setting values for new patrons.
+            this.userSettings.forEach( (val, key) => {
+                if (val !== null) settings[key] = val;
+            });
+        }
+
+        if (Object.keys(settings).length == 0) { return Promise.resolve(); }
+
+        return this.net.request(
+            'open-ils.actor',
+            'open-ils.actor.patron.settings.update',
+            this.auth.token(), this.modifiedPatron.id(), settings
+        ).toPromise();
+    }
+
+    printPatron() {
+        // TODO
+    }
 }
 
 
index f8fd34e..7508dc8 100644 (file)
@@ -1,4 +1,4 @@
-import {Injectable} from '@angular/core';
+import {Injectable, EventEmitter} from '@angular/core';
 import {IdlObject} from '@eg/core/idl.service';
 import {NetService} from '@eg/core/net.service';
 import {OrgService} from '@eg/core/org.service';
@@ -8,6 +8,9 @@ import {PatronSearch} from '@eg/staff/share/patron/search.component';
 import {StoreService} from '@eg/core/store.service';
 import {CircService, CircDisplayInfo} from '@eg/staff/share/circ/circ.service';
 
+export type EditorFieldOptions = 'required' | 'suggested' | 'all';
+
+
 export interface BillGridEntry extends CircDisplayInfo {
     xact: IdlObject; // mbt
     billingLocation?: string;
@@ -107,6 +110,15 @@ export class PatronContextService {
     // These should persist tab changes
     checkouts: CircGridEntry[] = [];
 
+    // Emitted by the patron edit toolbar, which is kept as a
+    // separate component so it can be positioned differently.
+    // We just act as a go-between.
+    saveClicked: EventEmitter<void> = new EventEmitter<void>();
+    saveCloneClicked: EventEmitter<void> = new EventEmitter<void>();
+    printClicked: EventEmitter<void> = new EventEmitter<void>();
+    showFieldsChanged: EventEmitter<EditorFieldOptions> =
+        new EventEmitter<EditorFieldOptions>();
+
     constructor(
         private store: StoreService,
         private net: NetService,