<eg-link-table-link i18n-label label="Cash Reports"
url="/eg/staff/admin/local/money/cash_reports"></eg-link-table-link>
<eg-link-table-link i18n-label label="Circulation Limit Sets"
- url="/eg/staff/admin/local/config/circ_limit_set"></eg-link-table-link>
+ routerLink="/staff/admin/local/config/circ_limit_set"></eg-link-table-link>
<eg-link-table-link i18n-label label="Circulation Policies"
url="/eg/staff/admin/local/config/circ_matrix_matchpoint"></eg-link-table-link>
<eg-link-table-link i18n-label label="Closed Dates Editor"
--- /dev/null
+<eg-staff-banner bannerText="Circulation Limit Set Administration" i18n-bannerText>
+ </eg-staff-banner>
+
+<eg-string #createFailedString i18n-text text="Created Circulation Limit
+ Set"></eg-string>
+<eg-string #createSuccessString i18n-text text="Failed to Create Circulation
+ Limit Set"></eg-string>
+<eg-string #deleteFailedString i18n-text text="Delete of Circulation Limit Set
+ failed or was not allowed"></eg-string>
+<eg-string #deleteSuccessString i18n-text text="Delete of Circulation Limit Set
+ succeeded"></eg-string>
+<eg-string #updateSuccessString i18n-text text="Circulation Limit Set Update
+ Succeeded"></eg-string>
+<eg-string #updateFailedString i18n-text text="Circulation Limit Set
+ Update Failed"></eg-string>
+
+<eg-grid #grid idlClass="ccls" [dataSource]="gridDataSource" hideFields="id">
+ <eg-grid-toolbar-button label="New Circulation Limit Set" i18n-label
+ (onClick)="createNew()"></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="Delete Selected" i18n-label
+ (onClick)="deleteSelected($event)"></eg-grid-toolbar-action>
+</eg-grid>
+
+<eg-fm-record-editor #editDialog hiddenFields="id" idlClass="ccls"></eg-fm-record-editor>
--- /dev/null
+import {Pager} from '@eg/share/util/pager';
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {Router} from '@angular/router';
+import {IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+
+@Component({
+ templateUrl: './circ_limit_set.component.html'
+})
+
+export class CircLimitSetComponent implements OnInit {
+
+ recId: number;
+ gridDataSource: GridDataSource;
+ initDone = false;
+ cspSource: GridDataSource = new GridDataSource();
+
+ @ViewChild('editDialog', {static: true}) editDialog: FmRecordEditorComponent;
+ @ViewChild('grid', {static: true}) grid: GridComponent;
+ @ViewChild('updateSuccessString', {static: true}) updateSuccessString: StringComponent;
+ @ViewChild('updateFailedString', {static: true}) updateFailedString: StringComponent;
+ @ViewChild('deleteFailedString', {static: true}) deleteFailedString: StringComponent;
+ @ViewChild('deleteSuccessString', {static: true}) deleteSuccessString: StringComponent;
+ @ViewChild('createSuccessString', {static: true}) createSuccessString: StringComponent;
+ @ViewChild('createErrString', {static: true}) createErrString: StringComponent;
+
+ @Input() dialogSize: 'sm' | 'lg' = 'lg';
+
+ constructor(
+ private pcrud: PcrudService,
+ private toast: ToastService,
+ private router: Router
+ ) {
+ this.gridDataSource = new GridDataSource();
+ }
+
+ ngOnInit() {
+ this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+ const orderBy: any = {};
+ const searchOps = {
+ offset: pager.offset,
+ limit: pager.limit,
+ order_by: orderBy
+ };
+ return this.pcrud.retrieveAll('ccls', searchOps, {fleshSelectors: true});
+ };
+
+ this.grid.onRowActivate.subscribe(
+ (set: IdlObject) => {
+ const idToEdit = set.id();
+ this.navigateToEditPage(idToEdit);
+ }
+ );
+ }
+
+ deleteSelected = (idlThings: IdlObject[]) => {
+ idlThings.forEach(idlThing => idlThing.isdeleted(true));
+ this.pcrud.autoApply(idlThings).subscribe(
+ val => {
+ this.deleteSuccessString.current()
+ .then(str => this.toast.success(str));
+ },
+ err => {
+ this.deleteFailedString.current()
+ .then(str => this.toast.danger(str));
+ },
+ () => this.grid.reload()
+ );
+ }
+
+ editSelected(sets: IdlObject[]) {
+ const idToEdit = sets[0].id();
+ this.navigateToEditPage(idToEdit);
+ }
+
+ navigateToEditPage(id: any) {
+ this.router.navigate(['/staff/admin/local/config/circ_limit_set/' + id]);
+ }
+
+ createNew() {
+ this.editDialog.mode = 'create';
+ this.editDialog.recordId = null;
+ this.editDialog.record = null;
+ this.editDialog.open({size: this.dialogSize}).subscribe(
+ ok => {
+ this.createSuccessString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ },
+ rejection => {
+ if (!rejection.dismissed) {
+ this.createErrString.current()
+ .then(str => this.toast.danger(str));
+ }
+ }
+ );
+ }
+
+ showEditDialog(standingPenalty: IdlObject): Promise<any> {
+ this.editDialog.mode = 'update';
+ this.editDialog.recordId = standingPenalty['id']();
+ return new Promise((resolve, reject) => {
+ this.editDialog.open({size: this.dialogSize}).subscribe(
+ result => {
+ this.updateSuccessString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ resolve(result);
+ },
+ error => {
+ this.updateFailedString.current()
+ .then(str => this.toast.danger(str));
+ reject(error);
+ }
+ );
+ });
+ }
+}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {CircLimitSetComponent} from './circ_limit_set.component';
+import {CircLimitSetEditComponent} from './circ_limit_set_edit.component';
+import {CircLimitSetRoutingModule} from './circ_limit_set_routing.module';
+import {ItemLocationSelectModule} from '@eg/share/item-location-select/item-location-select.module';
+
+@NgModule({
+ declarations: [
+ CircLimitSetComponent,
+ CircLimitSetEditComponent
+ ],
+ imports: [
+ AdminCommonModule,
+ CircLimitSetRoutingModule,
+ ItemLocationSelectModule,
+ ],
+ exports: [
+ ],
+ providers: [
+ ]
+})
+
+export class CircLimitSetModule {
+}
--- /dev/null
+<eg-staff-banner bannerText="Circulation Limit Set - {{recordName}}" i18n-bannerText>
+</eg-staff-banner>
+
+<ul ngbNav #editNav="ngbNav" class="nav-tabs mb-3"
+ [activeId]="circTab" (navChange)="onTabChange($event)">
+ <li [ngbNavItem]="'limitSet'">
+ <a ngbNavLink i18n>Edit Circulation Limit Set</a>
+ <ng-template ngbNavContent>
+ <div class="col-lg-6 offset-lg-3 mt-3">
+ <eg-fm-record-editor displayMode="inline"
+ hiddenFieldsList="id"
+ idlClass="ccls" mode="update" recordId="{{this.recordId}}">
+ </eg-fm-record-editor>
+ </div>
+ </ng-template>
+ </li>
+ <li [ngbNavItem]="'linked'">
+ <a ngbNavLink i18n>Edit Linked Entities</a>
+ <ng-template ngbNavContent>
+ <div class="row mt-3">
+ <div class="col-lg-4">
+ <eg-staff-banner bannerText="Linked Circ Modifiers" i18n-bannerText>
+ </eg-staff-banner>
+ <div class="row mt-2 mb-2">
+ <h5 class="col-lg-9">Name</h5>
+ <h5 class="col-lg-3 text-center">Remove</h5>
+ </div>
+ <div *ngFor="let mod of circMods">
+ <div class="row mt-2 mb-2" *ngIf="mod.deleted == false">
+ <div class="col-lg-9">
+ {{ mod.code }} : {{ mod.name }}
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-danger col-lg-12"
+ (click)="removeEntry(mod, circMods)" i18n>Remove</div>
+ </div>
+ </div>
+ </div>
+ <div class="row mt-2 mb-2">
+ <div class="col-lg-9">
+ <eg-combobox idlClass="ccm"
+ placeholder="Circulation Modifiers"
+ [asyncSupportsEmptyTermClick]="true"
+ (onChange)="circModChanged($event)"></eg-combobox>
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-outline-dark col-lg-12"
+ (click)="addCircMod()" i18n>Add</div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-4">
+ <eg-staff-banner bannerText="Linked Copy Locations"
+ i18n-bannerText class=""></eg-staff-banner>
+ <div class="row mt-2 mb-2">
+ <h5 class="col-lg-9">Name</h5>
+ <h5 class="col-lg-3 text-center">Remove</h5>
+ </div>
+ <div *ngFor="let location of locations">
+ <div class="row mt-2 mb-2" *ngIf="location.deleted == false">
+ <div class="col-lg-9">
+ {{ location.shortname }} : {{ location.name }}
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-danger col-lg-12"
+ (click)="removeLocation(location)" i18n>Remove</div>
+ </div>
+ </div>
+ </div>
+ <div class="row mt-2 mb-2">
+ <div class="col-lg-9">
+ <eg-item-location-select permFilter="ADMIN_CIRC_MATRIX_MATCHPOINT"
+ [(ngModel)]="locId" (valueChange)="selectedLocation = $event">
+ </eg-item-location-select>
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-outline-dark col-lg-12"
+ (click)="addLocation()" i18n>Add</div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-4">
+ <eg-staff-banner bannerText="Linked Limit Groups"
+ i18n-bannerText class=""></eg-staff-banner>
+ <div class="row mt-2 mb-2">
+ <h5 class="col-lg-7">Name</h5>
+ <h5 class="col-lg-2">Check Only?</h5>
+ <h5 class="col-lg-3">Remove</h5>
+ </div>
+ <div *ngFor="let group of limitGroups">
+ <div class="row mt-2 mb-2" *ngIf="group.deleted == false">
+ <div class="col-lg-7">
+ {{ group.name }}
+ </div>
+ <div class="col-lg-2">
+ <input
+ type="checkbox"
+ [(ngModel)]="group.checked"
+ class="form-control"
+ [checked]="group.checked" />
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-danger col-lg-12"
+ (click)="removeEntry(group, limitGroups)" i18n>Remove</div>
+ </div>
+ </div>
+ </div>
+ <div class="row mt-2 mb-2">
+ <div class="col-lg-9">
+ <eg-combobox idlClass="cclg"
+ placeholder="Limit Groups"
+ (onChange)="limitGroupChanged($event)"
+ [asyncSupportsEmptyTermClick]="true"></eg-combobox>
+ </div>
+ <div class="col-lg-3">
+ <div class="btn btn-outline-dark col-lg-12"
+ (click)="addLimitGroup()" i18n>Add</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="btn btn-success btn-lg offset-lg-5 col-lg-2 mt-4"
+ (click)="save()" i18n>Save</div>
+ </ng-template>
+ </li>
+</ul>
+<div [ngbNavOutlet]="editNav"></div>
+
+<eg-string #addingSuccess i18n-text text="Entry added."></eg-string>
+<eg-string #removingSuccess i18n-text text="Entry removed."></eg-string>
+<eg-string #savingEntryError i18n-text text="Error when trying to save entry."></eg-string>
+<eg-string #deletingEntryError i18n-text text="Error when trying to delete entry.">
+ </eg-string>
+<eg-string #updatingEntryError i18n-text text="Error when trying to update Limit Group.">
+ </eg-string>
+<eg-string #savedSuccess i18n-text text="Saved."></eg-string>
--- /dev/null
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {StringComponent} from '@eg/share/string/string.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {OrgService} from '@eg/core/org.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+import {IdlService } from '@eg/core/idl.service';
+import {NgbNav, NgbNavChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ templateUrl: './circ_limit_set_edit.component.html'
+})
+
+export class CircLimitSetEditComponent implements OnInit {
+ recordId: number;
+ recordName: String;
+ locations: any[];
+ circMods: any[];
+ allCircMods: any[];
+ limitGroups: any[];
+ allLimitGroups: any[];
+ selectedCircMod: any;
+ selectedLocation: any;
+ selectedLimitGroup: any;
+ locId = 0;
+
+ circTab: 'limitSet' | 'linked' = 'limitSet';
+
+ @ViewChild('addingSuccess', {static: true}) addingSuccess: StringComponent;
+ @ViewChild('removingSuccess', {static: true}) removingSuccess: StringComponent;
+ @ViewChild('savingEntryError', {static: true}) savingEntryError: StringComponent;
+ @ViewChild('deletingEntryError', {static: true}) deletingEntryError: StringComponent;
+ @ViewChild('updatingEntryError', {static: true}) updatingEntryError: StringComponent;
+ @ViewChild('savedSuccess', {static: true}) savedSuccess: StringComponent;
+
+ constructor(
+ private org: OrgService,
+ private route: ActivatedRoute,
+ private pcrud: PcrudService,
+ private toast: ToastService,
+ private idl: IdlService,
+ ) {
+ this.locations = [];
+ this.circMods = [];
+ this.allCircMods = [];
+ this.limitGroups = [];
+ this.allLimitGroups = [];
+ }
+
+ ngOnInit() {
+ this.recordId = parseInt(this.route.snapshot.paramMap.get('id'), 10);
+
+ // get current circ limit set name to display on the banner
+ this.pcrud.search('ccls',
+ {id: this.recordId}, {}).toPromise().then(rec => {
+ this.recordName = rec.name();
+ });
+
+ this.pcrud.search('cclscmm', {limit_set: this.recordId},
+ {
+ flesh: 1,
+ flesh_fields: {cclscmm: ['circ_mod', 'name', 'code']},
+ order_by: {}
+ }).subscribe(data => {
+ data.deleted = false;
+ data.name = data.circ_mod().name();
+ data.code = data.circ_mod().code();
+ this.circMods.push(data);
+ });
+
+ this.pcrud.retrieveAll('ccm', { order_by: {} },
+ {fleshSelectors: true}).subscribe(data => {
+ this.allCircMods.push(data);
+ });
+
+ this.pcrud.retrieveAll('cclg', { order_by: {} },
+ {fleshSelectors: true}).subscribe(data => {
+ this.allLimitGroups.push(data);
+ });
+
+ this.pcrud.search('cclsacpl', {limit_set: this.recordId},
+ {
+ flesh: 1,
+ flesh_fields: {cclsacpl: ['copy_loc', 'name']},
+ order_by: {}
+ }).subscribe(location => {
+ location.deleted = false;
+ location.shortname = this.org.get(location.copy_loc().owning_lib()).shortname();
+ location.name = location.copy_loc().name();
+ this.locations.push(location);
+ });
+
+ this.pcrud.search('cclsgm', {limit_set: this.recordId},
+ {
+ flesh: 1,
+ flesh_fields: {cclsgm: ['limit_group', 'check_only']},
+ order_by: {}
+ }).subscribe(data => {
+ const checked = data.check_only();
+ data.checked = (checked === 't');
+ data.checkedOriginalValue = (checked === 't');
+ data.name = data.limit_group().name();
+ data.deleted = false;
+ this.limitGroups.push(data);
+ });
+ }
+
+ onTabChange(event: NgbNavChangeEvent) {
+ this.circTab = event.nextId;
+ }
+
+ addLocation() {
+ if (!this.selectedLocation) { return; }
+ const newCircModMap = this.idl.create('cclsacpl');
+ newCircModMap.copy_loc(this.selectedLocation);
+ newCircModMap.limit_set(this.recordId);
+ newCircModMap.shortname =
+ this.org.get(this.selectedLocation.owning_lib()).shortname();
+ newCircModMap.name = this.selectedLocation.name();
+ newCircModMap.new = true;
+ newCircModMap.deleted = false;
+ this.locations.push(newCircModMap);
+ this.addingSuccess.current().then(msg => this.toast.success(msg));
+ }
+
+ addCircMod() {
+ if (!this.selectedCircMod) { return; }
+ const newName = this.selectedCircMod.name;
+ const newCode = this.selectedCircMod.code;
+ const newCircModMap = this.idl.create('cclscmm');
+ newCircModMap.limit_set(this.recordId);
+ newCircModMap.name = newName;
+ newCircModMap.code = newCode;
+ newCircModMap.new = true;
+ newCircModMap.deleted = false;
+ let newCircMod: any;
+ this.allCircMods.forEach(c => {
+ if ((c.name() === newName) && (c.code() === newCode)) {
+ newCircMod = this.idl.clone(c);
+ }
+ });
+ newCircModMap.circ_mod(newCircMod);
+ this.circMods.push(newCircModMap);
+ this.addingSuccess.current().then(msg => this.toast.success(msg));
+ }
+
+ circModChanged(entry: ComboboxEntry) {
+ if (entry) {
+ this.selectedCircMod = {
+ code: entry.id,
+ name: entry.label
+ };
+ } else {
+ this.selectedCircMod = null;
+ }
+ }
+
+ removeLocation(location) {
+ const id = location.copy_loc().id();
+ if (location.new) {
+ this.locations.forEach((loc, index) => {
+ if (loc.copy_loc().id() === id) {
+ this.locations.splice(index, 1);
+ }
+ });
+ }
+ location.deleted = true;
+ this.removingSuccess.current().then(msg => this.toast.success(msg));
+ }
+
+ removeEntry(entry, array) {
+ // if we haven't saved yet, then remove this entry from local array
+ if (entry.new) {
+ const name = entry.name;
+ array.forEach((item, index) => {
+ if (item.name === name) {
+ array.splice(index, 1);
+ }
+ });
+ }
+ entry.deleted = true;
+ this.removingSuccess.current().then(msg => this.toast.success(msg));
+ }
+
+ addLimitGroup() {
+ if (!this.selectedLimitGroup) { return; }
+ const newName = this.selectedLimitGroup.name;
+ let undeleting = false;
+ this.limitGroups.forEach(group => {
+ if (newName === group.name) {
+ if (group.deleted === true) {
+ group.deleted = false;
+ undeleting = true;
+ this.addingSuccess.current().then(msg => this.toast.success(msg));
+ }
+ }
+ });
+ if (undeleting) { return; }
+ const newLimitGroupMap = this.idl.create('cclsgm');
+ newLimitGroupMap.limit_set(this.recordId);
+ newLimitGroupMap.name = newName;
+ newLimitGroupMap.new = true;
+ newLimitGroupMap.checked = false;
+ newLimitGroupMap.check_only(false);
+ newLimitGroupMap.deleted = false;
+ let newLimitGroup: any;
+ this.allLimitGroups.forEach(c => {
+ if (c.name() === newName) {
+ newLimitGroup = this.idl.clone(c);
+ }
+ });
+ newLimitGroupMap.limit_group(newLimitGroup);
+ this.limitGroups.push(newLimitGroupMap);
+ this.addingSuccess.current().then(msg => this.toast.success(msg));
+ }
+
+ limitGroupChanged(entry: ComboboxEntry) {
+ if (entry) {
+ this.selectedLimitGroup = {
+ name: entry.label,
+ checked: false
+ };
+ } else {
+ this.selectedLimitGroup = null;
+ }
+ }
+
+ save() {
+ const allData = [this.circMods, this.locations, this.limitGroups];
+ let errorOccurred = false;
+ allData.forEach( array => {
+ array.forEach((item) => {
+ if (item.new) {
+ if (array === this.limitGroups) {
+ item.check_only(item.checked);
+ }
+ this.pcrud.create(item).subscribe(
+ ok => {
+ const id = ok.id();
+ item.id(id);
+ item.new = false;
+ if (array === this.limitGroups) {
+ item.checkedOriginalValue = item.checked;
+ }
+ },
+ err => {
+ errorOccurred = true;
+ this.savingEntryError.current().then(msg =>
+ this.toast.warning(msg));
+ }
+ );
+ // only delete this from db if we haven't deleted it before
+ } else if ((item.deleted) && (!item.deletedSuccess)) {
+ this.pcrud.remove(item).subscribe(
+ ok => {
+ item.deletedSuccess = true;
+ },
+ err => {
+ errorOccurred = true;
+ this.deletingEntryError.current().then(msg =>
+ this.toast.warning(msg));
+ }
+ );
+ // check limit group items to see if the checkbox changed since last write
+ } else if ((array === this.limitGroups) && (!item.deleted) &&
+ (!item.new) && (item.checked !== item.checkedOriginalValue)) {
+ item.check_only(item.checked);
+ this.pcrud.update(item).subscribe(
+ ok => {
+ item.checkedOriginalValue = item.checked;
+ },
+ err => {
+ errorOccurred = true;
+ this.updatingEntryError.current().then(msg =>
+ this.toast.warning(msg));
+ }
+ );
+ }
+ });
+ });
+
+ if (!errorOccurred) {
+ this.savedSuccess.current().then(msg => this.toast.success(msg));
+ }
+ }
+}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {CircLimitSetComponent} from './circ_limit_set.component';
+import {CircLimitSetEditComponent} from './circ_limit_set_edit.component';
+
+const routes: Routes = [{
+ path: '',
+ component: CircLimitSetComponent
+}, {
+ path: ':id',
+ component: CircLimitSetEditComponent
+}];
+
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class CircLimitSetRoutingModule {}
path: 'asset/course_module_term_course_map',
component: CourseTermMapComponent
}, {
+ path: 'config/circ_limit_set',
+ loadChildren: () =>
+ import('./circ_limit_set/circ_limit_set.module').then(m => m.CircLimitSetModule)
+}, {
path: 'config/standing_penalty',
component: StandingPenaltyComponent
}, {