volcopy detect saveable state
authorBill Erickson <berickxx@gmail.com>
Thu, 23 Jul 2020 18:03:23 +0000 (14:03 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 23 Jul 2020 18:03:23 +0000 (14:03 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/volcopy/copy-attrs.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/vol-edit.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.html
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.component.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.service.ts
Open-ILS/src/eg2/src/app/staff/cat/volcopy/volcopy.ts
Open-ILS/src/eg2/src/app/staff/share/holdings/copy-alerts-dialog.component.html
Open-ILS/src/eg2/src/app/staff/share/holdings/copy-alerts-dialog.component.ts

index 25743a9..0534ac9 100644 (file)
@@ -1,5 +1,5 @@
 import {Component, Input, OnInit, AfterViewInit, ViewChild,
-    QueryList, ViewChildren} from '@angular/core';
+    EventEmitter, Output, QueryList, ViewChildren} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {SafeUrl} from '@angular/platform-browser';
 import {tap} from 'rxjs/operators';
@@ -81,6 +81,9 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
     @ViewChildren(BatchItemAttrComponent)
         batchAttrs: QueryList<BatchItemAttrComponent>;
 
+    // Emitted when the save-ability of this form changes.
+    @Output() canSaveChange: EventEmitter<boolean> = new EventEmitter<boolean>();
+
     constructor(
         private router: Router,
         private route: ActivatedRoute,
@@ -116,6 +119,7 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
         this.fineLevelLabelMap[1] = this.fineLevelLow.text;
         this.fineLevelLabelMap[2] = this.fineLevelNormal.text;
         this.fineLevelLabelMap[3] = this.fineLevelHigh.text;
+
     }
 
     statCats(): IdlObject[] {
@@ -262,23 +266,27 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
         }
 
         if (field === 'owning_lib') {
-            return this.owningLibChanged(value, changeSelection);
-        }
+            this.owningLibChanged(value, changeSelection);
 
-        this.context.copyList().forEach(copy => {
-            if (!copy[field] || copy[field]() === value) { return; }
+        } else {
 
-            // Change selection indicates which items should be modified
-            // based on the display value for the selected field at
-            // time of editing.
-            if (changeSelection &&
-                !this.copyWantsChange(copy, field, changeSelection)) {
-                return;
-            }
+            this.context.copyList().forEach(copy => {
+                if (!copy[field] || copy[field]() === value) { return; }
 
-            copy[field](value);
-            copy.ischanged(true);
-        });
+                // Change selection indicates which items should be modified
+                // based on the display value for the selected field at
+                // time of editing.
+                if (changeSelection &&
+                    !this.copyWantsChange(copy, field, changeSelection)) {
+                    return;
+                }
+
+                copy[field](value);
+                copy.ischanged(true);
+            });
+        }
+
+        this.emitSaveChange();
     }
 
     owningLibChanged(orgId: number, changeSelection?: BatchChangeSelection) {
@@ -392,7 +400,8 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
 
     openCopyAlerts() {
         this.copyAlertsDialog.inPlaceMode = true;
-        this.copyAlertsDialog.mode = 'create';
+        this.copyAlertsDialog.copyIds = this.context.copyList().map(c => c.id());
+
         this.copyAlertsDialog.open({size: 'lg'}).subscribe(
             newAlert => {
                 if (newAlert) {
@@ -442,8 +451,6 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
 
         this.store.setLocalItem('cat.copy.last_template', entry.id);
 
-        // TODO: handle owning_lib
-
         const template = this.volcopy.templates[entry.id];
 
         Object.keys(template).forEach(field => {
@@ -590,6 +597,18 @@ export class CopyAttrsComponent implements OnInit, AfterViewInit {
         }
         return true;
     }
+
+    emitSaveChange() {
+
+        // Timeout allows the digest cycle which created the change to complete.
+        setTimeout(() => {
+
+            const canSave = this.batchAttrs.filter(
+                attr => attr.warnOnRequired()).length  === 0;
+
+            this.canSaveChange.emit(canSave)
+        });
+    }
 }
 
 
index 35ee7db..60c52a6 100644 (file)
@@ -1,4 +1,4 @@
-import {Component, OnInit, AfterViewInit, ViewChild, Input, Renderer2} from '@angular/core';
+import {Component, OnInit, AfterViewInit, ViewChild, Input, Renderer2, Output, EventEmitter} from '@angular/core';
 import {Router, ActivatedRoute, ParamMap} from '@angular/router';
 import {tap} from 'rxjs/operators';
 import {IdlService, IdlObject} from '@eg/core/idl.service';
@@ -26,7 +26,7 @@ export class VolEditComponent implements OnInit {
     // There are 10 columns in the editor form.  Set the flex values
     // here so they don't have to be hard-coded and repeated in the
     // markup.  Changing a flex value here will propagate to all
-    // rows in the form.
+    // rows in the form.  Column numbers are 1-based.
     flexSettings: {[column: number]: number} = {
         1: 1, 2: 1, 3: 2, 4: 1, 5: 2, 6: 1, 7: 1, 8: 2, 9: 1, 10: 1};
 
@@ -53,6 +53,9 @@ export class VolEditComponent implements OnInit {
     @ViewChild('confirmDelCopy', {static: false})
         confirmDelCopy: ConfirmDialogComponent;
 
+    // Emitted when the save-ability of this form changes.
+    @Output() canSaveChange: EventEmitter<boolean> = new EventEmitter<boolean>();
+
     constructor(
         private renderer: Renderer2,
         private idl: IdlService,
@@ -224,6 +227,8 @@ export class VolEditComponent implements OnInit {
             vol[key](value);
             vol.ischanged(true);
         }
+
+        this.emitSaveChange();
     }
 
     applyCopyValue(copy: IdlObject, key: string, value: any) {
@@ -360,7 +365,12 @@ export class VolEditComponent implements OnInit {
         copy.ischanged(true);
         copy._dupe_barcode = false;
 
-        if (barcode && !this.autoBarcodeInProgress) {
+        if (!barcode) {
+            this.emitSaveChange();
+            return;
+        }
+
+        if (!this.autoBarcodeInProgress) {
             // Manual barcode entry requires dupe check
 
             copy._dupe_barcode = false;
@@ -368,9 +378,13 @@ export class VolEditComponent implements OnInit {
                 deleted: 'f',
                 barcode: barcode,
                 id: {'!=': copy.id()}
-            }).subscribe(resp => {
-                if (resp) { copy._dupe_barcode = true; }
-            });
+            }).subscribe(
+                resp => {
+                    if (resp) { copy._dupe_barcode = true; }
+                },
+                err => {},
+                () => this.emitSaveChange()
+            );
         }
     }
 
@@ -487,5 +501,33 @@ export class VolEditComponent implements OnInit {
         this.volcopy.defaults.values.use_checkdigit = this.useCheckdigit === true;
         this.volcopy.saveDefaults();
     }
+
+    canSave(): boolean {
+
+        const copies = this.context.copyList();
+
+        const badCopies = copies.filter(copy => {
+            return copy._dupe_barcode || (!copy.isnew() && !copy.barcode());
+        }).length > 0;
+
+        if (badCopies) { return false; }
+
+        const badVols = this.context.volNodes().filter(volNode => {
+            const vol = volNode.target;
+            return !(
+                vol.prefix() && vol.label() && vol.suffix && vol.label_class()
+            );
+        }).length > 0;
+
+        return !badVols;
+    }
+
+    emitSaveChange() {
+
+        // Timeout allows the digest cycle which created the change to complete.
+        setTimeout(() => {
+            this.canSaveChange.emit(this.canSave());
+        });
+    }
 }
 
index e6a1379..be7ff28 100644 (file)
     <li ngbNavItem="holdings">
       <a ngbNavLink i18n>Holdings</a>
       <ng-template ngbNavContent>
-        <div class="mt-2"><eg-vol-edit [context]="context"></eg-vol-edit></div>
+        <div class="mt-2">
+          <eg-vol-edit [context]="context"
+            (canSaveChange)="volsCanSave = $event"></eg-vol-edit>
+        </div>
         <ng-container *ngIf="volcopy.defaults.values.unified_display">
-          <div class="mt-2"><eg-copy-attrs [context]="context"></eg-copy-attrs></div>
+          <div class="mt-2">
+            <eg-copy-attrs [context]="context" 
+              (canSaveChange)="attrsCanSave = $event"></eg-copy-attrs>
+          </div>
         </ng-container>
       </ng-template>
     </li>
       <li ngbNavItem="attrs">
         <a ngbNavLink i18n>Item Attributes</a>
         <ng-template ngbNavContent>
-          <div class="mt-2"><eg-copy-attrs [context]="context"></eg-copy-attrs></div>
+          <div class="mt-2">
+            <eg-copy-attrs [context]="context"
+              (canSaveChange)="attrsCanSave = $event"></eg-copy-attrs>
+          </div>
         </ng-template>
       </li>
     </ng-container>
             i18n>Print Labels?</label>                                             
         </div>
         <div class="flex-1"> </div>
-        <button class="btn btn-outline-dark" (click)="save()" i18n>Save</button>
-        <button class="btn btn-outline-dark ml-2" 
-          (click)="save(true)" i18n>Save &amp; Exit</button>
+        <button class="btn btn-outline-dark" (click)="save()" 
+          [ngClass]="{'border-danger': isNotSaveable()}"
+          [disabled]="isNotSaveable()" i18n>Save</button>
+        <button class="btn btn-outline-dark ml-2" (click)="save(true)"
+          [ngClass]="{'border-danger': isNotSaveable()}"
+          [disabled]="isNotSaveable()" i18n>Save &amp; Exit</button>
       </div>
     </div>
   </ng-container>
index 1d15c9c..f782eff 100644 (file)
@@ -58,6 +58,9 @@ export class VolCopyComponent implements OnInit {
     target: string;   // item | callnumber | record | session
     targetId: string; // id value or session string
 
+    volsCanSave = true;
+    attrsCanSave = true;
+
     constructor(
         private router: Router,
         private route: ActivatedRoute,
@@ -195,10 +198,6 @@ export class VolCopyComponent implements OnInit {
 
             this.context.recordId = editSession.record_id;
 
-            // These are currently ignored, since visibility is tab-based
-            this.context.hideVols = editSession.hide_vols === true;
-            this.context.hideCopies = editSession.hide_copies === true;
-
             if (editSession.copies && editSession.copies.length > 0) {
                 return this.fetchCopies(editSession.copies);
             }
@@ -467,6 +466,10 @@ export class VolCopyComponent implements OnInit {
             setTimeout(() => window.open(url, '_blank'));
         });
     }
+
+    isNotSaveable(): boolean {
+        return !(this.volsCanSave && this.attrsCanSave);
+    }
 }
 
 
index 855ba58..6d351cf 100644 (file)
@@ -15,8 +15,6 @@ import {ComboboxComponent, ComboboxEntry} from '@eg/share/combobox/combobox.comp
 
 /* Managing volcopy data */
 
-
-
 interface VolCopyDefaults {
     values: {[field: string]: any};
     hidden: {[field: string]: boolean};
index b47b59d..c76e786 100644 (file)
@@ -45,9 +45,6 @@ export class VolCopyContext {
     volsToDelete: IdlObject[] = [];
     copiesToDelete: IdlObject[] = [];
 
-    hideVols: boolean;
-    hideCopies: boolean;
-
     reset() {
         this.holdings = new HoldingsTree();
         this.volsToDelete = [];
@@ -192,12 +189,7 @@ export class VolCopyContext {
             o1.target.shortname() < o2.target.shortname() ? -1 : 1);
     }
 
-    // Changes pending and no unresolved issues.
-    isSaveable(): boolean {
-        const dupeBc = this.copyList().filter(c => c._dupe_barcode).length;
-
-        if (dupeBc) { return false; }
-
+    changesPending(): boolean {
         const modified = (o: IdlObject): boolean => {
             return o.isnew() || o.ischanged() || o.isdeleted();
         };
index 25fe919..5ccf7ff 100644 (file)
@@ -5,7 +5,7 @@
   <div class="modal-header">
     <h4 class="modal-title">
       <ng-container *ngIf="mode == 'create'">
-        <span i18n>Adding alerts for {{copies.length}} item(s).</span>
+        <span i18n>Adding alerts for {{copyIds.length}} item(s).</span>
       </ng-container>
       <ng-container *ngIf="mode == 'manage'">
         <span i18n>Managing alerts for item {{copies[0].barcode()}}</span>
index 06951b8..0a55f79 100644 (file)
@@ -25,21 +25,11 @@ import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
 export class CopyAlertsDialogComponent
     extends DialogComponent implements OnInit {
 
-    _copyIds: number[];
-    @Input() set copyIds(ids: number[]) {
-        this._copyIds = [].concat(ids);
-    }
-    get copyIds(): number[] {
-        return this._copyIds;
-    }
+    // If there are multiple copyIds, only new alerts may be applied.
+    // If there is only one copyId, then tags may be applied or removed.
+    @Input() copyIds: number[] = [];
 
-    _mode: string; // create | manage
-    @Input() set mode(m: string) {
-        this._mode = m;
-    }
-    get mode(): string {
-        return this._mode;
-    }
+    mode: string; // create | manage
 
     // If true, no attempt is made to save the new alerts to the
     // database.  It's assumed this takes place in the calling code.
@@ -50,6 +40,7 @@ export class CopyAlertsDialogComponent
     copies: IdlObject[];
     // In 'manage' mode we only handle a single copy.
     copy: IdlObject;
+
     alertTypes: ComboboxEntry[];
     newAlert: IdlObject;
     changesMade: boolean;
@@ -88,13 +79,11 @@ export class CopyAlertsDialogComponent
         }
 
         // In manage mode, we can only manage a single copy.
-        // But in create mode, we can add alerts to multiple copies.
-
-        if (this.mode === 'manage') {
-            if (this.copyIds.length > 1) {
-                console.warn('Attempt to manage alerts for multiple copies.');
-                this.copyIds = [this.copyIds[0]];
-            }
+        // But in create mode, we can add tags to multiple copies.
+        if (this.copyIds.length === 1 && !this.inPlaceMode) {
+            this.mode = 'manage';
+        } else {
+            this.mode = 'create';
         }
 
         // Observerify data loading
@@ -109,9 +98,8 @@ export class CopyAlertsDialogComponent
     }
 
     getAlertTypes(): Promise<any> {
-        if (this.alertTypes) {
-            return Promise.resolve();
-        }
+        if (this.alertTypes) { return Promise.resolve(); }
+
         return this.pcrud.retrieveAll('ccat',
         {   active: true,
             scope_org: this.org.ancestors(this.auth.user().ws_ou(), true)
@@ -122,7 +110,6 @@ export class CopyAlertsDialogComponent
     }
 
     getCopies(): Promise<any> {
-
         if (this.inPlaceMode) { return Promise.resolve(); }
 
         return this.pcrud.search('acp', {id: this.copyIds}, {}, {atomic: true})