LP 1855781 fixup
authorJason Etheridge <jason@EquinoxOLI.org>
Wed, 14 Dec 2022 21:45:10 +0000 (16:45 -0500)
committerJane Sandberg <js7389@princeton.edu>
Wed, 3 May 2023 12:52:42 +0000 (05:52 -0700)
* put out some fires after rebasing and smoketesting
* persistKey to enable Save Grid Settings
* disable Edit Selected action unless exactly one row is selected
* allow for the explicit setting of null for certain eligible fields in fmEditor
* support appending custom templates to fields in fmEditor
* simplify/robustify the insertion of dividers in fmEditor here with the new appendTemplate fmOption
* i18nize this helpText
* style this header the same way as the fmEditor
* required property seems to work for eg-org-select
  well, just the styling.  Could maybe replace eg-org-select with eg-org-family-select which has some form smarts
* release notes

Additional tweaks since the last pullrequest:

* replacing checkboxes with eg-bool-select in fm-editor
* required and validation for eg-bool-select
* Change Copy Location to Shelving Location in the IDL

    If we wanted to catch every instance of Copy Location, we
    could do something like

    ack -li 'copy location' > /tmp/list.txt
    # vim /tmp/list.txt # some care needed with pruning the list. Worthy of its own ticket and commit, IMO
    for x in `cat /tmp/list.txt` ; do sed -i 's/copy location/Shelving Location/gi' $x ; done

* fix a typo and relabel the new policy button
* relabel some more fields in the IDL

    "Copy Circ Lib" and "Copy Owning Lib" become "Item Circ Library" and "Item Owning Library", respectively.

* Org Unit becomes Checkout Library for the circ matrix.  Checkout is more prevalent in the code than Check Out, but we should pick one.
* grid filters and some cruft removal
* org selector. We should really find a way to leverage the AdminPageComponent here rather than crib code from it.

Signed-off-by: Jason Etheridge <jason@EquinoxOLI.org>
Signed-off-by: Jane Sandberg <js7389@princeton.edu>
15 files changed:
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.css [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/share/common-widgets.module.ts
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html
Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.component.ts
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module.ts
Open-ILS/src/eg2/src/app/staff/admin/local/circ_matrix_matchpoint/linked-circ-limit-sets.component.html
Open-ILS/src/eg2/src/app/staff/admin/local/routing.module.ts
Open-ILS/src/sql/Pg/950.data.seed-values.sql
Open-ILS/src/sql/Pg/upgrade/XXXX.data.circPolicy-grid-ws-settings.sql [new file with mode: 0644]
docs/RELEASE_NOTES_NEXT/miscellaneous.adoc

index 949ed2b..26313b8 100644 (file)
@@ -2060,12 +2060,12 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             <field reporter:label="Name" name="name" reporter:datatype="text"/>
             <field reporter:label="Renewal?" name="is_renewal" reporter:datatype="float"/>
             <field reporter:label="Org Unit" name="org_unit" reporter:datatype="float"/>
-            <field reporter:label="Copy Circ Lib" name="copy_circ_lib" reporter:datatype="float"/>
-            <field reporter:label="Copy Owning Lib" name="copy_owning_lib" reporter:datatype="float"/>
+            <field reporter:label="Item Circ Library" name="copy_circ_lib" reporter:datatype="float"/>
+            <field reporter:label="Item Owning Library" name="copy_owning_lib" reporter:datatype="float"/>
             <field reporter:label="User Home Lib" name="user_home_ou" reporter:datatype="float"/>
             <field reporter:label="Permission Group" name="grp" reporter:datatype="float"/>
             <field reporter:label="Circulation Modifier" name="circ_modifier" reporter:datatype="float"/>
-            <field reporter:label="Copy Location" name="copy_location" reporter:datatype="float"/>
+            <field reporter:label="Shelving Location" name="copy_location" reporter:datatype="float"/>
             <field reporter:label="MARC Type" name="marc_type" reporter:datatype="float"/>
             <field reporter:label="MARC Form" name="marc_form" reporter:datatype="float"/>
             <field reporter:label="MARC Bib Level" name="marc_bib_level" reporter:datatype="float"/>
@@ -2167,14 +2167,14 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                <fields oils_persist:primary="id" oils_persist:sequence="config.circ_matrix_matchpoint_id_seq">
                        <field reporter:label="Matchpoint ID" name="id" reporter:datatype="id"/>
                        <field reporter:label="Renewal?" name="is_renewal" reporter:datatype="bool"/>
-                       <field reporter:label="Active?" name="active" reporter:datatype="bool"/>
-                       <field reporter:label="Org Unit" name="org_unit" reporter:datatype="org_unit" oils_obj:required="true"/>
-                       <field reporter:label="Copy Circ Lib" name="copy_circ_lib" reporter:datatype="org_unit"/>
-                       <field reporter:label="Copy Owning Lib" name="copy_owning_lib" reporter:datatype="org_unit"/>
+                       <field reporter:label="Active?" name="active" reporter:datatype="bool" oils_obj:required="true"/>
+                       <field reporter:label="Checkout Library" name="org_unit" reporter:datatype="org_unit" oils_obj:required="true"/>
+                       <field reporter:label="Item Circ Library" name="copy_circ_lib" reporter:datatype="org_unit"/>
+                       <field reporter:label="Item Owning Library" name="copy_owning_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="User Home Lib" name="user_home_ou" reporter:datatype="org_unit"/>
                        <field reporter:label="Permission Group" name="grp" reporter:datatype="link" oils_obj:required="true"/>
                        <field reporter:label="Circulation Modifier" name="circ_modifier" oils_persist:primitive="string" reporter:datatype="link"/>
-                       <field reporter:label="Copy Location" name="copy_location" reporter:datatype="link"/>
+                       <field reporter:label="Shelving Location" name="copy_location" reporter:datatype="link"/>
                        <field reporter:label="MARC Type" name="marc_type" oils_persist:primitive="string" reporter:datatype="link"/>
                        <field reporter:label="MARC Form" name="marc_form" oils_persist:primitive="string" reporter:datatype="link"/>
                        <field reporter:label="MARC Bib Level" name="marc_bib_level" oils_persist:primitive="string" reporter:datatype="link"/>
@@ -2195,7 +2195,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
                        <field name="total_copy_hold_ratio" reporter:datatype="float" reporter:label="Minimum Total Copy/Hold Ratio"/>
                        <field name="available_copy_hold_ratio" reporter:datatype="float" reporter:label="Minimum Available Copy/Hold Ratio"/>
                        <field name="description" reporter:datatype="text" reporter:label="Description"/>
-                       <field name="renew_extends_due_date" reporter:datatype="bool" reporter:label="Early Renewal Extends Due Date"/>
+                       <field name="renew_extends_due_date" reporter:datatype="bool" reporter:label="Early Renewal Extends Due Date" oils_obj:required="true"/>
                        <field name="renew_extend_min_interval" reporter:datatype="interval" reporter:label="Early Renewal Minimum Duration Interval"/>
                </fields>
                <links>
@@ -2318,11 +2318,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
             </actions>
         </permacrud>
     </class>
-    <class id="cclsacpl" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::circ_limit_set_copy_loc_map" oils_persist:tablename="config.circ_limit_set_copy_loc_map" reporter:label="Circulation Limit Set Copy Location Map">
+    <class id="cclsacpl" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="config::circ_limit_set_copy_loc_map" oils_persist:tablename="config.circ_limit_set_copy_loc_map" reporter:label="Circulation Limit Set Shelving Location Map">
         <fields oils_persist:primary="id" oils_persist:sequence="config.circ_limit_set_copy_loc_map_id_seq">
             <field reporter:label="ID" name="id" reporter:datatype="id"/>
             <field reporter:label="Limit Set" name="limit_set" reporter:datatype="link"/>
-            <field reporter:label="Copy Location" name="copy_loc" reporter:datatype="link"/>
+            <field reporter:label="Shelving Location" name="copy_loc" reporter:datatype="link"/>
         </fields>
         <links>
             <link field="limit_set" reltype="has_a" key="id" map="" class="ccls"/>
@@ -5333,7 +5333,7 @@ SELECT  usr,
                        <field reporter:label="Patron Birth Year" name="usr_birth_year" reporter:datatype="int"/>
                        <field reporter:label="Call Number" name="copy_call_number" reporter:datatype="link"/>
                        <field reporter:label="Shelving Location" name="copy_location" reporter:datatype="link"/>
-                       <field reporter:label="Copy Owning Library" name="copy_owning_lib" reporter:datatype="link"/>
+                       <field reporter:label="Item Owning Library" name="copy_owning_lib" reporter:datatype="link"/>
                        <field reporter:label="Copy Circulating Library" name="copy_circ_lib" reporter:datatype="link"/>
                        <field reporter:label="Bib Record" name="copy_bib_record" reporter:datatype="link"/>
                        <field reporter:label="Archived Patron Stat-Cat Entries" name="aaactsc_entries" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -5423,7 +5423,7 @@ SELECT  usr,
                        <field reporter:label="Patron Birth Year" name="usr_birth_year" reporter:datatype="int"/>
                        <field reporter:label="Call Number" name="copy_call_number" reporter:datatype="link"/>
                        <field reporter:label="Shelving Location" name="copy_location" reporter:datatype="link"/>
-                       <field reporter:label="Copy Owning Library" name="copy_owning_lib" reporter:datatype="link"/>
+                       <field reporter:label="Item Owning Library" name="copy_owning_lib" reporter:datatype="link"/>
                        <field reporter:label="Copy Circulating Library" name="copy_circ_lib" reporter:datatype="link"/>
                        <field reporter:label="Bib Record" name="copy_bib_record" reporter:datatype="link"/>
                        <field reporter:label="Archived Patron Stat-Cat Entries" name="aaactsc_entries" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -5831,7 +5831,7 @@ SELECT  usr,
                        <field reporter:label="Name" name="name"  reporter:datatype="text" oils_persist:i18n="true"/>
                        <field reporter:label="Is OPAC Visible?" name="opac_visible" reporter:datatype="bool"/>
                        <field reporter:label="Owning Org Unit" name="owning_lib"  reporter:datatype="org_unit"/>
-                       <field reporter:label="Copy Location Orders" name="orders" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Shelving Location Orders" name="orders" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Copies" name="copies" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Label Prefix" name="label_prefix"  reporter:datatype="text" oils_persist:i18n="true"/>
                        <field reporter:label="Label Suffix" name="label_suffix"  reporter:datatype="text" oils_persist:i18n="true"/>
@@ -5862,7 +5862,7 @@ SELECT  usr,
                        <field reporter:label="Owning Library" name="owner" reporter:datatype="org_unit" />
             <field reporter:label="Position" name="pos" reporter:datatype="int"/>
             <field reporter:label="Display Above Orgs" name="top" reporter:datatype="bool"/>
-            <field reporter:label="Copy Location Mappings" name="location_maps" oils_persist:virtual="true" reporter:datatype="link"/>
+            <field reporter:label="Shelving Location Mappings" name="location_maps" oils_persist:virtual="true" reporter:datatype="link"/>
                </fields>
                <links>
                        <link field="owner" reltype="has_a" key="id" map="" class="aou"/>
@@ -5881,7 +5881,7 @@ SELECT  usr,
                <fields oils_persist:primary="id" oils_persist:sequence="asset.copy_location_group_map_id_seq">
                        <field reporter:label="ID" name="id" reporter:selector="name" reporter:datatype="id"/>
             <field reporter:label="Group" name="lgroup" reporter:datatype="link"/>
-            <field reporter:label="Copy Location" name="location" reporter:datatype="link"/>
+            <field reporter:label="Shelving Location" name="location" reporter:datatype="link"/>
                </fields>
                <links>
                        <link field="lgroup" reltype="has_a" key="id" map="" class="acplg"/>
@@ -6637,11 +6637,11 @@ SELECT  usr,
        <class id="aoupa" controller="open-ils.cstore open-ils.pcrud" oils_obj:fieldmapper="actor::org_unit_proximity_adjustment" oils_persist:tablename="actor.org_unit_proximity_adjustment" reporter:label="Org Unit Proximity Adjustment">
                <fields oils_persist:primary="id" oils_persist:sequence="actor.org_unit_proximity_adjustment_id_seq">
                        <field name="id" reporter:label="ID" reporter:datatype="id" />
-                       <field name="item_circ_lib" reporter:label="Item Circ Lib" reporter:datatype="org_unit"/>
-                       <field name="item_owning_lib" reporter:label="Item Owning Lib" reporter:datatype="org_unit"/>
+                       <field name="item_circ_lib" reporter:label="Item Circ Library" reporter:datatype="org_unit"/>
+                       <field name="item_owning_lib" reporter:label="Item Owning Library" reporter:datatype="org_unit"/>
                        <field name="hold_pickup_lib" reporter:label="Hold Pickup Lib" reporter:datatype="org_unit"/>
                        <field name="hold_request_lib" reporter:label="Hold Request Lib" reporter:datatype="org_unit"/>
-                       <field name="copy_location" reporter:label="Copy Location" reporter:datatype="link"/>
+                       <field name="copy_location" reporter:label="Shelving Location" reporter:datatype="link"/>
                        <field name="circ_mod" reporter:label="Circ Modifier" reporter:datatype="link"/>
                        <field name="pos" reporter:label="Position" reporter:datatype="int" oils_obj:required="true" />
                        <field name="absolute_adjustment" reporter:label="Absolute adjustment?" reporter:datatype="bool" />
@@ -7005,7 +7005,7 @@ SELECT  usr,
                        <field reporter:label="Notes" name="notes" reporter:datatype="link" oils_persist:virtual="true"/>
                        <field reporter:label="Current Shelf Lib" name="current_shelf_lib" reporter:datatype="org_unit"/>
                        <field reporter:label="Acquisition Request" name="acq_request" reporter:datatype="link" />
-                       <field reporter:label="Copy Location Sort Order" name="copy_location_order_position" reporter:datatype="int" />
+                       <field reporter:label="Shelving Location Sort Order" name="copy_location_order_position" reporter:datatype="int" />
                        <field reporter:label="User First Given Name" name="usr_first_given_name" reporter:datatype="text" />
                        <field reporter:label="User Second Given Name" name="usr_second_given_name" reporter:datatype="text" />
                        <field reporter:label="User Family Name" name="usr_family_name" reporter:datatype="text" />
@@ -7278,7 +7278,7 @@ SELECT  usr,
                        <field reporter:label="Checkins" name="checkins" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Workstations" name="workstations" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Fund Allocation Percentages" name="fund_alloc_pcts" oils_persist:virtual="true" reporter:datatype="link"/>
-                       <field reporter:label="Copy Location Orders" name="copy_location_orders" oils_persist:virtual="true" reporter:datatype="link"/>
+                       <field reporter:label="Shelving Location Orders" name="copy_location_orders" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Transit Copy Prev Destinations" name="atc_prev_dests" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Reservation Requests" name="resv_requests" oils_persist:virtual="true" reporter:datatype="link"/>
                        <field reporter:label="Reservation Pickups" name="resv_pickups" oils_persist:virtual="true" reporter:datatype="link"/>
@@ -8224,8 +8224,8 @@ SELECT  usr,
                        <field reporter:label="State" name="state" reporter:datatype="text"/>
                        <field reporter:label="Event" name="event" reporter:datatype="text" />
                        <field reporter:label="During Renewal" name="in_renew" reporter:datatype="bool" />
-                       <field reporter:label="Allow At Copy Circ Lib" name="at_circ" reporter:datatype="bool"/>
-                       <field reporter:label="Allow At Copy Owning Lib" name="at_owning" reporter:datatype="bool"/>
+                       <field reporter:label="Allow At Item Circ Library" name="at_circ" reporter:datatype="bool"/>
+                       <field reporter:label="Allow At Item Owning Library" name="at_owning" reporter:datatype="bool"/>
                        <field reporter:label="Invert allowed locations" name="invert_location" reporter:datatype="bool"/>
                        <field reporter:label="Next Statuses" name="next_status" reporter:datatype="text"/>
                </fields>
@@ -10653,7 +10653,7 @@ SELECT  usr,
                        <field reporter:label="Fund" name="fund" reporter:datatype="link" />
                        <field reporter:label="Fund Debit" name="fund_debit" reporter:datatype="link" />
                        <field reporter:label="Owning Library" name="owning_lib" reporter:datatype="org_unit" />
-                       <field reporter:label="Copy Location" name="location" reporter:datatype="link" />
+                       <field reporter:label="Shelving Location" name="location" reporter:datatype="link" />
                        <field reporter:label="Circ Modifier" name="circ_modifier" reporter:datatype="link" />
                        <field reporter:label="Note" name="note" reporter:datatype="text" />
                        <field reporter:label="Collection Code" name="collection_code" reporter:datatype="text" />
diff --git a/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.css b/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.css
new file mode 100644 (file)
index 0000000..43bf66b
--- /dev/null
@@ -0,0 +1,3 @@
+label {
+  margin-left: 8px;
+}
diff --git a/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.html b/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.html
new file mode 100644 (file)
index 0000000..0b45b6f
--- /dev/null
@@ -0,0 +1,14 @@
+<div *ngFor="let option of options; let i = index">
+  <input
+    type="radio"
+    [name]="name"
+    [value]="option.value"
+    [(ngModel)]="_value"
+    (change)="value = (i === 0)"
+    [checked]="_value === (i === 0)"
+    [required]="required"
+    [disabled]="disabled"
+    [id]="name + '-' + option.value"
+  />
+  <label [for]="name + '-' + option.value">{{ option.label }}</label>
+</div>
diff --git a/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.ts b/Open-ILS/src/eg2/src/app/share/boolean-select/boolean-select.component.ts
new file mode 100644 (file)
index 0000000..1468685
--- /dev/null
@@ -0,0 +1,83 @@
+import { Component, ChangeDetectorRef, forwardRef, OnInit, Input, Output } from '@angular/core';
+import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
+
+@Component({
+  selector: 'eg-bool-select',
+  templateUrl: './boolean-select.component.html',
+  styleUrls: ['./boolean-select.component.css'],
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => BooleanSelectComponent),
+      multi: true
+    }
+  ]
+})
+
+export class BooleanSelectComponent implements ControlValueAccessor {
+
+  @Input() label: string;
+  @Input() name: string;
+  @Input() options: Array<{ label: string; value: any }> = [
+    { label: $localize`Yes`, value: true },
+    { label: $localize`No`, value: false },
+  ];
+  private _value: boolean = false;
+  private _disabled: boolean = false;
+  private _required: boolean = false;
+
+  onChange: (value: boolean) => void = () => {};
+  onTouched: () => void = () => {};
+
+  get value(): boolean {
+    return this._value;
+  }
+
+  set value(value: boolean) {
+    this._value = value;
+    this.onChange(value);
+    this.onTouched();
+  }
+
+  writeValue(value: any): void {
+    this._value = value;
+    this.cdr.detectChanges();
+  }
+
+  registerOnChange(fn: (value: boolean) => void): void {
+    this.onChange = fn;
+  }
+
+  registerOnTouched(fn: () => void): void {
+    this.onTouched = fn;
+  }
+
+  get disabled(): boolean {
+    return this._disabled;
+  }
+
+  set disabled(value: boolean) {
+    this._disabled = value;
+  }
+
+  setDisabledState?(isDisabled: boolean): void {
+    this.disabled = isDisabled;
+  }
+
+  get required(): boolean {
+    return this._required;
+  }
+
+  set required(value: boolean) {
+    this._required = value;
+  }
+
+  setRequiredState?(isRequired: boolean): void {
+    this.required = isRequired;
+  }
+
+  constructor(public cdr: ChangeDetectorRef) {};
+
+  ngOnInit(): void {}
+
+}
index 49fdc7b..b4c0eb4 100644 (file)
@@ -11,6 +11,7 @@ import {EgCoreModule} from '@eg/core/core.module';
 import {ComboboxComponent, IdlClassTemplateDirective} from '@eg/share/combobox/combobox.component';
 import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
 import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
+import {BooleanSelectComponent} from '@eg/share/boolean-select/boolean-select.component';
 import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
 import {DateRangeSelectComponent} from '@eg/share/daterange-select/daterange-select.component';
 import {DateTimeSelectComponent} from '@eg/share/datetime-select/datetime-select.component';
@@ -25,6 +26,7 @@ import {ClipboardDialogComponent} from '@eg/share/clipboard/clipboard-dialog.com
     ComboboxComponent,
     ComboboxEntryComponent,
     DateSelectComponent,
+    BooleanSelectComponent,
     OrgSelectComponent,
     DateRangeSelectComponent,
     DateTimeSelectComponent,
@@ -49,6 +51,7 @@ import {ClipboardDialogComponent} from '@eg/share/clipboard/clipboard-dialog.com
     ComboboxComponent,
     ComboboxEntryComponent,
     DateSelectComponent,
+    BooleanSelectComponent,
     OrgSelectComponent,
     DateRangeSelectComponent,
     DateTimeSelectComponent,
index c0ea3e4..e338e52 100644 (file)
         <span i18n>Dates must be in the correct order</span>
       </div>
 
-      <div class="form-group row" *ngFor="let field of fields">
+      <ng-container *ngFor="let field of fields">
+      <div class="form-group row">
         <div class="col-lg-3">
           <label class="form-label" for="{{idPrefix}}-{{field.name}}">{{field.label}}</label>
           <eg-help-popover [placement]="'right'" *ngIf="field.helpText" helpText="{{field.helpTextValue}}"></eg-help-popover>
+          <ng-container *ngIf="isSafeToNull(field)">
+            <br />(<a (click)="setToNull(field)" href='javascript:;'><span i18n>Unset</span></a>)
+          </ng-container>
         </div>
         <div class="col-lg-9">
 
@@ -94,6 +98,7 @@
                 persistKey="{{field.persistKey}}"
                 [limitPerms]="modePerms[mode]"
                 [readOnly]="field.readOnly"
+                [required]="field.isRequired()"
                 [applyDefault]="field.orgDefaultAllowed"
                 [applyOrgId]="record[field.name]()"
                 (onChange)="record[field.name]($event)">
             </ng-container>
 
             <ng-container *ngSwitchCase="'bool'">
-              <input
-                class="form-check-input"
-                type="checkbox"
+              <eg-bool-select
                 name="{{field.name}}"
                 id="{{idPrefix}}-{{field.name}}"
                 [disabled]="field.readOnly"
+                [required]="field.isRequired()"
                 [ngModel]="record[field.name]()"
-                (ngModelChange)="record[field.name]($event)"/>
+                (ngModelChange)="record[field.name]($event)">
+              </eg-bool-select>
+
             </ng-container>
 
            <ng-container *ngSwitchCase="'readonly-au'">
           </a>
         </div>
       </div>
+        <ng-container *ngIf="field.append_template">
+          <ng-container
+            *ngTemplateOutlet="field.append_template; context:appendTemplateFieldContext(field)">
+          </ng-container> 
+        </ng-container> 
+      </ng-container>
       </ng-container>
     </form>
   </div>
index 77273ef..48b4820 100644 (file)
@@ -16,6 +16,7 @@ import {FormatService} from '@eg/core/format.service';
 import {TranslateComponent} from '@eg/share/translate/translate.component';
 import {FmRecordEditorActionComponent} from './fm-editor-action.component';
 import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {BooleanSelectComponent} from '@eg/share/boolean-select/boolean-select.component';
 import {Directive, HostBinding} from '@angular/core';
 import {AbstractControl, NG_VALIDATORS, ValidationErrors, Validator, Validators} from '@angular/forms';
 
@@ -84,6 +85,9 @@ export interface FmFieldOptions {
     // from the default set of form inputs.
     customTemplate?: CustomFieldTemplate;
 
+    // Follow the normal field rendering with this custom template
+    appendTemplate?: CustomFieldTemplate;
+
     // Use this persistKey if the field is an org field
     persistKey?: StringComponent;
 
@@ -429,9 +433,10 @@ export class FmRecordEditorComponent
             if (field.datatype === 'bool') {
                 if (rec[field.name]() === true) {
                     rec[field.name]('t');
-                // } else if (rec[field.name]() === false) {
-                } else { // TODO: some bools can be NULL
+                } else if (rec[field.name]() === false) {
                     rec[field.name]('f');
+                } else {
+                    rec[field.name](null);
                 }
             } else if (field.datatype === 'org_unit') {
                 const org = rec[field.name]();
@@ -526,6 +531,11 @@ export class FmRecordEditorComponent
             };
         }
 
+        if (fieldOptions.appendTemplate) {
+            field.append_template = fieldOptions.appendTemplate.template;
+            field.append_context = fieldOptions.appendTemplate.context;
+        }
+
         if (fieldOptions.customTemplate) {
             field.template = fieldOptions.customTemplate.template;
             field.context = fieldOptions.customTemplate.context;
@@ -601,6 +611,13 @@ export class FmRecordEditorComponent
             },  fieldDef.context || {}
         );
     }
+    appendTemplateFieldContext(fieldDef: any): CustomFieldContext {
+        return Object.assign(
+            {   record : this.record,
+                field: fieldDef // from this.fields
+            },  fieldDef.append_context || {}
+        );
+    }
 
     save() {
         const recToSave = this.idl.clone(this.record);
@@ -726,6 +743,23 @@ export class FmRecordEditorComponent
             }
         );
     }
+
+    isSafeToNull(field) {
+        if (field.datatype == 'id') {
+            return false;
+        }
+        if (field.readOnly) {
+            return false;
+        }
+        if (field.isRequired()) {
+            return false;
+        }
+        return true;
+    }
+
+    setToNull(field) {
+        this.record[field.name](null);
+    }
 }
 
 // https://stackoverflow.com/a/57812865
index 9bedaa5..76299e9 100644 (file)
@@ -5,15 +5,44 @@
 <eg-string #successString i18n-text text="Circulation Policy Update Succeeded"></eg-string>
 <eg-string #createString i18n-text text="Circulation Policy Creation Succeeded"></eg-string>
 
+<ng-container *ngIf="orgField || gridFilters">
+  <div class="row">
+    <div class="col-lg-6">
+      <ng-container *ngIf="orgField">
+        <eg-org-family-select
+          [limitPerms]="viewPerms" 
+          [selectedOrgId]="contextOrg.id()"
+          [(ngModel)]="searchOrgs"
+          (ngModelChange)="grid.reload()">
+        </eg-org-family-select>
+      </ng-container>
+    </div>
+    <div class="col-lg-6 d-flex">
+      <div class="flex-1"></div><!-- push right -->
+      <ng-container *ngIf="gridFilters">
+        <span i18n>Filters Applied: {{gridFilters | json}}</span>
+        <a class="pl-2 font-italic" 
+          [attr.href]="clearGridFiltersUrl()" i18n>Clear Filters</a>
+      </ng-container>
+    </div>
+  </div>
+  <hr/>
+</ng-container>
+
 <eg-grid #grid idlClass="ccmm"
   [dataSource]="dataSource"
   [sortable]="true"
+  [filterable]="true"
+  persistKey="admin.config.circ_matrix_matchpoint"
   (onRowActivate)="editSelected([$event])"
   [showFields]='"is_renewal,active,org_unit,copy_circ_lib,copy_owning_lib,user_home_ou"'>
   <eg-grid-toolbar-button 
-    label="New Circ Matrix Matchpoint" i18n-label (onClick)="createNew()">
+    label="New Circulation Policy" i18n-label (onClick)="createNew()">
   </eg-grid-toolbar-button>
-  <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
+  <eg-grid-toolbar-action
+    label="Edit Selected" i18n-label
+    (onClick)="editSelected($event)"
+    [disableOnRows]="notOneSelectedRow">
   </eg-grid-toolbar-action>
 </eg-grid>
 
   </div>
 
   <ng-template #active let-idPrefix="idPrefix" let-field="field" let-record="record">
-    <div class="col-lg-9">
-      <input
-        class="form-check-input"
-        type="checkbox"
-        name="{{field.name}}"
-        id="{{idPrefix}}-{{field.name}}"
-        [disabled]="field.readOnly"
-        [ngModel]="record[field.name]()"
-        (ngModelChange)="record[field.name]($event)"/>
-    </div>
-    <div class="row" [ngStyle]="{
-      width:'150%',
-      backgroundColor:'black',
-      marginLeft:'-43%',
-      marginTop:'6%',
-      marginBottom:'-1%',
-      opacity:'90%',
-      fontSize:'18px',
-      textAlign:'center'}">
-      <div [ngStyle]="{width:'100%', color:'white'}">Circulation Policies</div>
+    <div class="row">
+      <div class="col-lg-12">
+        <eg-staff-banner bannerText="Circulation Policy Matchpoints" i18n-bannerText>
+        </eg-staff-banner>
+      </div>
     </div>
   </ng-template>
 
   <ng-template #item_age let-idPrefix="idPrefix" let-field="field" let-record="record">
-    <div class="col-lg-9">
-      <input
-        class="form-control"
-        id="{{idPrefix}}-{{field.name}}" name="{{field.name}}"
-        type="text"
-        placeholder="{{field.label}}..." i18n-placeholder
-        [required]="field.isRequired()"
-        [ngModel]="record[field.name]()"
-        (ngModelChange)="record[field.name]($event)"/>
-    </div>
-    <div class="row" [ngStyle]="{
-      width:'150%',
-      backgroundColor:'black',
-      marginLeft:'-43%',
-      marginTop:'3%',
-      marginBottom:'-1%',
-      opacity:'90%',
-      fontSize:'18px',
-      textAlign:'center',
-      paddingTop:'3.5%',
-      paddingBottom:'5.5%',
-      paddingLeft:'3.5%',
-      paddingRight:'3.5%'}">
-      <div [ngStyle]="{width:'100%', color:'white'}">Circulation Policy Effects</div>
+    <div class="row">
+      <div class="col-lg-12">
+        <eg-staff-banner bannerText="Circulation Policy Effects" i18n-bannerText>
+        </eg-staff-banner>
+      </div>
     </div>
   </ng-template>
 
@@ -89,6 +84,6 @@
     fieldOrder="id,active,grp,org_unit,copy_circ_lib,copy_owning_lib,user_home_ou,is_renewal,juvenile_flag,circ_modifier,copy_location,marc_type,marc_form,marc_bib_level,marc_vr_format,ref_flag,usr_age_lower_bound,usr_age_upper_bound,item_age,circulate,duration_rule,renewals,hard_due_date,recurring_fine_rule,grace_period,max_fine_rule,available_copy_hold_ratio,total_copy_hold_ratio,script_test,description"
     requiredFields="active,grp,org_unit"
     (recordSaved)="configureLimitSets($event); clearLinkedCircLimitSets(); closeDialog()"
-    [fieldOptions]="{active:{customTemplate:{template:active}}, item_age:{customTemplate:{template:item_age}}}">
+    [fieldOptions]="{active:{appendTemplate:{template:active}}, item_age:{appendTemplate:{template:item_age}}}">
   </eg-fm-record-editor>
-</eg-circ-matrix-matchpoint-dialog>
\ No newline at end of file
+</eg-circ-matrix-matchpoint-dialog>
index 535aa0a..6dc8443 100644 (file)
@@ -2,22 +2,30 @@ import {Pager} from '@eg/share/util/pager';
 import {Component, OnInit, AfterViewInit, Input, ViewChild, ElementRef} from '@angular/core';
 import {GridComponent} from '@eg/share/grid/grid.component';
 import {GridDataSource, GridColumn, GridRowFlairEntry} from '@eg/share/grid/grid';
-import {IdlObject} from '@eg/core/idl.service';
+import {ActivatedRoute} from '@angular/router';
+import {Location} from '@angular/common';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
 import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
 import {LinkedCircLimitSetsComponent} from './linked-circ-limit-sets.component';
 import {CircMatrixMatchpointDialogComponent} from './circ-matrix-matchpoint-dialog.component';
 import {StringComponent} from '@eg/share/string/string.component';
 import {PcrudService} from '@eg/core/pcrud.service';
 import {ToastService} from '@eg/share/toast/toast.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PermService} from '@eg/core/perm.service';
+import {OrgService} from '@eg/core/org.service';
+import {OrgFamily} from '@eg/share/org-family-select/org-family-select.component';
 
   @Component({
     templateUrl: './circ-matrix-matchpoint.component.html'
 })
 export class CircMatrixMatchpointComponent implements OnInit {
     recId: number;
-    gridDataSource: GridDataSource;
+    orgField = 'org_unit';
+    disableOrgFilter = false;
     initDone = false;
-    dataSource: GridDataSource = new GridDataSource();
+    dataSource: GridDataSource;
+    gridFilters: {[key: string]: string | number};
     showLinkLimitSets = false;
     usedSetLimitList = {};
     linkedLimitSets = [];
@@ -27,6 +35,7 @@ export class CircMatrixMatchpointComponent implements OnInit {
         marginTop: '25px',
         marginLeft: '73%'
     };
+    notOneSelectedRow: (rows: IdlObject[]) => boolean;
 
     @ViewChild('limitSets', { static: false }) limitSets: ElementRef;
     @ViewChild('circLimitSets', { static: true }) limitSetsComponent: LinkedCircLimitSetsComponent;
@@ -44,20 +53,94 @@ export class CircMatrixMatchpointComponent implements OnInit {
 
     @Input() dialogSize: 'sm' | 'lg' = 'lg';
 
+    idlClassDef: any;
+    pkeyField: string;
+    contextOrg: IdlObject;
+    searchOrgs: OrgFamily;
+    orgFieldLabel: string;
+    viewPerms: string;
+    canCreate: boolean;
+
     constructor(
+        private route: ActivatedRoute,
         private pcrud: PcrudService,
-        private toast: ToastService
-    ) {
-        this.gridDataSource = new GridDataSource();
-    }
+        private toast: ToastService,
+        public idl: IdlService,
+        private org: OrgService,
+        public auth: AuthService,
+        private perm: PermService
+    ) {}
 
     ngOnInit() {
         this.initDone = true;
+        this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1);
+        this.idlClassDef = this.idl.classes[this.idlClass];
+        this.pkeyField = this.idlClassDef.pkey || 'id';
+
+        // Limit the view org selector to orgs where the user has
+        // permacrud-encoded view permissions.
+        const pc = this.idlClassDef.permacrud;
+        if (pc && pc.retrieve) {
+            this.viewPerms = pc.retrieve.perms;
+        }
+
+        const contextOrg = this.route.snapshot.queryParamMap.get('contextOrg');
+        this.checkCreatePerms();
+        this.applyOrgValues(Number(contextOrg));
+
+        this.initDataSource();
+    }
+
+    applyOrgValues(orgId?: number) {
+
+        if (this.disableOrgFilter) {
+            this.orgField = null;
+            return;
+        }
+
+        if (!this.orgField) {
+            // If no org unit field is specified, try to find one.
+            // If an object type has multiple org unit fields, the
+            // caller should specify one or disable org unit filter.
+            this.idlClassDef.fields.forEach(field => {
+                if (field['class'] === 'aou') {
+                    this.orgField = field.name;
+                }
+            });
+        }
+
+        if (this.orgField) {
+            this.orgFieldLabel = this.idlClassDef.field_map[this.orgField].label;
+            this.contextOrg = this.org.get(orgId) || this.org.get(this.auth.user().ws_ou()) || this.org.root();
+            this.searchOrgs = {primaryOrgId: this.contextOrg.id()};
+        }
+    }
+
+    checkCreatePerms() {
+        this.canCreate = false;
+        const pc = this.idlClassDef.permacrud || {};
+        const perms = pc.create ? pc.create.perms : [];
+        if (perms.length === 0) { return; }
+
+        this.perm.hasWorkPermAt(perms, true).then(permMap => {
+            Object.keys(permMap).forEach(key => {
+                if (permMap[key].length > 0) {
+                    this.canCreate = true;
+                }
+            });
+        });
+    }
+
+    initDataSource() {
+        this.dataSource = new GridDataSource();
+
         this.dataSource.getRows = (pager: Pager, sort: any[]) => {
             const orderBy: any = {};
+
             if (sort.length) {
                 // Sort specified from grid
                 orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir;
+
             } else if (this.sortField) {
                 // Default sort field
                 orderBy[this.idlClass] = this.sortField;
@@ -68,7 +151,42 @@ export class CircMatrixMatchpointComponent implements OnInit {
                 limit: pager.limit,
                 order_by: orderBy
             };
-            return this.pcrud.retrieveAll('ccmm', searchOps, {fleshSelectors: true});
+
+            if (!this.contextOrg && !this.gridFilters && !Object.keys(this.dataSource.filters).length) {
+                // No org filter -- fetch all rows
+                return this.pcrud.retrieveAll(
+                    this.idlClass, searchOps, {fleshSelectors: true});
+            }
+
+            const search: any[] = new Array();
+            const orgFilter: any = {};
+
+            if (this.orgField && (this.searchOrgs || this.contextOrg)) {
+                orgFilter[this.orgField] =
+                    this.searchOrgs.orgIds || [this.contextOrg.id()];
+                search.push(orgFilter);
+            }
+
+            Object.keys(this.dataSource.filters).forEach(key => {
+                Object.keys(this.dataSource.filters[key]).forEach(key2 => {
+                    search.push(this.dataSource.filters[key][key2]);
+                });
+            });
+
+            // FIXME - do we want to remove this, which is used in several
+            // secondary admin pages, in favor of switching it to the built-in
+            // grid filtering?
+            if (this.gridFilters) {
+                // Lay the URL grid filters over our search object.
+                Object.keys(this.gridFilters).forEach(key => {
+                    const urlProvidedFilters = {};
+                    urlProvidedFilters[key] = this.gridFilters[key];
+                    search.push(urlProvidedFilters);
+                });
+            }
+
+            return this.pcrud.search(
+                this.idlClass, search, searchOps, {fleshSelectors: true});
         };
     }
 
@@ -199,7 +317,7 @@ export class CircMatrixMatchpointComponent implements OnInit {
     }
 
     deleteLimitSets(limitSet) {
-        return new Promise((resolve, reject) => {
+        return new Promise<void>((resolve, reject) => {
             if (limitSet.isDeleted) {
                 if (limitSet.linkedLimitSet.id()) {
                     this.pcrud.remove(limitSet.linkedLimitSet).subscribe(res => {
index 07ef8c5..04f46ec 100644 (file)
@@ -2,7 +2,7 @@
 <eg-string #errorString i18n-text text="The Linked Set Name Already Exists on Another Matchpoint"></eg-string>
 
 <div *ngIf="showLinkLimitSets">
-    <div class="col-lg-15 bg-info d-flex justify-content-center"><h3 class="modal-title mt-3 mb-3" i18n>Linked Limit Sets</h3></div>
+    <div class="modal-header bg-info"><h4 class="modal-title" i18n>Linked Limit Sets</h4></div>
     <ng-container *ngIf="getObjectKeys().length > 0">
         <ng-container *ngFor="let key of getObjectKeys(); let i = index">
             <div *ngIf="!linkedSetList[i].isDeleted" class="col-lg-15 d-flex justify-content-center">
@@ -64,7 +64,7 @@
                 (click)="addLinkedSet()"
                 i18n-title title="Add" i18n>Add
             </button>
-            <eg-help-popover [helpText]="'Go to Local Admin -> Circulation Limit Sets to Create a Link Limit Set'" ></eg-help-popover>
+            <eg-help-popover i18n-helpText helpText="'Go to Local Admin -> Circulation Limit Sets to Create a Link Limit Set'" ></eg-help-popover>
         </div>
     </div>
 </div>
index b79c7a9..4bd839c 100644 (file)
@@ -101,7 +101,8 @@ const routes: Routes = [{
       import('./negative-balances/negative-balances.module').then(m => m.NegativeBalancesModule)
 }, {
     path: 'config/circ_matrix_matchpoint',
-    loadChildren: '@eg/staff/admin/local/circ_matrix_matchpoint/circ-matrix-matchpoint.module#CircMatrixMathpointModule'
+    loadChildren: () =>
+      import('./circ_matrix_matchpoint/circ-matrix-matchpoint.module').then(m => m.CircMatrixMatchpointModule)
 }, {
     path: 'asset/stat_cat',
     component: BasicAdminPageComponent,
index 2867f6f..ed62798 100644 (file)
@@ -23370,3 +23370,12 @@ INSERT INTO config.global_flag (name, enabled, label) VALUES (
 );
 
 UPDATE config.global_flag SET value = '20' WHERE name = 'ingest.queued.max_threads';
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label) 
+VALUES (
+    'eg.grid.admin.config.circ_matrix_matchpoint', 'gui', 'object',
+    oils_i18n_gettext(
+        'eg.grid.admin.config.circ_matrix_matchpoint',
+        'Grid Config: admin.config.circ_matrix_matchpoint',
+        'cwst', 'label'
+    )
+);
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.circPolicy-grid-ws-settings.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.circPolicy-grid-ws-settings.sql
new file mode 100644 (file)
index 0000000..4fca655
--- /dev/null
@@ -0,0 +1,15 @@
+BEGIN;
+
+SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version);
+
+INSERT INTO config.workstation_setting_type (name, grp, datatype, label) 
+VALUES (
+    'eg.grid.admin.config.circ_matrix_matchpoint', 'gui', 'object',
+    oils_i18n_gettext(
+        'eg.grid.admin.config.circ_matrix_matchpoint',
+        'Grid Config: admin.config.circ_matrix_matchpoint',
+        'cwst', 'label'
+    )
+);
+
+COMMIT;
index a17df9a..9aa5c6d 100644 (file)
 * Adds new Local Administration entries for Item Statistical Categories Editor and Patron Statistical Categories Editor, which are angularized interfaces.
 * Tweaks eg-grids to underline hyperlinks within cells.  This potentially affects multiple interfaces.
 * eg-org-family-select now supports persistKey
+* Angularized the Local Administration -> Circulation Policies interface.
+* Added an option to fmEditor for allowing one to unset a field (aka set to null)
+* Added some misc fmEditor tweaks/additions for developers
+* Replaced checkboxes for boolean fields in fmEditor with radio buttons
+* Changed instances of Copy Location to Shelving Location in the IDL, which wil be reflected in many interfaces.
+* Also, in the IDL, "Copy Circ Lib" and "Copy Owning Lib" become "Item Circ Library" and "Item Owning Library", respectively.
+* Org Unit becomes Checkout Library for the circ matrix.  Checkout is more prevalent in the code than Check Out, but we should pick one.