--- /dev/null
+<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">×</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"
+ [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()"
+ (onChange)="formula.owner($event); myForm.form.markAsDirty()">
+ </eg-org-select>
+ </div>
+ <label for="formula-skip_count" class="col-sm-1 col-form-skip_count" i18n>Skip Count
+ </label>
+ <div class="col-sm-1">
+ <input class="form-control" type="number" id="formula-name"
+ [ngModel]="formula.skip_count()" name="skip_count"
+ (ngModelChange)="formula.skip_count($event)">
+ </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-1" i18n>Fund</div>
+ <div class="col-sm-2" i18n>Circ Modifier</div>
+ <div class="col-sm-2" 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()"
+ (onChange)="entry.owning_lib($event); myForm.form.markAsDirty()">
+ </eg-org-select>
+ </div>
+ <div class="col-sm-2">
+ <eg-item-location-select
+ permFilter="CREATE_PURCHASE_ORDER"
+ [ngModel]="entry.location()" name="location-{{idx}}"
+ (ngModelChange)="entry.location($event)"
+ (valueChange)="myForm.form.markAsDirty()">
+ </eg-item-location-select>
+ </div>
+ <div class="col-sm-1">
+ <eg-combobox i18n-placeholder placeholder="Fund..." idlClass="acqf"
+ 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-2">
+ <input class="form-control" type="text" i18n-placeholder placeholder="Collection Code..."
+ id="entry-collection-code-{{idx}}"
+ [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"
+ [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>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgForm} 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: string = '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) => { return 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(
+ res => {},
+ err => this.close(err),
+ () => this.close(true)
+ );
+ }, err => this.close(false));
+ }
+
+}
</eg-grid-toolbar-button>
<eg-grid-toolbar-action label="Edit Selected" i18n-label (onClick)="editSelected($event)">
</eg-grid-toolbar-action>
+ <eg-grid-toolbar-action label="Clone Selected" i18n-label (onClick)="cloneSelected($event)">
+ </eg-grid-toolbar-action>
<eg-grid-toolbar-action label="Delete Selected" i18n-label (onClick)="deleteSelected($event)">
</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>
[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>
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'
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;
cellTextGenerator: GridCellTextGenerator;
const searchOps = {
offset: pager.offset,
limit: pager.limit,
- order_by: orderBy
+ 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);
+ this.idlClass, searchOps, reqOps)
+ .pipe(mergeMap((row) => this.countItems(row)));
}
const search: any = new Array();
});
return this.pcrud.search(
- this.idlClass, search, searchOps, reqOps);
+ this.idlClass, search, searchOps, reqOps)
+ .pipe(mergeMap((row) => this.countItems(row)));
};
super.ngOnInit();
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: boolean = 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();
+ }
+ }
+ );
+ }
+ }
}
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
+ DistributionFormulasComponent,
+ DistributionFormulaEditDialogComponent
],
imports: [
StaffCommonModule,
AdminCommonModule,
+ ItemLocationSelectModule,
DistributionFormulasRoutingModule
],
exports: [