LP#1904244: Angular distribution formulas interface
authorGalen Charlton <gmc@equinoxOLI.org>
Fri, 26 Mar 2021 22:13:01 +0000 (18:13 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 12 Aug 2021 19:29:26 +0000 (15:29 -0400)
Signed-off-by: Galen Charlton <gmc@equinoxOLI.org>
Signed-off-by: Ruth Frasur <rfrasur@library.in.gov>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.html [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/routing.module.ts [new file with mode: 0644]
Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts

index f5de0e2..202ff65 100644 (file)
     <eg-link-table-link i18n-label label="Currency Types"
       routerLink="/staff/admin/acq/currency_type"></eg-link-table-link>
     <eg-link-table-link i18n-label label="Distribution Formulas"
-      url="/eg/staff/admin/acq/conify/distribution_formula"></eg-link-table-link>
-      <!-- TODO
       routerLink="/staff/admin/acq/distribution_formula"></eg-link-table-link>
-      -->
     <eg-link-table-link i18n-label label="EDI Accounts"
       routerLink="/staff/admin/acq/edi_account"></eg-link-table-link>
     <eg-link-table-link i18n-label label="EDI Messages"
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.html
new file mode 100644 (file)
index 0000000..5c537df
--- /dev/null
@@ -0,0 +1,128 @@
+<ng-template #dialogContent>
+  <div class="modal-header bg-info">
+    <h3 *ngIf="mode === 'create'" class="modal-title" i18n>New Distribution Formula</h3>
+    <h3 *ngIf="mode === 'update'" class="modal-title" i18n>Modify Distribution Formula</h3>
+    <h3 *ngIf="mode === 'clone'" class="modal-title" i18n>Clone Distribution Formula (from {{clonedLabel}})</h3>
+    <button type="button" class="close"
+      i18n-aria-label aria-label="Close" (click)="close()">
+      <span aria-hidden="true">&times;</span>
+    </button>
+  </div>
+  <div class="modal-body">
+    <form #myForm="ngForm" role="form" class="form-validated">
+      <div class="form-group row mt-2" *ngIf="formula">
+        <label for="formula-name" class="col-sm-1 col-form-label" i18n>Formula Name
+        </label>
+        <div class="col-sm-2">
+          <input class="form-control" type="text" id="formula-name"
+            required="required" pattern="[\s\S]*\S[\s\S]*"
+            [ngModel]="formula.name()" name="name"
+            (ngModelChange)="formula.name($event)">
+        </div>
+        <label for="formula-owner" class="col-sm-1 col-form-owner" i18n>Formula Owner
+        </label>
+        <div class="col-sm-2">
+          <eg-org-select
+            placeholder="Owner..."
+            i18n-placeholder
+            domId="formula-owner"
+            [applyOrgId]="formula.owner()"
+            [limitPerms]="['ADMIN_ACQ_DISTRIB_FORMULA']"
+            (onChange)="formula.owner($event); myForm.form.markAsDirty()">
+          </eg-org-select>
+        </div>
+      </div>
+      <div class="row mt-2">
+        <div class="col-sm-1"></div>
+        <div class="col-sm-1" i18n>Owning Library</div>
+        <div class="col-sm-2" i18n>Shelving Location</div>
+        <div class="col-sm-2" i18n>Fund</div>
+        <div class="col-sm-2" i18n>Circ Modifier</div>
+        <div class="col-sm-1" i18n>Collection Code</div>
+        <div class="col-sm-1" i18n>Item Count</div>
+        <div class="col-sm-1"></div>
+      </div>
+      <div class="form-group row mt-2" *ngFor="let entry of formula?.entries(); index as idx; last as isLast; first as isFirst; count as count">
+        <div class="col-sm-1">
+          <button *ngIf="!isLast" class="btn btn-sm material-icon-button" type="button"
+            (click)="removeRow(idx)"
+            i18n-title title="Remove Entry"><span class="sr-only">Remove Entry</span>
+            <span class="material-icons" aria-hidden="true">delete</span>
+          </button>
+        </div>
+        <div class="col-sm-1">
+          <eg-org-select
+            placeholder="Owning Library..."
+            i18n-placeholder
+            domId="entry-owning-lib-{{idx}}"
+            [applyOrgId]="entry.owning_lib()"
+            [limitPerms]="['ADMIN_ACQ_DISTRIB_FORMULA']"
+            (onChange)="entry.owning_lib($event); myForm.form.markAsDirty()">
+          </eg-org-select>
+        </div>
+        <div class="col-sm-2">
+          <eg-item-location-select
+            permFilter="ADMIN_ACQ_DISTRIB_FORMULA"
+            [ngModel]="entry.location()" name="location-{{idx}}"
+            (ngModelChange)="entry.location($event)"
+            (valueChange)="myForm.form.markAsDirty()">
+          </eg-item-location-select>
+        </div>
+        <div class="col-sm-2">
+          <eg-combobox i18n-placeholder placeholder="Fund..." idlClass="acqf"
+            [idlBaseQuery]="{'active':{'=':'t'}}"
+            id="entry-fund-{{idx}}"
+            [asyncSupportsEmptyTermClick]="true"
+            [selectedId]="entry.fund()"
+            name="fund-{{idx}}"
+            (onChange)="entry.fund($event.id)">
+          </eg-combobox>
+        </div>
+        <div class="col-sm-2">
+          <eg-combobox i18n-placeholder placeholder="Circ Modifier..." idlClass="ccm"
+            id="entry-circ_modifier-{{idx}}"
+            [asyncSupportsEmptyTermClick]="true"
+            [selectedId]="entry.circ_modifier()"
+            name="circ_modifier-{{idx}}"
+            (onChange)="entry.circ_modifier($event.id)">
+          </eg-combobox>
+        </div>
+        <div class="col-sm-1">
+          <input class="form-control" type="text" i18n-placeholder placeholder="Collection Code..."
+            id="entry-collection-code-{{idx}}" pattern="[\s\S]*\S[\s\S]*"
+            [ngModel]="entry.collection_code()" name="collection_code-{{idx}}"
+            (ngModelChange)="entry.collection_code($event)">
+        </div>
+        <div class="col-sm-1">
+          <input class="form-control" type="number" min="0" pattern="^[0-9]+$"
+            [ngModel]="entry.item_count()" name="item_count-{{idx}}"
+            (ngModelChange)="entry.item_count($event)">
+        </div>
+        <div class="col-sm-1">
+          <button *ngIf="!isLast" class="btn btn-sm material-icon-button" type="button"
+            [disabled]="isFirst"
+            (click)="moveUp(idx)"
+            i18n-title title="Move Up"><span class="sr-only">Move Up</span>
+            <span class="material-icons" aria-hidden="true">keyboard_arrow_up</span>
+          </button>
+          <button *ngIf="!isLast" class="btn btn-sm material-icon-button" type="button"
+            (click)="moveDown(idx)"
+            [disabled]="count < 3 || idx === count - 2"
+            i18n-title title="Move Down"><span class="sr-only">Move Down</span>
+            <span class="material-icons" aria-hidden="true">keyboard_arrow_down</span>
+          </button>
+          <button *ngIf="isLast" type="button" class="btn btn-info" (click)="addRow()" i18n>Add</button>
+        </div>
+      </div>
+    </form>
+  </div>
+  <div class="modal-footer">
+    <button type="button" class="btn btn-info" [disabled]="!myForm?.valid || !(myForm?.dirty)"
+      (click)="save()" i18n>Save</button>
+    <button type="button" class="btn btn-warning"
+      (click)="close()" i18n>Cancel</button>
+  </div>
+</ng-template>
+
+<ng-template #defaultCloneLabelTmpl i18n>{{clonedLabel}} (clone)</ng-template>
+<eg-string #defaultCloneLabel [template]="defaultCloneLabelTmpl"></eg-string>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formula-edit-dialog.component.ts
new file mode 100644 (file)
index 0000000..7c0335c
--- /dev/null
@@ -0,0 +1,205 @@
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm, NG_VALIDATORS} from '@angular/forms';
+import {IdlService, 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 {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {Pager} from '@eg/share/util/pager';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PermService} from '@eg/core/perm.service';
+
+@Component({
+  selector: 'eg-distribution-formula-edit-dialog',
+  templateUrl: './distribution-formula-edit-dialog.component.html'
+})
+
+export class DistributionFormulaEditDialogComponent
+  extends DialogComponent implements OnInit {
+
+    @Input() mode = 'create';
+    @Input() formulaId: number;
+    @Input() cloneSource: number;
+
+    @ViewChild('defaultCloneLabel', { static: true }) defaultCloneLabel: StringComponent;
+    formula: IdlObject;
+    deadEntries: IdlObject[];
+    clonedLabel = '';
+
+    constructor(
+        private idl: IdlService,
+        private evt: EventService,
+        private net: NetService,
+        private auth: AuthService,
+        private org: OrgService,
+        private pcrud: PcrudService,
+        private perm: PermService,
+        private toast: ToastService,
+        private modal: NgbModal
+    ) {
+        super(modal);
+    }
+
+    ngOnInit() {
+        this.onOpen$.subscribe(() => this._initRecord());
+    }
+
+    private _initRecord() {
+        this.formula = null;
+        this.deadEntries = [];
+        this.clonedLabel = '';
+        if (this.mode === 'update') {
+            this.pcrud.retrieve('acqdf', this.formulaId, {
+                flesh: 1,
+                flesh_fields: { acqdf: ['entries'] }
+            }).subscribe(res => {
+                this.formula = res;
+                this._generateFormulaInputs();
+            });
+        } else if (this.mode === 'clone') {
+            this.pcrud.retrieve('acqdf', this.cloneSource, {
+                flesh: 1,
+                flesh_fields: { acqdf: ['entries'] }
+            }).subscribe(res => {
+                this.clonedLabel = res.name();
+                this.formula = this.idl.clone(res);
+                this.formula.id(null);
+                this.defaultCloneLabel.current().then(str => this.formula.name(str));
+                this.formula.entries().forEach((e) => e.formula(null));
+                this._generateFormulaInputs();
+            });
+        } else if (this.mode === 'create') {
+            this.formula = this.idl.create('acqdf');
+            this.formula.entries([]);
+            this._generateFormulaInputs();
+        }
+    }
+
+    _generateFormulaInputs() {
+        this.formula.entries().sort((a, b) => a.position() < b.position() ? -1 : 1 );
+        const entry = this.idl.create('acqdfe');
+        entry.id(-9999); // magic placeholder for new record
+        this.formula.entries().push(entry);
+    }
+
+    org_root(): number {
+        return this.org.root().id();
+    }
+
+    addRow() {
+        if (this.formula.entries().slice(-1)[0].id() === -9999) {
+            this.formula.entries().slice(-1)[0].id(-1); // magic placheholder for new entry that we intend to keep
+        }
+        const entry = this.idl.create('acqdfe');
+        entry.id(-9999); // magic placeholder for new record
+        this.formula.entries().push(entry);
+    }
+    removeRow(idx: number) {
+        this.deadEntries.push(this.formula.entries().splice(idx, 1)[0]);
+    }
+    moveUp(idx: number) {
+        const temp = this.formula.entries()[idx - 1];
+        this.formula.entries()[idx - 1] = this.formula.entries()[idx];
+        this.formula.entries()[idx] = temp;
+    }
+    moveDown(idx: number) {
+        const temp = this.formula.entries()[idx + 1];
+        this.formula.entries()[idx + 1] = this.formula.entries()[idx];
+        this.formula.entries()[idx] = temp;
+    }
+
+
+    save() {
+        // grab a copy to preserve the list of entries
+        const formulaCopy = this.idl.clone(this.formula);
+        if (this.formula.id() === undefined || this.formula.id() === null) {
+            this.formula.isnew(true);
+            this.formula.owner(this.formula.owner().id());
+        } else {
+            this.formula.ischanged(true);
+        }
+        this.pcrud.autoApply([this.formula]).subscribe(res => {
+            const dfId = this.mode === 'update' ? res : res.id();
+            const updates: IdlObject[] = [];
+            if (this.mode === 'create' || this.mode === 'clone') {
+                formulaCopy.entries().forEach((entry, idx) => {
+                    if (entry.id() === -1) { entry.id(null); }
+                    if (entry.id() === -9999) { entry.id(null); }
+                    if (entry.item_count() == null) {
+                        // we got nothing; ignore
+                        return;
+                    }
+                    if (entry.owning_lib() == null &&
+                        entry.fund() == null &&
+                        entry.location() == null &&
+                        entry.circ_modifier() == null &&
+                        entry.collection_code() == null
+                       ) {
+                        // this is a pointless entry; ignore
+                        return;
+                    }
+
+                    entry.formula(dfId);
+                    if (entry.owning_lib()) { entry.owning_lib(entry.owning_lib().id()); }
+                    entry.id(null);
+                    entry.position(idx); // re-writing all the positions
+                    entry.isnew(true);
+                    updates.push(entry);
+                });
+            } else {
+                // updating an existing set
+                formulaCopy.entries().forEach((entry, idx) => {
+                    if (entry.id() === -1) { entry.id(null); }
+                    if (entry.id() === -9999) { entry.id(null); }
+                    if (entry.id()) {
+                        entry.formula(dfId);
+                        entry.position(idx);
+                        if (entry.owning_lib()) { entry.owning_lib(entry.owning_lib().id()); }
+                        const delEntry = this.idl.clone(entry);
+                        // have to delete and recreate because of the
+                        // check constraint on formula, position
+                        this.deadEntries.push(delEntry);
+                        entry.isnew(true);
+                        updates.push(entry);
+                    } else {
+                        if (entry.item_count() == null) {
+                            // we got nothing; ignore
+                            return;
+                        }
+                        if (entry.owning_lib() == null &&
+                            entry.fund() == null &&
+                            entry.location() == null &&
+                            entry.circ_modifier() == null &&
+                            entry.collection_code() == null
+                        ) {
+                            // this is a pointless entry; ignore
+                            return;
+                        }
+
+                        entry.formula(dfId);
+                        if (entry.owning_lib()) { entry.owning_lib(entry.owning_lib().id()); }
+                        entry.position(idx); // re-writing all the positions
+                        entry.isnew(true);
+                        updates.push(entry);
+                    }
+                });
+            }
+            this.deadEntries.forEach((entry) => {
+                if (entry.id()) {
+                    entry.isdeleted(true);
+                    updates.unshift(entry); // deletions have to be processed first
+                }
+            });
+            this.pcrud.autoApply(updates).subscribe(
+                ret => {},
+                err => this.close(err),
+                () => this.close(true)
+            );
+        }, err => this.close(false));
+    }
+
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.html
new file mode 100644 (file)
index 0000000..d9e102a
--- /dev/null
@@ -0,0 +1,97 @@
+<eg-staff-banner bannerText="Distribution Formulas" i18n-bannerText>
+</eg-staff-banner>
+
+<eg-title i18n-prefix prefix="Distribution Formulas"></eg-title>
+
+<ng-template #successStrTmpl i18n>{{idlClassDef.label}} Update Succeeded</ng-template>
+<eg-string #successString [template]="successStrTmpl"></eg-string>
+
+<ng-template #updateFailedStrTmpl i18n>Update of {{idlClassDef.label}} failed</ng-template>
+<eg-string #updateFailedString [template]="updateFailedStrTmpl"></eg-string>
+
+<ng-template #deleteFailedStrTmpl i18n>Delete of {{idlClassDef.label}} failed or was not allowed</ng-template>
+<eg-string #deleteFailedString [template]="deleteFailedStrTmpl"></eg-string>
+
+<ng-template #deleteSuccessStrTmpl i18n>{{idlClassDef.label}} Successfully Deleted</ng-template>
+<eg-string #deleteSuccessString [template]="deleteSuccessStrTmpl"></eg-string>
+
+<ng-template #createStrTmpl i18n>{{idlClassDef.label}} Successfully Created</ng-template>
+<eg-string #createString [template]="createStrTmpl"></eg-string>
+
+<ng-template #createErrStrTmpl i18n>Failed to create new {{idlClassDef.label}}</ng-template>
+<eg-string #createErrString [template]="createErrStrTmpl"></eg-string>
+
+<ng-container *ngIf="orgField">
+  <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>
+  <hr/>
+</ng-container>
+
+<!-- idlObject and fieldName applied programmatically -->
+<eg-translate #translator></eg-translate>
+
+<ng-container *ngIf="helpTemplate">
+  <ng-container *ngTemplateOutlet="helpTemplate"></ng-container>
+</ng-container>
+
+<ng-template #configFieldLink let-row="row" let-col="col">
+  <a i18n-title title="Link To {{col.label}}"
+    [attr.href]="configFieldLinkUrl(row, col)">{{configLinkLabel(row, col)}}</a>
+</ng-template>
+
+<eg-grid #grid idlClass="{{idlClass}}" [dataSource]="dataSource" hideFields="{{hideGridFields}}"
+    [sortable]="true" persistKey="{{persistKey}}" autoGeneratedColumnOrder="{{fieldOrder}}"
+    [filterable]="true" [stickyHeader]="true"
+    (onRowActivate)="editSelected([$event])"
+    [cellTextGenerator]="cellTextGenerator">
+  <eg-grid-toolbar-button [disabled]="!canCreate" 
+    label="New {{idlClassDef.label}}" i18n-label (onClick)="createNew()">
+  </eg-grid-toolbar-button>
+  <eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)"
+    [disableOnRows]="notOneSelectedRow">
+  </eg-grid-toolbar-action>
+  <eg-grid-toolbar-action label="Clone Selected" i18n-label (onClick)="cloneSelected($event)"
+    [disableOnRows]="notOneSelectedRow">
+  </eg-grid-toolbar-action>
+  <eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)"
+    [disableOnRows]="notOneSelectedRow">
+  </eg-grid-toolbar-action>
+
+  <eg-grid-column path="name"></eg-grid-column>
+  <eg-grid-column path="owner"></eg-grid-column>
+  <eg-grid-column path="item_count" [filterable]="false" [sortable]="false" i18n-label label="Item Count"></eg-grid-column>
+  <eg-grid-column path="skip_count" [hidden]="true"></eg-grid-column>
+  <eg-grid-column path="id" [hidden]="true"></eg-grid-column>
+
+
+</eg-grid>
+
+<eg-fm-record-editor #editDialog idlClass="{{idlClass}}" 
+    [fieldOptions]="fieldOptions"
+    [fieldOrder]="fieldOrder"
+    [defaultNewRecord]="defaultNewRecord"
+    [preloadLinkedValues]="true"
+    [readonlyFields]="readonlyFields">
+</eg-fm-record-editor>
+
+<eg-distribution-formula-edit-dialog #distributionFormulaEditDialog></eg-distribution-formula-edit-dialog>
+
+
+<eg-confirm-dialog #confirmDel
+  dialogTitle="Delete?" i18n-dialogTitle
+  dialogBody="Delete distribution formula?" i18n-dialogBody>
+</eg-confirm-dialog>
+<eg-alert-dialog #alertDialog
+ i18n-dialogBody
+  dialogBody="Distribution formula cannot be deleted as it has been applied at least once">
+</eg-alert-dialog>
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.component.ts
new file mode 100644 (file)
index 0000000..4b25921
--- /dev/null
@@ -0,0 +1,191 @@
+import {Component, Input, ViewChild, OnInit} from '@angular/core';
+import {Location} from '@angular/common';
+import {FormatService} from '@eg/core/format.service';
+import {GridDataSource, GridCellTextGenerator} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
+import {Pager} from '@eg/share/util/pager';
+import {ActivatedRoute} from '@angular/router';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {PermService} from '@eg/core/perm.service';
+import {AuthService} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {map, mergeMap} from 'rxjs/operators';
+import {StringComponent} from '@eg/share/string/string.component';
+import {DistributionFormulaEditDialogComponent} from './distribution-formula-edit-dialog.component';
+import {Observable, forkJoin, of} from 'rxjs';
+import {AlertDialogComponent} from '@eg/share/dialog/alert.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+
+@Component({
+    templateUrl: './distribution-formulas.component.html'
+})
+
+export class DistributionFormulasComponent extends AdminPageComponent implements OnInit {
+    idlClass = 'acqdf';
+    classLabel: string;
+
+    @ViewChild('grid', { static: true }) grid: GridComponent;
+    @ViewChild('distributionFormulaEditDialog', { static: false }) distributionFormulaEditDialog: DistributionFormulaEditDialogComponent;
+    @ViewChild('alertDialog', {static: false}) private alertDialog: AlertDialogComponent;
+    @ViewChild('confirmDel', { static: true }) confirmDel: ConfirmDialogComponent;
+
+    notOneSelectedRow: (rows: IdlObject[]) => boolean;
+    cellTextGenerator: GridCellTextGenerator;
+
+    constructor(
+        route: ActivatedRoute,
+        ngLocation: Location,
+        format: FormatService,
+        idl: IdlService,
+        org: OrgService,
+        auth: AuthService,
+        pcrud: PcrudService,
+        perm: PermService,
+        toast: ToastService,
+        private net: NetService
+    ) {
+        super(route, ngLocation, format, idl, org, auth, pcrud, perm, toast);
+        this.dataSource = new GridDataSource();
+    }
+
+    ngOnInit() {
+        this.notOneSelectedRow = (rows: IdlObject[]) => (rows.length !== 1);
+        this.cellTextGenerator = {
+            name: row => row.name()
+        };
+        this.fieldOrder = 'name,code,year,org,active,currency_type,balance_stop_percentage,balance_warning_percentage,propagate,rollover';
+        this.defaultNewRecord = this.idl.create('acqdf');
+        this.defaultNewRecord.owner(this.auth.user().ws_ou());
+
+        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;
+            }
+
+            const searchOps = {
+                offset: pager.offset,
+                limit: pager.limit,
+                order_by: orderBy,
+                flesh: 1,
+                flesh_fields: {
+                    acqdf: ['entries']
+                }
+            };
+            const reqOps = {
+                fleshSelectors: true,
+            };
+
+            if (!this.contextOrg && !Object.keys(this.dataSource.filters).length) {
+                // No org filter -- fetch all rows
+                return this.pcrud.retrieveAll(
+                    this.idlClass, searchOps, reqOps)
+                        .pipe(mergeMap((row) => this.countItems(row)));
+            }
+
+            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]);
+                });
+            });
+
+            return this.pcrud.search(
+                this.idlClass, search, searchOps, reqOps)
+                    .pipe(mergeMap((row) => this.countItems(row)));
+        };
+
+        super.ngOnInit();
+
+        this.classLabel = this.idlClassDef.label;
+        this.includeOrgDescendants = true;
+    }
+
+    countItems(row: IdlObject): Observable<IdlObject> {
+        row['item_count'] = 0;
+        row.entries().forEach((e) => row['item_count'] += e.item_count());
+        return of(row);
+    }
+
+    showEditDistributionFormulaDialog(successString: StringComponent, failString: StringComponent): Promise<any> {
+        return new Promise((resolve, reject) => {
+            this.distributionFormulaEditDialog.open({size: 'xl', scrollable: true}).subscribe(
+                result => {
+                    this.successString.current()
+                        .then(str => this.toast.success(str));
+                    resolve(result);
+                },
+                error => {
+                    this.updateFailedString.current()
+                        .then(str => this.toast.danger(str));
+                    reject(error);
+                },
+                () => this.grid.reload()
+            );
+        });
+    }
+
+    createNew() {
+        this.distributionFormulaEditDialog.mode = 'create';
+        this.showEditDistributionFormulaDialog(this.createString, this.createErrString);
+    }
+
+    editSelected(rows: IdlObject[]) {
+        if (rows.length <= 0) { return; }
+        this.distributionFormulaEditDialog.mode = 'update';
+        this.distributionFormulaEditDialog.formulaId = rows[0].id();
+        this.showEditDistributionFormulaDialog(this.successString, this.updateFailedString);
+    }
+
+    cloneSelected(rows: IdlObject[]) {
+        if (rows.length <= 0) { return; }
+        this.distributionFormulaEditDialog.mode = 'clone';
+        this.distributionFormulaEditDialog.cloneSource = rows[0].id();
+        this.showEditDistributionFormulaDialog(this.createString, this.createErrString);
+    }
+
+    deleteSelected(rows: IdlObject[]) {
+        if (rows.length > 0) {
+            const id = rows[0].id();
+            let can = true;
+            forkJoin([
+                this.pcrud.search('acqdfa',  { formula: id }, { limit: 1 }, { atomic: true }),
+            ]).subscribe(
+                results => {
+                    results.forEach((res) => {
+                        if (res.length > 0) {
+                            can = false;
+                        }
+                    });
+                },
+                err => {},
+                () => {
+                    if (can) {
+                        this.confirmDel.open().subscribe(confirmed => {
+                            if (!confirmed) { return; }
+                            super.deleteSelected([ rows[0] ]);
+                        });
+                    } else {
+                        this.alertDialog.open();
+                    }
+                }
+            );
+        }
+    }
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/distribution-formulas.module.ts
new file mode 100644 (file)
index 0000000..313f4b7
--- /dev/null
@@ -0,0 +1,27 @@
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {DistributionFormulasRoutingModule} from './routing.module';
+import {DistributionFormulasComponent} from './distribution-formulas.component';
+import {DistributionFormulaEditDialogComponent} from './distribution-formula-edit-dialog.component';
+import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
+
+@NgModule({
+  declarations: [
+    DistributionFormulasComponent,
+    DistributionFormulaEditDialogComponent
+  ],
+  imports: [
+    StaffCommonModule,
+    AdminCommonModule,
+    ItemLocationSelectModule,
+    DistributionFormulasRoutingModule
+  ],
+  exports: [
+  ],
+  providers: [
+  ]
+})
+
+export class DistributionFormulasModule {
+}
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/distribution_formula/routing.module.ts
new file mode 100644 (file)
index 0000000..e67b402
--- /dev/null
@@ -0,0 +1,15 @@
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {DistributionFormulasComponent} from './distribution-formulas.component';
+
+const routes: Routes = [{
+    path: '',
+    component: DistributionFormulasComponent
+}];
+
+@NgModule({
+  imports: [RouterModule.forChild(routes)],
+  exports: [RouterModule]
+})
+
+export class DistributionFormulasRoutingModule {}
index a1a011e..0ae8e0a 100644 (file)
@@ -32,6 +32,10 @@ const routes: Routes = [{
     path: 'claim_type',
     redirectTo: 'claiming' // from legacy auto-generated admin page
 }, {
+    path: 'distribution_formula',
+    loadChildren: () =>
+      import('./distribution_formula/distribution-formulas.module').then(m => m.DistributionFormulasModule)
+}, {
     path: 'edi_attr_set',
     loadChildren: () =>
       import('./edi_attr_set/edi-attr-sets.module').then(m => m.EdiAttrSetsModule)